crag is currently pre-1.0 and ships from master. Security fixes land in the
latest minor version and are released as a patch bump. Older minor versions do
not receive backports.
| Version | Supported |
|---|---|
| 0.3.x | yes |
| < 0.3 | no |
Do not open a public GitHub issue for security reports.
Email alexc.forbusiness@gmail.com with:
- A description of the issue and the threat model it breaks (attacker capabilities, access assumed, impact).
- A minimal reproducer — ideally a
governance.mdor workspace layout plus the exactcragcommand that triggers the bug. - The crag version (
crag version) and platform (OS + Node version).
You can expect:
- An acknowledgment within 72 hours.
- An initial assessment (confirmed / not a vuln / needs more info) within one week.
- A fix, release, and public advisory coordinated with you once the patch lands on npm.
GitHub Security Advisories are also accepted via the "Report a vulnerability" button on the Security tab of the repository.
crag is designed to run on developer machines and in CI with the following assumptions:
governance.mdis trusted-but-authored-by-humans. The parser rejects absolute paths and..in path/condition annotations, and every downstream generator escapes user values for its target format (shell, YAML, markdown). However, governance content can still contain arbitrary commands that crag will faithfully wire into CI hooks. Review changes togovernance.mdin PRs the same way you review CI configs.- The
src/skills/*.mdfiles that ship in the npm package are trusted. Skill sync validates that sources are regular files inside the package's ownsrc/skills/directory viafs.realpathSync, blocking symlink redirection. - crag never executes code from the npm registry at install time. The
package has no
postinstall/preinstallscripts. It has zero runtime dependencies, so there is no transitive supply chain. - The auto-update check contacts
registry.npmjs.orgover HTTPS with a 3-second timeout, 100 KB response cap, and 24-hour cache. It fails silently on any error. SetCRAG_NO_UPDATE_CHECK=1to disable entirely.
- Shell injection — all user-controlled values interpolated into shell
commands are routed through
shellEscapeDoubleQuoted()which escapes\,`,$,"in the correct order. - YAML injection — all user-controlled values interpolated into YAML
(GitHub Actions, pre-commit config) go through
yamlScalar()which quotes colons,#, control chars, boolean-like and number-like strings. - Path traversal — workspace enumeration uses
fs.realpathSyncto resolve symlinks and rejects any path whose real location falls outside the workspace root. Gate annotations are rejected if absolute or containing..segments. - Symlink attacks on skill sync —
isTrustedSource()rejects symlinks and any source outsidesrc/skills/. - ReDoS —
parseGovernance()caps input at 256 KB and uses line-scan extraction instead of complex regexes. - CRLF portability — skill hashes are computed after
\r\n → \nnormalization so Windows and Unix contributors produce identical integrity values. - Atomic writes — every compile target writes via
atomicWrite()(temp file with unpredictable crypto-random suffix + rename). Partial failures leave the old state intact. - CI secrets —
NPM_TOKENis scoped to the publish job only.id-token: writeis scoped to the release workflow for SLSA provenance.
In scope:
- Any of the command surface (
init,install,check,compile,analyze,diff,upgrade,workspace,audit,hook,demo,doctor,auto). - The generated artifacts (CI configs, hooks, AI agent rule files).
- The auto-update and skill-sync machinery.
- The release pipeline (
.github/workflows/release.yml).
Out of scope:
- Vulnerabilities in Claude Code itself, the npm registry, or GitHub Actions.
- Configuration mistakes in the user's own
governance.md(e.g. explicitly writing a destructive command). - Social engineering of maintainers.