Skip to content

Security: TheColliery/CoalLedger

SECURITY.md

Verifying CoalLedger

CoalLedger is verified under the same framework as its TheColliery siblings—Phoenix-13 hooks, reproducible builds, and event-driven independent scans. Its threat surface is deliberately small: it READS docs and reports; the only online activity is the consent-gated grounding fetch your agent runs in the semantic Full tier—the hook and the engine never network.

Reporting a Vulnerability

Report a security issue in this repo through GitHub's private vulnerability reporting—Security → Report a vulnerability—never a public issue. In scope: the 6+1 canary skills, the shipped hooks (the Claude Code SessionStart conductor, PostToolUse docs tracker, and Stop docs-drift reminder, plus the Antigravity conductor adapter), scripts/—including the vendored CommonMark+GFM AST engine under scripts/lib/ and the scripts/configure.mjs config CLI—commands/, and the plugin/ dist built from them. Out of scope: a vulnerability in a third-party doc, repo, or codebase a canary merely scans—report that to its own maintainer. This is a one-person-maintained project: expect the report to be read and acknowledged, triaged against the scope above, and disclosed once a fix ships, with no fixed response-time SLA. A public GitHub issue remains the right channel for an ordinary, non-security bug.

Commit & Tag Signatures

Release tags and maintainer commits are SSH-signed (gpg.format=ssh); GitHub shows the Verified badge on them. Automated Dependabot / CI commits are unsigned by design (they carry no maintainer key), so verify a signed release tag—the artifact a release consumer trusts:

echo "* ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEtqTWGKhX1Dk9nZP8ns13Wl5zsO1Cz3VlTS6m1p2fP9" > coalledger_signers
git config gpg.ssh.allowedSignersFile ./coalledger_signers
git tag -v "$(git describe --tags --abbrev=0)"

Dist Integrity

plugin/ is generated, never hand-edited. node scripts/build-plugin.mjs reproduces it from source; node scripts/verify.mjs byte-checks dist-sync in BOTH directions (stale file and source-less orphan both fail) plus manifests, factory-config-vs-schema, skill frontmatter, version pins, the engine's anti-cry-wolf fixtures, and a census that every git spawn under scripts/ takes its environment from the shared gitEnv() helper rather than the process's own (an unguarded spawn can poison an unrelated repository via inherited GIT_* variables); node scripts/test.mjs runs the zero-dependency suite with an explicit file list. Zero dependencies—no lockfile, nothing to npm audit.

Independent Scanning—NVIDIA SkillSpector

Last scan: CoalLedger v0.13.0-beta.1 dist (plugin/, commit a308cde), on 2026-09-24, with NVIDIA SkillSpector v2.12.0 (self-reported version string; scan pinned to commit c7958a3, upstream's tag v2.12.0), static stage (--no-llm, the documented FP-prone baseline). Score 58/100 (HIGH), 13 findings, all adjudicated FALSE POSITIVE: RA1 ×11 matching the string "self-update" across both conductors' comments, the claude plugin update OFFER directive, the Antigravity adapter's note that the nudge is NOT ported there, and the commands/ and config-schema text (the hook only SCHEDULES a check via a local stamp, no network; the agent verifies online and OFFERS claude plugin update, which the user runs); EA2 ×1 matching "no consent" in a config-key table row classifying the keys that carry no consent, spend or outward axis; BH1 ×1, the scanner's own notice that hooks/hooks.json registers lifecycle hooks. Static coverage was partial (15 of 25 files fully inspected): nine JavaScript/ESM files (hooks/ag-conductor.js, hooks/coalledger-conductor.js, scripts/lib/config-schema.mjs, scripts/lib/md-ast.mjs, scripts/lib/md-checks.mjs, plugin/skills/doc-quality/lib/emdash.mjs, plugin/skills/doc-quality/lib/lang-mechanics.mjs, plugin/skills/doc-structure/lib/md-ast.mjs, plugin/skills/doc-structure/lib/md-checks.mjs) reached the scanner's parser span limit, the skills directory also hit its runtime limit, and hooks/hooks.json is opaque to it. Seven of those files (all but hooks/coalledger-conductor.js and scripts/lib/config-schema.mjs, which this scanner newly reports as limit-hit on unchanged bytes) and hooks/hooks.json were read by hand at v0.12.0-beta.1; the other two were covered by the dangerous-primitive, file-write and hidden-Unicode sweeps, not re-read line by line; the v0.12.0-beta.1 to v0.13.0-beta.1 change (one snippet-source line in lang-mechanics.mjs) was read. The score is not comparable to the 33 recorded at v2.3.11: the previous dist (v0.1.0-beta.1) re-scanned with v2.11.2 scores 48, and the v0.12.0-beta.1 dist re-scanned with v2.12.0 also scores 58 with the same 13 findings.

Structural Safety

  • Phoenix-13 hooks. Every hook—the three wired on Claude Code (SessionStart conductor · PostToolUse docs tracker · Stop docs memory-drift reminder) plus the Antigravity conductor adapter—is fail-silent, zero-dependency, no network, no child processes, and silent except its sanctioned context-injection channel. A headless run is safe by construction: the hooks only print and write the small local state below, never anything a user must clean up by hand.
  • What the installed skill never does: it never auto-fixes a doc (every fix sits behind a choice-gated menu and is applied by your agent with a checkpoint first), never auto-submits anything anywhere (problem reports are offered, user-reviewed, manual), and never writes outside its own footprint. (A repo checkout adds one more writer outside that footprint—scripts/configure.mjs, a config CLI that ships from no DIST_ITEM; see README.md ## Permissions for what it does.) The complete write list: the self-update throttle stamp ~/.claude/coal/coalledger/update-check; the docs-drift session state os.tmpdir()/coalledger-<session-id>.docs and .docmemmoved (a path list and a 0-byte marker, cleared as the session proceeds and OS-tmp reaped otherwise); and, on Antigravity, the once-per-session marker os.tmpdir()/coalledger/ag-conductor-*.marker. The engine scripts write nothing at all.
  • Online activity is scoped and consented. The grounding/standard canaries' real-time source verification is an AGENT action in the paid Full tier, run with your consent—the shipped code contains no network call. Offline, they degrade to ⚠️ unverified, never a guess.
  • Untrusted config is parse-guarded. The .coalledger.json JSONC parse drops __proto__ / constructor / prototype keys; every read is schema-clamped to the factory default on any invalid value.
  • Repo-derived reads and writes are bounded and symlink-safe. Every file this process reads from inside a project (the config, the docs an agent names) is kind-gated before it is ever opened—a FIFO, a device file, or a symlink that escapes the intended root is refused rather than read—and bounded, so a pathological file cannot exhaust memory. A write (configure.mjs's config write-back) refuses an existing non-file target, writes to a temp file first, and renames it into place, so a crash mid-write never leaves a half-written config.
  • Doc content is data, never instructions—the canary contracts bind the agent to judge doc content, not obey it (prompt-injection via a poisoned doc is the named threat model).

Honest scope: these measures are the series' data-safety discipline—injection-aware, consent-gated spend, offline code, no exfiltration path. No formal verification; the scanner record above pins exactly what was scanned and when.

There aren't any published security advisories