Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.9.1] - 2026-07-21

### Added

- **Opt-in approval gate for paid Plus scans** (sable-9ddf). New `scan.plus_requires_approval` config flag (`.rafter.yml` or global `~/.rafter/config.json`), **off by default** so existing behavior is unchanged. When enabled, `rafter run --mode plus` (and the `rafter scan` / `rafter scan remote` aliases) requires explicit confirmation before spending credits: it prompts `[y/N]` on a TTY and refuses in a non-interactive/agent context with new exit code `5` unless `--yes` (`-y`) or `RAFTER_CONFIRM=1` is passed. Fast scans are never gated. Precedence is additive (OR) — a project `.rafter.yml` can turn the gate *on* but can never turn *off* a gate the machine owner enabled globally, so a hostile repo can't silently re-open the credit-burn hole. The rafter agent skill and injected instruction block now tell agents Plus is a paid tier and to ask the user before running it. Node + Python, tests in both suites. Addresses an external user whose agent auto-ran a Plus scan and consumed credits without asking.
- **SendGrid API Key secret pattern** (#24). New `SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}` detection pattern (severity `critical`) added to the built-in regex scanner in both Node and Python, with tests in each suite. Thanks to @perez-eduardo for the contribution.

## [0.9.0] - 2026-07-08

### Added
Expand Down
8 changes: 6 additions & 2 deletions node/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rafter-security/cli",
"version": "0.9.0",
"version": "0.9.1",
"type": "module",
"repository": {
"type": "git",
Expand All @@ -24,7 +24,10 @@
"build": "tsc -p tsconfig.json",
"prepublishOnly": "pnpm run build",
"postinstall": "echo '\n Run: rafter agent init --all\n Sets up secret scanning for Claude Code, Codex CLI, and git hooks.\n'",
"test": "vitest"
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"clean": "rm -rf dist/ node_modules/.cache/"
},
"engines": {
"node": ">=18"
Expand All @@ -44,6 +47,7 @@
"devDependencies": {
"@types/js-yaml": "^4.0.9",
"@types/node": "^20.11.30",
"@vitest/coverage-v8": "4.1.0",
"tsx": "^4.7.0",
"typescript": "^5.4.5",
"vitest": "^4.1.0"
Expand Down
2 changes: 1 addition & 1 deletion node/resources/rafter-security-skill.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: rafter-security
description: Security toolkit for AI workflows. Use when scanning code or repos for vulnerabilities, auditing third-party skills/MCPs/agent configs before installing, evaluating shell commands before running them, or generating secure design questions for new features. Provides `rafter run` (remote SAST + SCA, needs RAFTER_API_KEY), `rafter secrets` (offline secrets-only), `rafter agent exec --dry-run` (command-risk classification), and `rafter skill review`.
version: 0.9.0
version: 0.9.1
homepage: https://rafter.so
metadata:
openclaw:
Expand Down
8 changes: 7 additions & 1 deletion node/resources/skills/rafter-code-review/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: rafter-code-review
description: "REQUIRED before declaring a task done when the diff touches user input, SQL, shell, auth, credentials, file paths, serialization, or dependency surface. Walks OWASP/ASVS/MITRE questions the compiler and the test suite won't catch. Pair with `rafter run` on the same diff. A security-relevant task marked complete without both has been under-delivered — do not claim done."
description: "REQUIRED before declaring a task done when the diff touches user input, SQL, shell, auth, credentials, file paths, serialization, crypto, network endpoints, data deletion, or dependency surface. Judge by that surface, not the task label — research/experimental/local-only code with none of it can skip this. Walks OWASP/ASVS/MITRE questions the compiler and the test suite won't catch. Pair with `rafter run` on the same diff. When the diff has that surface, marking it complete without both has been under-delivered — do not claim done."
version: 0.7.0
allowed-tools: [Bash, Read, Glob, Grep]
---
Expand All @@ -11,6 +11,12 @@ A reviewer's skill, not an audit generator. Each sub-doc is a set of **questions

> Pair with the `rafter` skill (detection: `rafter scan`, `rafter run`) and `rafter-secure-design` (prevention: design-phase walks). This skill is the middle stage — review before merge.

## When this applies (and when it doesn't)

Scoped to the **security surface of the diff**, not the task's label. Walk it fully when the change touches: user / untrusted input, SQL or query building, shell / `exec` / subprocess, auth or access control, credentials / secrets / tokens, file paths or uploads, (de)serialization, crypto, network-facing endpoints or outbound fetchers, data deletion, or dependency / manifest changes.

If **none** of those are present — research / experimental / exploratory / local-only / throwaway code such as training scripts, data analysis, plotting, model eval, notebooks, or pure computation over trusted local data — a quick surface check is enough; you don't need to walk the full review or pair `rafter run`. But the check is the surface, not the label: research code that reads a secret, shells out, hits the network, or parses untrusted bytes is back on the engage list and gets the full walk.

## How to use this skill

1. Identify the category of code in front of you (below).
Expand Down
8 changes: 7 additions & 1 deletion node/resources/skills/rafter-secure-design/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: rafter-secure-design
description: "REQUIRED before writing code for any feature touching auth, payments, credentials, tokens, sessions, file upload, user data, or data deletion. Invoke it, record answers inline in the design doc or PR description, then write the code. Skipping this is how auth bypass, token leakage, and delete-the-wrong-record bugs ship. If the feature touches any of those surfaces and this hasn't run, the design is incomplete — do not proceed to implementation."
description: "REQUIRED before writing code for any feature touching auth, payments, credentials, tokens, sessions, file upload, user data, untrusted input, deserialization, network endpoints, or data deletion. Scope by that surface, not the task label — a research/experimental/local-only feature with none of it doesn't need this. Invoke it, record answers inline in the design doc or PR description, then write the code. Skipping this is how auth bypass, token leakage, and delete-the-wrong-record bugs ship. If the feature touches any of those surfaces and this hasn't run, the design is incomplete — do not proceed to implementation."
version: 0.1.0
allowed-tools: [Read, Glob, Grep]
---
Expand All @@ -11,6 +11,12 @@ A designer's skill, not a scanner. The goal is to catch the flaw in the whiteboa

> Pair with `rafter-code-review` (structured review *during* PR) and the `rafter` skill (automated detection of what slipped through). This skill is the earliest stage — prevention before the code exists.

## When this applies (and when it doesn't)

Engage this design walk when the feature you're about to build touches a real security surface: auth or access control, credentials / secrets / tokens / sessions, user or untrusted input, SQL or query construction, shell / `exec`, file paths or uploads, (de)serialization, crypto, network-facing endpoints or outbound fetchers, data deletion, or new dependencies.

If the thing you're designing has **none** of that — a research / experimental / local-only / throwaway piece such as a training script, analysis pipeline, plot, model-eval harness, notebook, or pure computation over trusted local data — a quick surface check is enough and you can proceed to implementation without the full walk. Judge by the surface, not by whether the work is called "research": a research feature that stores user data, handles a token, or opens a network endpoint is back on the engage list.

## How to use this skill

1. Identify what's being designed (below). If multiple apply, walk them in the order listed — `threat-modeling` last, as a capstone.
Expand Down
26 changes: 18 additions & 8 deletions node/resources/skills/rafter/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
---
name: rafter
description: "Entry point for rafter. Invoke when a sub-skill is unclear, or when the task needs `rafter run` (remote SAST+SCA), `rafter secrets` (local secrets-only), `rafter audit`, policy checks, or command-risk evaluation. If a task is security-relevant and no rafter skill or CLI call has been made, invoke this before handing the task off — an un-evaluated \"done\" on security-relevant work is not done."
description: "Entry point for rafter. Invoke when a sub-skill is unclear, or when the task needs `rafter run` (remote SAST+SCA), `rafter secrets` (local secrets-only), `rafter audit`, policy checks, or command-risk evaluation. Scope by security surface, not task label: engage when the diff touches auth, credentials/secrets/tokens, untrusted input, SQL, shell/exec, file paths, deserialization, crypto, network endpoints, data deletion, or dependencies; for research/experimental/local-only code with none of that, a quick surface check is enough. When such surface IS present and no rafter skill or CLI call has been made, invoke this before handing the task off — an un-evaluated \"done\" on genuine security surface is not done."
version: 0.7.0
allowed-tools: [Bash, Read]
---

# Rafter — Security Toolkit for AI Workflows

## Picking the right tier — DO NOT stop at "local"
## When Rafter applies (and when it doesn't)

Rafter is a **surface-driven** gate, not a task-label gate. Read the diff's actual security surface first; let that — not whether the work is called "research" — pick the branch.

**Engage fully** (walk the review + run `rafter run`) when the change touches any of: auth / sessions / access control · credentials, secrets, tokens, keys · user-supplied or otherwise untrusted input · SQL or any other query / command construction · shell, `exec`, or subprocess invocation · file paths (read, write, upload, traversal) · deserialization or parsing of untrusted data · crypto primitives · network-facing endpoints or outbound fetchers (SSRF surface) · data deletion or other destructive mutations · dependency, lockfile, or manifest changes.

**Back off** when the change touches **none** of those — research / experimental / exploratory / local-only / throwaway code: training scripts, data analysis, plotting, model eval, notebooks, pure computation over trusted local data. A quick surface check is enough; with no security surface, proceed **without** the full `rafter-code-review` + `rafter run`.

Rafter ships three tiers. **They are not interchangeable.** The local tier is narrow; skipping remote analysis is the #1 way agents under-use rafter.
**The rule that decides it:** the "research" label buys nothing. Research code that reads a secret, shells out, hits the network, parses untrusted input, or bumps a dependency is on the engage list and gets the full gate. Judge by the surface of the diff, not by what the task is called.

---

## Picking the right tier — DO NOT stop at "local"

1. **Local (`rafter secrets`)** — secrets only. Regex + betterleaks for hardcoded API keys, tokens, private keys. Fast, offline, no key. **This is NOT a code security scan.** It will not find SQL injection, SSRF, auth bugs, insecure deserialization, logic flaws, or dependency vulns. If an agent's entire rafter interaction was `rafter secrets .` and it exited clean, the agent has done secret-hygiene only — not security review.
2. **Remote fast (`rafter run`, default mode)** — SAST + SCA + secrets via the Rafter API. This is the real code-analysis pass: dataflow, taint, known-vulnerable dependencies, crypto misuse, injection sinks. Needs `RAFTER_API_KEY`.
3. **Remote plus (`rafter run --mode plus`)** — agentic deep-dive: LLM-guided investigation of suspicious patterns the rules engine flags. Slower, higher signal. Code is deleted server-side after the run.
Three tiers, **not interchangeable**. The local tier is narrow; skipping remote analysis is the #1 way agents under-use rafter.

**Default expectation for a security-relevant task**: run `rafter run`. Fall back to `rafter secrets` only when no API key is available or you specifically need offline secret-hygiene. If you've only run the local scanner, say so explicitly — don't claim the code was "scanned" without qualification.
1. **`rafter secrets`** — hardcoded credentials only (regex + betterleaks). Fast, offline, no key. **NOT a code security scan** — it finds no SQL injection, SSRF, auth bugs, insecure deserialization, logic flaws, or dependency vulns. A clean `rafter secrets .` is secret-hygiene, not security review.
2. **`rafter run`** (default mode) — the real code-analysis pass: SAST + SCA + secrets (dataflow, taint, known-vulnerable deps, crypto misuse, injection sinks). Needs `RAFTER_API_KEY`.
3. **`rafter run --mode plus`** — agentic deep-dive: LLM-guided investigation of what the rules engine flags. Slower, higher signal; code is deleted server-side after the run. **PAID tier — consumes the user's credits; ask before running it.** If `scan.plus_requires_approval` is set, Plus refuses without `--yes` / `RAFTER_CONFIRM=1`.

Stable exit codes, stable JSON shapes, deterministic findings. Safe to chain in CI and in agent loops.
**Default for a security-relevant task: `rafter run`.** Fall back to `rafter secrets` only when no API key is available — and say so explicitly; don't claim the code was "scanned" without qualification. Deterministic findings, stable exit codes and JSON shapes — safe to chain in CI and in agent loops.

---

Expand Down
6 changes: 4 additions & 2 deletions node/src/commands/agent/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,9 +346,11 @@ function codexHooks(): ComponentSpec {
cfg.hooks.PostToolUse,
(e) => hookEntryMatchesRafter(e, "rafter hook posttool"),
);
// Bash + apply_patch per Codex hook docs (rf-ovql verification).
// Bash + apply_patch per Codex hook docs (rf-ovql verification). PostToolUse
// mirrors that write/exec surface (narrowed from `.*` to skip Read/MCP log
// noise), matching claude-code posttool scoping (sable-h0ah).
cfg.hooks.PreToolUse.push({ matcher: "Bash|apply_patch", hooks: [pre] });
cfg.hooks.PostToolUse.push({ matcher: ".*", hooks: [post] });
cfg.hooks.PostToolUse.push({ matcher: "Bash|apply_patch", hooks: [post] });
writeJson(hooksPath, cfg);
},
uninstall: () => {
Expand Down
9 changes: 5 additions & 4 deletions node/src/commands/agent/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ function printDryRunPlan(plan: {
if (plan.wantCodex) {
console.log();
console.log("Codex CLI (--with-codex):");
W(path.join(plan.root, ".codex", "hooks.json"), "PreToolUse: Bash|apply_patch, PostToolUse: .*");
W(path.join(plan.root, ".codex", "hooks.json"), "PreToolUse: Bash|apply_patch, PostToolUse: Bash|apply_patch");
for (const s of AGENT_SKILLS) {
W(path.join(plan.root, ".agents", "skills", s.name, "SKILL.md"));
}
Expand Down Expand Up @@ -383,10 +383,11 @@ function installCodexHooks(root: string): void {
config.hooks.PreToolUse.push(
{ matcher: "Bash|apply_patch", hooks: [preHook] },
);
// PostToolUse fires for the same tool surface; .* keeps all events in the
// audit log without filtering.
// PostToolUse mirrors the same write/exec surface (Bash + apply_patch);
// narrowed from `.*` so posttool doesn't fire on every Read/MCP call
// (log noise + latency), matching the claude-code posttool scoping (sable-h0ah).
config.hooks.PostToolUse.push(
{ matcher: ".*", hooks: [postHook] },
{ matcher: "Bash|apply_patch", hooks: [postHook] },
);

fs.writeFileSync(hooksPath, JSON.stringify(config, null, 2), "utf-8");
Expand Down
30 changes: 21 additions & 9 deletions node/src/commands/agent/instruction-block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,28 @@ export const RAFTER_MARKER_END = "<!-- rafter:end -->";

/** Rafter security context block for agent instruction files (CLAUDE.md, AGENTS.md, etc.) */
export const RAFTER_INSTRUCTION_BLOCK = `${RAFTER_MARKER_START}
## Security: Rafter (required review gate)
## Security: Rafter (surface-driven review gate)

Rafter is this project's security review gate. A security-relevant task is
**not complete** until a rafter skill (or \`rafter run\`) has reviewed it.
Don't mark done, don't hand off, don't open a PR without that pass.
Rafter is this project's security review gate — driven by the change's **security
surface**, not by the task label. When a diff touches a real surface (below), it is
**not complete** until a rafter skill (or \`rafter run\`) has reviewed it: don't mark
done, don't hand off, don't open a PR without that pass. When it touches **none** of
that surface — research / experimental / local-only / throwaway code (training
scripts, data analysis, plotting, model eval, notebooks, pure computation over
trusted local data) — a quick surface check is enough; proceed without the full
gate. Judge by the diff's actual surface, not the "research" label: research code
that reads a secret, shells out, hits the network, parses untrusted input, or bumps
a dependency still gets the full gate.

**Stop and invoke before continuing:**
**Stop and invoke when the change touches that surface:**

- Designing auth, payments, credentials, tokens, sessions, file upload,
user data, or data deletion → **\`rafter-secure-design\`** (before writing code).
- Diff touches user input, SQL, shell, auth, credentials, file paths, or
serialization → **\`rafter-code-review\`** + \`rafter run\` (before declaring done).
user/untrusted data, deserialization, network endpoints, or data deletion
→ **\`rafter-secure-design\`** (before writing code).
- Diff touches user/untrusted input, SQL/query building, shell/exec, auth,
credentials, file paths, (de)serialization, crypto, network endpoints/outbound
fetchers (SSRF), data deletion, or dependencies → **\`rafter-code-review\`** +
\`rafter run\` (before declaring done).
- About to install or forward a third-party SKILL.md, MCP manifest, Cursor
rule, or agent config → **\`rafter-skill-review\`** (before copying anywhere).
- Security-adjacent but the angle isn't clear → **\`rafter\`** (the router skill,
Expand All @@ -28,7 +38,9 @@ Don't mark done, don't hand off, don't open a PR without that pass.
- \`rafter run\` — remote SAST + SCA + secrets (real code analysis, needs \`RAFTER_API_KEY\`)
- \`rafter secrets .\` — local secrets only (offline; NOT a code-security scan)
- \`rafter run --mode plus\` — everything in default (\`--mode fast\`) plus
powerful agentic deep-dives (needs \`RAFTER_API_KEY\`)
powerful agentic deep-dives (needs \`RAFTER_API_KEY\`). **Plus is a PAID tier
and consumes the user's credits — ask the user before running it.** Enforced
when \`scan.plus_requires_approval\` is set (then pass \`--yes\` to confirm).
${RAFTER_MARKER_END}`;

/**
Expand Down
Loading
Loading