Continuous security monitoring for Solana / Anchor programs. Not a one-shot audit — a daily watch loop that pulls fresh ecosystem disclosures and re-confronts your program's code against each new exploit technique.
Audits are point-in-time; exploit techniques surface weekly. This skill turns security into a repeatable loop:
Collect fresh disclosures (exploits, CVEs, RUSTSEC, auditor advisories) → Confront them against your program's code → Report a dated finding with severity,
file:line, and a proposed fix. Propose only — never auto-patch production code.
It ships an executable /security-watch command (and a security-auditor
subagent for autonomous runs) that scans a target Anchor repo for known
vulnerability classes (account substitution, init_if_needed re-init,
rounding arbitrage, donation attacks, unbounded casts, governance capture, oracle
manipulation, supply-chain risk) and emits a dated report — backed, where a
class has one, by a runnable proof-of-concept instead of just a grep
pattern and a paragraph.
Related: for cross-program-invocation vulnerabilities specifically (arbitrary
CPI, return-data spoofing, stale account after CPI, PDA signing), see the
sibling skill solana-cpi-safety-skill
— deep, CPI-only coverage with its own PoC suite. This skill stays broad
(18 classes, continuous watch); install both, they don't overlap.
solana-security-watch/
├── skills/solana-security-watch/
│ ├── SKILL.md # entry hub (progressive disclosure)
│ ├── daily-watch.md # the collect → confront → report procedure + severity rubric
│ ├── vuln-classes.md # 18 Solana/Anchor bug classes with grep patterns + safe patterns
│ ├── case-studies.md # anonymised real findings (HIGH/MEDIUM/LOW/INFO) for calibration
│ └── poc-harness.md # how the poc/ suites work + how to extend them
├── commands/
│ └── security-watch.md # executable slash command: deps + grep + advisories → report
├── agents/
│ └── security-auditor.md # read-only subagent running the same workflow autonomously
├── poc/
│ └── account-substitution/ # EXPLOIT/DEFENSE/POSITIVE CONTROL LiteSVM suite, class #1
├── bin/cli.mjs # installer (global or --project)
└── README.md
One-line install (recommended) — full bundle (skill + /security-watch command
security-auditoragent), global (~/.claude):
npx solana-security-watch
# project-local instead: npx solana-security-watch --projectFrom a clone (runs the same installer locally):
git clone https://github.com/OxToF/solana-security-watch.git
cd solana-security-watch
node bin/cli.mjs # global (~/.claude)
node bin/cli.mjs --project # project-local: ./.claude
node bin/cli.mjs --target <dir> # custom base: installs <dir>/skills, <dir>/commands, <dir>/agentsOr add the skill to the Solana AI Kit skill registry.
/security-watch . # one watch pass over the current repo
/loop 1d /security-watch . # self-paced daily watch
Or invoke the security-auditor subagent directly for the same workflow run
autonomously, or point a scheduled agent / cron job at the command with the
repo path as argument, appending each run to the repo's SECURITY_WATCH.md
journal.
For class #1 (account substitution — the highest-yield surface per the
checklist), poc/account-substitution/ ships a
compiled Anchor program with a vulnerable/fixed instruction pair and a LiteSVM
test suite:
cd poc/account-substitution
yarn && yarn test # 3 passing in ~20ms — no validator, no rebuild requiredIt proves, with an actual transaction rather than a read of the source, that a
forged account bypasses the vulnerable instruction and is rejected —
specifically on Anchor's AccountOwnedByWrongProgram check, not some
unrelated failure — by the fixed one. See
poc-harness.md for the
EXPLOIT/DEFENSE/POSITIVE CONTROL pattern and how to extend it to the next class.
Below is an unedited report from running /security-watch against a public
third-party Anchor repo: coral-xyz/sealevel-attacks
— the canonical teaching corpus of Solana exploits. It was chosen deliberately:
its vulnerabilities are public and intentional (each ships an insecure +
secure pair), so the demo proves the detection works without accusing any
production protocol. Reproduce with:
git clone --depth 1 https://github.com/coral-xyz/sealevel-attacks
/security-watch sealevel-attacks
Target: coral-xyz/sealevel-attacks (intentionally-vulnerable teaching repo —
findings below are by-design, used to validate detection coverage).
- RUSTSEC advisory DB (
anchor-lang,solana-program) — for the pinned versions. - Anchor release security notes (
init_if_neededgating, account-close discriminator) — relevant to the stale pins below.
| Check | Result |
|---|---|
anchor-lang |
0.20.1 / 0.25.0 — predates numerous security hardenings (e.g. init_if_needed was gated behind a feature flag in 0.24 precisely because of reinitialization attacks; account-close discriminator handling improved through later releases). Finding: Low — upgrade to a current 0.3x line. |
solana-program |
1.10.31 — ancient. Info — bump in lockstep with Anchor. |
overflow-checks |
Absent from [profile.release]. Finding: Low — set overflow-checks = true to turn silent wrapping into a panic (neutralises a whole class of arithmetic advisories). |
The grep flags leads; each was confirmed by reading the source. Mapping to the
skill's vuln-classes.md:
| # | Program (insecure variant) |
Class | Surface | Severity |
|---|---|---|---|---|
| 1 | 0-signer-authorization |
#8 missing Signer |
authority: AccountInfo with no signature check → anyone impersonates |
High |
| 2 | 2-owner-checks |
#1 missing owner check | SplTokenAccount::unpack on an AccountInfo without verifying the account's program owner is the Token program → spoofed data |
High |
| 3 | 1-account-data-matching |
#1 account substitution | reads token data without binding the account to the expected authority | High |
| 4 | 3-type-cosplay |
#3 manual deser, no discriminator | User::try_from_slice with no type tag → account-type confusion |
High |
| 5 | 4-initialization |
#2 / #3 reinit | try_from_slice + unconditional init on a possibly-existing account |
High |
| 6 | 5-arbitrary-cpi |
#15 arbitrary CPI | token_program: AccountInfo passed to invoke unconstrained → malicious program substitution |
High |
| 7 | 9-closing-accounts |
#16 close/revival | manual lamport-zeroing close, account revivable in-tx | High |
| 8 | 6-duplicate-mutable-accounts |
#17 duplicate mutable | two same-type mut accounts, no inequality constraint |
Medium |
| 9 | 7-bump-seed-canonicalization |
#18 bump canonicalization | PDA re-derived from a user-supplied bump | Medium |
| 10 | 10-sysvar-address-checking |
#1 address constraint | sysvar passed as AccountInfo with no address = pin |
Medium |
A naive grep for AccountInfo also matched the secure variants — but reading
the body cleared them, exactly as the procedure prescribes ("a hit is a lead, not a
finding"). Example: 0-signer-authorization/secure keeps authority: AccountInfo
yet adds if !ctx.accounts.authority.is_signer { return Err(..) } in the handler →
not flagged. 13 insecure variants flagged; 22 secure/recommended variants
correctly passed.
This pass surfaced four canonical Solana classes missing from the checklist at
the time — arbitrary CPI, account closing/revival, duplicate mutable accounts, and
bump-seed canonicalization. They were added as classes #15–#18 in
vuln-classes.md. That is the watch loop working as
intended: each pass can harden the skill itself, not only the target.
- Continuous, not point-in-time — complements one-shot audit skills; keeps you current as new techniques drop.
- Bug classes, not signatures — an EVM exploit this week becomes a Solana checklist item, because the class (rounding, donation, oracle, capture) transposes.
- Diff the siblings — most real findings are a context that drifted from a correct peer instruction, not a novel bug.
- Propose, don't apply — on production code, flag and propose; a human gates every change. After any account-context change, the IDL must be rebuilt first.
MIT — see headers. Author: OxToF.