README.md

July 23, 2026 · View on GitHub

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:

MomentQuestionCommand
Before installing a cloned repoDoes this project resolve to a known-malicious package, including aliases or lockfile-only evidence?aguara check .
Before handing a repo to an AI coding agentAre there agent instructions, settings, MCP configs, or tool definitions that change what the agent will obey or run?aguara scan .
Before CI executes project codeDo package intel and content findings together make this build unsafe to run or merge?aguara audit . --ci
When adopting a new gateCan we keep old findings visible without failing every build on day one?aguara audit . --write-baseline .aguara-baseline.json
When reviewing package-manager postureHas 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.

Findings remain visible, but visibility is not the same as a reason to block execution. aguara audit marks ordinary local shell-script execution (CMDEXEC_013), ordinary pip install and system-package installation commands (EXTDL_009, EXTDL_011), and a configured remote MCP endpoint (MCPCFG_004) as supporting context. They describe trust boundaries or nearby behavior without forcing an agent handoff into review-only mode by themselves. Every other built-in or custom rule defaults to review, and an explicit --fail-on policy remains authoritative for both classes.

What Aguara Checks

SurfaceExamplesCommand
Packages and lockfilesnpm, pnpm, PyPI, Go, Rust, PHP, Ruby, Java, .NETaguara check .
Package manager policynpm v12 install-trust decisions in package.json / .npmrc; pnpm supply-chain settings in pnpm-workspace.yamlaguara scan ., aguara audit .
Install scriptsnpm lifecycle hooks, install-time JS / Python / Rust behavioraguara scan ., aguara check .
MCP configsClaude Desktop, Cursor, VS Code, Cline, and 13 moreaguara discover, aguara scan --auto
Agent skills and toolsskills, prompts, tool descriptions, persistent instruction files, agent host settingsaguara scan <path>
CI workflowsGitHub Actions trust-chain risksaguara scan .github/workflows
Combined auditpackages + content, one verdictaguara audit . --ci

Quick Start

# 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:

git clone <repo>
cd <repo>
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:

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:

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 — 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 — 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.

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

EcosystemEvidence readCoverage
npmnode_modules, pnpm .pnpm store, pnpm-lock.yaml, package-lock.json, yarn.lock (classic v1 + Berry v2+), bun.lockStrong 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.
PyPIsite-packages, .pth, pip/uv/npx cachesStrong malicious-package + persistence coverage; exact versions and all-versions advisories.
RubyGemsGemfile.lockStrong malicious-package coverage.
NuGetpackages.lock.json, *.csproj/*.fsproj/*.vbprojStrong exact-version coverage.
Gogo.sum, go.modParser ready; exact-version and all-versions matches (the OSV malicious set for Go is small).
crates.ioCargo.lock (public registry only)Parser ready; exact-version and all-versions matches.
Packagistcomposer.lockParser ready; exact-version and all-versions matches.
Mavenpom.xml, Gradle lockfilesParser 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:

BehaviorDetector
npm lifecycle hook runs local JS (preinstall/postinstall/preparenode/bun)pkgmeta (SUPPLY_026)
Node downloads and runs a Bun second stage to evade Node-focused monitoringjsrisk (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/resolverjsrisk (JS_SUDOERS_TAMPER_001, JS_HOST_TRUST_TAMPER_001)
Destructive cleanup: deletes credential stores, agent files, evidence, or wipes the home directoryjsrisk (JS_WIPER_TRIPWIRE_001)
Python install hook fetches remote JavaScript and runs it through node -epyrisk (PY_IMPORTTIME_REMOTE_JS_001)
Rust build.rs reads wallet/keystore material and sends it to a network sinkrsbuild (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:

FindingSeveritySetting
All dependencies may run install scriptsHIGHdangerouslyAllowAllBuilds: true
Unapproved build scripts warn instead of failingMEDIUMstrictDepBuilds: false
Transitive deps may resolve from git/tarball URLsMEDIUMblockExoticSubdeps: false
Lockfile entries skip supply-chain verificationMEDIUMtrustLockfile: true
Build approval still pending for a packageMEDIUMundecided allowBuilds entry
Release-age window disabled or not enforcedLOWminimumReleaseAge: 0, non-strict mode
Trust policy explicitly opted outLOWtrustPolicy: off
pnpm v10 build settings that v11 no longer honorsINFOonlyBuiltDependencies 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 <RULE_ID>.

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:

FindingSeverityWhat it catches
Hook downloads and executes remote codeCRITICALa hook command piping a network fetch into a shell (curl | sh), run automatically on session open
Code-execution environment variableHIGHenv setting NODE_OPTIONS --require, LD_PRELOAD, BASH_ENV, and similar
Permissions default to bypassHIGHdefaultMode: "bypassPermissions" shipped in the repo
MCP servers auto-approvedMEDIUMenableAllProjectMcpServers: true
Dangerous command pre-approvedMEDIUMallow rules like Bash(*) or Bash(curl *)
Secret read pre-approvedMEDIUMallow rules over .env, ~/.ssh, ~/.aws, private keys
Repo-shipped credential helperMEDIUMapiKeyHelper / awsAuthRefresh pointing at a repo-relative script
Auto-approving default modeLOWdefaultMode: "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:

# 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)

brew install garagon/tap/aguara

Docker

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

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. Default install location is ~/.local/bin; override with INSTALL_DIR for CI or containers.

GitHub Action

- 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 for all inputs.

From source

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.

Outputs and Integrations

Output / IntegrationHow
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 libraryimport "github.com/garagon/aguara"Scan, ScanContent, Discover, ListRules, ExplainRule
MCP serverAguara MCP: lets an agent call Aguara before it installs or trusts a tool

A short Go example:

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.

JSON findings and rule metadata include decision_impact: "context" | "review". The additive audit.triage block also reports context_observations and review_findings, so an agent or CI wrapper can apply Aguara's trust decision without hiding lower-impact evidence or inferring policy from severity alone.

# 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 }
# 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 258 cataloged detections through aguara list-rules:

  • 192 embedded YAML pattern rules across 13 categories
  • 66 analyzer-emitted detections from ci-trust, pkgmeta, jsrisk, pyrisk, script-risk, skill-policy, skill-chain, 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 <RULE_ID>. Custom rules load from --rules <dir> (validated at load time; unknown fields rejected). See RULES.md for the full catalog with IDs and severities.

aguara list-rules                 # full catalog
aguara explain CRED_002           # one rule with remediation
aguara scan . --rules ./my-rules/ # add custom YAML rules

Architecture

The scan pipeline combines fourteen per-file analyzers (thirteen by default; rug-pull joins with --monitor) with project-level correlation that connects evidence only dangerous when two files form one execution path. The table below covers both phases:

AnalyzerEngineWhat it catches
Pattern MatcherAho-Corasick + regex, 8 decodersAttack signatures, credential patterns, dangerous commands; decodes obfuscated payloads and re-scans
CI TrustGitHub Actions YAMLpull_request_target chains, cache poisoning, OIDC token surface, persisted-credentials checkouts
PkgMetapackage.json JSONnpm lifecycle + git-source / publish-surface chains, install-time local JS
JSRiskJavaScript single-passObfuscation, install-time daemonization, CI secret harvest, OIDC runner pivot, DNS-TXT exfil, Bun second stage, GitHub C2, host-trust tampering
PyRiskPython install-hook scannersetup.py/__init__.py that fetch remote JS and run it via node -e (flow-sensitive)
Script RiskPython + shell evidence scannerDecoded or remotely fetched Python execution, sensitive-context transmission, world-writable permissions, systemd/cron persistence, and unencrypted pip/npm sources
RSBuildCargo build-script scannerbuild.rs reading wallet/keystore material and sending it to a network sink (flow-sensitive)
Npm Policypackage.json + .npmrcnpm 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 Policypnpm-workspace.yaml YAMLpnpm supply-chain settings weakened below the v11 defaults (build approval, release age, exotic sources, trust policy)
Agent Policy.claude/settings.json JSONClaude 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
Skill PolicySKILL.md YAML frontmatterA whole-value allowed-tools wildcard that requests broad tool pre-approval instead of an explicit tool set
NLPGoldmark AST + JSON/YAMLPrompt 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 FlowCapability correlationDangerous source/sink combinations within a file and across files in a directory
Skill ChainInstruction-to-helper correlationA SKILL.md directive that requires a local helper to run, bound to strong hidden behavior in that exact helper
Rug-PullSHA256 change trackingTool 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 for the full package layout.

Verifying signed releases

Every release is signed with 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.

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
# 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:

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 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 for development setup, adding rules, and the PR process. For security vulnerabilities, see SECURITY.md.

License

Apache License 2.0