|
| 1 | +# Security Scanning |
| 2 | + |
| 3 | +Local, containerized security scanning for the Solidity codebase, set up per the |
| 4 | +[Continuous Security Testing/Auditing](https://www.notion.so/3521aee1232480958886c3666758b9f0) |
| 5 | +recommendation. Supplementary to formal audits, not a replacement. |
| 6 | + |
| 7 | +## Threat model: why local + containerized + pinned |
| 8 | + |
| 9 | +This repository is **public** and the contracts **hold real value**, which drives |
| 10 | +two distinct controls: |
| 11 | + |
| 12 | +1. **No public CI.** Every place a GitHub Action surfaces output is public on a |
| 13 | + public repo — issues, PR comments, **Actions logs, and artifacts**. Scanners |
| 14 | + print findings to the log, so running them in CI would leak live, unfixed |
| 15 | + vulnerabilities. All scanning runs locally; reports go to `security/reports/` |
| 16 | + (gitignored) and are never committed or posted. |
| 17 | + |
| 18 | +2. **The AI skills are an untrusted supply chain.** The community skill repos are |
| 19 | + third-party and community-writable, and their content is loaded as |
| 20 | + *instructions* into a tool-enabled agent. Two mitigations: |
| 21 | + - **Pinning (integrity):** `security/vendor-skills.sh` checks out exact, |
| 22 | + reviewed commit SHAs and fails closed if a commit is gone — so a poisoned |
| 23 | + upstream commit can't silently run. Bump a SHA only after reviewing the diff. |
| 24 | + - **Containment:** every scanner runs inside a locked-down Docker container so |
| 25 | + a malicious skill can't read host files/secrets or exfiltrate: |
| 26 | + - **Static tier** (Slither/Aderyn/Solhint): `--network none`, `--cap-drop ALL`, |
| 27 | + source mounted **read-only**. Fully airtight. |
| 28 | + - **AI tier** (review/audit/skills/summarize): egress restricted to the |
| 29 | + **Anthropic API only** (`security/docker/init-firewall.sh`), |
| 30 | + the Claude credential is the only secret present, source mounted read-only. |
| 31 | + |
| 32 | +Pinning + container = integrity + containment. Note what containment does **not** |
| 33 | +fix: a poisoned skill can still produce dishonest *output* (e.g. hide a finding), |
| 34 | +so AI results stay advisory and should be cross-checked against the static tools. |
| 35 | + |
| 36 | +## Prerequisites |
| 37 | + |
| 38 | +- **Docker.** Build the pinned toolchain image once: |
| 39 | + ```sh |
| 40 | + make security-build |
| 41 | + ``` |
| 42 | + The image (`security/docker/Dockerfile`) bundles Foundry, Slither, Aderyn, |
| 43 | + Solhint, and the Claude CLI at pinned versions, with `solc` pre-cached so the |
| 44 | + static tier compiles with no network. |
| 45 | +- **AI tier only:** a credential, resolved at run time — no permanent env var |
| 46 | + needed. See [Credentials](#credentials) below. The static tier needs nothing. |
| 47 | + |
| 48 | +## Credentials |
| 49 | + |
| 50 | +The AI tier authenticates with a **Claude Code OAuth token** (tied to your own |
| 51 | +Claude subscription — no separate API billing). The token is **per-developer**, |
| 52 | +so each person stores their own; there is no shared key. It is fetched at run |
| 53 | +time and injected into the sealed container by reference (`-e NAME`, never on the |
| 54 | +command line), and is never written to disk in the repo or kept in your shell. |
| 55 | + |
| 56 | +One-time setup, per developer (default: macOS Keychain, zero extra tooling): |
| 57 | + |
| 58 | +```sh |
| 59 | +claude setup-token # mint a long-lived token tied to your account |
| 60 | +make security-set-token # paste it once; stored encrypted in your login Keychain |
| 61 | +``` |
| 62 | + |
| 63 | +After that, `make security-ai-*` fetches it automatically. |
| 64 | + |
| 65 | +Credential resolution (precedence, highest first): |
| 66 | + |
| 67 | +1. `CLAUDE_CODE_OAUTH_TOKEN` in the environment → used directly (CI / power users). |
| 68 | +2. `ANTHROPIC_API_KEY` in the environment → used directly. |
| 69 | +3. **1Password**, *only if* `FCM_OP_TOKEN_REF='op://<your-vault>/<item>/credential'` |
| 70 | + is set (use your own/Private vault — the token is personal, not shared). |
| 71 | +4. **macOS Keychain** (the default) — what `make security-set-token` writes. |
| 72 | + |
| 73 | +## Make targets |
| 74 | + |
| 75 | +| Target | Tier | Notes | |
| 76 | +|--------|------|-------| |
| 77 | +| `make security` | all | everything: build + all static + all AI | |
| 78 | +| `make security-build` | — | build the scanner image (run once / after updates) | |
| 79 | +| `make security-set-token` | — | store your Claude OAuth token in the Keychain (one-time) | |
| 80 | +| `make security-check-cred` | — | verify a Claude credential is available (AI-tier preflight) | |
| 81 | +| `make security-static` | static | all static analyzers: Slither + Aderyn + Solhint | |
| 82 | +| `make security-slither` | static | report → `security/reports/slither-report-<ts>.txt` | |
| 83 | +| `make security-aderyn` | static | report → `security/reports/aderyn-report-<ts>.md` | |
| 84 | +| `make security-solhint` | static | report → `security/reports/solhint-report-<ts>.txt` | |
| 85 | +| `make security-ai` | AI | all AI tiers: review + audit + skills + summarize | |
| 86 | +| `make security-ai-review` | AI | reviews current branch changes | |
| 87 | +| `make security-ai-audit` | AI | full-codebase audit | |
| 88 | +| `make security-ai-skills` | AI | `SKILL=<name>`; vendors pinned skills, then audits | |
| 89 | +| `make security-ai-summarize` | AI | rolls up all reports by severity to stdout | |
| 90 | + |
| 91 | +## How it fits together |
| 92 | + |
| 93 | +- **Prompts** live in `security/prompts/` and encode the vault/curator threat |
| 94 | + model. They forbid creating issues/comments or writing findings to tracked files. |
| 95 | +- **`security/scan.sh`** is the single dispatcher: it builds the image on demand |
| 96 | + and runs each tool with the right isolation flags (static = no network, AI = |
| 97 | + API-only egress + key). |
| 98 | +- **`security/docker/`** holds the `Dockerfile`, the two entrypoints |
| 99 | + (`entry-static.sh`, `entry-ai.sh`), and the egress allowlist |
| 100 | + (`init-firewall.sh`). |
| 101 | +- **`security/vendor-skills.sh`** clones the pinned skill SHAs on the host, into a |
| 102 | + gitignored `.claude/skills/`, which is mounted read-only into the sealed |
| 103 | + container (so the container needs no GitHub egress). |
| 104 | + |
| 105 | +## Reports |
| 106 | + |
| 107 | +All reports are written under `security/reports/` (gitignored). **Do not commit or |
| 108 | +share these files** — they may describe live, unfixed vulnerabilities. AI is |
| 109 | +non-deterministic; run audits more than once and cross-check. |
| 110 | + |
| 111 | +## Updating a skill |
| 112 | + |
| 113 | +Review the upstream diff, then update the corresponding `*_SHA` in |
| 114 | +`security/vendor-skills.sh`. Never point at a floating branch. |
| 115 | + |
| 116 | +## Config files (safe to commit) |
| 117 | + |
| 118 | +- `solidity/slither.config.json` — filters out `lib/`, `test/`, `script/`. |
| 119 | +- `solidity/.solhint.json`, `solidity/.solhintignore` — Solhint rules/ignores. |
0 commit comments