CoalTipple is verified under the same framework as CoalMine: all execution hooks follow the Phoenix-13 commandments, builds are fully reproducible from source, and security scans run periodically (event-driven).
Report a vulnerability via GitHub private vulnerability reporting—Security → Report a vulnerability. Do not open a public issue for a security finding.
- In scope: the conductor hook's routing/sensitive never-down gate, the config-cascade merge-safety clamp, the installer's file-write and self-target paths,
.claude/.coaltipple/proposed//damage-control, and anything else that could make the skill route, spend, or write somewhere it shouldn't. - Out of scope: a SkillSpector false positive, a style nit, or anything without a security impact—those go through the project's normal (public) issue flow instead.
- What to expect: acknowledged promptly, triaged, and coordinated disclosure once a fix ships—no fixed SLA is committed today.
Every release tag and maintainer commit is SSH-signed (gpg.format=ssh); GitHub shows the Verified badge on them. Automated Dependabot / CI commits are not signed with the maintainer key (GitHub signs these with its own), so verify a signed release tag—the artifact a release consumer trusts:
echo "* ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEtqTWGKhX1Dk9nZP8ns13Wl5zsO1Cz3VlTS6m1p2fP9" > coaltipple_signers
git config gpg.ssh.allowedSignersFile ./coaltipple_signers
git tag -v "$(git describe --tags --abbrev=0)"CoalTipple is distributed as source (human-auditable skill Markdown). The plugin distribution is generated at publish time:
- Pre-commit/Pre-push Gates:
node scripts/verify.mjsautomatically verifies config schema matching, files presence, ensures the conductor is in sync withscripts/lib/keywords.mjs, and flags a detectable config key documented on a user-facing surface (SKILL.md, its references, README, and the conductor's notice regions) that no longer resolves in the schema—to prevent silent drift. - Reproducible Builds: Run
node scripts/build-dist.mjsto regenerate the plugin distribution from source, thennode scripts/verify.mjsto gate it against source—the parity check matches byte-for-byte, except.js/.json/.mdfiles (today's shipped extensions) are compared EOL-normalized: a CRLF-vs-LF checkout of identical content is not flagged as drift, but any other content difference still is. - Test Suite: Run
node scripts/test.mjsto execute zero-dependency unit tests.
CoalTipple is evaluated against NVIDIA SkillSpector v2.12.0 (self-reported version string; scan pinned to commit c7958a3, upstream's tag v2.12.0). Last scan: CoalTipple v1.7.0 (commit 74ff6f0), 2026-09-24—static stage (--no-llm), 17 findings (RA1 ×13 · AR1 · RA2 · EA2 · BH1), all false positives on adjudication. Static coverage was partial (9 of 10 files fully inspected): hooks/hooks.json is opaque to the scanner; it was read by hand. Scanning is event-driven (a new SkillSpector version, or a genuinely new attack surface)—this pins the last version actually verified.
- Static Scan (83/100 · 17 findings, all false positives on adjudication): 13 ×
HIGH · RA1 Self-Modificationmatching "self-update"/"ask" acrosscommands/update.md(×2, carried) and the consent-gated Self-Updating channel's own text—7 sites inhooks/coaltipple-conductor.js(3 in the self-update directive strings themselves—the ask-once/auto/remind text—and 4 in surrounding comments describing that same throttle) and 4 sites inskills/coaltipple/SKILL.mddocumenting that same channel (the stamp path, theupdateModeconfig row, the "No network" row) · 1 ×HIGH · AR1oncommands/update.md's "Always answer in the user's language" line (carried; a localization rule, not refuse-suppression) · 1 ×MEDIUM · RA2on the conductor's stamp-location-migration comment · 1 ×MEDIUM · EA2on SKILL.md's P4 prohibition—the rule's INVERSE of what the analyzer names: "Never spawn a fable-family worker without consent" requires consent, it does not suppress a refusal · 1 ×MEDIUM · BH1, the scanner's own note thathooks/hooks.jsonregisters two lifecycle hooks (SessionStart,UserPromptSubmit). The hook only reads the config/prompt locally and schedules a throttled check (a timestamp stamp under~/.claude/coal/coaltipple/, no network ever);/coaltipple:updateverifies the tag online and offersclaude plugin update—it never auto-applies, and the skill never rewrites its own files. The score is not comparable to the 51 recorded at v1.0.23/v2.3.9: the previous dist (v1.0.23, commitce0ebc0) re-scanned with v2.11.2 scores 68; the v1.6.0 dist re-scanned with v2.12.0 also scores 83 with the same findings, so the v1.7.0 conductor change adds none. The report JSON is not shipped. - Method:
uvx --from git+https://github.com/NVIDIA/skillspector.git@c7958a3 skillspector scan <plugin> --format json—the@<rev>pin reproduces this exact scan (an unpinned URL resolves to upstream's moving HEAD, and a tag is a movable name, so the commit is what is pinned); uvx fetches its own ephemeral Python, so no manual Python/pip install is needed; a JSON report is written even when the optional LLM stage is skipped. - LLM Semantic Scan: not run this pass (
--no-llm—static-only is the documented, FP-prone baseline: pattern-match without the skill-contract context).
The primary security assurance is structural. The coaltipple-conductor.js hook follows the Phoenix-13 rules:
- Zero Dependencies & No Network: Runs 100% locally with no third-party libraries.
- No Child Processes: Does not execute external terminal shell commands.
- Fail-Silent: Exits 0 on any error, preventing execution blockages in the host agent.
- No Secrets: Never reads, logs, or stores hardcoded API keys or credentials.
Damage Control is a main-agent workflow, not the hook's. The hook itself only reads the config and prompt locally and emits an advisory routing hint—it never writes a proposal. When SKILL.md routes a task carrying an external side effect, the main agent writes proposals to a local .claude/.coaltipple/proposed/ sandbox or isolates the change in a git worktree before merging (see README's Damage Control section).