Repository navigation
docs(spec,adr-0069): server.trustProxy names both client-address consumers; D5 mechanism amended - #24859
Conversation
…umers; D5's mechanism cell amended The `server.trustProxy` describe named only the inbound rate limiter; since the auth allow-list fix, `objectstack serve` passes it to AuthPlugin too, whose D5 IP allow-list and D7 `last_login_ip` audit use the same client-address rule (`@objectstack/plugin-auth/client-address`). The generated reference is regenerated with gen:docs. ADR-0069 D5 gains a dated amendment note: forwarded headers (X-Forwarded-For, then X-Real-IP) only under server.trustProxy, otherwise the transport peer; CF-Connecting-IP no longer read; an undetermined address refused (403 IP_NOT_ALLOWED) while ranges are set. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 143 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 5827991de3bebd2652ca3148d9682a98705587ba && git checkout 5827991de3bebd2652ca3148d9682a98705587ba
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin abd5acdaf788df2092064c56583fd73d837fb858 d4393185dd890b3ee4de3b53ad4d44ba85b1eea5 && git checkout -B drift-repro abd5acdaf788df2092064c56583fd73d837fb858 && git merge --no-ff d4393185dd890b3ee4de3b53ad4d44ba85b1eea5
node scripts/docs-audit/affected-docs.mjs --json abd5acdaf788df2092064c56583fd73d837fb858 |
Seat check: PASS ·
|
Contract reviewServed-tier: Retroactive. This record is written after the merge, as ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
Closes #24818
Clause-②: no
Two texts were behind the code after PR #24795 (
25e16ab00c). This PR fixes both. No code, schema shape, key, default or accepted value changes.What changed
packages/spec/src/system/stack-server.zod.ts, theserver.trustProxy.describe()and TSDoc. The describe named only the inbound rate limiter. It now names both consumers of the one client-address rule: the inbound rate limiter (whenserver.security.rateLimit.enabledis set), and the authentication plugin. That plugin's IP allow-list (allowed_ip_ranges) judges the address the rule establishes, and its login audit stamps it aslast_login_ip. The TSDoc names the shared function (resolveClientAddressfrom@objectstack/plugin-auth/client-address) and says that a header believed without a rewriting proxy bypasses the allow-list as well as the rate limit.content/docs/references/system/stack-server.mdx, regenerated withpnpm --filter @objectstack/spec gen:docs.check:generatednamed this as the only stale artifact. The diff is the one table row.docs/adr/0069-enterprise-authentication-hardening.md, D5. I added a dated amendment note under the D5 table. It follows the in-file precedent: the D3 note "What the gate holds (amended 2026-10-14 ...)", plus the matching· **Amended** (...)entry on the Status line. The original mechanism cell is unchanged, apart from an appended pointer, (client-address mechanism amended 2026-10-20 — see the note below). The note records:X-Forwarded-For(leftmost entry), thenX-Real-IP, are believed only underserver.trustProxy. Otherwise the address is the transport's peer address.CF-Connecting-IPis no longer read.403 IP_NOT_ALLOWED. With no ranges set, nothing is judged.objectstack servepassesserver.trustProxytoAuthPlugin..changeset/24818-trustproxy-consumers-describe.md, apatchfor@objectstack/spec, because the describe string ships in the spec's publisheddist.Each claim checked against the code at
origin/main41646a5788packages/plugins/plugin-auth/src/client-address.tsresolveClientAddressreadsx-forwarded-for, thenx-real-ip, only wheninput.trustProxyis true. Otherwise it usesremoteAddress. With neither available it returnsundefined.packages/plugins/plugin-auth/package.json,exports["./client-address"], a tsup entry) and imported back bypackages/runtime/src/security/inbound-rate-limit.ts:40.servewiring:packages/cli/src/commands/serve.ts:4263passestrustProxy: (config as any).server?.trustProxy === truetonew AuthPlugin(...).packages/plugins/plugin-auth/src/auth-plugin.ts, in the D5 middleware, callsmgr.resolveClientAddress(...).AuthManager.resolveClientAddressapplies the shared rule withthis.config.trustProxy.AuthManager.isClientIpAllowedreturns true when no ranges are set, andif (!ip) return falseotherwise. The middleware then answers403witherror.code: 'IP_NOT_ALLOWED'.CF-Connecting-IP:git grep -i cf-connecting-ip -- packagesfinds onlyCHANGELOG.mdentries and no source.stampLastLogin(uid, await readRequestClientAddress()).stampLastLoginwriteslast_login_iponlyif (ip).Governed surface
docs/adr/**is a governed surface at Tier H. This PR lands only after an authorized approval by an account inGOVERNED_APPROVERS. The seat handles that approval.Verification (at
d4393185dd)pnpm --filter @objectstack/spec buildpassed (VERDICT command-exit 0).pnpm --filter @objectstack/spec check:generated: before regenerating, it reported "1 of 14 artifact(s) stale: content/docs/references/**". Aftergen:docsit reported "✓ All 14 generated artifacts are up to date".pnpm --filter @objectstack/spec typecheckpassed (VERDICT command-exit 0).vitest run src/system/stack-server.test.ts src/system/http-server.test.ts src/compose-stacks-key-loss.test.ts scripts/build-schemas-check-mode.test.tsgaveTest Files 4 passed (4),Tests 154 passed (154). These are the spec tests that namestack-serverortrustProxy. The full spec suite did not finish inside the container's foreground cap on a shared box, so it is left to CI'sTest Core. No test pins the old describe text:git grep "Consumed by the inbound"has no hit outside the changed source.node scripts/pm/dispatch-gates.mjs --rangave "111 derived famil(ies) accounted for — 109 run, 2 NOT-MEASURED". Every command that ran exited 0, after building the@objectstack/lint...and@objectstack/client-react...closures so four of them could measure.check:dual-build-cjs-loads, reason: needs a whole-workspacedist(exit 3, prerequisite not met). Left to CI.check:lean-entry-closure, reason: needspackages/objectql/dist(exit 3). Left to CI.pnpm check:nul-bytes: OK.Acceptance notes
维护者速读(草稿)
server.trustProxy的说明原来只写了"限流器读它";现在写明认证插件的 IP 白名单和登录审计(last_login_ip)也按同一条规则取客户端地址。ADR-0069 D5 补一条带日期的修订注记,写明现在的取址方式。CF-Connecting-IP)。运维照旧文档配置,可能不知道这个开关同时影响登录白名单。Generated by Claude Code