This release includes breaking changes for platform teams planning a safe upgrade.
✓ No known CVEs patched in this version
Topics
+12 more
Summary
AI summaryFixed cross-file existence advisory blind to non-root monorepo workspaces, sanitized deleted-module paths, persisted module deletions across session turns, corrected reviewer-reply parser shadowing, expanded comment/string blanking for reference checks, and added JSX‑text handling.
Full changelog
Round-2 real-hook QA of the Stop backstop and injection paths, orchestrated as a
six-agent probe workflow over exclusive real repos with each finding
adversarially verified through the real hook binaries. It targeted round 1's
deferred cross-file gaps, the second-order effects of the _stop_gates
restructure (which came back clean), and framework/edge-case depth round 1 did
not reach. Fifteen fixes; several close gaps in round-1 code.
Fixed
- The cross-file existence advisory was blind to non-root workspaces in a
monorepo. At the repo-root cwd it loaded one reverse/constant index for the
git top-level, so removing an exported symbol in a sub-workspace (e.g. a
monorepo's web app) that other files in that workspace still import was silent
at turn end, even though the edit-time check fired. Each edited file's break is
now resolved against its OWN workspace index (the same per-file resolution the
edit-time check uses), keyed and cached per workspace root. - The deleted-module advisory (crossfile PATH) rendered the module path
unsanitized, so a crafted deleted filename carrying ANSI escapes, a newline,
or a forged[chameleon-untrusted-data:]/[🦎 chameleon:]marker reached the
Stop context verbatim. The module path is now stripped of control bytes and run
through the context sanitizer like the symbol and site fields already are; every
single-line path field in the advisory gets the same treatment so a newline can
no longer split the line. - A deleted module's break was lost whenever the turn's first Stop
short-circuited (the once-per-session idiom-review block prunes the deleted
file from state before the advisory pipeline runs), which silenced the
deleted-module advisory on any repo with taught idioms. Deletions are now
persisted for the session and surfaced exactly once on the Stop that reaches the
advisory pipeline. - The reviewer-reply parser let a throwaway array shadow the real findings. A
[1, 2, 3]or an empty[]appearing in reviewer prose before the findings
array was returned instead of it — the empty case silently reporting "reviewed,
no bugs." The parser now prefers the first array that contains objects, falling
back to a lone object reply, then to any array (a genuine empty[]clean pass),
so junk before the real payload no longer wins. - A completed rename that left the old name only in inert Ruby text false-fired
the cross-file advisory. The presence check blanked quoted strings but not#
comments,%w/%i/%q/%Q/%()literals, or heredoc bodies, so a stale
mention in any of those read as a live reference. All are now blanked, keeping
the#{Const}interpolation carve-out (a real reference). - The cross-file reference check now blanks strings and comments with a proper
character scan, closing a class of comment/string edge cases the regex approach
could not. A comment marker inside a string (a URL's//) must not make the
comment pass swallow real code, and a quote inside a comment (an apostrophe in
// don't remove) must not open a string that spans the comment and eats real
code — no regex ordering handles both, and paired apostrophe-comments could even
over-report a comment-only mention. A single left-to-right scan that recognizes
comments before strings (so an in-comment quote never opens a string, and an
in-string marker never starts a comment), handles TS////* *//template and
Python#/triple-quote/f-string, preserves the TS#private-field sigil, and
respects escapes. This subsumes the round-1 comment-blanking and the multi-line
block-comment fix. - The Stop block for a Python-only deletion handed a
//ignore hint (a
syntax error in Python): on a pure-deletion turn the surviving-files list was
empty, so the hint fell through to the C-style default. The deleted path's
extension now informs the hint token. - FastAPI stale-test pairing never fired for the standard tiangolo layout
(backend/app/…↔backend/tests/…): the source-root mirror only swapped a
LEADINGapp/src/lib. It now also swaps a mid-path source root, so the
backend/app/api/routes/x.py→backend/tests/api/routes/test_x.pymapping is
recognized. - The Rails co-change applicability check was silently disabled on large
monorepos: its bounded file walk visited alphabetically-last trees first and
spent its budget before reachingapp//db//config/. The walk now visits the
co-change-relevant trees first (ranked,appahead ofdb) and the budget was
raised so both the trigger and companion sides are sampled. - The prose read-path injection denylist now also catches
email/mail(and
forward/share) exfiltration verbs and "disregard the conventions" phrasing,
closing adjacent bypasses of the round-1 broadening. - SessionStart now resolves the repo from the payload's
cwdrather than the hook
process cwd, so a harness whose process cwd diverges from the session cwd no
longer risks injecting the wrong repo's conventions. - Alembic
versions/migration files (underalembic/ormigrations/) are now
roled as migrations at bootstrap, so their auto-generated globals no longer
pollute the generic app archetype's reuse list. (Takes effect on re-bootstrap.) - An unopenable
CHAMELEON_HOOK_ERROR_LOG(a broken symlink, or a path under a
missing parent dir) made every hook silently skip its Python and no-op — the
round-1 FIFO guard only caught an existing non-regular file. The guard now also
falls back to/dev/nullfor a broken symlink or a missing parent dir. - A rename inside a file re-exported through a barrel (
export * from './x')
was silent at turn end. The reverse index attributes a star-re-exported name
to the barrel module, not the origin, so removing it from the origin left the
barrel's importers broken while the origin's own key had no reverse entry. The
advisory now finds siblingindex.*barrels that re-export an edited origin,
recomputes the barrel's effective export set (expanding its stars one level),
and reports the broken importers. It fails safe: any unreadable or nested-star
source suppresses the finding, so a name still provided by another star of the
same barrel never false-fires, and an importer updated in the same turn is
dropped by the live re-check. - The TS export-name reader missed
export type { X } fromre-exports and
mis-read an inlineexport { type Foo, Bar }clause (droppingFoo, adding a
spurioustype), so a re-exported type read as a broken existence-break on a
clean file — a cross-file false positive, most visible through the barrel path
above. The export-clause parser now matches thetypemodifier and strips the
inlinetypespecifier prefix, so type-only exports are counted like any other
export. - The cross-file reference check now reuses the proven TS lexer, adding
JSX-text handling. The cross-file reference check blanks literal JSX text
children so a removed export lingering only as<Tag>Name</Tag>text (which
renders text, not the variable — unlike{Name}) no longer false-fires. It
works by iteratively collapsing complete JSX elements, which only exist in
genuine JSX because a closing</is unforgeable by real code once strings,
comments, and templates are blanked;{expr}spans are always kept, so a real
reference in an expression — even nested between closing tags
(<A><B>x</B>{Foo}</A>) — still fires, and a comparisonx > Foo, division
a / Foo / b, or genericArray<Foo>(none of which forms a complete element)
is never mistaken for JSX and never hidden. The check uses the char-scan
tokenizer, not the export reader's lexer, deliberately: that lexer's regex
detector misreads the/of a</span>closing tag as a regex and would blank
a real reference between two closing tags. Instead, regex-literal handling lives
in the char-scan tokenizer with a JSX-safe guard — a/in expression position
(including right afterreturn/typeof/etc.) opens a regex and is blanked,
EXCEPT when it immediately follows<(a</Tag>close or< /re/comparison).
Soconst r = /Foo/stops false-firing whilea / Foo / b,x > Foo, a
variable namedmyreturn / Foo, and a real{Foo}between closing tags are
never touched.
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.
Share this release
About Chameleon
All releases →Beta — feedback welcome: [email protected]