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
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "lr-20261111-017-s1"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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:
forgejo_comment_asand theseat-posthelper used to build their API base fromFORGEJO_BASE_URL/--forgejo, so a session that set that variable to its own proxy received theld-reviewertoken — and the proxy'sGET /usercould answer any login it liked. Both now send their credential-bearing requests (GET /user, the POST, and theverdict-clichild) 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.process.execArgv(flags on the node command line, whichNODE_OPTIONSnever sees) andNODE_USE_ENV_PROXY(routesfetchthroughHTTP(S)_PROXY) are now refused on a GO-gated landing, alongside theNODE_OPTIONS/TLS refusals #132 added.HEAD:
883da9a5ed426d012f32fcb1a4a8709237c8dacf(branchlr-20261111-017-s1, 5 stacked commits overc5ac96f).The commits (stacked)
275f3f4—api.ts:process.execArgvallowlist +NODE_USE_ENV_PROXYrefusal on gated landings;NODE_OPTIONStokenizing now applies Node's own quote rules with a separator set at least as wide as Node's; newforgejo/origin.tsis the single owner ofDEFAULT_BASE_URL(api.tsre-exports it).11d41f2—comment-as.ts: the identity write's base is the constant, neverFORGEJO_BASE_URL.208e99f—seat-post.ts: the identityGET /userand the verdict-cli child env are pinned to the constant;childEnvironmentkeepsPATH/HOME/FORGEJO_REQUEST_TIMEOUT_MSonly.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 theforgejo_comment_asrules, 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'sexecArgvsetup file states what it hides.Flows, each proven by a test
FORGEJO_BASE_URLreceives nothing fromforgejo_comment_as: every recorded request intests/comment-as-pinned-origin-s1.test.tsgoes to the constant, the POST still lands, the token is never echoed, and exactly one ignored-lever line appears (no value).seat-postdials 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--importshim that rewrites the built-in default and stamps the request.NODE_USE_ENV_PROXYrefuses on a gated call (1and0both — fail-closed on any value):tests/landing-gated-nodeoptions-grammar-s3.test.ts.process.execArgvrefuses--require,--require=,--loader=,-r,--import=and accepts[],--experimental-strip-types,--no-warnings,--trace-warnings, the two heap shapes: same file.NODE_OPTIONSis 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.tshas no imports and owns the exact literal;api.tsre-exports the same value:tests/landing-origin-owner-s3.test.ts.--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 at883da9a;tsc --noEmitclean. (The one pre-existingtests/merge-identity.test.tsfailure 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)
FORGEJO_BASE_URLis an in-cluster URL; after this change the seat-posting helpers dialhttps://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.--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 plainnode(emptyexecArgv) for the tool andnode --experimental-strip-types verdict-cli.tsfor a seat's verdict run.process.execArgv = []intests/setup-execargv.tsbecause 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.verdict-cliruns (not throughseat-post) still honour--base-url/FORGEJO_BASE_URLoff the gated list, including the verdict POST. The sanctioned route no longer hands the lever down; the direct invocation is a residual.--bao/BAO_ADDR/VAULT_ADDR) and Zitadel (--zitadel) endpoints keep their flag/env configuration: not the forge, different credential class.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
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-017Correction 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-93is 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-88keeps 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.LD-VERDICT APPROVED head=883da9a5ed426d012f32fcb1a4a8709237c8dacf session=LR-20261111-017-seatA-20261112T0945Z
Checked the full range
c5ac96f5->883da9a5in 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) andtsc --noEmitclean. No image-level harness, nothing outside my folder.(1) CREDENTIAL ORIGIN — no remaining session input decides where the identity token goes.
args.forgejois the constant (the--forgejo/env values are parsed and dropped, with only the lever NAMES kept inignoredOriginLevers);childEnvironmentkeeps 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.(2) GATE SOUNDNESS — I could not construct an accepted value that interposes. Verified on node v22.23.4 that V8 flags and
--conditions/--importDO 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-proxyand 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,0included.(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
--forgejovalue. Hermetic: route-table fetch fakes plus a loopback fixture whose only path is a temp--importshim 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-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 4096form refuses rather than passes, and--experimental-strip-typesis 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-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 carryingLab-Run: LR-20261111-017as 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 --noEmitclean. 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-typesloadscomment-as.ts,seat-post.tsandorigin.tsdirectly (the claim the source-only drop depends on), not just under vitest's transform.identityBaseConfig()answersDEFAULT_BASE_URLfor{}, for a session-set base and for a default-plus-slashes value, and the end-to-end case drivescommentAswithFORGEJO_BASE_URL=http://127.0.0.1:1/proxyand 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-postis the harder half and is covered the right way: the signal-cleanup case no longer relies onFORGEJO_BASE_URLpointing at the fixture. It spawns the helper with both levers pointed at a dead port and reaches the fixture only through an ESM--importshim 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-urland no loopback (n3), so the env-only assertion no longer hides an argv leak.childEnvironmentdroppingFORGEJO_BASE_URLand 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.nodeOptionsTokensrewrite 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_PROXYrefuses 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 emptyexecArgv, and the one sanctioned invocation (node --experimental-strip-types verdict-cli.ts, including the childseat-postspawns) 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, averdict-clirun made directly) honestly, andorigin.tsre-exporting throughapi.tskeeps every existing importer working (tsc + suite confirm).Non-blocking findings, none of which I hold on:
NODE_USE_ENV_PROXYgate. The identifiers (head 40-hex, thec5ac96f5..883da9arange, 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.tests/merge-identity.test.ts("merges with the dedicated landing credential") is red in my environment becauseclearLandingEnv()runs only inafterEach, so the file's first case inherits an ambientFORGEJO_LANDING_TOKEN_FILE, and the file source correctly outranksFORGEJO_LANDING_TOKEN. Identical failure at basec5ac96f5, and the whole suite is green with that ambient var unset. So this is old test-hygiene debt, not a regression from883da9a— but worth a one-linebeforeEachclear in its own fix, since it will bite the next seat that runs the suite with a staged landing token in its env.NODE_USE_ENV_PROXYrefusal lives in the gated path, soseat-post's andforgejo_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.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 inNODE_OPTIONSis 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-seatA-20261112T1125Z
model: omniroute/deepseek-flash
session: LR-20261111-017-seatA-20261112T1125Z
Seat A,
c5ac96f5..883da9a5(head verified in-clone at883da9a5ed). 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 --noEmitclean. This matches the head commit's claim ("65 files / 1356 green, tsc clean"), after accounting for the ambient-env artefact in (1).entrypoint-sim.sh, nothing installed or edited under the agent dir; no process left behind (checkedpsand/proc/*/cwd).NON-BLOCKING FINDINGS
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: expectedlanding-dedicated-credential, got the staged file's 40-hex.clearLandingEnv()only runs inafterEach, 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 abeforeAll(clearLandingEnv)would make the suite's green claim host-independent.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".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.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/nodeand argv exactly["pi"], i.e.process.execArgvis empty, so an in-process gated landing is allowed here. Conversely a host started with--enable-source-maps/--conditions/-r xnow refuses EVERY gated landing, and that is stated only intests/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).The S1 scope is right and the evidence is strong:
commentAshas 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--forgejoandFORGEJO_BASE_URL, and in the signal path a real--importshim 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 losesforgejo_comment_as/seat-postentirely (documented, deliberate), and droppingHTTP(S)_PROXYfrom 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.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.