Skip to content

Fix cloaking transcript leak + scope-blind status/audit + scanner/sweep false positives - #315

Merged
Ar9av merged 1 commit into
mainfrom
fix/cloak-scope-audit
Aug 24, 2026
Merged

Fix cloaking transcript leak + scope-blind status/audit + scanner/sweep false positives#315
Ar9av merged 1 commit into
mainfrom
fix/cloak-scope-audit

Conversation

@Ar9av

@Ar9av Ar9av commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Five issues surfaced while exercising a fresh enforce + global-scope install with a real Claude Code agent in the loop, on an isolated host. All are runtime/CLI fixes; no behavior change for anyone not hitting the specific paths.

# Area Severity One-liner
1 cloaking/hooks/decloak.sh High Resolved secret was inlined into the command Claude Code records verbatim → raw value hit ~/.claude/projects/*.jsonl
2 cli.py, audit.py Medium cloak status / audit were project-scope-only → a global install reported "not installed" while prismor status said the opposite
3 hooks.py Medium hook_installed() missed plugin-path agents (openclaw/hermes/opencode) → coverage under-reported, ensure_global_coverage re-ran every time
4 scanner.py Medium prismor scan flagged Prismor's own installed skill as CRITICAL shell-injection
5 sweep-gitleaks.toml Low mistral/together rules used bare length classes → 88 phantom "secrets" in one transcript

1. Cloaking transcript leak (the important one)

decloak.sh has to hand Claude Code a runnable command, and Claude Code records that command verbatim in its on-disk transcript. Substituting the real secret into that string therefore wrote the raw value to disk — the exact leak the cloaking layer exists to prevent (it never reached the model, but it did reach ~/.claude/projects/*.jsonl).

Fix: resolution moves into the child process (decloak-exec.sh); the recorded command keeps its @@SECRET:name@@ placeholders.

Before — the wrapped command handed back contains the raw key (highlighted):

before

After — placeholders preserved, resolution deferred to decloak-exec.sh:

after

End-to-end check with a real agent: after asking Claude to use the key, a value-grep of every transcript file returns 0 raw-secret occurrences.

claude under cloaking

A regression test is added that fails against the old hook and passes now (tests/test_cloak_output_scrub.py).


2 + 3. Scope-blind status/audit & plugin-path coverage

prismor setup --scope global writes hooks to ~/.claude, but audit/cloak status only looked at project scope, and hook_installed() only recognised the inline-command form (openclaw/hermes/opencode register a plugin path instead). Net effect on a correctly-installed global machine:

Before — CRITICAL "no hooks" + HIGH "cloaking not installed", both false:

audit before

After — hooks detected in both scopes and across plugin-path agents:

audit after


4. Scanner flagged Prismor's own skill

prismor setup installs Prismor's own skill, which documents the very commands and credential paths the skill rules look for — so a clean install scanned itself as CRITICAL. Now skipped while byte-identical to the bundled copy; an edited copy is scanned again, and a malicious-skill positive control still fires.

Before (own skill flagged) → After (clean):

scan before
scan after


5. Sweep false positives

mistral-api-key / together-api-key used bare length classes ([A-Za-z0-9]{32}) gated only by a file-level keyword, so one mention of "mistral" anywhere in a file matched every 32-char run in it (88 phantom hits in a single Claude transcript). Both are now bound to an assignment context; a positive control on a real MISTRAL_API_KEY=… still detects.


Cloaking, end to end

cloak flow

Testing

  • New regression test for the transcript leak (fails on old hook, passes now).
  • 453 passed, 2 skipped across the cloak/audit/sweep/scanner/hooks/surface subset (the one remaining failure, test_cloak_add_env_file_imports_each_entry, is pre-existing on main — unrelated).
  • python -m build confirms the new decloak-exec.sh ships in the wheel.

Screenshots/GIFs are real captures from a clean host; they live on the throwaway pr-assets/cloak-fix branch (not part of this diff, safe to delete after merge).

Five issues surfaced while exercising a fresh enforce+global install with a
real Claude Code agent in the loop on an isolated host:

1. decloak.sh inlined the resolved secret into the command string it handed
   back to Claude Code, which records tool_input.command verbatim in
   ~/.claude/projects/*.jsonl. The raw value never reached the model but did
   reach disk. Resolution now happens in the child (decloak-exec.sh); the
   recorded command keeps the placeholders. Regression test added (fails
   against the old hook, passes now); leak re-audit shows 0 raw-secret
   occurrences across the transcripts.

2. cloak status and audit looked only at project scope, so a global-scope
   install (what setup --scope global writes) reported not-installed / a
   CRITICAL no-hooks finding while prismor status said the opposite. Both now
   check project+user, matching status.

3. hook_installed() text-searched for the inline dispatcher marker, which
   openclaw/hermes/opencode never have (they register a plugin path in
   config[plugins] and write the dispatcher into the scaffolded plugin). They
   read as unhooked forever, under-reporting coverage and re-triggering
   ensure_global_coverage every run. Now recognises the plugin-path form.

4. prismor scan flagged Prismor's OWN installed skill as CRITICAL shell
   injection + credential access, because the skill documents the very commands
   and paths the rules look for. Skip it while byte-identical to the bundled
   copy; an edited copy is scanned again. Malicious-skill positive control
   still fires.

5. sweep-gitleaks.toml mistral/together rules used bare length classes gated
   only by a file-level keyword, so one mention of mistral in a file matched
   every 32-char run in it (88 phantom secrets in one transcript). Bound both
   to an assignment context; positive control on a real key still detects.
@Ar9av
Ar9av merged commit 6bbb506 into main Aug 24, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant