chameleon 2.22.5
A behavioral-test hardening release. Where the v2.22.4 work came from a static
audit, this came from running the plugin: 100 scenarios on real bootstrapped
repos through the live hooks (the exact JSON payloads Claude Code sends), with
every failure independently re-run on a fresh repo. 7 findings, each re-verified
across three independent rounds against live code. It closes a set of fail-open
and visibility gaps and completes the v2.22.4 silent-failure sweep.
Fixed
- A typo in an unrelated config section no longer disables credential / import
blocking. The enforcement gates readconfig.jsonthroughload_config,
which validates the WHOLE file and raises on any malformed section, so a typo
inauto_refreshortrustmade the gate swallow the error and fall through,
silently disabling the secret deny. The gates (PreToolUse secret + import deny,
PostToolUse block, Stop backstop) now read the enforcement section in isolation
viaload_config_enforcement_only; an unrelated-section typo can no longer
disable enforcement, while a genuinely malformed enforcement section still
fails open WITH the degraded check-event from v2.22.4. Not fail-closed: that is
circular -- the mode is exactly what could not be parsed -- and would wedge
every edit on a stray typo. - A real
eval()/exec()is now hard-blocked.eval-callwas an active,
error-severity block rule with no enforcement path -- detected everywhere,
blocked nowhere: only the secret and import rules had a deny gate, and eval-call
was gated behind an archetype match, so a brand-new or unarchetyped file (where
eval(userInput)most often lands) got no hard block while a leaked credential
in the same file was denied. eval-call is now archetype-independent (like the
secret rule) and gets a pre-write PreToolUse deny.is_hard_classkeeps this to
the error-severity direct form, soclass_eval/instance_evalstay advisory;
a NAMEDchameleon-ignore eval-callclears it, a bare directive does not. - A no-remote repo's git worktree now inherits the main checkout's identity.
repo_idfor a remote-less repo is path-derived, so a linked worktree got a
distinct id, readuntrusted, and silently no-opped both the advisory and the
deny._compute_repo_idnow resolves a worktree to its main root first, so
trust and enforcement transfer (the remote-backed case was already fixed in
v2.22.4). /chameleon-statussurfaces a malformed config instead of hiding it. A
broken config made status reportmode: offbeside anactivesecret rule
with no signal -- reading as a deliberate opt-out, not a typo. Status now flags
config_malformedand does not list rules as active when the mode is unreadable
(doctor already surfaced this; status was silent). It reads the enforcement
section in isolation too, so an unrelated-section typo no longer makes status
report "off" while the gates are in fact still enforcing.would_blockno longer counts enforce-mode blocks. A real enforce deny
incremented the shadow report'swould_blocktally -- the very signal the
shadow -> enforce promotion reads. would_block is now shadow-only at both
outlier sites (matching the three already-correct ones); an enforce block is
recorded in the decision log instead (the same audit channel the PostToolUse
block uses), so/chameleon-explainstill sees it.
Changed
/chameleon-statusreportscorrectness_judge. The flag was parsed but
never surfaced; status now returns it alongsideidiom_review/idiom_judge.refreshon a never-bootstrapped repo tags the resultimplicit_bootstrap.
Refresh on a repo with no profile implicitly bootstraps (the documented
idempotent design -- it does NOT refuse), and the envelope now flags that an
initial bootstrap happened rather than a re-derive (status stayssuccess).