# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Status

Aguara v0.27.0 (2026-06-12; aggregates the terminal UX round #220 TTY detection + #221 shared style layer + `audit --verbose`, the native fuzz harness #223 with 22 targets + nightly workflow, and dependency bumps). Previous: v0.26.0 (2026-06-11; C3 range-matching round: #216 measurement, #217 all-versions advisory matching, #218 npm bounded ranges, snapshot 26,268 records + 202,526 all-versions entries). 193 YAML rules + 57 analyzer-emitted (250 cataloged), 13 YAML categories (+ analyzer categories incl. agent-trust), 12 scan analyzers (pattern, ci-trust, pkgmeta, jsrisk, pyrisk, rsbuild, npmpolicy, pnpmpolicy, agentpolicy, NLP, toxicflow, rugpull) plus the `aguara check` incident command (npm/PyPI installed trees + pre-install npm lockfiles pnpm-lock.yaml / package-lock.json / yarn.lock classic v1 / yarn.lock Berry v2+ / bun.lock text - all with `npm:` alias resolution to the real package - and Go/Rust/PHP/Ruby/Java/.NET lockfiles), 0 lint issues.

Distribution: install.sh (mandatory checksum verification, bounded curl + retry), Homebrew tap, Docker (GHCR, multi-arch `linux/amd64+arm64`, runs as non-root UID 10001, base images digest-pinned, signed at digest with Cosign + SBOM + SLSA provenance attestations), GoReleaser (releases signed via Cosign keyless, SPDX SBOM per archive, `-trimpath` for reproducibility), GitHub Action, go install.

GitHub: 84 stars, 15 forks. 3 awesome-list PRs pending review in external repos (sbilly/awesome-security, Puliczek/awesome-mcp-security, ottosulin/awesome-ai-security; 3 others merged, forks deleted 2026-07-01). Issue #230: external contributor building an out-of-tree ATR rule pack for `--rules` (welcomed; link from README when it ships).

Pattern matcher already uses an Aho-Corasick keyword prefilter (active in production since v0.14.0). Real perf is ~210ms/op on the synthetic bench (was ~770ms without AC), so do not treat "pattern matcher perf" as an open backlog item unless pprof shows a concrete regression. Pending improvements: WASM build (cmd/wasm/ incomplete), adoption/marketing.

Internal docs (gitignored) in `DOCS/` as an Obsidian vault. See `DOCS/Aguara/CONVENTIONS.md` for vault rules and `DOCS/Aguara/00 Dashboard.md` for the entry point.

Key locations:
- Dashboard (MOC): `DOCS/Aguara/00 Dashboard.md`
- Conventions: `DOCS/Aguara/CONVENTIONS.md` - frontmatter schema, types, statuses, tags, naming
- Product versions: `DOCS/Aguara/product/v0.10.0/_index.md` (version hub with links to all generated content; v0.11.0–v0.14.0 hubs not yet created)
- Distribution channels: `DOCS/Aguara/distribution/<channel>/_index.md` (each has submission log)
- Growth plan: `DOCS/Aguara/product/roadmap.md`
- Templates: `DOCS/Aguara/templates/` (channel.md, product-version.md)
- Demo video project: `/Users/dev/Dev/videos/aguara-demo/` (Remotion, React-based)
- Oktsec docs (separate project): `DOCS/Oktsec/`

## Build & Test Commands

```bash
make build          # Production binary with version/commit ldflags
make test           # go test -race -count=1 ./...
make lint           # golangci-lint run ./...
make vet            # go vet ./...
make fmt            # gofmt -w .
make run ARGS="scan ./path"  # Development run

# Single package test
go test -race -count=1 ./internal/rules/...

# Single test function
go test -race -count=1 -run TestFunctionName ./internal/engine/pattern/...

# Benchmarks
go test -bench=. -benchtime=3x ./internal/engine/pattern/
go test -bench=. -benchtime=3x ./internal/engine/nlp/
go test -bench=. -benchtime=3x ./internal/scanner/
```

CI requires: `make build && make test && make vet && make lint` all passing.

## Architecture

Aguara is a deterministic static security scanner for AI agent skills and MCP servers. No LLM, no network calls. Go 1.25, module `github.com/garagon/aguara`.

### Public Library API (`aguara.go`, `options.go`)

Root package re-exports types from `internal/types` and exposes: `Scan()`, `ScanContent()`, `ListRules()`, `ExplainRule()`, `Discover()`. Used by external consumers like `aguara-mcp`. Functional options pattern (`WithMinSeverity()`, `WithWorkers()`, etc.).

Analyzer wiring is centralized: add an analyzer in `internal/engine/engine.go` (`DefaultAnalyzers` + `RuleMetadata`) - one place, not the old four call sites. Emit-site RuleName/Severity/Category derive from each analyzer's `RuleMetadata()` via `rulemeta.Index` (no string drift).

### Analysis Pipeline (11 default analyzers + rug-pull when --monitor is set, run sequentially per file)

1. **Pattern Matcher** (`internal/engine/pattern/`) - Regex/contains matching against compiled rules. 8 decoders (base64, hex, URL encoding, Unicode escapes, HTML entities, hex escapes, base32, C-style octal escapes) for encoded evasion detection. Markdown code-block severity downgrade. Dynamic confidence based on pattern hit ratio.
2. **CI Trust** (`internal/engine/ci/`) - YAML parser for `.github/workflows/*.yml`. Detects `pull_request_target` pwn-request chains, cache poisoning, OIDC token surface, and persisted-credentials checkouts. Emits `GHA_PWN_REQUEST_001` / `GHA_CACHE_001` / `GHA_OIDC_001` / `GHA_CHECKOUT_001`.
3. **PkgMeta** (`internal/engine/pkgmeta/`) - JSON parser for `package.json`. Detects npm install-time lifecycle scripts paired with git-sourced dependencies, optional-git deps, and publish-surface chains. Emits `NPM_LIFECYCLE_GIT_001` / `NPM_OPTIONAL_GIT_001` / `NPM_PUBLISH_SURFACE_001`.
4. **JSRisk** (`internal/engine/jsrisk/`) - Single-pass JavaScript scanner for `.js`/`.mjs`/`.cjs`. Detects obfuscator-shape payloads, install-time daemonization, CI secret harvesting, runner process-memory pivots, and Claude Code/VS Code persistence. Emits `JS_OBF_001` / `JS_DAEMON_001` / `JS_CI_SECRET_HARVEST_001` / `JS_PROC_MEM_OIDC_001` / `AGENT_PERSISTENCE_001`.
5. **PyRisk** (`internal/engine/pyrisk/`) - Flow-sensitive single-pass scanner for `setup.py`/`__init__.py`. Binds a remote-JS fetch to a `node -e`/`--eval` execution sink (≤2 hops, taint cleared on safe reassignment). Emits `PY_IMPORTTIME_REMOTE_JS_001` (moved from a co-presence YAML rule in v0.22.0).
6. **RSBuild** (`internal/engine/rsbuild/`) - Flow-sensitive single-pass scanner for `build.rs`. Binds a wallet/keystore read to a network send sink (the tainted value must reach a send/body argument; ≤2 hops). Emits `RS_BUILD_WALLET_EXFIL_001` (moved from a co-presence YAML rule in v0.22.0).
7. **NpmPolicy** (`internal/engine/npmpolicy/`) - Reads `package.json` (`allowScripts` policy) and project `.npmrc` for npm v12 install-trust weakenings: the `dangerously-allow-all-scripts` escape hatch, unpinned approvals, `allow-git`/`allow-remote` relaxations. Ground-truthed against npm 11.16.0 (deny entries are name-only false and never fire; quoted+comment values are inert; last-wins; empty boolean = true; manifest keys case-sensitive). Emits 4 `NPM_*` rules (1 HIGH, 3 MEDIUM), category supply-chain.
8. **PnpmPolicy** (`internal/engine/pnpmpolicy/`) - yaml.Node parser for `pnpm-workspace.yaml` (exact target, base name only). Flags pnpm supply-chain settings weakened below the v11 defaults; absence of a setting never fires. Only exact camelCase keys match (ground truth pnpm 11.5.2: kebab-case in pnpm-workspace.yaml is silently IGNORED by pnpm - kebab is the .npmrc spelling - so kebab must NOT fire), YAML merge keys expand (visited-set bounded), explicit beats merged, booleans follow pnpm's js-yaml 1.1 loader (yes/no/on/off ARE booleans - do not "fix" to yaml.v3 tags). Emits 9 `PNPM_*` rules (1 HIGH `PNPM_DANGEROUS_BUILDS_001`, 4 MEDIUM, 3 LOW, 1 INFO), all category supply-chain.
9. **AgentPolicy** (`internal/engine/agentpolicy/`) - JSON parser for Claude Code `.claude/settings.json` / `settings.local.json` (exact target via `.claude/` path suffix). Flags host config dangerous to inherit from a cloned repo: hooks that fetch-and-execute, code-injection env vars (NODE_OPTIONS --require, LD_PRELOAD, BASH_ENV…), `bypassPermissions`, `enableAllProjectMcpServers`, broad/secret-read allow rules, repo-relative credential helpers. Per-key independent decode (one bad block doesn't blind the rest); absence never fires; fires on dangerous value SHAPE only. Emits 8 `AGENTCFG_*` rules (1 CRITICAL, 2 HIGH, 4 MEDIUM, 1 LOW), category `agent-trust`.
10. **NLP Analyzer** (`internal/engine/nlp/`) - Goldmark AST walker for markdown; JSON/YAML string extractor for structured files. Keyword classification with proximity weighting. Detects prompt injection, authority claims, credential+exfil combos.
11. **ToxicFlow** (`internal/engine/toxicflow/`) - Single-file source/sink co-occurrence + cross-file capability correlation (`crossfile.go`). Detects dangerous capability combinations within and across files in the same directory. Flat-dir filter (>50 files) prevents FPs on registries.
12. **Rug-Pull** (`internal/engine/rugpull/`) - SHA256-based tool description change detection. CLI via `--monitor`, library via `WithStateDir()`.

All twelve implement the `Analyzer` interface (`internal/scanner/analyzer.go`): `Name() string` + `Analyze(ctx, *Target) ([]Finding, error)`. The first eleven run on every scan; rug-pull only joins when the caller passes `--monitor` (CLI) or `WithStateDir` (library). `aguara check` (`internal/incident/`, `internal/packagecheck/`) is a separate command-line entry point, not part of the scan pipeline.

### Key Package Relationships

- `internal/types/` - Lowest layer. `Finding`, `Severity`, `ScanResult`. No internal imports. **`Severity` is `type int`, serializes as a number in JSON (0=INFO, 1=LOW, 2=MEDIUM, 3=HIGH, 4=CRITICAL), NOT a string.**
- `internal/rules/` - YAML to CompiledRule. `builtin/` embeds 13 YAML files via `go:embed`.
- `internal/scanner/` - Orchestrator. Discovers files, spawns workers, runs analyzers, applies inline ignore filtering, aggregates results. Imports `meta/` for post-processing.
- `internal/meta/` - Dedup, scoring, correlation. Imports `types/` only (NOT `scanner/` - this prevents import cycles).
- `internal/output/` - Formatters (terminal, JSON, SARIF, markdown) implementing `Formatter` interface. Markdown header is `## Aguara Security Scan`, not `# Aguara Scan Report`.
- `internal/config/` - `.aguara.yml` loader. Supports `disable_rules` list and `rule_overrides` map.
- `discover/` - MCP client auto-discovery. `Scan()`, `FormatTree()`, `FormatMarkdown()`. Public package.
- `cmd/aguara/commands/` - Cobra CLI wiring. Version/Commit injected via ldflags. **Uses global package-level flag vars that leak between tests - always call `resetFlags()` in tests.** `discover` command supports `--format` flag: `terminal` (default, tree), `json`, `markdown`/`md`.

### Import Cycle Constraint

`scanner` -> `meta` -> `types`. The `meta` package must never import `scanner`.

## Rules System

193 rules in `internal/rules/builtin/*.yaml` across 13 files. Each rule requires:

- `id`, `name`, `severity`, `category`, `patterns` (type: `regex` or `contains`)
- `match_mode`: `any` (OR, default) or `all` (AND)
- `remediation`: actionable fix guidance (all 193 rules have this)
- `examples.true_positive` and `examples.false_positive` - **self-tested automatically by `make test`**
- Optional: `targets` (file globs), `exclude_patterns` (context-based suppression)

### Rule Field Placement

The `remediation:` field goes after `match_mode:` (or `category:`/`targets:` if no match_mode) and before `patterns:`.

### Go Regexp Constraint

Go's `regexp` does NOT support lookaheads (`(?!...)`) or lookbehinds. Use `exclude_patterns` or `match_mode: all` with multiple patterns instead. For JSON keys, account for optional quotes: `["']?key["']?`. Max pattern length: 4096 chars (enforced at compile time).

## Testing Notes

- Race detector (`-race`) is mandatory - all tests run with it.
- Integration tests in `internal/scanner/integration_test.go` skip when `testdata/` is absent. Testdata exists locally (1275 files, gitignored) with 10 malicious + 6 benign scenarios.
- NLP classifier uses Go maps - tied scores produce nondeterministic winners. Don't write tests asserting a single winner on tied scores.
- Rule self-tests validate `true_positive` matches and `false_positive` non-matches respecting `match_mode`.
- CLI tests (`cmd/aguara/commands/scan_test.go`) use `scanToFile` helper that writes output to temp file via `-o` flag, since Cobra `SetOut()` doesn't capture output from `writeOutput` which writes to `os.Stdout`.
- Always call `resetFlags()` at the start of each CLI test to avoid global flag state leaking.

## Release Process

1. Create branch `release/vX.Y.Z` from main
2. Update `CHANGELOG.md`
3. Commit, push, create PR, merge
4. Tag: `git tag vX.Y.Z && git push origin vX.Y.Z`
5. GoReleaser auto-runs: builds binaries, updates Homebrew tap (`garagon/homebrew-tap`), publishes Docker image to `ghcr.io/garagon/aguara`
6. Clean up: delete release branch locally and remotely

## Obsidian Vault Workflow (DOCS/Aguara/)

The `DOCS/Aguara/` directory is an Obsidian vault. Claude Code must follow `CONVENTIONS.md` when creating or editing docs.

### Working with the vault

1. **Start from the Dashboard**: Read `DOCS/Aguara/00 Dashboard.md` to understand current state.
2. **Follow CONVENTIONS.md**: Every `.md` file needs frontmatter (type, tags, status, dates). Read `DOCS/Aguara/CONVENTIONS.md` before creating files.
3. **Use templates**: Copy from `DOCS/Aguara/templates/` when creating new channels or product versions.
4. **Use wikilinks**: Link between docs with `[[path/to/file]]` syntax (Obsidian-style).

### Automatic checklist and status tracking

When completing a distribution task (submitting a post, publishing a blog, opening a PR):
1. **Update the submission log** in the channel's `_index.md` - add a row with date, action, status, URL.
2. **Update frontmatter status** - change `status: ready` to `status: published`, add `published_url:` and `posted: YYYY-MM-DD`.
3. **Update the Dashboard** - mark the channel as done in the Distribution table (`00 Dashboard.md`).
4. **Update the version hub** - if content was for a specific version, confirm links in `product/vX.Y.Z/_index.md`.

When completing a product task (fixing a bug, adding a feature, releasing a version):
1. **Update Dashboard metrics** - star count, rule count, test count, coverage.
2. **Update CLAUDE.md** - if rule count, test count, or version changes.
3. **Create status file** - `product/vX.Y.Z/status-YYYY-MM-DD.md` for significant milestones.

### Data consistency rule

When any of these values change, update ALL references across the vault:
- Rule count (currently 193 YAML / 250 cataloged)
- Test count (currently ~750)
- Coverage (currently 82% on the Codecov badge; badge scope excludes tools/ and benchmarks/)
- Star/fork count (currently 48/6)
- Watch skill count (currently 28,000+)
- Version number (currently v0.27.0)

Use `Grep` to find all occurrences before updating.

### Content generation workflow

All external content (blog posts, social media, Reddit, newsletters) MUST go through this 4-stage pipeline:

1. **Literature Scout** - Research what already exists on the topic. Check competitor tools, recent posts in the target community, trending discussions. Identify gaps and angles that haven't been covered.
2. **Cybersecurity Analyzer** - Validate every technical claim against the actual codebase. Cross-reference rule IDs, analysis layers, detection capabilities. No exaggeration, no hand-waving.
3. **Research Cybersecurity** - Ground the content in real threat models, CVEs, attack patterns, or published research. Link claims to concrete examples (e.g., specific prompt injection techniques, real supply chain attacks).
4. **Editor** - Final pass for tone and quality. Must read like a human wrote it - natural, accessible, didactic. No AI patterns ("leverage", "utilize", "robust", "cutting-edge", "seamlessly", "game-changing", "in today's landscape"). No em-dashes. No filler. Direct, technical, conversational.

Content must pass ALL four stages before being marked `status: ready`.

### Review checklist (before publishing any content)

- [ ] Data check: rule count, test count, star count, Watch skill count are current
- [ ] Technical accuracy: claims match actual implementation
- [ ] No AI patterns: no "leverage", "utilize", "robust", "cutting-edge", "game-changing"
- [ ] No em-dashes: use hyphens (-) not em-dashes
- [ ] Natural voice: reads like a person wrote it
- [ ] Links work: GitHub URLs, Watch URL, install commands are correct

## Growth & Visibility

- **Awesome list PRs**: Create fork -> PR -> delete fork immediately after merge/close (keep garagon account clean). Currently 7 forks + 7 open PRs across external repos.
- **punkpeye awesome-mcp repos** use emoji format: `🏎️` (Go), `🏠` (local), `🍎🪟🐧` (OS).
- **wong2/awesome-mcp-servers** does NOT accept PRs - must use mcpservers.org/submit web form.
- **GitHub topics**: 15 keywords max useful for SEO.
- **OpenAlternative.co / OpenSourceAlternative.to**: Don't accept CLIs. Descartados.
- **awesome-go**: Eligible 2026-07-17 (repo created 2026-02-17; requires 5-month age). Codecov badge shipped via coverage workflow; entry draft in the vault's awesome-lists channel.
- **Official MCP Registry**: Only accepts npm/TypeScript packages. Not applicable for Go.
- Distribution channels with submission tracking: `DOCS/Aguara/distribution/`.
- Demo video: Remotion project at `/Users/dev/Dev/videos/aguara-demo/`. Terminal segments recorded with VHS (charmbracelet/vhs), slides as React components, rendered with `npx remotion render AguaraDemo output.mp4`.

## Community Contributions

When reviewing external PRs:
- CI doesn't run on fork PRs (branch protection). Pull locally and run `make build && make test && make vet && make lint` before merging.
- Use `gh pr merge <N> --merge --admin --delete-branch` to bypass branch protection for verified fork PRs.
- After merging rule PRs, update rule count references in README.md, CONTRIBUTING.md, and CLAUDE.md.
- Good-first-issue PRs that close issues will auto-close the linked issues on merge.

## Communication Rules

- Never include `Co-Authored-By: Claude` in commits to this repository.
- Never include "Generated with Claude Code" in PR descriptions.
- Never use em dashes (-) in LinkedIn posts or external communications. Use hyphens (-) instead.
- All commits and PRs are authored entirely under garagon's name.
