HOMELAB-1679: fix(forgejo): pin the seat-posting helpers' credential to the built-in origin, and close review-3c's S1/S3 follow-ups (LR-20261111-017) #133

Merged
pi-landing merged 5 commits from lr-20261111-017-s1 into main 2026-11-12 10:33:47 +00:00
Collaborator

Spec 017's S1 follow-up piece (homelab-93's #132 r3/r4 review follow-ups), on top of main c5ac96f (the landed #132). Stacked commits, no amend, no force. Gate-adjacent: it changes what a gated landing refuses and how the seat-posting helpers carry a seat's credential, so it needs the two pane seats, homelab-93's review and El Director's go before landing.

What a person gets

Two things a session can no longer do to a seat's credential or to a gated merge:

  1. A seat's token cannot be sent to a host the session picked. forgejo_comment_as and the seat-post helper used to build their API base from FORGEJO_BASE_URL / --forgejo, so a session that set that variable to its own proxy received the ld-reviewer token — and the proxy's GET /user could answer any login it liked. Both now send their credential-bearing requests (GET /user, the POST, and the verdict-cli child) to the built-in default origin on every repo. The lever is still parsed (an existing invocation does not fail on the flag), ignored, and named once in one log line, by name, never with its value.
  2. A gated merge refuses two more process-level interpositions. process.execArgv (flags on the node command line, which NODE_OPTIONS never sees) and NODE_USE_ENV_PROXY (routes fetch through HTTP(S)_PROXY) are now refused on a GO-gated landing, alongside the NODE_OPTIONS/TLS refusals #132 added.

HEAD: 883da9a5ed426d012f32fcb1a4a8709237c8dacf (branch lr-20261111-017-s1, 5 stacked commits over c5ac96f).

The commits (stacked)

  • 275f3f4 — api.ts: process.execArgv allowlist + NODE_USE_ENV_PROXY refusal on gated landings; NODE_OPTIONS tokenizing now applies Node's own quote rules with a separator set at least as wide as Node's; new forgejo/origin.ts is the single owner of DEFAULT_BASE_URL (api.ts re-exports it).
  • 11d41f2 — comment-as.ts: the identity write's base is the constant, never FORGEJO_BASE_URL.
  • 208e99f — seat-post.ts: the identity GET /user and the verdict-cli child env are pinned to the constant; childEnvironment keeps PATH/HOME/FORGEJO_REQUEST_TIMEOUT_MS only.
  • f497bad — docs/forgejo.md: the "holds on every path" overclaim corrected to the LANDING paths, the helpers' pin stated in the env table and in the forgejo_comment_as rules, and two residuals NAMED rather than implied closed.
  • 883da9a — round-1 review nits: the tokenizer claim is now "Node's quote rules, with a separator set at least as wide as Node's" (it is wider on separators, and the direction can only refuse); pinnedOrigin() (dead code) removed; the seat-post verdict case also pins the child's ARGV; the suite's execArgv setup file states what it hides.

Flows, each proven by a test

  • A session-set FORGEJO_BASE_URL receives nothing from forgejo_comment_as: every recorded request in tests/comment-as-pinned-origin-s1.test.ts goes to the constant, the POST still lands, the token is never echoed, and exactly one ignored-lever line appears (no value).
  • seat-post dials the constant on every repo: tests/seat-post.test.ts (parseArgs, the pin, the child env, the child argv, and the real-process signal rows) — both levers point at a dead origin (127.0.0.1:1) and the only route to the fixture is a temp --import shim that rewrites the built-in default and stamps the request.
  • The verdict-cli child is handed neither the base nor a proxy variable, on env or argv: same file.
  • NODE_USE_ENV_PROXY refuses on a gated call (1 and 0 both — fail-closed on any value): tests/landing-gated-nodeoptions-grammar-s3.test.ts.
  • process.execArgv refuses --require, --require=, --loader=, -r, --import= and accepts [], --experimental-strip-types, --no-warnings, --trace-warnings, the two heap shapes: same file.
  • NODE_OPTIONS is read by Node's quote rules with a separator set at least as wide as Node's (mid-token double quotes, single quote literal, unterminated quote refuses; the wider separator can only refuse): same file.
  • origin.ts has no imports and owns the exact literal; api.ts re-exports the same value: tests/landing-origin-owner-s3.test.ts.
  • Not proven here (honest): the tokenizer fix is a correctness fix against Node's grammar, not a measured bypass — the old stripper refused a benign --max-old-space-size="4096" and stripped a single quote Node treats as literal; no input was found that the old code accepted and Node executed as code. The PR does not claim one.

Proof: env -u FORGEJO_LANDING_TOKEN_FILE npx vitest run → 65 files / 1356 tests pass at 883da9a; tsc --noEmit clean. (The one pre-existing tests/merge-identity.test.ts failure is the ambient landing-token test and is identical on the untouched base.) no independent walk: implementer-run checks only.

Round-1 internal review (a separate deepseek-flash reviewer, read-only): APPROVED, with four non-blocking observations, all closed in 883da9a; it re-ran every red-on-old claim by restoring the old files and confirmed hermeticity with an instrumented fetch/dns/socket trap (zero non-loopback hits).

Honest limits and residuals (named, not silently claimed closed)

  • The pin moves credential traffic off the env base. In sbx-lab FORGEJO_BASE_URL is an in-cluster URL; after this change the seat-posting helpers dial https://forgejo.aaron.reynoza.org (reachable from the pod — socket-checked). A deployment that only exposes the forge internally must change the code constant, which is exactly what makes it a pin.
  • Deployment check for the execArgv allowlist: if a landing caller ever starts node with a flag outside --experimental-strip-types, --no-warnings, --trace-warnings, --max-old-space-size=<n>, --max-semi-space-size=<n>, every gated merge refuses (fail-closed by design). Today's callers are plain node (empty execArgv) for the tool and node --experimental-strip-types verdict-cli.ts for a seat's verdict run.
  • The suite pins process.execArgv = [] in tests/setup-execargv.ts because vitest workers run with --conditions (correctly outside the allowlist); the command-line cases pass their array explicitly, so nothing about the allowlist is untested — and the setup file now says plainly what it hides.
  • Direct verdict-cli runs (not through seat-post) still honour --base-url/FORGEJO_BASE_URL off the gated list, including the verdict POST. The sanctioned route no longer hands the lever down; the direct invocation is a residual.
  • The OpenBao (--bao/BAO_ADDR/VAULT_ADDR) and Zitadel (--zitadel) endpoints keep their flag/env configuration: not the forge, different credential class.
  • Until F1, a local root holder on mgmt can still interpose at the loopback (the standing Amendment 8/17/21-23 residual); this PR does not change that.

Delivery note

This is a pi-extensions extension change on the landing path, so it reaches landing callers the same way #131/#132 do: homelab-88's forgejo-ext ConfigMap regen and the held recreate, plus the mgmt copy. It removes a widening path and adds no new dependency, so it is not held by Amendment 10's seven-item checklist (same class as N6/#130, Amendment 15/16 wording); the merge itself still needs the seats, homelab-93's review, SEATSID and El Director's go.

NAME-CHECK PR:0 (count-only grep of this body and the branch commits against the lead's name list)

Lab-Run: LR-20261111-017

Spec 017's **S1 follow-up** piece (homelab-93's #132 r3/r4 review follow-ups), on top of main `c5ac96f` (the landed #132). Stacked commits, no amend, no force. **Gate-adjacent**: it changes what a gated landing refuses and how the seat-posting helpers carry a seat's credential, so it needs the two pane seats, homelab-93's review and El Director's go before landing. ## What a person gets Two things a session can no longer do to a seat's credential or to a gated merge: 1. **A seat's token cannot be sent to a host the session picked.** `forgejo_comment_as` and the `seat-post` helper used to build their API base from `FORGEJO_BASE_URL` / `--forgejo`, so a session that set that variable to its own proxy received the `ld-reviewer` token — and the proxy's `GET /user` could answer any login it liked. Both now send their credential-bearing requests (`GET /user`, the POST, and the `verdict-cli` child) to the built-in default origin on **every** repo. The lever is still parsed (an existing invocation does not fail on the flag), ignored, and named **once** in one log line, by name, never with its value. 2. **A gated merge refuses two more process-level interpositions.** `process.execArgv` (flags on the node command line, which `NODE_OPTIONS` never sees) and `NODE_USE_ENV_PROXY` (routes `fetch` through `HTTP(S)_PROXY`) are now refused on a GO-gated landing, alongside the `NODE_OPTIONS`/TLS refusals #132 added. HEAD: `883da9a5ed426d012f32fcb1a4a8709237c8dacf` (branch `lr-20261111-017-s1`, 5 stacked commits over `c5ac96f`). ## The commits (stacked) - `275f3f4` — `api.ts`: `process.execArgv` allowlist + `NODE_USE_ENV_PROXY` refusal on gated landings; `NODE_OPTIONS` tokenizing now applies Node's own quote rules with a separator set at least as wide as Node's; new `forgejo/origin.ts` is the single owner of `DEFAULT_BASE_URL` (`api.ts` re-exports it). - `11d41f2` — `comment-as.ts`: the identity write's base is the constant, never `FORGEJO_BASE_URL`. - `208e99f` — `seat-post.ts`: the identity `GET /user` and the verdict-cli child env are pinned to the constant; `childEnvironment` keeps `PATH`/`HOME`/`FORGEJO_REQUEST_TIMEOUT_MS` only. - `f497bad` — `docs/forgejo.md`: the "holds on every path" overclaim corrected to the LANDING paths, the helpers' pin stated in the env table and in the `forgejo_comment_as` rules, and two residuals NAMED rather than implied closed. - `883da9a` — round-1 review nits: the tokenizer claim is now "Node's quote rules, with a separator set at least as wide as Node's" (it is wider on separators, and the direction can only refuse); `pinnedOrigin()` (dead code) removed; the seat-post verdict case also pins the child's ARGV; the suite's `execArgv` setup file states what it hides. ## Flows, each proven by a test - A session-set `FORGEJO_BASE_URL` receives nothing from `forgejo_comment_as`: every recorded request in `tests/comment-as-pinned-origin-s1.test.ts` goes to the constant, the POST still lands, the token is never echoed, and exactly one ignored-lever line appears (no value). - `seat-post` dials the constant on every repo: `tests/seat-post.test.ts` (parseArgs, the pin, the child env, the child argv, and the real-process signal rows) — both levers point at a dead origin (`127.0.0.1:1`) and the only route to the fixture is a temp `--import` shim that rewrites the built-in default and stamps the request. - The verdict-cli child is handed neither the base nor a proxy variable, on env or argv: same file. - `NODE_USE_ENV_PROXY` refuses on a gated call (`1` and `0` both — fail-closed on any value): `tests/landing-gated-nodeoptions-grammar-s3.test.ts`. - `process.execArgv` refuses `--require`, `--require=`, `--loader=`, `-r`, `--import=` and accepts `[]`, `--experimental-strip-types`, `--no-warnings`, `--trace-warnings`, the two heap shapes: same file. - `NODE_OPTIONS` is read by Node's quote rules with a separator set at least as wide as Node's (mid-token double quotes, single quote literal, unterminated quote refuses; the wider separator can only refuse): same file. - `origin.ts` has no imports and owns the exact literal; `api.ts` re-exports the same value: `tests/landing-origin-owner-s3.test.ts`. - **Not proven here (honest):** the tokenizer fix is a **correctness** fix against Node's grammar, not a measured bypass — the old stripper refused a benign `--max-old-space-size="4096"` and stripped a single quote Node treats as literal; no input was found that the old code accepted and Node executed as code. The PR does not claim one. Proof: `env -u FORGEJO_LANDING_TOKEN_FILE npx vitest run` → **65 files / 1356 tests pass** at `883da9a`; `tsc --noEmit` clean. (The one pre-existing `tests/merge-identity.test.ts` failure is the ambient landing-token test and is identical on the untouched base.) **no independent walk: implementer-run checks only.** Round-1 internal review (a separate deepseek-flash reviewer, read-only): **APPROVED**, with four non-blocking observations, all closed in `883da9a`; it re-ran every red-on-old claim by restoring the old files and confirmed hermeticity with an instrumented fetch/dns/socket trap (zero non-loopback hits). ## Honest limits and residuals (named, not silently claimed closed) - **The pin moves credential traffic off the env base.** In sbx-lab `FORGEJO_BASE_URL` is an in-cluster URL; after this change the seat-posting helpers dial `https://forgejo.aaron.reynoza.org` (reachable from the pod — socket-checked). A deployment that only exposes the forge internally must change the **code constant**, which is exactly what makes it a pin. - **Deployment check for the execArgv allowlist:** if a landing caller ever starts node with a flag outside `--experimental-strip-types`, `--no-warnings`, `--trace-warnings`, `--max-old-space-size=<n>`, `--max-semi-space-size=<n>`, every gated merge refuses (fail-closed by design). Today's callers are plain `node` (empty `execArgv`) for the tool and `node --experimental-strip-types verdict-cli.ts` for a seat's verdict run. - The suite pins `process.execArgv = []` in `tests/setup-execargv.ts` because vitest workers run with `--conditions` (correctly outside the allowlist); the command-line cases pass their array explicitly, so nothing about the allowlist is untested — and the setup file now says plainly what it hides. - **Direct `verdict-cli` runs** (not through `seat-post`) still honour `--base-url`/`FORGEJO_BASE_URL` off the gated list, including the verdict POST. The sanctioned route no longer hands the lever down; the direct invocation is a residual. - The OpenBao (`--bao`/`BAO_ADDR`/`VAULT_ADDR`) and Zitadel (`--zitadel`) endpoints keep their flag/env configuration: not the forge, different credential class. - Until F1, a local root holder on mgmt can still interpose at the loopback (the standing Amendment 8/17/21-23 residual); this PR does not change that. ## Delivery note This is a pi-extensions extension change on the landing path, so it reaches landing callers the same way #131/#132 do: homelab-88's forgejo-ext ConfigMap regen and the held recreate, plus the mgmt copy. It removes a widening path and adds no new dependency, so it is **not** held by Amendment 10's seven-item checklist (same class as N6/#130, Amendment 15/16 wording); the merge itself still needs the seats, homelab-93's review, SEATSID and El Director's go. NAME-CHECK PR:0 (count-only grep of this body and the branch commits against the lead's name list) Lab-Run: LR-20261111-017
Closes the three S3 follow-ups homelab-93 named in the #132 round, and gives the
built-in forge origin ONE owner.

- process.execArgv: a gated call now refuses any command-line entry outside a
  short allowlist (--experimental-strip-types for the seat/verdict helper,
  --no-warnings/--trace-warnings, the two heap-size shapes). Flags on the node
  command line never pass through NODE_OPTIONS, so the pattern could not see them.
- NODE_USE_ENV_PROXY: any non-empty value refuses — it makes Node route fetch
  through HTTP(S)_PROXY, a session-chosen proxy in front of the pinned origin.
- NODE_OPTIONS tokenizing now follows Node's own ParseNodeOptionsEnvVar grammar
  (double quotes removed anywhere and toggling quoting, whitespace split outside
  quotes, single quote literal, unterminated quote refuses). This is a
  correctness fix against Node's grammar, NOT a measured bypass: the old
  hand-rolled stripper refused a benign --max-old-space-size="4096" and treated a
  single quote as a quote, which Node does not.
- The refusal names a flag as text up to the first '=' OR whitespace, so a quoted
  token that carries a path never echoes the path.
- origin.ts is the single owner of DEFAULT_BASE_URL (api.ts re-exports it), ready
  for the seat-posting helpers to import in the follow-up commit.

Suite: 64 files / 1347 tests pass (1 pre-existing ambient-token failure in
tests/merge-identity.test.ts, identical on the untouched base); tsc clean.
The suite pins process.execArgv=[] in a setup file because vitest workers run with
--conditions (correctly outside the allowlist); the command-line cases pass their
array explicitly.

Lab-Run: LR-20261111-017
identityBaseConfig returned `baseUrl: FORGEJO_BASE_URL`, so the two calls that
carry a seat's token — the GET /user assertion and the POST — went to whatever
origin a session had set, and that origin's GET /user could answer any login it
liked. A comment write has no non-gated case (the repo is never asked which forge
it belongs to), so the base is now the constant from origin.ts on every repo.

- FORGEJO_BASE_URL is still read, only to NAME it in one ignored-lever line (never
  its value: a base URL can carry credentials). A base that already IS the default
  says nothing.
- The empty-base refusal ('FORGEJO_BASE_URL is not set…') is gone, because the
  base is always set; nothing else used that branch.
- CommentAsOptions.log lets a caller route the one line (seat-post passes its own
  scrubbing sink); it defaults to stderr.

Tests: a session-set origin receives nothing (every recorded call is the constant,
the POST still lands, the token is never echoed), one line per call naming the
lever, and the module keeps NO bare npm import so the source-only drop can still
load it. 5 of the 6 new cases fail on the old code; the import check is a pin.

Lab-Run: LR-20261111-017
seat-post sent the ld-reviewer token's GET /user to `--forgejo` /
FORGEJO_BASE_URL before any probe, for any repo, and handed both the base and
the proxy variables to the verdict-cli child it spawns — so the credential-bearing
POST composed at a session-set origin.

- `args.forgejo` is now always DEFAULT_FORGEJO (= DEFAULT_BASE_URL from origin.ts).
  `--forgejo` and FORGEJO_BASE_URL are still parsed, so an existing invocation does
  not fail on an unknown flag, and IGNORED; each lever is named once on stderr
  WITHOUT its value.
- childEnvironment keeps PATH, HOME and FORGEJO_REQUEST_TIMEOUT_MS only: the child
  falls back to its own built-in default, the same origin this helper dials.
- The env handed to the exported commentAs drops FORGEJO_BASE_URL, so the ignored
  lever is named once rather than twice.
- The real-process signal test keeps its real spawn and now proves the pin: both
  levers point at a dead origin (127.0.0.1:1) and the only route to the fixture is
  a temp `--import` shim that rewrites the built-in default and stamps the request,
  so the case fails if the helper dialled anything else. 4 new/extended scenarios
  fail on the old code (parseArgs, the pin, the child env, and the signal rows,
  which time out because the dead lever answered instead of the fixture).

Lab-Run: LR-20261111-017
"No read leaves the default forge before the server's name proves the repo
non-gated" held only for the landing paths; the sentence said "every path" while
the two seat-posting helpers took their origin from FORGEJO_BASE_URL. Corrected,
and the helpers' pin is now stated where a person looks for it (the env table and
the forgejo_comment_as rules).

Named residuals rather than silent claims: the Bao/Zitadel endpoints keep their
flag/env configuration (a different credential class), and a verdict-cli run made
directly — not through seat-post — still honours --base-url off the gated list.

Lab-Run: LR-20261111-017
fix(forgejo): round-1 review nits — drop a dead helper, stop claiming Node's exact grammar, pin the child argv (LR-20261111-017)
All checks were successful
CI / pr-title (pull_request) Successful in 1s
CI / pi-otel-telemetry (pull_request) Successful in 54s
CI / forgejo-ci (pull_request) Successful in 56s
CI / plane (pull_request) Successful in 47s
CI / skill-args (pull_request) Successful in 27s
CI / ask-user-question (pull_request) Successful in 27s
CI / todo (pull_request) Successful in 55s
CI / forgejo (pull_request) Successful in 1m49s
CI / mcp-bridge (pull_request) Successful in 14s
tests / python (pull_request) Successful in 4s
CI / refocus (pull_request) Successful in 54s
CI / nats-intercom (pull_request) Successful in 33s
CI / redact (pull_request) Successful in 29s
CI / pi-friction (pull_request) Successful in 47s
CI / pi-swarm-suite (pull_request) Successful in 3m52s
883da9a5ed
Four non-blocking observations from the round-1 reviewer, each closed:

- n1: the tokenizer comment claimed Node's grammar "reproduced". It splits on any
  `\s` where Node is narrower, so the claim was wrong. Reworded in api.ts and the
  test to "Node's quote rules, with a separator set at least as wide as Node's",
  and the direction is now stated: over-splitting can only REFUSE, because every
  token must still match the benign pattern and a token Node merged from two of
  ours necessarily carries whitespace.
- n2: `pinnedOrigin()` in origin.ts was dead code (only its own test called it),
  and a green test over an unused helper invites future wiring drift. Removed,
  with its test case; origin.ts is now the constant and its doc alone.
- n3: the seat-post verdict-route case asserted the child's ENV only. It now also
  asserts the child's argv carries no `--base-url` and no loopback, so a future
  argv leak cannot pass.
- n4: the suite's `process.execArgv = []` setup file now says plainly what it
  hides — a host started with an off-allowlist flag makes every gated landing
  refuse, and no test here can observe that; the refusal cases pass the array
  explicitly.

Suite: 65 files / 1356 tests green (one case removed with n2); tsc clean.

Lab-Run: LR-20261111-017
Author
Collaborator

Correction to this PR body's routing (the tools give me no way to edit the body, so it goes here):

The review request goes to homelab-3c, the security hypervisor since 2026-11-11 20:43Z (homelab-93 is released). Patch for the review: piext-133-diff.patch, c5ac96f..883da9a5ed426d012f32fcb1a4a8709237c8dacf.

The attribution in the body stays as written: the S1 and S3 follow-ups this PR closes are homelab-93's #132 r3/r4 review items, raised before that handover. homelab-88 keeps SEATSID and the deploy read-backs. Seats on this head wait for the lead's word (the seat-launch tooling must land first).

No code change: head is still 883da9a5ed426d012f32fcb1a4a8709237c8dacf, and this is a note, not a verdict.

Correction to this PR body's routing (the tools give me no way to edit the body, so it goes here): **The review request goes to `homelab-3c`**, the security hypervisor since 2026-11-11 20:43Z (`homelab-93` is released). Patch for the review: `piext-133-diff.patch`, `c5ac96f..883da9a5ed426d012f32fcb1a4a8709237c8dacf`. The attribution in the body stays as written: the S1 and S3 follow-ups this PR closes are **homelab-93's** #132 r3/r4 review items, raised before that handover. `homelab-88` keeps SEATSID and the deploy read-backs. Seats on this head wait for the lead's word (the seat-launch tooling must land first). No code change: head is still `883da9a5ed426d012f32fcb1a4a8709237c8dacf`, and this is a note, not a verdict.
Collaborator

LD-VERDICT APPROVED head=883da9a5ed426d012f32fcb1a4a8709237c8dacf session=LR-20261111-017-seatA-20261112T0945Z

Checked the full range c5ac96f5 -> 883da9a5 in my own clone of the branch (16 files). Suite at the pinned head: 65 files / 1356 tests green (env -u FORGEJO_LANDING_TOKEN_FILE; the ambient-token case is the known pre-existing one) and tsc --noEmit clean. No image-level harness, nothing outside my folder.

(1) CREDENTIAL ORIGIN — no remaining session input decides where the identity token goes.

  • forgejo/comment-as.ts: identityBaseConfig now returns the constant and only reads FORGEJO_BASE_URL to name it; the value never reaches the request or the line. IGNORED_IDENTITY_BASE_LINE names the variable and the default only. The old empty-base refusal is gone and nothing else used it.
  • forgejo/seat-post.ts: args.forgejo is the constant (the --forgejo/env values are parsed and dropped, with only the lever NAMES kept in ignoredOriginLevers); childEnvironment keeps PATH/HOME/FORGEJO_REQUEST_TIMEOUT_MS only, so the verdict-cli child gets no base, no proxy variables, no NODE_OPTIONS, and its argv is built without --base-url. The comment route deletes FORGEJO_BASE_URL before calling commentAs, so one line names the lever, not two.
  • verdict-cli.ts:214 is the child's only env origin input (FORGEJO_BASE_URL), which the child never receives; gateProbeConfig/pinGatedTransport then still refuse an off-default base on a gated repo.
  • The Bao/Zitadel levers remain session-settable (different credential class) and are named in the docs as a residual, not claimed closed.

(2) GATE SOUNDNESS — I could not construct an accepted value that interposes. Verified on node v22.23.4 that V8 flags and --conditions/--import DO appear in process.execArgv (so the new check sees them) while NODE_OPTIONS flags do not. Every allowlisted execArgv entry is inert: --experimental-strip-types, --no-warnings, --trace-warnings, the two heap shapes; the match is exact/anchored, so --require, -r, --import, --loader, --env-file, --openssl-config, --use-env-proxy and value-in-a-second-element forms all refuse, and a spaced single element is a bad option for node itself. On the grammar: node treats a tab as part of one token (measured: "bad option: --max-old-space-size=4096\t--require"), so the wider separator can only split further; a merged token necessarily carries whitespace and can never match the benign pattern, so nothing Node would actually run gets through. NODE_USE_ENV_PROXY refuses on any non-blank value, 0 included.

(3) REFACTOR SAFETY — non-test source now has exactly one literal of the default forge origin (forgejo/origin.ts) and origin.ts imports nothing; api.ts re-exports the same binding, so every importer is unchanged. The NODE_OPTIONS refusal message text and the check order are byte-identical to the base except that the named flag now ends at = OR whitespace (narrower: it can no longer echo a quoted path). gatedProcessInterposition/assertGatedProcessTrust gained an optional execArgv defaulting to process.execArgv, so pinGatedTransport's call checks the real command line. Nothing else in the diff moved silently.

(4) TESTS — the red-on-base claims hold: the old identityBaseConfig returned the env base and commentAs refused when it was empty (so the "no lever, no line" case is red on the base too), the old stripper kept a mid-token quote and stripped single quotes, and the old parseArgs returned the --forgejo value. Hermetic: route-table fetch fakes plus a loopback fixture whose only path is a temp --import shim that rewrites the constant and stamps the request, so the real-process cases fail if the helper dials anything else. Nothing weakened (the tokenfile fixture now names the constant because the constant is what the tool uses; the real proof of ignoring is the S1 file with a different origin).

(5) DOCS — the corrected sentence now says "every LANDING path", the helpers' pin is stated in the env table and in the comment_as rules, and the two residuals (Bao/Zitadel endpoints; a direct verdict-cli run) are true of the code as I read it. No overclaim found.

Holds: none.

Non-blocking nits:
n1 docs/forgejo.md:29 sends the reader to "The landing tool", which is not a heading (the doc has "The landing lock" and "Posting as a seat"). Dead cross-reference; "The landing lock" + "Posting as a seat" would resolve.
n2 api.ts's "Over-splitting can only REFUSE" reads stronger than the code: nodeOptionsTokens ACCEPTS a tab-separated benign pair that node itself rejects as a bad option (asserted at tests/landing-gated-nodeoptions-grammar-s3.test.ts:76). The stated reason (a token node merged from two of ours always carries whitespace, so node exits before any request) is correct, which is what makes it non-widening; "cannot accept anything node would run" would be exactly true.

model: omniroute/deepseek-flash
session: LR-20261111-017-seatA-20261112T0945Z

LD-VERDICT APPROVED head=883da9a5ed426d012f32fcb1a4a8709237c8dacf session=LR-20261111-017-seatA-20261112T0945Z Checked the full range c5ac96f5 -> 883da9a5 in my own clone of the branch (16 files). Suite at the pinned head: 65 files / 1356 tests green (env -u FORGEJO_LANDING_TOKEN_FILE; the ambient-token case is the known pre-existing one) and `tsc --noEmit` clean. No image-level harness, nothing outside my folder. (1) CREDENTIAL ORIGIN — no remaining session input decides where the identity token goes. - forgejo/comment-as.ts: identityBaseConfig now returns the constant and only reads FORGEJO_BASE_URL to name it; the value never reaches the request or the line. IGNORED_IDENTITY_BASE_LINE names the variable and the default only. The old empty-base refusal is gone and nothing else used it. - forgejo/seat-post.ts: `args.forgejo` is the constant (the `--forgejo`/env values are parsed and dropped, with only the lever NAMES kept in `ignoredOriginLevers`); `childEnvironment` keeps PATH/HOME/FORGEJO_REQUEST_TIMEOUT_MS only, so the verdict-cli child gets no base, no proxy variables, no NODE_OPTIONS, and its argv is built without `--base-url`. The comment route deletes FORGEJO_BASE_URL before calling commentAs, so one line names the lever, not two. - verdict-cli.ts:214 is the child's only env origin input (FORGEJO_BASE_URL), which the child never receives; gateProbeConfig/pinGatedTransport then still refuse an off-default base on a gated repo. - The Bao/Zitadel levers remain session-settable (different credential class) and are named in the docs as a residual, not claimed closed. (2) GATE SOUNDNESS — I could not construct an accepted value that interposes. Verified on node v22.23.4 that V8 flags and `--conditions`/`--import` DO appear in process.execArgv (so the new check sees them) while NODE_OPTIONS flags do not. Every allowlisted execArgv entry is inert: `--experimental-strip-types`, `--no-warnings`, `--trace-warnings`, the two heap shapes; the match is exact/anchored, so `--require`, `-r`, `--import`, `--loader`, `--env-file`, `--openssl-config`, `--use-env-proxy` and value-in-a-second-element forms all refuse, and a spaced single element is a bad option for node itself. On the grammar: node treats a tab as part of one token (measured: "bad option: --max-old-space-size=4096\t--require"), so the wider separator can only split further; a merged token necessarily carries whitespace and can never match the benign pattern, so nothing Node would actually run gets through. NODE_USE_ENV_PROXY refuses on any non-blank value, `0` included. (3) REFACTOR SAFETY — non-test source now has exactly one literal of the default forge origin (forgejo/origin.ts) and origin.ts imports nothing; api.ts re-exports the same binding, so every importer is unchanged. The NODE_OPTIONS refusal message text and the check order are byte-identical to the base except that the named flag now ends at `=` OR whitespace (narrower: it can no longer echo a quoted path). gatedProcessInterposition/assertGatedProcessTrust gained an optional execArgv defaulting to process.execArgv, so pinGatedTransport's call checks the real command line. Nothing else in the diff moved silently. (4) TESTS — the red-on-base claims hold: the old identityBaseConfig returned the env base and commentAs refused when it was empty (so the "no lever, no line" case is red on the base too), the old stripper kept a mid-token quote and stripped single quotes, and the old parseArgs returned the `--forgejo` value. Hermetic: route-table fetch fakes plus a loopback fixture whose only path is a temp `--import` shim that rewrites the constant and stamps the request, so the real-process cases fail if the helper dials anything else. Nothing weakened (the tokenfile fixture now names the constant because the constant is what the tool uses; the real proof of ignoring is the S1 file with a different origin). (5) DOCS — the corrected sentence now says "every LANDING path", the helpers' pin is stated in the env table and in the comment_as rules, and the two residuals (Bao/Zitadel endpoints; a direct verdict-cli run) are true of the code as I read it. No overclaim found. Holds: none. Non-blocking nits: n1 docs/forgejo.md:29 sends the reader to "*The landing tool*", which is not a heading (the doc has "The landing lock" and "Posting as a seat"). Dead cross-reference; "The landing lock" + "Posting as a seat" would resolve. n2 api.ts's "Over-splitting can only REFUSE" reads stronger than the code: nodeOptionsTokens ACCEPTS a tab-separated benign pair that node itself rejects as a bad option (asserted at tests/landing-gated-nodeoptions-grammar-s3.test.ts:76). The stated reason (a token node merged from two of ours always carries whitespace, so node exits before any request) is correct, which is what makes it non-widening; "cannot accept anything node would run" would be exactly true. model: omniroute/deepseek-flash session: LR-20261111-017-seatA-20261112T0945Z
Collaborator

LD-VERDICT APPROVED head=883da9a5ed426d012f32fcb1a4a8709237c8dacf session=LR-20261111-017-seatB-20261112T0946Z

Reviewed a fresh read-only clone at 883da9a (the PR's current head is the same sha), diff c5ac96f..883da9a, 16 files. Ran the unit suite inside the clone (65 files / 1356 tests green, ambient landing-token env unset) and tsc --noEmit (clean). No image-level harness, no network beyond loopback.

(1) CREDENTIAL ORIGIN — closed. Every credential-bearing call in comment-as.ts resolves its base from origin.ts (identityBaseConfig returns the constant; the old "FORGEJO_BASE_URL is not set" refusal is gone, and no doc/test referenced it); seat-post.ts folds --forgejo and FORGEJO_BASE_URL into the constant at parseArgs and keeps only the lever NAMES, one stderr line each, value never echoed; the verdict-cli child gets exactly PATH, HOME, FORGEJO_REQUEST_TIMEOUT_MS and an argv with no --base-url. I traced the whole diff for a second origin input (a file, a bail-out flag, a host parsed out of repo_ref): none remains.

(2) GATE SOUNDNESS — no widening found. Analytic: the new tokenizer keeps every backslash in a token (so a backslash value can never be benign) and only ever merges at a character it also keeps, so a token Node applies as a code-loading flag would have to contain whitespace. Measured: 4000 generated NODE_OPTIONS strings -> 15 accepted, all pure benign pairs, and none of them loaded a --require marker module; U+00A0/U+2003 separators do not merge into a flag Node applies (node exits "illegal value for flag"), tab splits for both; a mid-token quote really does load code (--req"uire=<path>) and the gate refuses it, naming --require without the path; the execArgv allowlist is exact-string plus two anchored digit regexes, so the space-separated --max-old-space-size 4096 form refuses rather than passes, and --experimental-strip-types is the flag the seat/verdict child actually uses.

(3) REFACTOR SAFETY — behaviour unchanged: the NODE_OPTIONS refusal text is byte-identical, TLS ordering is unchanged (TLS check still first, pinned by an existing case), origin.ts imports nothing and api.ts re-exports the same live binding (both asserted). No other file moved silently; the four identity test files only swap a placeholder base for the constant.

(4) TESTS — I re-ran the old tokenizer against the two "red on old code" claims and both hold: the old code refused --max-old-space-size="4096" (node accepts it) and accepted '--no-warnings' (node takes the quotes literally). Hermetic: fetch-stubbed or loopback-only. Nothing weakened.

(5) DOCS — the "holds on every path" overclaim is now scoped to LANDING paths, and every added sentence is true of the code, including the two named residuals (Bao/Zitadel endpoints; a direct verdict-cli run off the gated list). The env-table row's "never consult it at all" is self-corrected by the next clause (named as ignored), so it does not overclaim.

Nits, non-blocking, none is a hold:
n1 api.ts:1098-1105 — the comment says NODE_USE_ENV_PROXY refuses "any value at all" but the check trims, so a whitespace-only value is accepted and a case pins that. Measured on node v22.23.4 only exactly "1" enables the proxy, so there is no widening today, but the stated rule and the code disagree; smallest proof: assert the blank value refuses (or say "non-blank" in the comment).
n2 api.ts (nodeOptionsTokens doc) and tests/landing-gated-nodeoptions-grammar-s3.test.ts — "a '--no-warnings' is a token Node would also fail to parse" is not true of node v22.23.4: it exits 0 and silently ignores that token. The gate's refusal is still the stricter, correct direction; only the sentence needs rewording.
n3 tests/setup-execargv.ts — no case proves the production default reads process.execArgv (the setup pins it to [] and every command-line case passes the array explicitly), so a default of [] would pass the suite. Smallest proof: set process.execArgv to ["--require","/tmp/h.js"] and expect assertGatedProcessTrust({}) to throw naming process.execArgv.
n4 docs/forgejo.md process-interposition section (unchanged by this diff) still lists only the TLS/NODE_OPTIONS levers, and "a flag name being read whole (split on whitespace, up to the first =)" no longer describes the new [=\s] split, so this PR's two new refusal classes are undocumented there.
n5 operational, disclosed in the PR body and agreed: any off-allowlist flag on the node process that hosts the landing tool refuses every gated landing. It is fail-closed, no test can observe the host's array from inside the suite, and it is named as a human deployment check rather than claimed covered.

model: omniroute/deepseek-flash
session: LR-20261111-017-seatB-20261112T0946Z

LD-VERDICT APPROVED head=883da9a5ed426d012f32fcb1a4a8709237c8dacf session=LR-20261111-017-seatB-20261112T0946Z Reviewed a fresh read-only clone at 883da9a (the PR's current head is the same sha), diff c5ac96f..883da9a, 16 files. Ran the unit suite inside the clone (65 files / 1356 tests green, ambient landing-token env unset) and tsc --noEmit (clean). No image-level harness, no network beyond loopback. (1) CREDENTIAL ORIGIN — closed. Every credential-bearing call in comment-as.ts resolves its base from origin.ts (identityBaseConfig returns the constant; the old "FORGEJO_BASE_URL is not set" refusal is gone, and no doc/test referenced it); seat-post.ts folds --forgejo and FORGEJO_BASE_URL into the constant at parseArgs and keeps only the lever NAMES, one stderr line each, value never echoed; the verdict-cli child gets exactly PATH, HOME, FORGEJO_REQUEST_TIMEOUT_MS and an argv with no --base-url. I traced the whole diff for a second origin input (a file, a bail-out flag, a host parsed out of repo_ref): none remains. (2) GATE SOUNDNESS — no widening found. Analytic: the new tokenizer keeps every backslash in a token (so a backslash value can never be benign) and only ever merges at a character it also keeps, so a token Node applies as a code-loading flag would have to contain whitespace. Measured: 4000 generated NODE_OPTIONS strings -> 15 accepted, all pure benign pairs, and none of them loaded a --require marker module; U+00A0/U+2003 separators do not merge into a flag Node applies (node exits "illegal value for flag"), tab splits for both; a mid-token quote really does load code (`--req"uire=<path>`) and the gate refuses it, naming --require without the path; the execArgv allowlist is exact-string plus two anchored digit regexes, so the space-separated `--max-old-space-size 4096` form refuses rather than passes, and `--experimental-strip-types` is the flag the seat/verdict child actually uses. (3) REFACTOR SAFETY — behaviour unchanged: the NODE_OPTIONS refusal text is byte-identical, TLS ordering is unchanged (TLS check still first, pinned by an existing case), origin.ts imports nothing and api.ts re-exports the same live binding (both asserted). No other file moved silently; the four identity test files only swap a placeholder base for the constant. (4) TESTS — I re-ran the old tokenizer against the two "red on old code" claims and both hold: the old code refused `--max-old-space-size="4096"` (node accepts it) and accepted `'--no-warnings'` (node takes the quotes literally). Hermetic: fetch-stubbed or loopback-only. Nothing weakened. (5) DOCS — the "holds on every path" overclaim is now scoped to LANDING paths, and every added sentence is true of the code, including the two named residuals (Bao/Zitadel endpoints; a direct verdict-cli run off the gated list). The env-table row's "never consult it at all" is self-corrected by the next clause (named as ignored), so it does not overclaim. Nits, non-blocking, none is a hold: n1 api.ts:1098-1105 — the comment says NODE_USE_ENV_PROXY refuses "any value at all" but the check trims, so a whitespace-only value is accepted and a case pins that. Measured on node v22.23.4 only exactly "1" enables the proxy, so there is no widening today, but the stated rule and the code disagree; smallest proof: assert the blank value refuses (or say "non-blank" in the comment). n2 api.ts (nodeOptionsTokens doc) and tests/landing-gated-nodeoptions-grammar-s3.test.ts — "a `'--no-warnings'` is a token Node would also fail to parse" is not true of node v22.23.4: it exits 0 and silently ignores that token. The gate's refusal is still the stricter, correct direction; only the sentence needs rewording. n3 tests/setup-execargv.ts — no case proves the production default reads process.execArgv (the setup pins it to [] and every command-line case passes the array explicitly), so a default of [] would pass the suite. Smallest proof: set process.execArgv to ["--require","/tmp/h.js"] and expect assertGatedProcessTrust({}) to throw naming process.execArgv. n4 docs/forgejo.md process-interposition section (unchanged by this diff) still lists only the TLS/NODE_OPTIONS levers, and "a flag name being read whole (split on whitespace, up to the first =)" no longer describes the new [=\s] split, so this PR's two new refusal classes are undocumented there. n5 operational, disclosed in the PR body and agreed: any off-allowlist flag on the node process that hosts the landing tool refuses every gated landing. It is fail-closed, no test can observe the host's array from inside the suite, and it is named as a human deployment check rather than claimed covered. model: omniroute/deepseek-flash session: LR-20261111-017-seatB-20261112T0946Z
Collaborator

LD-VERDICT APPROVED head=883da9a5ed426d012f32fcb1a4a8709237c8dacf session=LR-20261111-017-seatB-20261112T1125Z

model: omniroute/deepseek-flash
session: LR-20261111-017-seatB-20261112T1125Z

Seat B (NO-READ), judged the pinned range c5ac96f5..883da9a5ed426d012f32fcb1a4a8709237c8dacf (5 commits, every one carrying Lab-Run: LR-20261111-017 as its last trailer) in the clone checked out at that head. I did not run verdict-cli and did not read any comment or review on this PR.

Evidence, from the clone only:

  • npx vitest run → 65 files / 1356 tests passed, exactly the counts the head commit claims; npx tsc --noEmit clean. The four suites this change adds or rewrites (comment-as-pinned-origin-s1, landing-origin-owner-s3, landing-gated-nodeoptions-grammar-s3, seat-post) pass 51/51 on their own. node --experimental-strip-types loads comment-as.ts, seat-post.ts and origin.ts directly (the claim the source-only drop depends on), not just under vitest's transform.
  • The pin is asserted positively, not merely adapted to: identityBaseConfig() answers DEFAULT_BASE_URL for {}, for a session-set base and for a default-plus-slashes value, and the end-to-end case drives commentAs with FORGEJO_BASE_URL=http://127.0.0.1:1/proxy and asserts every call's origin is the constant, exactly one ignored-lever line, no value in that line, and no token in the result. The one refusal that existed only because the base could be empty is correctly gone (there is no unset-base state left).
  • seat-post is the harder half and is covered the right way: the signal-cleanup case no longer relies on FORGEJO_BASE_URL pointing at the fixture. It spawns the helper with both levers pointed at a dead port and reaches the fixture only through an ESM --import shim that rewrites the constant and marks each request, then asserts every /api/ request carried the marker and both levers were named. That case fails if the helper ever honours either lever again. The verdict route additionally asserts the child's argv carries no --base-url and no loopback (n3), so the env-only assertion no longer hides an argv leak.
  • childEnvironment dropping FORGEJO_BASE_URL and the six proxy variables is right, and the test pins the surviving allowlist (PATH, HOME, FORGEJO_REQUEST_TIMEOUT_MS) so the trim cannot silently widen.
  • The nodeOptionsTokens rewrite is a genuine correctness fix and its fail-closed argument holds: the benign pattern cannot match a token containing whitespace, so the deliberately wider separator set can only over-split a token that would have been refused anyway; a ' is treated as an ordinary character as Node does; an unterminated " refuses instead of guessing. The refusal still names the flag and never the path (--req"uire=/tmp/h.js" → --require).
  • NODE_USE_ENV_PROXY refuses on any non-blank value, and the execArgv allowlist is default-deny with each entry justified. I checked the real deployment shape rather than trusting the default parameter: the pi host runs with an empty execArgv, and the one sanctioned invocation (node --experimental-strip-types verdict-cli.ts, including the child seat-post spawns) is allowlisted — so the new gate does not refuse the sanctioned landing route. Docs, --help, and the module doc all state the ignore and the residuals (OpenBao/Zitadel endpoints, a verdict-cli run made directly) honestly, and origin.ts re-exporting through api.ts keeps every existing importer working (tsc + suite confirm).

Non-blocking findings, none of which I hold on:

  1. Brief/label mismatch (not this PR's): my seat brief describes the change's subject as "the landing census". The pinned diff is homelab-93 S1/S3 — the seat-write origin pin and the execArgv / NODE_USE_ENV_PROXY gate. The identifiers (head 40-hex, the c5ac96f5..883da9a range, the run-id trailer) match exactly and the diff is coherent, so I judged the diff itself; the topic label in the brief is wrong and worth correcting upstream.
  2. Pre-existing red test, not caused here: tests/merge-identity.test.ts ("merges with the dedicated landing credential") is red in my environment because clearLandingEnv() runs only in afterEach, so the file's first case inherits an ambient FORGEJO_LANDING_TOKEN_FILE, and the file source correctly outranks FORGEJO_LANDING_TOKEN. Identical failure at base c5ac96f5, and the whole suite is green with that ambient var unset. So this is old test-hygiene debt, not a regression from 883da9a — but worth a one-line beforeEach clear in its own fix, since it will bite the next seat that runs the suite with a staged landing token in its env.
  3. Residual not in the docs' residual list: the S3 NODE_USE_ENV_PROXY refusal lives in the gated path, so seat-post's and forgejo_comment_as's own credential-bearing calls are not covered by it. On a Node that honours that variable, a session-chosen proxy could still front the seat write. Marginal (that session already holds the token, and the deployed Node here predates the variable), so I do not treat it as a blocker — but it belongs in the named-residual paragraph next to the OpenBao/Zitadel one, or the helper should apply the same one-line check.
  4. Cosmetic: node --max-old-space-size 512 tool.ts (space form, which Node accepts) refuses, because only the = form is allowlisted. Fail-closed, so harmless; a comment noting the accepted spelling would save the next reader a puzzle. Same for the pre-existing case where a stray positional token in NODE_OPTIONS is echoed as its own "flag".

No defect I found moves traffic, code or trust off the built-in default, and every new refusal fails closed. Approving at 883da9a5ed426d012f32fcb1a4a8709237c8dacf.

LD-VERDICT APPROVED head=883da9a5ed426d012f32fcb1a4a8709237c8dacf session=LR-20261111-017-seatB-20261112T1125Z model: omniroute/deepseek-flash session: LR-20261111-017-seatB-20261112T1125Z Seat B (NO-READ), judged the pinned range `c5ac96f5..883da9a5ed426d012f32fcb1a4a8709237c8dacf` (5 commits, every one carrying `Lab-Run: LR-20261111-017` as its last trailer) in the clone checked out at that head. I did not run verdict-cli and did not read any comment or review on this PR. Evidence, from the clone only: - `npx vitest run` → **65 files / 1356 tests passed**, exactly the counts the head commit claims; `npx tsc --noEmit` clean. The four suites this change adds or rewrites (comment-as-pinned-origin-s1, landing-origin-owner-s3, landing-gated-nodeoptions-grammar-s3, seat-post) pass 51/51 on their own. `node --experimental-strip-types` loads `comment-as.ts`, `seat-post.ts` and `origin.ts` directly (the claim the source-only drop depends on), not just under vitest's transform. - The pin is asserted positively, not merely adapted to: `identityBaseConfig()` answers `DEFAULT_BASE_URL` for `{}`, for a session-set base and for a default-plus-slashes value, and the end-to-end case drives `commentAs` with `FORGEJO_BASE_URL=http://127.0.0.1:1/proxy` and asserts every call's origin is the constant, exactly one ignored-lever line, no value in that line, and no token in the result. The one refusal that existed only because the base could be empty is correctly gone (there is no unset-base state left). - `seat-post` is the harder half and is covered the right way: the signal-cleanup case no longer relies on `FORGEJO_BASE_URL` pointing at the fixture. It spawns the helper with both levers pointed at a dead port and reaches the fixture only through an ESM `--import` shim that rewrites the *constant* and marks each request, then asserts every `/api/` request carried the marker and both levers were named. That case fails if the helper ever honours either lever again. The verdict route additionally asserts the child's **argv** carries no `--base-url` and no loopback (n3), so the env-only assertion no longer hides an argv leak. - `childEnvironment` dropping `FORGEJO_BASE_URL` and the six proxy variables is right, and the test pins the surviving allowlist (`PATH`, `HOME`, `FORGEJO_REQUEST_TIMEOUT_MS`) so the trim cannot silently widen. - The `nodeOptionsTokens` rewrite is a genuine correctness fix and its fail-closed argument holds: the benign pattern cannot match a token containing whitespace, so the deliberately wider separator set can only over-split a token that would have been refused anyway; a `'` is treated as an ordinary character as Node does; an unterminated `"` refuses instead of guessing. The refusal still names the flag and never the path (`--req"uire=/tmp/h.js"` → `--require`). - `NODE_USE_ENV_PROXY` refuses on any non-blank value, and the execArgv allowlist is default-deny with each entry justified. I checked the real deployment shape rather than trusting the default parameter: the pi host runs with an empty `execArgv`, and the one sanctioned invocation (`node --experimental-strip-types verdict-cli.ts`, including the child `seat-post` spawns) is allowlisted — so the new gate does not refuse the sanctioned landing route. Docs, `--help`, and the module doc all state the ignore and the residuals (OpenBao/Zitadel endpoints, a `verdict-cli` run made directly) honestly, and `origin.ts` re-exporting through `api.ts` keeps every existing importer working (tsc + suite confirm). Non-blocking findings, none of which I hold on: 1. **Brief/label mismatch (not this PR's):** my seat brief describes the change's subject as "the landing census". The pinned diff is homelab-93 **S1/S3** — the seat-write origin pin and the execArgv / `NODE_USE_ENV_PROXY` gate. The identifiers (head 40-hex, the `c5ac96f5..883da9a` range, the run-id trailer) match exactly and the diff is coherent, so I judged the diff itself; the topic label in the brief is wrong and worth correcting upstream. 2. **Pre-existing red test, not caused here:** `tests/merge-identity.test.ts` ("merges with the dedicated landing credential") is red in *my* environment because `clearLandingEnv()` runs only in `afterEach`, so the file's first case inherits an ambient `FORGEJO_LANDING_TOKEN_FILE`, and the file source correctly outranks `FORGEJO_LANDING_TOKEN`. Identical failure at base `c5ac96f5`, and the whole suite is green with that ambient var unset. So this is old test-hygiene debt, not a regression from `883da9a` — but worth a one-line `beforeEach` clear in its own fix, since it will bite the next seat that runs the suite with a staged landing token in its env. 3. **Residual not in the docs' residual list:** the S3 `NODE_USE_ENV_PROXY` refusal lives in the *gated* path, so `seat-post`'s and `forgejo_comment_as`'s own credential-bearing calls are not covered by it. On a Node that honours that variable, a session-chosen proxy could still front the seat write. Marginal (that session already holds the token, and the deployed Node here predates the variable), so I do not treat it as a blocker — but it belongs in the named-residual paragraph next to the OpenBao/Zitadel one, or the helper should apply the same one-line check. 4. Cosmetic: `node --max-old-space-size 512 tool.ts` (space form, which Node accepts) refuses, because only the `=` form is allowlisted. Fail-closed, so harmless; a comment noting the accepted spelling would save the next reader a puzzle. Same for the pre-existing case where a stray positional token in `NODE_OPTIONS` is echoed as its own "flag". No defect I found moves traffic, code or trust off the built-in default, and every new refusal fails closed. Approving at `883da9a5ed426d012f32fcb1a4a8709237c8dacf`.
Collaborator

LD-VERDICT APPROVED head=883da9a5ed426d012f32fcb1a4a8709237c8dacf session=LR-20261111-017-seatA-20261112T1125Z

model: omniroute/deepseek-flash
session: LR-20261111-017-seatA-20261112T1125Z

Seat A, c5ac96f5..883da9a5 (head verified in-clone at 883da9a5ed). Judged diff, tests and guard only; no PR comments/reviews read.

WHAT I RAN (in the clone only)

  • npx vitest run → 65 files / 1356 tests PASS; npx tsc --noEmit clean. This matches the head commit's claim ("65 files / 1356 green, tsc clean"), after accounting for the ambient-env artefact in (1).
  • No image-level harness, no entrypoint-sim.sh, nothing installed or edited under the agent dir; no process left behind (checked ps and /proc/*/cwd).

NON-BLOCKING FINDINGS

  1. Suite is green but one case is ambient-env sensitive (NOT from this diff). With this seat's environment carry-ing FORGEJO_LANDING_TOKEN_FILE, tests/merge-identity.test.ts > "merges with the dedicated landing credential" fails: expected landing-dedicated-credential, got the staged file's 40-hex. clearLandingEnv() only runs in afterEach, so the file's FIRST case inherits the ambient file, and the file-preferred rule rightly beats the env var. env -u FORGEJO_LANDING_TOKEN_FILE npx vitest run → all 1356 pass. The file is untouched by this diff, so no hold — but a beforeAll(clearLandingEnv) would make the suite's green claim host-independent.

  2. Grammar comments overclaim Node's behaviour in the opposite direction (the round-1 n1 class, one leftover). Measured on Node v22.23.4: a NODE_OPTIONS token that does not begin with - is silently IGNORED (NODE_OPTIONS="'--no-warnings'" node -e 0 → exit 0; NODE_OPTIONS="'--bogus-flag'" → exit 0; --bogus-flag → exit 9). So api.ts's "a '--no-warnings' is a literal-quote token Node would also fail to parse", and the test's "Node would also fail to parse", are not reproducible: Node neither parses nor rejects it, it ignores it. The gate refusing it is still fail-closed and safe — the wording should be "Node ignores a non-- token; the gate refuses it rather than mirror that ignore".

  3. Conversely the gate ACCEPTS --no-warnings\t--trace-warnings (new test, line "accepts a tab-separated pair"), which Node 22 refuses at startup (node: --trace-warnings\t--max-old-space-size= is not allowed in NODE_OPTIONS, exit 9). Still safe — Node never runs — and the doc already says the separator set is wider; one sentence in the test saying "Node refuses this at startup; accepting it is harmless because the value cannot start" would stop a future reader reading the case as "Node accepts this". I checked the allow direction properly and found no bypass: every accepted token is one of five exact shapes, and a Node token merged from two accepted tokens always contains whitespace, so it either fails the option-name parse or a numeric value parse.

  4. execArgv allowlist (n4) does not false-refuse in THIS deployment, which is worth recording: my own session's pi process (pid 3956622) has exe /usr/local/bin/node and argv exactly ["pi"], i.e. process.execArgv is empty, so an in-process gated landing is allowed here. Conversely a host started with --enable-source-maps/--conditions/-r x now refuses EVERY gated landing, and that is stated only in tests/setup-execargv.ts — worth a line in docs/forgejo.md (the PR body is claimed to carry the human check; I did not read it).

  5. The S1 scope is right and the evidence is strong: commentAs has exactly two callers (index.ts:1857, seat-post.ts:519), both seat-credential writes, so pinning the base to the constant "on every repo" cannot hit a non-seat path. The new tests prove it the hard way — dead loopback origin for both --forgejo and FORGEJO_BASE_URL, and in the signal path a real --import shim that only rewrites the built-in default, with per-request marking; the ignored-lever line is asserted to name the lever and never its value (both helpers). Two named consequences are worth keeping in view: a non-default forge deployment loses forgejo_comment_as/seat-post entirely (documented, deliberate), and dropping HTTP(S)_PROXY from the verdict child means a deployment that needs a proxy to reach the forge will now fail to connect rather than retarget — fail-closed, but no test covers it, and the doc's residual list does not name it.

  6. Census: all five commits carry the trailer, the head's LAST trailer is Lab-Run: LR-20261111-017, matching the run id (grammar-valid); head = 883da9a5.

Nothing I found is security-relevant in the allow direction, and the credential-bearing calls are pinned, tested and scrubbed. Approve.

LD-VERDICT APPROVED head=883da9a5ed426d012f32fcb1a4a8709237c8dacf session=LR-20261111-017-seatA-20261112T1125Z model: omniroute/deepseek-flash session: LR-20261111-017-seatA-20261112T1125Z Seat A, `c5ac96f5..883da9a5` (head verified in-clone at 883da9a5ed426d012f32fcb1a4a8709237c8dacf). Judged diff, tests and guard only; no PR comments/reviews read. WHAT I RAN (in the clone only) - `npx vitest run` → 65 files / 1356 tests PASS; `npx tsc --noEmit` clean. This matches the head commit's claim ("65 files / 1356 green, tsc clean"), after accounting for the ambient-env artefact in (1). - No image-level harness, no `entrypoint-sim.sh`, nothing installed or edited under the agent dir; no process left behind (checked `ps` and `/proc/*/cwd`). NON-BLOCKING FINDINGS 1. Suite is green but one case is ambient-env sensitive (NOT from this diff). With this seat's environment carry-ing `FORGEJO_LANDING_TOKEN_FILE`, `tests/merge-identity.test.ts` > "merges with the dedicated landing credential" fails: expected `landing-dedicated-credential`, got the staged file's 40-hex. `clearLandingEnv()` only runs in `afterEach`, so the file's FIRST case inherits the ambient file, and the file-preferred rule rightly beats the env var. `env -u FORGEJO_LANDING_TOKEN_FILE npx vitest run` → all 1356 pass. The file is untouched by this diff, so no hold — but a `beforeAll(clearLandingEnv)` would make the suite's green claim host-independent. 2. Grammar comments overclaim Node's behaviour in the opposite direction (the round-1 n1 class, one leftover). Measured on Node v22.23.4: a NODE_OPTIONS token that does not begin with `-` is silently IGNORED (`NODE_OPTIONS="'--no-warnings'" node -e 0` → exit 0; `NODE_OPTIONS="'--bogus-flag'"` → exit 0; `--bogus-flag` → exit 9). So api.ts's "a `'--no-warnings'` is a literal-quote token Node would also fail to parse", and the test's "Node would also fail to parse", are not reproducible: Node neither parses nor rejects it, it ignores it. The gate refusing it is still fail-closed and safe — the wording should be "Node ignores a non-`-` token; the gate refuses it rather than mirror that ignore". 3. Conversely the gate ACCEPTS `--no-warnings\t--trace-warnings` (new test, line "accepts a tab-separated pair"), which Node 22 refuses at startup (`node: --trace-warnings\t--max-old-space-size= is not allowed in NODE_OPTIONS`, exit 9). Still safe — Node never runs — and the doc already says the separator set is wider; one sentence in the test saying "Node refuses this at startup; accepting it is harmless because the value cannot start" would stop a future reader reading the case as "Node accepts this". I checked the allow direction properly and found no bypass: every accepted token is one of five exact shapes, and a Node token merged from two accepted tokens always contains whitespace, so it either fails the option-name parse or a numeric value parse. 4. execArgv allowlist (n4) does not false-refuse in THIS deployment, which is worth recording: my own session's pi process (pid 3956622) has exe `/usr/local/bin/node` and argv exactly `["pi"]`, i.e. `process.execArgv` is empty, so an in-process gated landing is allowed here. Conversely a host started with `--enable-source-maps`/`--conditions`/`-r x` now refuses EVERY gated landing, and that is stated only in `tests/setup-execargv.ts` — worth a line in docs/forgejo.md (the PR body is claimed to carry the human check; I did not read it). 5. The S1 scope is right and the evidence is strong: `commentAs` has exactly two callers (`index.ts:1857`, `seat-post.ts:519`), both seat-credential writes, so pinning the base to the constant "on every repo" cannot hit a non-seat path. The new tests prove it the hard way — dead loopback origin for both `--forgejo` and `FORGEJO_BASE_URL`, and in the signal path a real `--import` shim that only rewrites the built-in default, with per-request marking; the ignored-lever line is asserted to name the lever and never its value (both helpers). Two named consequences are worth keeping in view: a non-default forge deployment loses `forgejo_comment_as`/`seat-post` entirely (documented, deliberate), and dropping `HTTP(S)_PROXY` from the verdict child means a deployment that needs a proxy to reach the forge will now fail to connect rather than retarget — fail-closed, but no test covers it, and the doc's residual list does not name it. 6. Census: all five commits carry the trailer, the head's LAST trailer is `Lab-Run: LR-20261111-017`, matching the run id (grammar-valid); head = 883da9a5. Nothing I found is security-relevant in the allow direction, and the credential-bearing calls are pinned, tested and scrubbed. Approve.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Homelab/pi-extensions!133
No description provided.