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.

CI Coverage Go Report Card Go Reference GitHub Release License GitHub Stars Docker Homebrew

Why AguaraWhen to use itWhat it checksQuick StartBefore install / delegation / CIThreat intelBehavioral detectionCI adoptionInstall

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)