Skip to content

fix: output cap on the server, HTTP warning case, and a stricter store gate (review D-03, D-04, D-05) - #34

Merged
HackTuah merged 3 commits into
mainfrom
fix/sec-d-small
Oct 7, 2026
Merged

HackTuah merged 3 commits into
mainfrom
fix/sec-d-small

Conversation

@HackTuah

@HackTuah HackTuah commented Oct 7, 2026

Copy link
Copy Markdown
Member

Three findings from the fourth security reviewer on v1.0.0-rc.2 (slice 54, SCR-476). Each comes with a regression test that failed on the old code.

  • D-03 (= SCR-761): the server's model client now sends the output cap on every request: 1200 for a lookup, 4000 for a respelling. The field comes from the matching spec/providers.json preset, so OpenAI gets max_completion_tokens and everyone else gets max_tokens.
  • D-04: the plain-HTTP key warning reads the scheme through URI.parse, so HTTP:// and Http:// warn too. The OpenRouter check now ignores capital letters in the host.
  • D-05: the store gate normalises each line (NFKC, invisible characters removed) and treats anything that looks like a gate line as one. A lookalike, zero-width or fullwidth-colon "open" line now keeps the gate open, and the closing line must be plain ASCII. When the gate stays open, the command says why. The lead reran reviewer D's 16 variants: the three disguises now read as open, and the controls are unchanged.

Checks: lint; npm test (2046 pass); mix test (1168 tests, 23 properties, 91.59 %); credo strict; Sobelow; REUSE; DCO.

🤖 Generated with Claude Code

Ayla Croft and others added 3 commits October 7, 2026 11:51
The server sent the spec's output cap (policy.max_tokens: 1,200 for a
lookup, 4,000 for a respelling) only when OpenRouter's catalogue said
the model takes it. A model the catalogue doesn't know, such as an
LLM_MODEL on a paid custom endpoint, got no cap at all, so a model that
kept writing could bill the owner's key up to the provider's own limit
(security review D-03, Linear SCR-761).

The cap now goes on every request, as the extension already does. Its
field is the maxTokensField of the spec/providers.json preset on
LLM_URL's host (max_completion_tokens for OpenAI), max_tokens
otherwise; the server now embeds providers.json with the rest of the
spec. Four new tests in llm_test.exs (custom endpoint, OpenRouter with
an unknown model, OpenAI's field, a respelling) failed before the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Ayla Croft <dev@scriptkittyos.com>
The warning for an API key sent over plain HTTP to another machine
matched "http://" case-sensitively, so LLM_URL=HTTP://203.0.113.7/v1
with a key gave no warning, while the request still went out in the
clear (security review D-04). The check now takes the scheme from
URI.parse/1, which lowercases it.

Checking the other URL comparisons in config.ex and exposure.ex: the
URL validator already compared URI.new/1's lowercased scheme and
Exposure.network/1 already lowercases the host, but openrouter?/1
compared the host as written, so https://OpenRouter.ai/... wasn't
treated as OpenRouter; it now lowercases the host too. The new tests in
config_test.exs (HTTP://, Http://, hTtP://, HTTP:// for the transcriber,
and an OpenRouter host in capitals) failed before the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Ayla Croft <dev@scriptkittyos.com>
The store gate counts the lines above a review's "## Appendix" heading
that read as a gate line and needs exactly one, the exact closed line.
Its pattern was ASCII-only, so an open gate line written with a Cyrillic
letter, behind a zero-width space or with a fullwidth colon wasn't
counted: beside the closed line, a reader saw "Gate: open" while the
check read the gate as closed (security review D-05).

Each line is now NFKC-normalised and stripped of invisible characters
(format and default-ignorable code points) and combining marks before
the check. A line then reads as a gate line when its first four
characters after any leading markup are g, a, t, e (any case) or any
character outside ASCII, with no letter or digit after them. Letting
any non-ASCII character stand in for a letter catches lookalikes from
every script, and colon lookalikes NFKC keeps, without a confusables
table; it errs on the side of keeping the gate open. The closing line
must also be plain ASCII. When the gate stays open, the command now
says why for each report and names the lines.

The new tests in security-gate.test.mjs cover the reviewer's three
lines (Cyrillic a, a leading U+200B, a fullwidth colon) and seven more
disguises, before and after the closed line, plus the exact closed line
and ordinary prose still closing it. Against the old script, all three
of the reviewer's reports read as closed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Ayla Croft <dev@scriptkittyos.com>
@HackTuah
HackTuah requested a review from minitru as a code owner October 7, 2026 15:58
@HackTuah
HackTuah merged commit 3f966df into main Oct 7, 2026
14 checks passed
@HackTuah
HackTuah deleted the fix/sec-d-small branch October 7, 2026 16:12
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