Aguara
Open source security engine for AI agent and supply-chain trust.
Run it before install, before CI, or before handing a repo to an AI coding agent. Aguara checks packages, lockfiles, install scripts, package-manager policy, MCP configs, CI workflows, agent settings, and instruction files locally and deterministically.
Why Aguara •
When to use it •
What it checks •
Quick Start •
Before install / delegation / CI •
Threat intel •
Behavioral detection •
CI adoption •
Install
https://github.com/user-attachments/assets/851333be-048f-48fa-aaf3-f8cc1d4aa594
**No SaaS account. No telemetry. No LLM calls. Signed releases. Signed threat intel.**
- **Runs locally** — your code, prompts, configs, and dependency data never leave the machine.
- **No telemetry** — nothing is phoned home.
- **No LLM calls** — deterministic static analysis, same input gives the same result.
- **Signed threat intel** — an embedded snapshot ships in the binary; fresh updates are signed and opt-in.
## Why Aguara
Modern software does not only run your code. It runs package install scripts, lockfile-resolved dependencies, CI workflows, MCP servers, agent skills, and tool configs.
Aguara checks those trust points before they execute or become part of your workflow. The recurring supply-chain pattern is simple: a legitimate package publishes a malicious version, a project installs it, and the install-time code steals tokens, cloud credentials, CI secrets, or local files. The same risk now extends to the agent layer, where an MCP server or a third-party tool description is trusted before a single line of your code runs.
So Aguara looks at the trust layer around your project and your agents, locally and deterministically, before it runs.
## When to Use Aguara
Use Aguara when the next step would grant trust to a repository:
| Moment | Question | Command |
|---|---|---|
| Before installing a cloned repo | Does this project resolve to a known-malicious package, including aliases or lockfile-only evidence? | `aguara check .` |
| Before handing a repo to an AI coding agent | Are there agent instructions, settings, MCP configs, or tool definitions that change what the agent will obey or run? | `aguara scan .` |
| Before CI executes project code | Do package intel and content findings together make this build unsafe to run or merge? | `aguara audit . --ci` |
| When adopting a new gate | Can we keep old findings visible without failing every build on day one? | `aguara audit . --write-baseline .aguara-baseline.json` |
| When reviewing package-manager posture | Has the repo weakened npm or pnpm install-time trust decisions? | `aguara scan .` |
The output is meant for a developer, maintainer, CI job, or agent workflow that needs a clear preflight signal: proceed, review first, or stop.
## What Aguara Checks
| Surface | Examples | Command |
|---|---|---|
| Packages and lockfiles | npm, pnpm, PyPI, Go, Rust, PHP, Ruby, Java, .NET | `aguara check .` |
| Package manager policy | npm v12 install-trust decisions in `package.json` / `.npmrc`; pnpm supply-chain settings in `pnpm-workspace.yaml` | `aguara scan .`, `aguara audit .` |
| Install scripts | npm lifecycle hooks, install-time JS / Python / Rust behavior | `aguara scan .`, `aguara check .` |
| MCP configs | Claude Desktop, Cursor, VS Code, Cline, and 13 more | `aguara discover`, `aguara scan --auto` |
| Agent skills and tools | skills, prompts, tool descriptions, persistent instruction files, agent host settings | `aguara scan ` |
| CI workflows | GitHub Actions trust-chain risks | `aguara scan .github/workflows` |
| Combined audit | packages + content, one verdict | `aguara audit . --ci` |
## Quick Start
```bash
# Does this project depend on a known-compromised package?
aguara check .
# Full project audit for CI (packages + content, one verdict)
aguara audit . --ci
# Discover and scan every MCP config on this machine
aguara scan --auto
# Refresh signed threat intel for future offline checks (opt-in network)
aguara update
```
By default every command uses the threat-intel snapshot embedded in the binary. Network access is opt-in, through `aguara update` or `--fresh`.
## Before install, before delegation, before CI
Aguara is organized around the moments where trust is granted.
### Before install
`aguara check .` answers: does this project depend on a package version already known to be malicious? It reads resolved lockfiles where it has parsers, so a freshly cloned project can be checked **before any install runs**:
```bash
git clone
cd
aguara check . # reads pnpm-lock.yaml / go.sum / Cargo.lock / ... directly
```
It also matches installed package trees (`node_modules`, the pnpm `.pnpm` store, Python `site-packages`) so existing projects and CI workspaces can be audited after the fact.
### Before delegation
Before you let an agent use a third-party skill or tool, or accept a new MCP server config, scan what the agent is about to trust:
```bash
aguara scan .claude/skills/ # skills, prompts, tool descriptions
aguara discover # find every MCP config on the machine
aguara scan --auto # discover and scan them
```
This catches prompt injection, tool poisoning, unsafe MCP command definitions, hardcoded secrets, exfiltration patterns, and Unicode/encoded evasion in the files agents and MCP clients consume directly.
### Before CI execution
`aguara audit . --ci` composes the package check and the content scan into a single gate, so CI can stop before it executes install-time scripts or merges a workflow change:
```bash
aguara audit . --ci # --fail-on critical, no color, exit 1 on compromised packages
```
JSON output carries both sub-results (`.check` and `.scan`) plus per-section counts, so a dashboard can drill into either side.
## Threat Intel
Aguara matches package names and versions against a threat-intel snapshot built from:
- **[OSV.dev](https://osv.dev)** — high-confidence records only: OpenSSF Malicious Packages (`MAL-` namespace), records flagged malicious-package origin, and keyword-qualified records with exact affected versions. Version ranges and all-versions advisories are imported only from the firm malicious-package signals, never from keywords - a keyword false positive on a range would flag every version below the bound. Generic CVE / DoS records are filtered out at import time, so Aguara stays focused on malicious packages, not general SCA.
- **[OpenSSF Malicious Packages](https://github.com/ossf/malicious-packages)** — surfaced through the OSV import above.
- **Manual emergency advisories** — a short hand-curated list of high-priority incidents, taking display precedence when an advisory ID also appears in OSV.
The snapshot ships **inside the binary**, so checks run offline by default. `aguara update` fetches fresh records over the network (the only commands that do, alongside `--fresh`), verifies them, and seeds a local cache at `~/.aguara/intel/snapshot.json` that later checks layer over the embedded snapshot automatically. A refresh that returns zero records is refused, so cached intel cannot be silently wiped.
```bash
aguara status # version, snapshot date + record count, local-cache state (no network)
aguara update # refresh + cache locally (opt-in network)
aguara check . --fresh # refresh only the ecosystems this run touches, then check
```
### Coverage by ecosystem
| Ecosystem | Evidence read | Coverage |
|---|---|---|
| npm | `node_modules`, pnpm `.pnpm` store, `pnpm-lock.yaml`, `package-lock.json`, `yarn.lock` (classic v1 + Berry v2+), `bun.lock` | Strong malicious-package coverage: exact versions, semver ranges, and all-versions advisories; the lockfiles work before install. `npm:` alias entries resolve to the real registry package in pnpm-lock, package-lock, Berry yarn.lock, and bun.lock, so an alias cannot hide a compromised package; classic yarn.lock v1 has no clean real-package field, so aliased entries are skipped (conservative) rather than mis-attributed. |
| PyPI | `site-packages`, `.pth`, pip/uv/npx caches | Strong malicious-package + persistence coverage; exact versions and all-versions advisories. |
| RubyGems | `Gemfile.lock` | Strong malicious-package coverage. |
| NuGet | `packages.lock.json`, `*.csproj`/`*.fsproj`/`*.vbproj` | Strong exact-version coverage. |
| Go | `go.sum`, `go.mod` | Parser ready; exact-version and all-versions matches (the OSV malicious set for Go is small). |
| crates.io | `Cargo.lock` (public registry only) | Parser ready; exact-version and all-versions matches. |
| Packagist | `composer.lock` | Parser ready; exact-version and all-versions matches. |
| Maven | `pom.xml`, Gradle lockfiles | Parser ready; exact-version matches. |
Aguara focuses on known malicious-package records and high-confidence advisories. An advisory that marks every version of a package malicious matches in any ecosystem; version-range evaluation is npm semver only, by measurement - over 99% of malicious range advisories are the all-versions shape, so per-ecosystem version grammars would buy almost nothing. General CVE/range matching is a different product layer, not a claim today.
## Behavioral Detection
Beyond "is this package version known-bad," Aguara has analyzers that flag install-time and runtime *behavior* in package code itself, locally and deterministically:
| Behavior | Detector |
|---|---|
| npm lifecycle hook runs local JS (`preinstall`/`postinstall`/`prepare` → `node`/`bun`) | `pkgmeta` (`SUPPLY_026`) |
| Node downloads and runs a Bun second stage to evade Node-focused monitoring | `jsrisk` (`JS_BUN_SECOND_STAGE_001`) |
| GitHub API used as a payload/command channel (write mutations, Octokit writes, REST git-data) | `jsrisk` (`JS_GITHUB_C2_001`) |
| Host trust tampering: writes to sudoers, loader preload, CA stores, SSH, hosts/resolver | `jsrisk` (`JS_SUDOERS_TAMPER_001`, `JS_HOST_TRUST_TAMPER_001`) |
| Destructive cleanup: deletes credential stores, agent files, evidence, or wipes the home directory | `jsrisk` (`JS_WIPER_TRIPWIRE_001`) |
| Python install hook fetches remote JavaScript and runs it through `node -e` | `pyrisk` (`PY_IMPORTTIME_REMOTE_JS_001`) |
| Rust `build.rs` reads wallet/keystore material and sends it to a network sink | `rsbuild` (`RS_BUILD_WALLET_EXFIL_001`) |
These are structural detections bound to real calls (a bound `child_process`/`fs` call, a real `process.env` read, a flow from a fetch to an execution sink), not text matches, so a documented command or an example string does not trigger them.
## pnpm Supply-Chain Posture
pnpm v11 ships some of the strongest supply-chain controls in the Node ecosystem: build-script approval, a release-age window for new versions, exotic-source blocking, and trust policies. Aguara verifies a project is actually using them. The `pnpm-policy` analyzer reads `pnpm-workspace.yaml` and flags settings that weaken those protections:
| Finding | Severity | Setting |
|---|---|---|
| All dependencies may run install scripts | HIGH | `dangerouslyAllowAllBuilds: true` |
| Unapproved build scripts warn instead of failing | MEDIUM | `strictDepBuilds: false` |
| Transitive deps may resolve from git/tarball URLs | MEDIUM | `blockExoticSubdeps: false` |
| Lockfile entries skip supply-chain verification | MEDIUM | `trustLockfile: true` |
| Build approval still pending for a package | MEDIUM | undecided `allowBuilds` entry |
| Release-age window disabled or not enforced | LOW | `minimumReleaseAge: 0`, non-strict mode |
| Trust policy explicitly opted out | LOW | `trustPolicy: off` |
| pnpm v10 build settings that v11 no longer honors | INFO | `onlyBuiltDependencies` and friends |
A missing setting is treated as the secure pnpm v11 default and never reported; only an explicit value less safe than the default fires. Each finding points at the exact line and ships remediation, and every rule is explainable via `aguara explain `.
## Agent Host Config Posture
A cloned repo can ship a `.claude/settings.json` that Claude Code loads when you open it. After the one-time workspace-trust prompt, its hooks and credential helpers run automatically (a `SessionStart` hook fires on session open), it can inject environment variables into every subprocess, and it can pre-disable the tool-approval prompt - all from a checked-in file. The `agent-policy` analyzer reads that file and flags what is dangerous to inherit from someone else's repo:
| Finding | Severity | What it catches |
|---|---|---|
| Hook downloads and executes remote code | CRITICAL | a hook command piping a network fetch into a shell (`curl \| sh`), run automatically on session open |
| Code-execution environment variable | HIGH | `env` setting `NODE_OPTIONS --require`, `LD_PRELOAD`, `BASH_ENV`, and similar |
| Permissions default to bypass | HIGH | `defaultMode: "bypassPermissions"` shipped in the repo |
| MCP servers auto-approved | MEDIUM | `enableAllProjectMcpServers: true` |
| Dangerous command pre-approved | MEDIUM | `allow` rules like `Bash(*)` or `Bash(curl *)` |
| Secret read pre-approved | MEDIUM | `allow` rules over `.env`, `~/.ssh`, `~/.aws`, private keys |
| Repo-shipped credential helper | MEDIUM | `apiKeyHelper` / `awsAuthRefresh` pointing at a repo-relative script |
| Auto-approving default mode | LOW | `defaultMode: "acceptEdits"` / `"auto"` shipped in the repo |
The analyzer judges the dangerous shape of a value, never the mere presence of hooks or permissions (both normal). A benign config with narrow allow rules and local hooks stays quiet.
## Adopting Aguara in CI
Adopt Aguara without turning the first CI run into a wall of pre-existing findings. `aguara audit` (and `aguara scan`) support a baseline so a new gate fails only on **new** scan findings:
```bash
# 1. Record the current scan state once.
aguara audit . --write-baseline .aguara-baseline.json
# 2. From then on, gate only on findings not in the baseline.
aguara audit . --ci --baseline .aguara-baseline.json
```
- Existing scan findings stay visible in the report; they just do not gate.
- Only **new** scan findings fail the build.
- Compromised-package findings are never baselineable — a known-malicious dependency always gates, even on the first run.
- A missing or malformed baseline fails closed.
Sensitive findings (credential leaks) are skipped when writing a baseline, so a baseline file never carries a secret forward.
## Installation
### Homebrew (macOS/Linux)
```bash
brew install garagon/tap/aguara
```
### Docker
```bash
docker run --rm -v "$PWD:/repo:ro" ghcr.io/garagon/aguara:0.27.0 check /repo
```
Multi-arch (`linux/amd64` + `linux/arm64`), runs as non-root UID 10001, base images digest-pinned, and signed at the digest with Cosign plus SPDX SBOM and SLSA provenance attestations. Pin a specific release tag for reproducibility.
### Install script
```bash
curl -fsSL https://raw.githubusercontent.com/garagon/aguara/main/install.sh \
| VERSION=v0.27.0 sh
```
`install.sh` downloads `checksums.txt` and verifies the archive's SHA256 against it, aborting if neither `sha256sum` nor `shasum` is available. This catches a tampered archive at the registry layer but does not verify the Cosign signature on `checksums.txt` itself; for full keyless-signature verification on the curl-pipe path, follow the Cosign step in [Verifying signed releases](#verifying-signed-releases). Default install location is `~/.local/bin`; override with `INSTALL_DIR` for CI or containers.
### GitHub Action
```yaml
- uses: garagon/aguara@v0.27.0
with:
path: .
fail-on: high
version: v0.27.0
```
Both pins are required: the action ref pins the composite action and its install script, and `version:` pins the Aguara binary it installs. Setting both keeps the workflow reproducible and dependabot-friendly. See [`action.yml`](action.yml) for all inputs.
### From source
```bash
go install github.com/garagon/aguara/cmd/aguara@latest
```
Requires Go 1.25+. Binaries built this way report `dev` version metadata (Go does not inject release ldflags). For signed releases use Homebrew, Docker, or the install script. Pre-built binaries for Linux, macOS, and Windows are on the [Releases page](https://github.com/garagon/aguara/releases).
## Outputs and Integrations
| Output / Integration | How |
|---|---|
| Terminal | `--format terminal` (default): color, severity dashboard, top-files chart |
| JSON | `--format json`: machine processing, API integration |
| SARIF | `--format sarif`: GitHub Code Scanning, IDE / SAST dashboards |
| Markdown | `--format markdown`: GitHub Actions job summaries, PR comments |
| Go library | `import "github.com/garagon/aguara"` — `Scan`, `ScanContent`, `Discover`, `ListRules`, `ExplainRule` |
| MCP server | [Aguara MCP](https://github.com/garagon/mcp-aguara): lets an agent call Aguara before it installs or trusts a tool |
A short Go example:
```go
import "github.com/garagon/aguara"
result, err := aguara.Scan(ctx, "./skills/")
result, err = aguara.ScanContent(ctx, content, "skill.md") // no disk I/O, NFKC-normalized
detail, err := aguara.ExplainRule("PROMPT_INJECTION_001")
```
GitHub Code Scanning, GitLab SAST, and plain Docker-in-CI examples are below.
```yaml
# GitHub Action with SARIF upload (needs security-events: write)
- uses: garagon/aguara@v0.27.0
with: { path: ., severity: medium, fail-on: high, version: v0.27.0 }
```
```yaml
# GitLab CI
security-scan:
script:
- curl -fsSL https://raw.githubusercontent.com/garagon/aguara/main/install.sh | VERSION=v0.27.0 sh
- aguara scan . --format sarif -o gl-sast-report.sarif --fail-on high
artifacts:
reports:
sast: gl-sast-report.sarif
```
## What Aguara Is Not
- **Not a full SCA platform.** It matches known malicious-package records and high-confidence advisories, not every CVE across every version range.
- **Not a CVE scanner for arbitrary ranges.** Malicious-package advisories match across versions (all-versions advisories everywhere, semver ranges for npm); general CVE range evaluation is not a claim today.
- **Not a hosted dashboard.** There is no SaaS account, no upload, no telemetry.
- **Not an LLM judge.** Detection is deterministic static analysis; there are no model calls.
Aguara complements tools like Semgrep, Snyk, CodeQL, and traditional SCA: use them for your application source and CVE coverage, and use Aguara for the trust layer around it — packages, lockfiles, install-time behavior, MCP configs, CI workflows, and agent tools.
## Rules
Aguara exposes **250 cataloged detections** through `aguara list-rules`:
- **193 embedded YAML pattern rules** across 13 categories
- **57 analyzer-emitted detections** from ci-trust, pkgmeta, jsrisk, pyrisk, rsbuild, npm-policy, pnpm-policy, agent-policy, NLP, toxic-flow, and rug-pull
Every YAML rule ships remediation text, surfaced in every output format and via `aguara explain `. Custom rules load from `--rules ` (validated at load time; unknown fields rejected). See [RULES.md](RULES.md) for the full catalog with IDs and severities.
```bash
aguara list-rules # full catalog
aguara explain CRED_002 # one rule with remediation
aguara scan . --rules ./my-rules/ # add custom YAML rules
```
## Architecture
Twelve scan analyzers run per file (eleven by default; rug-pull joins with `--monitor`), each catching a different class of attack:
| Analyzer | Engine | What it catches |
|---|---|---|
| Pattern Matcher | Aho-Corasick + regex, 8 decoders | Attack signatures, credential patterns, dangerous commands; decodes obfuscated payloads and re-scans |
| CI Trust | GitHub Actions YAML | `pull_request_target` chains, cache poisoning, OIDC token surface, persisted-credentials checkouts |
| PkgMeta | `package.json` JSON | npm lifecycle + git-source / publish-surface chains, install-time local JS |
| JSRisk | JavaScript single-pass | Obfuscation, install-time daemonization, CI secret harvest, OIDC runner pivot, DNS-TXT exfil, Bun second stage, GitHub C2, host-trust tampering |
| PyRisk | Python install-hook scanner | `setup.py`/`__init__.py` that fetch remote JS and run it via `node -e` (flow-sensitive) |
| RSBuild | Cargo build-script scanner | `build.rs` reading wallet/keystore material and sending it to a network sink (flow-sensitive) |
| Npm Policy | `package.json` + `.npmrc` | npm v12 install-trust decisions weakened or pinned open: the `dangerously-allow-all-scripts` escape hatch, unpinned `allowScripts` approvals, `allow-git` / `allow-remote` relaxed; plus INFO readiness findings for git and remote-tarball dependencies that will need explicit trust under npm v12 |
| Pnpm Policy | `pnpm-workspace.yaml` YAML | pnpm supply-chain settings weakened below the v11 defaults (build approval, release age, exotic sources, trust policy) |
| Agent Policy | `.claude/settings.json` JSON | Claude Code host config that is dangerous to inherit from a cloned repo: hooks that fetch-and-execute, code-injection env vars, `bypassPermissions`, MCP auto-approval, dangerous allow rules, repo-shipped credential helpers |
| NLP | Goldmark AST + JSON/YAML | Prompt injection, tool poisoning, proximity-weighted keyword classification. Agent instruction files (`.cursorrules`, `.windsurfrules`, `.clinerules`, `AGENTS.md`, `copilot-instructions.md`) are scanned even without a `.md` extension and weighted as high-trust prompt surfaces |
| Toxic Flow | Capability correlation | Dangerous source/sink combinations within a file and across files in a directory |
| Rug-Pull | SHA256 change tracking | Tool descriptions that change between scans (`--monitor`) |
A separate `aguara check` / `aguara audit` path inspects installed package trees and lockfiles against the threat-intel snapshot. All content is NFKC-normalized before scanning to defeat Unicode evasion. Findings carry severity, a dynamic confidence score (0.50–0.95), matched text, file location with context, and remediation. The public Go API and CLI share one engine. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the full package layout.
## Verifying signed releases
Every release is signed with [Cosign](https://github.com/sigstore/cosign) keyless, ships an SPDX SBOM per archive, and is built with `-trimpath` for reproducibility. The container image is signed at the digest with SBOM + SLSA provenance attestations.
```bash
VERSION=v0.27.0
ARCHIVE=aguara_${VERSION#v}_linux_amd64.tar.gz
curl -fsSLO https://github.com/garagon/aguara/releases/download/${VERSION}/${ARCHIVE}
curl -fsSLO https://github.com/garagon/aguara/releases/download/${VERSION}/checksums.txt
curl -fsSLO https://github.com/garagon/aguara/releases/download/${VERSION}/checksums.txt.bundle
cosign verify-blob \
--bundle checksums.txt.bundle \
--certificate-identity "https://github.com/garagon/aguara/.github/workflows/release.yml@refs/tags/${VERSION}" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
checksums.txt
sha256sum --check --ignore-missing checksums.txt
```
```bash
# Container image signature
cosign verify ghcr.io/garagon/aguara:${VERSION#v} \
--certificate-identity "https://github.com/garagon/aguara/.github/workflows/docker.yml@refs/tags/${VERSION}" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"
```
## Configuration
Create `.aguara.yml` in your project root:
```yaml
severity: medium
fail_on: high
ignore:
- "vendor/**"
- "node_modules/**"
rule_overrides:
CRED_004: { severity: low }
EXTDL_004: { disabled: true }
TC-005: { apply_to_tools: ["Bash"] } # only enforce on Bash
MCPCFG_004: { exempt_tools: ["WebFetch"] } # enforce on all except WebFetch
```
Suppress individual findings inline with `# aguara-ignore RULE_ID` (also `-next-line`, HTML/`//` comment variants).
## Aguara MCP
[Aguara MCP](https://github.com/garagon/mcp-aguara) is an MCP server that lets AI agents call Aguara before they install or trust third-party tools. It imports Aguara as a Go library (no shelling out) and exposes four tools: `scan_content`, `check_mcp_config`, `list_rules`, and `explain_rule`. No network, no LLM, millisecond scans.
## Aguara Watch
Aguara Watch is being reworked. The previous public observatory is stale and is not a supported surface for v0.27.0. The supported surfaces are the CLI, GitHub Action, Docker image, signed releases, and Go library.
## Contributing
Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, adding rules, and the PR process. For security vulnerabilities, see [SECURITY.md](SECURITY.md).
## License
[Apache License 2.0](LICENSE)