Skip to content

Latest commit

 

History

History
1188 lines (881 loc) · 51.6 KB

File metadata and controls

1188 lines (881 loc) · 51.6 KB

Rafter CLI Command Spec

Overview

Rafter is the security toolkit for developers. Stable output contracts mean any developer can classify outcomes (clean / findings / retryable error / fatal error) and act without reading prose.

Free forever for individuals and open source. No account required. No telemetry. All local security features (secret scanning, policy enforcement, pre-commit hooks, audit logging, MCP server) work without an API key, without network access, and without usage limits.

The CLI follows UNIX principles:

  • Scan results go to stdout — consistent JSON structure, pipe-friendly
  • Status messages go to stderr
  • Exit codes are a stable contract — documented semantics across versions
  • Deterministic — same inputs produce the same findings for a given CLI version
  • Side effects are explicit — config and audit logs write to ~/.rafter/; some commands (e.g. ci init, policy export) accept --output to write files
  • No exfiltration — no code leaves your machine unless you explicitly use the remote API, and is deleted immediately after analysis completes

Exit Codes

Remote Commands (rafter run, rafter get, rafter usage)

Code Meaning
0 Success
1 General error
2 Scan not found (HTTP 404)
3 Quota exhausted (HTTP 429 or 403 scan-mode limit)
4 Insufficient scope / forbidden (HTTP 403)

Local Secret Scan (rafter secrets)

Code Meaning
0 Clean — no secrets detected
1 Findings — one or more secrets detected
2 Runtime error — path not found, not a git repo, invalid ref

Docs (rafter docs list, rafter docs show)

Code Meaning
0 Success
1 General error (read failure, URL fetch failure with no cache)
2 Selector did not match any configured doc
3 No docs configured in .rafter.yml

Global Options

  • -a, --agent — Plain output (no colors, no emoji)
  • -V, --version — Print version and exit (preferred form; rafter version subcommand also works)
  • -h, --help — Show help

Remote Code Analysis Commands

Important: The code analysis engine runs against the remote repository (e.g., on GitHub), not your local files. Auto-detection uses your local Git configuration to determine which remote repository and branch to scan.

rafter run [OPTIONS]

Aliases: rafter scan, rafter scan remote

Trigger a new security scan for a repository.

  • -k, --api-key TEXT — API key or RAFTER_API_KEY env var
  • -r, --repo TEXT — org/repo (default: auto-detected from git remote)
  • -b, --branch TEXT — branch (default: current branch or 'main')
  • -f, --format [json|md] — output format (default: md)
  • -m, --mode [fast|plus] — scan mode (default: fast). Fast runs SAST, secret detection, and dependency checks. Plus adds agentic deep-dive analysis that examines your codebase the way a professional cybersecurity auditor would — tracing data flows and reasoning about business logic on top of the full SAST/SCA toolchain.
  • --github-token TEXT — GitHub PAT for private repos (or RAFTER_GITHUB_TOKEN env var)
  • --skip-interactive — fire-and-forget mode (don't poll for completion)
  • --quiet — suppress status messages on stderr
  • -h, --help

rafter get SCAN_ID [OPTIONS]

Retrieve results from a scan.

  • -k, --api-key TEXT — API key or RAFTER_API_KEY env var
  • -f, --format [json|md] — output format (default: md)
  • --interactive — poll until scan completes (10-second intervals)
  • --quiet — suppress status messages on stderr
  • -h, --help

Vulnerability levels (JSON output): The level field on each vulnerability uses SARIF standard values: "error", "warning", or "note".

rafter usage [OPTIONS]

Check API quota and usage statistics.

  • -k, --api-key TEXT — API key or RAFTER_API_KEY env var
  • -h, --help

Brief — Agent-Independent Knowledge Delivery

rafter brief [TOPIC]

Print rafter knowledge reformatted for CLI output. Designed for any agent on any platform — pipe to memory, save to instructions, or just read in-session.

With no topic, lists available topics and usage examples.

Topics:

Topic Description
security Local security toolkit — scanning, auditing, policy enforcement
scanning Remote SAST/SCA code analysis via Rafter API
commands Condensed command reference for all rafter commands
setup Setup instructions for all supported platforms
setup/<platform> Platform-specific setup (claude-code, codex, gemini, cursor, windsurf, aider, openclaw, continue, generic)
all Everything — full security + scanning + setup briefing

Examples:

# List available topics
rafter brief

# Get the local security briefing
rafter brief security

# Platform-specific setup guide
rafter brief setup/claude-code

# For agents without native skill support — load context manually
rafter brief security    # save to memory/instructions
rafter brief commands    # save command reference

# Pipe to a file for manual skill creation
rafter brief scanning > ~/.agents/skills/rafter/SKILL.md

Local Security Commands

All local security commands work offline. No API key required.

rafter agent init [OPTIONS]

Initialize local security system. Creates config and detects available development environments. Integrations are opt-in — use --with-* flags or --all to install. There are NO --skip-* flags.

  • --risk-level <level>minimal, moderate (default), or aggressive
  • --with-openclaw — install OpenClaw integration
  • --with-claude-code — install Claude Code integration
  • --with-codex — install Codex CLI integration
  • --with-gemini — install Gemini CLI integration
  • --with-aider — install Aider integration
  • --with-cursor — install Cursor integration
  • --with-windsurf — install Windsurf integration
  • --with-continue — install Continue.dev integration
  • --with-betterleaks — download and install Betterleaks binary (the gitleaks successor)
  • --all — install all detected integrations and download Betterleaks
  • -i, --interactive — guided setup — prompts for each detected integration (Node only)
  • --update — re-download betterleaks and reinstall integrations without resetting config
  • --local — install integration configs into the current working directory instead of the user home. Writes to ./.claude/, ./.agents/, ./.gemini/, ./.cursor/ etc. Supports --with-claude-code, --with-codex, --with-gemini, --with-cursor. User-level side effects (global config, agent.environments.*.enabled, auto-detection, betterleaks download) are suppressed in this mode. Intended for benchmark harnesses, one-off project setup, and ephemeral containers.

rafter agent list [OPTIONS]

List every installable component across all supported platforms with its current state. Useful for auditing what's active and for scripting toggles.

  • --json — machine-readable output
  • --installed — show only components currently installed
  • --detected — show only components whose platform was detected on disk

A component is a <platform>.<kind> pair, where kind ∈ {hooks, mcp, instructions, skills}. Each component has a state:

State Meaning
installed Rafter is active in this platform's config
not-installed Platform is detected but Rafter is not wired in
not-detected Platform config directory does not exist on disk

JSON Output (--json)

{
  "components": [
    {
      "id": "cursor.mcp",
      "platform": "cursor",
      "kind": "mcp",
      "description": "Cursor MCP server entry",
      "path": "/home/user/.cursor/mcp.json",
      "detected": true,
      "installed": true,
      "state": "installed"
    }
  ]
}

Exit code: 0 on success.

rafter agent enable COMPONENTS... [OPTIONS]

Install one or more components. Accepts one or more component IDs (e.g. cursor.mcp, claude-code.hooks). Aliases: claude.*claude-code.*, continuedev.*continue.*.

  • --force — install even when the platform is not detected (creates the platform directory)

Exit codes:

  • 0 — all components installed (or were already installed — the operation is idempotent)
  • 1 — unknown component ID (the error lists known IDs on stderr)
  • 2 — platform not detected for one or more components (use --force to override)

Persists agent.components.<id>.enabled = true in ~/.rafter/config.json.

rafter agent disable COMPONENTS... [OPTIONS]

Uninstall one or more components without affecting other components on the same platform. For MCP files, removes the rafter server entry while preserving unrelated entries. For hooks, removes only rafter's hook commands. For instruction files, strips the <!-- rafter:start --> / <!-- rafter:end --> block and leaves surrounding content intact.

Exit code: 0 on success (missing/not-installed components are reported, not errored).

Persists agent.components.<id>.enabled = false in ~/.rafter/config.json.

rafter skill list [OPTIONS]

List rafter-authored skills shipped with this CLI and their install state across every supported platform. A skill is a bundled SKILL.md file (e.g. rafter, rafter-agent-security, rafter-secure-design, rafter-code-review, rafter-skill-review). A platform is one of claude-code, codex, openclaw, cursor.

  • --json — machine-readable output
  • --installed — only show (skill, platform) pairs where the skill is installed
  • --platform <name> — restrict to a single platform

JSON shape (--json):

{
  "skills": [
    { "name": "rafter-secure-design", "version": "0.1.0", "description": "..." }
  ],
  "installations": [
    {
      "name": "rafter-secure-design",
      "platform": "claude-code",
      "path": "/home/user/.claude/skills/rafter-secure-design/SKILL.md",
      "detected": true,
      "installed": true,
      "version": "0.1.0"
    }
  ]
}

Exit codes: 0 on success; 1 on unknown --platform.

rafter skill install NAME [OPTIONS]

Install a rafter-authored skill. The SKILL.md file is copied (not symlinked) so the install is reproducible and does not depend on the CLI's installation path at run time.

  • --platform <name...> — target platform(s); repeatable. Default: every platform whose config directory is detected on this machine.
  • --to <path> — explicit destination. If <path> ends in .md or .mdc, used as the literal file path. Otherwise treated as a skills base directory, installing to <path>/<name>/SKILL.md.
  • --force — when no --platform is given and no platform is detected, install to every known platform anyway.

Destinations per platform:

Platform Path
claude-code ~/.claude/skills/<name>/SKILL.md
codex ~/.agents/skills/<name>/SKILL.md
openclaw ~/.openclaw/skills/<name>.md
cursor ~/.cursor/rules/<name>.mdc

Exit codes:

  • 0 — install(s) succeeded (re-running is idempotent; the file is overwritten in place)
  • 1 — unknown skill name or unknown platform
  • 2 — no target platform detected (and --force was not passed), or an explicit --platform was not detected and --force was not passed

Persists skillInstallations.<platform>.<name> = { enabled: true, version, updatedAt } in ~/.rafter/config.json.

rafter skill uninstall NAME [OPTIONS]

Remove a rafter-authored skill from one or more platforms.

  • --platform <name...> — target platform(s). Default: every platform on which the skill is currently installed.

Missing files are reported, not errored. If the skill is not installed anywhere, the command exits 0 and reports "no changes".

Exit codes:

  • 0 — uninstall(s) succeeded or were already absent
  • 1 — unknown skill name or unknown platform

Persists skillInstallations.<platform>.<name>.enabled = false in ~/.rafter/config.json.

rafter secrets [PATH] [OPTIONS]

Aliases: rafter scan local, rafter agent scan (both still supported for backward compatibility)

Scan files or directories for hardcoded secrets (21+ patterns + betterleaks). Secrets only — not a full code-security scan. For SAST + SCA, use rafter run.

The secrets spelling is preferred because it makes the scope explicit; scan local reads as "the full scan, locally" which it is not.

  • [PATH] — file or directory (default: .)
  • -q, --quiet — only output if secrets found
  • --json — output as JSON
  • --format <format> — output format: text, json, or sarif (default: text)
  • --staged — scan git staged files only
  • --diff <ref> — scan files changed since a git ref (e.g., HEAD~1, main)
  • --engine <engine>betterleaks, patterns, or auto (default).
  • --baseline — filter findings present in the saved baseline (see rafter agent baseline)
  • --watch — watch path for file changes and re-scan on each change; Ctrl+C exits
  • --history — scan the full git history for previously-committed secrets (requires --engine betterleaks; invokes betterleaks git against the repo history)
  • --gitignore / --no-gitignore — when scanning a directory, honor .gitignore rules (default: on). Implemented via git check-ignore --stdin --no-index -z against the scan root's git work tree; honors nested .gitignore, negations, .git/info/exclude, and the configured global excludes file. Silently falls back to no-op when the scan target is outside any git work tree.

Exit codes: 0 = clean, 1 = secrets found, 2 = runtime error.

Note: --watch mode does not exit on findings — it prints results inline and keeps watching. Findings are logged to audit.jsonl in real time. Requires chokidar (Node, bundled) or watchdog (Python: pip install watchdog).

JSON Output (--json)

When --json is passed, output is a JSON object to stdout with a results array and scan-mode metadata. Both Node and Python produce identical schema:

{
  "_note": "Local-only scan: pattern-based detection without agentic-intelligence triage. Findings have not been evaluated for context (public exposure, key validity, deployment environment). Investigate each before acting; do not dismiss. Run 'rafter run' for backend agentic analysis.",
  "scan_mode": "local",
  "triage_applied": false,
  "results": [
    {
      "file": "/absolute/path/to/file.ts",
      "matches": [
        {
          "pattern": {
            "name": "AWS Access Key",
            "severity": "critical",
            "description": "Detects AWS access key IDs"
          },
          "line": 42,
          "column": 7,
          "redacted": "AKIA************MPLE"
        }
      ]
    }
  ]
}

Top-level field reference:

Field Type Description
_note string Human-readable scan-mode note. JSON has no comments — this _* key is the convention. Surface it to users when reporting findings.
scan_mode string Always "local" for local secret scans. Programmatic flag for agents to detect that no agentic-intelligence triage was applied.
triage_applied boolean Always false for local scans. true would indicate backend agentic context evaluation (i.e., rafter run).
results array Per-file findings.

Per-file field reference:

Field Type Description
results[].file string Absolute path to the scanned file
results[].matches array List of secret matches in this file
results[].matches[].pattern.name string Human-readable pattern name
results[].matches[].pattern.severity string "low", "medium", "high", or "critical"
results[].matches[].pattern.description string Pattern description (may be empty)
results[].matches[].line number|null 1-based line number, null if unknown
results[].matches[].column number|null 1-based column number, null if unknown
results[].matches[].redacted string Redacted secret value (first/last 4 chars visible for values >8 chars, fully masked otherwise)

The raw secret value is never included in JSON output.

Why _note? Local scans are pattern-only — they cannot tell whether a finding is in a public-facing file, whether the key is still valid, or whether it ever shipped. Backend scans (rafter run) apply agentic context. The _note exists so agents and reviewers don't treat local findings as final verdicts — they should investigate each, but the absence of agentic triage is not an excuse to dismiss findings.

Suppression-aware output shape

When .rafter.yml ignore: rules (or .rafterignore) hide one or more findings, an _suppressed field is added to the wrapper so the consumer can see what was suppressed and why. The field is omitted when no findings are hidden.

{
  "_note": "Local-only scan: pattern-based detection without agentic-intelligence triage. ...",
  "scan_mode": "local",
  "triage_applied": false,
  "results": [ /* same shape as without suppression */ ],
  "_suppressed": [
    {
      "file": "/abs/path/tests/fixtures/fake.env",
      "line": 3,
      "column": 7,
      "rule": "AWS Access Key",
      "severity": "critical",
      "reason": "test fixtures with fake AWS keys",
      "source": ".rafter.yml"
    }
  ]
}
Field Type Description
_suppressed array (optional) Each hidden finding, with file/line/column, rule name, severity, reason, and source. Absent when no suppression occurred.
_suppressed[].source string ".rafter.yml" for policy ignore rules, ".rafterignore" for the legacy file
_suppressed[].reason string|null The reason: from the matching ignore rule, or null for .rafterignore lines

Exit code is unaffected by suppression — exit 1 is returned only when at least one non-suppressed finding remains.

rafter agent exec COMMAND [OPTIONS]

Execute shell command with risk assessment and approval workflow.

  • COMMAND — shell command string
  • --skip-scan — skip pre-execution file scanning
  • --force — skip approval prompts (logged as override)

Risk tiers: critical (blocked), high (approval required), medium (approval on moderate+), low (allowed).

rafter skill review [PATH_OR_URL] [OPTIONS]

Security review of a third-party skill, plugin, or agent extension before installing it — or (--installed) an audit of every skill already on disk across detected agent directories. Operates on a local file, a local directory, a git URL (https / ssh / .git), or (in --installed mode) the whole machine. Emits a structured deterministic report: secrets, external URLs, high-risk shell patterns, obfuscation signals, binary/suspicious file inventory, and SKILL.md frontmatter (name, version, allowed-tools).

  • PATH_OR_URL — one of:

    • a local path (file or directory)
    • a raw git URL (https / ssh / .git) — shallow-cloned to a temp dir for the review
    • a remote shorthand:
      • github:<owner>/<repo>[/<subpath>] — resolves to https://github.com/<owner>/<repo>.git
      • gitlab:<owner>/<repo>[/<subpath>] — resolves to https://gitlab.com/<owner>/<repo>.git
      • npm:<pkg>[@<version>] — fetches the tarball from the npm registry (default @latest); supports scoped packages (e.g. npm:@scope/pkg@1.2.3)

    Omit when using --installed.

  • --json — emit JSON to stdout (shortcut for --format json)

  • --format text|json — output format (default: text)

  • --cache-ttl <duration> — TTL for the persistent shorthand-resolution cache. Accepts <n>[s|m|h|d] (e.g. 30s, 5m, 24h, 1d). Default: 24h. Only applies to shorthand forms.

  • --no-cache — bypass the persistent shorthand cache: always fetch fresh and never write to cache. Temp dirs used during the review are removed on exit.

  • --installed — audit every installed skill across detected agent skill directories instead of a path

  • --agent <name> — restrict --installed to a single agent (claude-code, codex, openclaw, or cursor)

  • --summary — print a terse human-readable table instead of JSON (only with --installed)

Persistent shorthand cache

Shorthand resolutions and fetched content are cached under ~/.rafter/skill-cache/ (override: RAFTER_SKILL_CACHE_DIR) in a two-level layout:

~/.rafter/skill-cache/
├── resolutions/<sha256(shorthand)>.json   # { shorthand, sha|version, resolvedAt }
└── content/<content-key>/                 # keyed by git SHA or npm version
    ├── meta.json                          # { source, shorthand, key, sha|version, fetchedAt }
    └── content/                           # extracted working tree
  • github:/gitlab: content keys: git-<kind>-<owner>-<repo>-<sha-40> (content-addressed; immutable).
  • npm: content keys: npm-<pkg-safe>-<version> (immutable per version).
  • --cache-ttl applies only to the resolution layer (how long github:foo/bar → SHA stays fresh). Content under a resolved SHA/version is immutable and kept until manually pruned.
  • Corrupt cache entries (missing meta.json, empty content/) are detected and dropped automatically — the next run re-fetches.
  • With --no-cache, neither layer is read or written; content is extracted to a temp dir and deleted after the review.

Remote fetch exit codes

In addition to the path-mode codes listed below, shorthand forms may exit 2 for:

  • unresolvable git ls-remote (network failure, invalid owner/repo, private repo without creds)
  • npm registry 404, HTTP failure, or unknown @version
  • requested subpath missing in the fetched tree
  • malformed shorthand (e.g. npm: with no package name)

JSON shape (--json):

{
  "target": {
    "input": "./their-skill/",
    "kind": "directory",
    "resolvedPath": "/abs/path/to/their-skill"
  },
  "frontmatter": [
    {
      "file": "SKILL.md",
      "name": "their-skill",
      "version": "0.1.0",
      "description": "...",
      "allowedTools": ["Bash", "Read"]
    }
  ],
  "secrets": [
    { "pattern": "AWS Secret Access Key", "severity": "critical", "file": "SKILL.md", "line": 17, "redacted": "AWS_***EKEY" }
  ],
  "urls": ["https://example.com/install.sh"],
  "highRiskCommands": [
    { "command": "curl | sh", "file": "SKILL.md", "line": 11 }
  ],
  "obfuscation": [
    { "kind": "bidi-override", "file": "SKILL.md", "line": 16, "sample": "U+202E" }
  ],
  "inventory": {
    "textFiles": 8,
    "binaryFiles": 1,
    "suspiciousFiles": [ { "path": "helper.so", "bytes": 4, "kind": "binary" } ]
  },
  "summary": {
    "severity": "critical",
    "findings": 4,
    "reasons": ["1 secret finding(s)", "1 high-risk command(s)", "1 hard obfuscation signal(s)"]
  }
}

Shorthand provenance (target.source): when PATH_OR_URL is a shorthand, target.kind is "github", "gitlab", or "npm" (instead of "directory"/"file"/"git-url"), and target.source is added with provenance info. When a subpath was requested, target.skillRelDir is the path within the cloned tree that was audited.

// github/gitlab:
"source": { "url": "https://github.com/foo/bar.git", "sha": "<40-hex>", "subpath": "skills/review", "cacheHit": false }
// npm:
"source": { "url": "https://registry.npmjs.org/.../pkg-1.2.3.tgz", "version": "1.2.3", "cacheHit": true }

Multi-SKILL.md mode: when a directory or fetched tree contains more than one SKILL.md, the review audits each independently (scoped to its containing directory) and emits a combined report. The JSON shape switches to:

{
  "target": {
    "input": "github:foo/bar",
    "kind": "github",
    "resolvedPath": "/abs/.../content",
    "mode": "multi-skill",
    "source": { "url": "...", "sha": "...", "cacheHit": false }
  },
  "skills": [
    {
      "relDir": "skills/alpha",
      "name": "alpha",
      "version": "1.0.0",
      "report": { "...": "same shape as the single-skill JSON above, with target.skillRelDir set" }
    }
  ],
  "summary": {
    "totalSkills": 2,
    "severityCounts": { "clean": 1, "low": 0, "medium": 0, "high": 0, "critical": 1 },
    "findings": 4,
    "worst": "critical",
    "reasons": ["1 critical skill(s)"]
  }
}

The exit code for multi-skill mode follows the worst per-skill severity: 0 iff every skill is clean, else 1.

Obfuscation kinds: zero-width-char, bidi-override, base64-blob, hex-escape-rope, html-comment-imperative.

Severity tiers (in summary.severity): clean, low, medium, high, critical. A bidi-override or html-comment-imperative is always critical. High-risk commands escalate to at least high. Long base64 blobs / zero-width chars / binary-blob files escalate to at least medium.

Exit codes (default PATH_OR_URL mode):

  • 0summary.severity == "clean" (no findings)
  • 1 — one or more findings (any non-clean severity)
  • 2 — input path does not exist, or git clone failed; or neither PATH_OR_URL nor --installed was given

Limits: directory walks skip .git / node_modules / .venv, cap at 2,000 files, and treat files larger than 2 MiB as suspicious binaries. Files containing null bytes in the first 4 KiB are treated as binary.

--installed mode

Walks every detected agent skill directory and runs the same audit pipeline on each SKILL.md (or .md / .mdc) file found. Directory layout per agent:

Platform Skill path
claude-code ~/.claude/skills/<name>/SKILL.md
codex ~/.agents/skills/<name>/SKILL.md
openclaw ~/.openclaw/skills/<name>.md
cursor ~/.cursor/rules/<name>.mdc

Missing dirs and unreadable entries are silently skipped — a single permission-denied subdir never aborts the walk. Results are ordered deterministically by (platform, name) for golden-file stability.

JSON shape (--installed, default output):

{
  "target": { "mode": "installed", "agent": "all" },
  "installations": [
    {
      "platform": "claude-code",
      "skill": "rafter-secure-design",
      "path": "/home/user/.claude/skills/rafter-secure-design/SKILL.md",
      "report": { "...": "per-skill report, same shape as `rafter skill review <path>`" }
    }
  ],
  "summary": {
    "totalSkills": 3,
    "severityCounts": { "clean": 1, "low": 0, "medium": 1, "high": 0, "critical": 1 },
    "platformCounts": { "claude-code": 2, "openclaw": 1 },
    "findings": 6,
    "worst": "critical"
  }
}

target.agent is "all" by default or the platform name when --agent <name> is given.

Exit codes (--installed mode):

  • 0 — no installed skill has a high or critical finding
  • 1 — at least one installed skill has high or critical severity, or an argument was invalid (--agent unknown, both PATH_OR_URL and --installed passed)

Note the looser gate vs. the PATH_OR_URL mode: --installed tolerates medium and below so a routine rafter skill review --installed in CI doesn't fail on noisy obfuscation signals across dozens of skills. Use rafter skill review <path> for a strict single-skill gate.

rafter agent audit-skill SKILL_PATH [OPTIONS]

Deprecated — use rafter skill review <path-or-url> instead. Still functional; emits a deprecation warning to stderr.

  • SKILL_PATH — path to skill file (.md)
  • --skip-openclaw — skip OpenClaw integration, show manual review prompt
  • --json — output as JSON

Quick scan: secrets, URLs, high-risk commands. Deep analysis (OpenClaw): 12-dimension review.

rafter agent audit [OPTIONS]

View security audit log.

  • --last <n> — show last N entries (default: 10)
  • --event <type> — filter by event type
  • --agent <type> — filter by agent type (openclaw, claude-code)
  • --since <date> — entries since date (YYYY-MM-DD)
  • --share — generate a redacted excerpt for issue reports
  • --verify — verify the tamper-evident hash chain and report any breaks (exit 0 = intact, 1 = tampering detected)

Event types: command_intercepted, secret_detected, content_sanitized, policy_override, scan_executed, config_changed.

Audit Log Schema

The audit log is written to ~/.rafter/audit.jsonl as newline-delimited JSON (JSONL). Each line is one event. Both Node and Python CLIs write to the same file.

Base fields (all events):

Field Type Required Description
timestamp string (ISO 8601) yes UTC timestamp of the event
sessionId string yes Unique session identifier ({epoch_ms}-{random})
eventType string yes One of the event types below
agentType string no "openclaw" or "claude-code"
cwd string no Working directory where the event was recorded
gitRepo string no Absolute path to the enclosing git repository root, if any
prevHash string|null yes SHA-256 of the prior line (or null for the first entry) — forms the tamper-evident hash chain verified by rafter agent audit --verify

Log location: Defaults to ~/.rafter/audit.jsonl. Overridable per project via .rafter.ymlagent.audit.logPath (e.g. set to .rafter/audit.jsonl for a repo-local log that each contributor can verify independently).

action object (optional):

Field Type Required Description
action.command string no Shell command string (present on command_intercepted, optional on policy_override)
action.tool string no Tool name that triggered the event
action.riskLevel string no "low", "medium", "high", or "critical"

securityCheck object (required):

Field Type Required Description
securityCheck.passed boolean yes Whether the security check passed
securityCheck.reason string no Human-readable explanation
securityCheck.details object no Structured details (used by content_sanitized)

resolution object (required):

Field Type Required Description
resolution.actionTaken string yes "blocked", "allowed", "overridden", or "redacted"
resolution.overrideReason string no Reason for override (only on policy_override)

Event types:

Event Description Typical actionTaken
command_intercepted Shell command evaluated against policy allowed, blocked, overridden
secret_detected Secret found in files or staged content blocked, allowed
content_sanitized Sensitive patterns redacted from output redacted
policy_override User overrode a security policy overridden
scan_executed File scan performed
config_changed Security configuration modified

Redaction behavior: The audit log never contains raw secret values. For secret_detected events, only the secret type and location are recorded (e.g., "AWS Access Key detected in config.js"). For content_sanitized, only the count and content type are stored.

Example entries:

{"timestamp":"2026-02-20T10:30:45.123Z","sessionId":"1740047445123-abc123","eventType":"command_intercepted","agentType":"claude-code","action":{"command":"git push --force","riskLevel":"high"},"securityCheck":{"passed":false,"reason":"High-risk command requires approval"},"resolution":{"actionTaken":"blocked"}}
{"timestamp":"2026-02-20T10:25:12.456Z","sessionId":"1740047445123-abc123","eventType":"secret_detected","agentType":"openclaw","action":{"riskLevel":"critical"},"securityCheck":{"passed":false,"reason":"AWS Access Key detected in config.js"},"resolution":{"actionTaken":"blocked"}}

Size and rotation:

  • No automatic rotation or size limit. The file grows unbounded until cleanup runs.
  • Time-based retention: entries older than agent.audit.retentionDays (default: 30) are purged by cleanup().
  • Cleanup is not scheduled automatically; invoke via the API or manually.

Configuration (in ~/.rafter/config.json or .rafter.yml):

Key Type Default Description
agent.audit.logAllActions boolean true Master switch — if false, no events are written
agent.audit.retentionDays number 30 Days to retain log entries
agent.audit.logLevel string "info" Stored but not currently used for filtering

Webhook Notifications

When configured, the audit logger sends a POST request to a webhook URL for events at or above a minimum risk level. This works with Slack incoming webhooks, Discord webhooks, and generic HTTP endpoints.

Configuration (in ~/.rafter/config.json or .rafter.yml):

Key Type Default Description
agent.notifications.webhook string Webhook URL to POST notifications to
agent.notifications.minRiskLevel string "high" Minimum risk level to trigger notification ("high" or "critical")

Webhook payload:

{
  "event": "command_intercepted",
  "risk": "high",
  "command": "git push --force",
  "timestamp": "2026-02-21T10:30:45.123Z",
  "agent": "claude-code",
  "text": "[rafter] high-risk event: command_intercepted — git push --force",
  "content": "[rafter] high-risk event: command_intercepted — git push --force"
}

The text field provides Slack compatibility. The content field provides Discord compatibility. Both contain a human-readable summary.

Webhook delivery is fire-and-forget with a 5-second timeout. Failures are silently ignored to avoid disrupting audit logging.

Setup examples:

# Configure webhook URL
rafter agent config set agent.notifications.webhook https://hooks.slack.com/services/T.../B.../xxx

# Only notify on critical events
rafter agent config set agent.notifications.minRiskLevel critical

# Disable notifications
rafter agent config set agent.notifications.webhook ""

rafter agent install-hook [OPTIONS]

Install git pre-commit hook for automatic secret scanning.

  • --global — install globally for all repos (sets core.hooksPath)
  • --push — install pre-push hook instead of pre-commit

rafter agent config SUBCOMMAND

Manage agent configuration (dot-notation paths).

  • rafter agent config show — display full config
  • rafter agent config get <key> — read value
  • rafter agent config set <key> <value> — write value

Config keys: agent.riskLevel, agent.skills.autoUpdate, agent.skills.installOnInit, agent.skills.backupBeforeUpdate, agent.commandPolicy.mode, agent.commandPolicy.blockedPatterns, agent.commandPolicy.requireApproval, agent.outputFiltering.redactSecrets, agent.audit.logAllActions, agent.audit.retentionDays, agent.audit.logLevel, agent.notifications.webhook, agent.notifications.minRiskLevel.

rafter agent init-project [OPTIONS]

Generate project-level instruction files so AI agents discover Rafter at session start. Creates .cursorrules, AGENTS.md, or other platform-specific files in the project root.

  • --only <platforms> — comma-separated list of platforms to target (claude-code, codex, gemini, cursor, windsurf, continue, aider)
  • --list — list which files would be created without writing them

Node only. Not yet implemented in Python.

rafter agent verify [OPTIONS]

Check agent security integration status. Reports whether config files, hooks, and platform integrations are properly installed across all 8 supported platforms.

Options:

  • --json — emit results as a single JSON object (one entry per check + a summary). Stable schema; intended for CI consumption.
  • --probe — runtime probe: synthesize a known-dangerous tool-call payload, pipe it to rafter hook pretool, and assert the resulting command_intercepted entry landed in ~/.rafter/audit.jsonl. Catches the failure mode where rafter wrote the right files but the hook command itself doesn't actually fire (rf-65zg). Currently covers Claude Code; Codex/Cursor/Gemini probes are planned follow-ups.

Checks (10 total, in order):

Name Severity Detection Pass criterion
Config hard (exit 1 on fail) ~/.rafter/config.json exists and parses valid JSON
Betterleaks hard binary on PATH or at ~/.rafter/bin/betterleaks version succeeds
Claude Code optional ~/.claude/ exists settings.json PreToolUse contains rafter hook pretool
OpenClaw optional ~/.openclaw/skills/ exists rafter-security.md skill present
Codex CLI optional ~/.codex/ exists ~/.agents/skills/rafter/SKILL.md present
Gemini CLI optional ~/.gemini/ exists settings.json mcpServers.rafter set
Cursor optional ~/.cursor/ exists mcp.json mcpServers.rafter set
Windsurf optional ~/.codeium/windsurf/ exists mcp_config.json mcpServers.rafter set
Continue.dev optional ~/.continue/ exists config.json mcpServers contains rafter (array or object format)
Aider optional <cwd>/.aider.conf.yml or ~/.aider.conf.yml exists read: list includes RAFTER.md AND RAFTER.md exists on disk

With --probe, an additional Claude Code (probe) check appears as the last entry.

Exit codes:

  • 0 — all hard checks passed (optional checks may be unconfigured / warning)
  • 1 — at least one hard check failed

JSON schema (--json):

{
  "checks": [
    { "name": "Config",      "status": "pass",  "detail": "/home/u/.rafter/config.json" },
    { "name": "Betterleaks", "status": "fail",  "detail": "Not found on PATH or at ..." },
    { "name": "Claude Code", "status": "warn",  "detail": "Not detected — run 'rafter agent init --with-claude-code' to enable" }
  ],
  "summary": {
    "passed": 1,
    "warned": 1,
    "failed": 1,
    "total": 3,
    "probe": false
  }
}

status is one of pass | warn | fail. warn is reserved for optional integrations that aren't installed; fail is reserved for hard failures (Config, Betterleaks, or any failing --probe check).

rafter agent status

Show agent security status dashboard. Displays config summary, installed integrations, audit log summary, and recent events.

rafter agent update-betterleaks [OPTIONS]

Update (or reinstall) the managed betterleaks binary.

  • --version <version> — specific betterleaks version to install (default: current bundled version)

rafter agent baseline SUBCOMMAND

Manage the findings baseline (allowlist for known findings). Baseline entries suppress matched findings in rafter secrets --baseline.

  • rafter agent baseline create [path] — scan and save all current findings as the baseline
  • rafter agent baseline show — show current baseline entries
  • rafter agent baseline clear — remove all baseline entries
  • rafter agent baseline add — manually add a finding to the baseline

rafter ci init [OPTIONS]

Generate CI/CD pipeline configuration for secret scanning.

  • --platform <platform>github, gitlab, or circleci (default: auto-detect)
  • --output <path> — output file path (default: platform-specific)
  • --with-remote — include remote security audit job (requires RAFTER_API_KEY)
  • --with-backend — deprecated alias for --with-remote

Auto-detection: checks for .github/, .gitlab-ci.yml, .circleci/ in cwd.

GitHub Action (action.yml)

Composite action at repo root. Usage:

- uses: Raftersecurity/rafter-cli@v1
  with:
    scan-path: '.'        # default
    args: '--quiet'       # default
    version: 'latest'     # default
    install-method: 'npm' # or 'pip'

rafter hook pretool [OPTIONS]

PreToolUse hook handler. Reads tool call JSON from stdin, evaluates risk, and writes a JSON decision to stdout. Used by agent platforms (Claude Code, Cursor, etc.) for pre-tool interception.

  • --format <format> — output format: claude (default, also works for Codex/Continue), cursor, gemini, windsurf

rafter hook posttool [OPTIONS]

PostToolUse hook handler. Reads tool output from stdin, redacts any secrets found, and writes JSON to stdout.

  • --format <format> — output format: claude (default, also works for Codex/Continue), cursor, gemini, windsurf

rafter mcp serve [OPTIONS]

Start MCP server over stdio transport. Exposes 6 tools and 3 resources.

  • --transport <type> — transport type (currently only stdio, default: stdio)

MCP Tools

Tool Description Required inputs
scan_secrets Scan files or directories for leaked secrets, API keys, tokens, passwords, and credentials path (string)
evaluate_command Check if a shell command is safe to run per the active security policy command (string)
read_audit_log Read security event history — blocked commands, detected secrets, policy overrides none (optional: limit, event_type, since)
get_config Read active Rafter configuration and policy none (optional: key dot-path)
list_docs List repo-specific security docs declared in .rafter.yml (metadata only, no content) none (optional: tag)
get_doc Return the content of a repo-specific security doc by id or tag id_or_tag (string); optional: refresh (bool)

scan_secrets inputs:

  • path (required) — file or directory path to scan
  • engine (optional) — auto (default), betterleaks, or patterns.

evaluate_command output schema:

{
  "allowed": true,
  "risk_level": "low",
  "requires_approval": false,
  "reason": "optional explanation string"
}

read_audit_log inputs:

  • limit (optional, number) — max entries to return (default: 20)
  • event_type (optional, string) — filter by event type (e.g. command_intercepted, secret_detected)
  • since (optional, ISO 8601 string) — only return entries after this timestamp

get_config inputs:

  • key (optional, string) — dot-path config key (e.g. agent.commandPolicy); omit for full config

list_docs output schema: array of { id, source, source_kind, description, tags, cache_status } where source_kind is "path" or "url" and cache_status is one of local (path-backed), cached, not-cached, stale.

get_doc output schema: array of { id, source, source_kind, stale, content }. Returns multiple entries when id_or_tag matches a tag shared by several docs; returns a single entry when it matches an id exactly.

MCP Resources

URI MIME type Description
rafter://config application/json Current Rafter configuration (~/.rafter/config.json)
rafter://policy application/json Active security policy — merged .rafter.yml + global config
rafter://docs application/json Repo-specific security docs declared in .rafter.yml (metadata only, no content)

rafter://policy returns the result of merging the project-level .rafter.yml (if present) over the global config. It reflects the effective policy that evaluate_command and scan_secrets enforce.

rafter://docs returns the same array shape as the list_docs tool. Use get_doc to retrieve actual content.

rafter policy export [OPTIONS]

Export Rafter policy for agent platforms.

  • --format <format> — target format: claude or codex
  • --output <path> — write to file instead of stdout

rafter docs list [OPTIONS]

List repo-specific security docs declared under docs: in .rafter.yml. Never performs network I/O — for URL-backed docs, reports cache status only.

  • --tag <tag> — filter to docs whose tags include this value
  • --json — output as JSON

Human-readable output format:

<id>  <source>[ (cached|stale|not-cached)][ [tag1, tag2]][ — <description>]

JSON output (array):

[
  {
    "id": "secure-coding",
    "source": "docs/security/secure.md",
    "source_kind": "path",
    "description": "Internal secure-coding rules",
    "tags": ["owasp", "internal"],
    "cache_status": "local"
  }
]

Exit codes: 0 on success, 3 if no docs configured.

rafter docs show <ID_OR_TAG> [OPTIONS]

Print the content of a doc. If the argument exactly matches a doc id, prints that doc. Otherwise, any doc whose tags include the argument is concatenated with separator headers.

  • --refresh — force re-fetch for URL-backed docs (bypass cache)
  • --json — output as JSON array of { id, source, source_kind, stale, content }

Behavior for URL docs:

  • On cache hit within TTL, reads from cache.
  • On miss or expired, fetches and updates cache.
  • On network failure with a stale cache present, returns stale content and prints a warning to stderr.
  • On network failure with no cache, exits 1.

Exit codes: 0 ok, 1 fetch/read error, 2 selector did not match, 3 no docs configured.

rafter notify [SCAN_ID] [OPTIONS]

Post scan results to Slack or Discord channels via webhooks.

  • [SCAN_ID] — scan ID to fetch and post results for
  • -w, --webhook <url> — webhook URL (Slack or Discord)
  • -k, --api-key <key> — API key for fetching scan results
  • -p, --platform <platform> — force platform: slack, discord, or generic
  • --quiet — suppress status messages
  • --dry-run — print payload without posting

rafter report [INPUT] [OPTIONS]

Generate a standalone HTML security report from scan results.

  • [INPUT] — path to JSON scan results (default: read from stdin)
  • -o, --output <path> — output file path (default: stdout)
  • --title <title> — report title (default: "Rafter Security Report")

Node only. Not yet implemented in Python.

rafter issues create SUBCOMMAND

GitHub Issues integration — create issues from scan findings or natural text.

rafter issues create from-scan [OPTIONS]

Create GitHub issues from scan results.

  • --scan-id <id> — remote scan ID to create issues from
  • --from-local <path> — path to local scan JSON (from rafter secrets --format json)
  • -r, --repo <repo> — target GitHub repo (org/repo)
  • -k, --api-key <key> — Rafter API key (required with --scan-id)
  • --no-dedup — skip deduplication check (create even if matching issue exists)
  • --dry-run — show issues that would be created without actually creating them
  • --quiet — suppress status messages

rafter issues create from-text [OPTIONS]

Create a GitHub issue from natural language text (stdin, file, or inline).

  • -r, --repo <repo> — target GitHub repo (org/repo)
  • -t, --text <text> — inline text to convert to an issue
  • -f, --file <path> — read text from file
  • --title <title> — override extracted title
  • --labels <labels> — comma-separated labels to add
  • --dry-run — show parsed issue without creating it
  • --quiet — suppress status messages

rafter completion

Generate shell completion scripts.

  • <shell> — shell type: bash, zsh, or fish

Pre-Commit Framework (.pre-commit-hooks.yaml)

Integration with pre-commit:

repos:
  - repo: https://github.com/Raftersecurity/rafter-cli
    rev: v0.8.2
    hooks:
      - id: rafter-scan           # Node.js
      # - id: rafter-scan-python  # Python alternative

Policy File (.rafter.yml)

Project-level security policy. Placed in project root; CLI walks from cwd to git root.

version: "1"
risk_level: moderate
command_policy:
  mode: approve-dangerous
  blocked_patterns: ["rm -rf /"]
  require_approval: ["npm publish"]
scan:
  exclude_paths: ["vendor/", "third_party/"]
  custom_patterns:
    - name: "Internal API Key"
      regex: "INTERNAL_[A-Z0-9]{32}"
      severity: critical
ignore:
  - paths: ["tests/fixtures/**", "*.example.env"]
    rules: ["AWS Access Key", "Generic API Key"]
    reason: "test fixtures with fake credentials"
  - paths: ["docs/**"]
    reason: "documentation examples"
audit:
  retention_days: 90
  log_level: info
docs:
  - id: secure-coding                 # optional — defaults to basename(path) without extension, or sha256(url)[:8]
    path: docs/security/secure.md     # exactly one of { path, url } is required
    description: Internal secure-coding rules
    tags: [owasp, internal]
  - id: app-threat-model
    url: https://internal.example.com/threat-model.md
    description: Application threat model
    tags: [threat-model]
    cache:
      ttl_seconds: 86400              # optional, default 86400; only valid with url

Precedence: policy file overrides ~/.rafter/config.json. Arrays replace, not append.

Docs validation rules:

  • Each entry must have exactly one of path or url — both or neither is skipped with a warning.
  • Duplicate ids are skipped with a warning (first one wins).
  • tags must be a list of strings if present.
  • cache.ttl_seconds must be a positive number and is only valid for url entries.
  • Unknown keys per-entry are ignored with a warning.

URL caching: URL-backed docs are cached at ~/.rafter/docs-cache/ keyed by sha256(url)[:32]. Default TTL is 86400 seconds. On network failure, a stale cached copy is served and a warning is printed. docs list never fetches; docs show fetches on miss/expired or when --refresh is set.

Ignore rules (ignore:): suppress findings without removing them from the audit trail. Each entry needs paths: (a non-empty list of globs); rules: is optional (omitting it suppresses every rule on the matched paths) and reason: is surfaced verbatim in the JSON _suppressed output. Path globs are matched anywhere along absolute scan paths — tests/fixtures/** matches /abs/project/tests/fixtures/foo. Rule-name matching is case-insensitive; non-existent rule names are harmless (they just never match). First entry that matches wins, so put more specific entries earlier.


Usage Examples

Remote Code Analysis

# Run scan, auto-detect repo/branch
rafter run

# Scan specific repo
rafter scan --repo myorg/myrepo --branch main

# Get results as JSON
rafter get SCAN_ID --format json

# Pipe to jq
rafter get SCAN_ID --format json | jq '.vulnerabilities[] | select(.level=="error")'

# Count vulnerabilities
rafter get SCAN_ID --format json | jq '.vulnerabilities | length'

# Save to file
rafter get SCAN_ID > scan_results.json

# CSV export
rafter get SCAN_ID --format json --quiet | jq -r '.vulnerabilities[] | [.level, .rule_id, .file, .line] | @csv'

# CI gate
if rafter get SCAN_ID --format json | jq -e '.vulnerabilities | length > 0'; then
    echo "Vulnerabilities found!" && exit 1
fi

Quiet Mode

# Suppress status messages, get just data
rafter get SCAN_ID --quiet | jq '.scan_id'

# Capture output in variable
scan_data=$(rafter run --quiet)

Error Handling

if rafter get SCAN_ID; then
    echo "Scan found"
else
    case $? in
        2) echo "Scan not found" ;;
        3) echo "Quota exhausted or scan limit reached" ;;
        4) echo "Forbidden — check API key scope" ;;
        *) echo "Other error" ;;
    esac
fi

Local Security

# Full setup
rafter agent init

# Scan for secrets
rafter secrets .
rafter secrets --staged --quiet  # CI-friendly

# Aliases still work (deprecated)
# rafter scan local .   — deprecated, use rafter secrets
# rafter agent scan .   — deprecated, use rafter secrets

# Pre-commit hook
rafter agent install-hook --global

# Safe command execution
rafter agent exec "git push origin main"

# Audit a skill before installing
rafter agent audit-skill untrusted-skill.md

# View security log
rafter agent audit --last 20

# Configure
rafter agent config set agent.riskLevel aggressive

Notes

  • API key: provided via --api-key flag, RAFTER_API_KEY env var, or .env file
  • Git auto-detection works in CI (supports GITHUB_REPOSITORY, GITHUB_REF_NAME, CI_REPOSITORY, CI_COMMIT_BRANCH, CI_BRANCH)
  • Remote code analysis targets the remote repository, not local files
  • All scan data to stdout, all status messages to stderr
  • --quiet suppresses stderr; stdout is unaffected
  • Agent commands are available in both Node and Python implementations