Skip to content

fix: compose gate read the wrong directory; report CSS escape emitted the wrong glyph - #4

Merged
MyAlterLego merged 2 commits into
mainfrom
fix/compose-dir-arg-and-css-escape
Jul 25, 2026
Merged

MyAlterLego merged 2 commits into
mainfrom
fix/compose-dir-arg-and-css-escape

Conversation

@MyAlterLego

Copy link
Copy Markdown
Contributor

Two bugs found while running a full-surface analysis against a large TypeScript monorepo (~3,600 files, 35 control actions, 140 cells). Both were hit in the course of a normal stpa run, and one of them put a false banner on a finished report, which is why I'd treat it as the more urgent of the pair.

1 · ComposeChains.ts discarded its own directory argument

argv.find((a) => !a.startsWith("--") && a !== argv[depthArg + 1])

With --max-depth absent, depthArg is -1, so argv[depthArg + 1] is argv[0] — the positional directory itself. It was filtered out of its own lookup and dir fell back to ".".

stpa run calls compose without --max-depth, so the gate read the current working directory, found no remediation.json, and printed:

missing remediation.json in <cwd> — run `stpa plan` first
!! UNRATED COMPOSITION (exit 1).

…against an analysis whose composition was fully rated (0 upgrades, 0 band changes when invoked with an explicit --max-depth). A gate reporting a failure it did not observe is worse than one that stays silent, because the banner it stamps on the report is false — and this gate exists precisely to stop an under-rated band reaching a reader.

Guarding on depthArg !== -1 changes behaviour only in the two broken cases. Verified across argv permutations:

argv before after
["dir","--check"] . dir ✅
["dir"] . dir ✅
["dir","--check","--max-depth","4"] dir dir
["--check","--max-depth","4"] . .
["--max-depth","4","dir"] dir dir
[] . .

Note the fourth row: "4" is still correctly not mistaken for the directory.

2 · The disclosure-triangle escape never reached CSS

content:"\25B8"      // inside the `html` template literal

\25 is consumed by the JavaScript parser before CSS sees it, so the emitted stylesheet never contained \25B8 (U+25B8 ▸). Where a parser accepts it as a legacy octal escape it yields U+0015 followed by the literal B8, making the rule content:"\x15B8" — a control character instead of a marker. Template literals also forbid legacy octal escapes outright, so a spec-compliant parser rejects the file at parse time rather than mis-rendering it, which is how it surfaced.

Doubling the backslash emits \25B8 into the stylesheet. Verified in a rendered REPORT.html.

One honest caveat, stated in the commit too: I could not exercise this under Bun in the environment where I found it, so the "what Bun renders" claim is derived from escape semantics rather than observed. The emitted-CSS fix itself is verified.

Deliberately not included

  • A bun→node shim I used locally. That's an environment workaround, not a toolkit change, and CONTRIBUTING.md is explicit that this is a Bun project — making it runtime-agnostic is a scope decision for you, not something to smuggle into a bugfix PR.
  • Tools/UcaGrid.ts readJson() calls Bun.file(path) and then discards the result (void f) while reading via fs.readFileSync. Harmless dead code, but it's the only thing forcing a Bun global in that file. Happy to strip it in a separate PR if you want it gone.

No dependencies added; no behaviour changed beyond the two defects.

`ComposeChains.ts` resolved its analysis directory with:

    argv.find((a) => !a.startsWith("--") && a !== argv[depthArg + 1])

When `--max-depth` is not passed, `depthArg` is -1, so `argv[depthArg + 1]`
is `argv[0]` — the positional directory itself. The directory was therefore
filtered out of its own lookup and `dir` fell back to `"."`.

The visible symptom is worse than a wrong path: `stpa run` invokes compose
without `--max-depth`, so the gate read the *current working directory*,
found no `remediation.json`, and printed

    missing remediation.json in <cwd> — run `stpa plan` first
    !! UNRATED COMPOSITION (exit 1).

against an analysis whose composition was fully rated. A gate that reports
a failure it did not actually observe is worse than one that stays quiet,
because the banner it puts on the report is false.

Guarding on `depthArg !== -1` changes behaviour only in the two broken
cases (no `--max-depth`). Verified against all argv permutations, including
`--max-depth 4` with no positional directory, which still correctly
resolves to "." rather than treating "4" as the directory.
…t \x15B8

The collapsible-section marker was written as a single-backslash escape
inside the `html` template literal:

    content:"\25B8"

`\25` is consumed by the *JavaScript* parser before CSS ever sees it, so the
emitted stylesheet did not contain the intended `\25B8` (U+25B8 ▸). Where a
parser accepts it as a legacy octal escape it yields U+0015 followed by the
literal characters `B8`, so the rule becomes `content:"\x15B8"` and the
triangle renders as a control character rather than a marker.

Template literals also forbid legacy octal escapes outright, so a
spec-compliant parser rejects the file at parse time rather than
mis-rendering it — which is how this surfaced.

Doubling the backslash emits the two characters `\` `2` `5` `B` `8` into the
CSS, which is what the CSS escape needs. Verified in a rendered REPORT.html:
the stylesheet now contains content:"\25B8".

Note: I could not exercise this under Bun (not available in the environment
where it was found), so the Bun-side rendering claim above is derived from
the escape semantics rather than observed. The emitted-CSS fix is verified.
@MyAlterLego
MyAlterLego merged commit 2e0ddf3 into main Jul 25, 2026
2 checks passed
@MyAlterLego
MyAlterLego deleted the fix/compose-dir-arg-and-css-escape branch July 25, 2026 23:04
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.

2 participants