From 177ffab8324fc2aad26f98b32e01a01cadf6a5e3 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Oct 2026 10:14:54 +0000 Subject: [PATCH 1/2] docs(spec): server.trustProxy names the endpoint policy chain as a third client-address consumer The describe and TSDoc said "two consumers". The per-endpoint declarative rate limiter keys anonymous callers by the same rule under the same trustProxy whenever an endpoint's own rateLimit is enabled, independent of server.security.rateLimit.enabled. Prose only. Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz Co-authored-by: Claude --- .../25058-trustproxy-three-consumers.md | 9 ++++++++ packages/spec/src/system/stack-server.zod.ts | 21 ++++++++++++------- 2 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 .changeset/25058-trustproxy-three-consumers.md diff --git a/.changeset/25058-trustproxy-three-consumers.md b/.changeset/25058-trustproxy-three-consumers.md new file mode 100644 index 00000000000..1e7c2890ccd --- /dev/null +++ b/.changeset/25058-trustproxy-three-consumers.md @@ -0,0 +1,9 @@ +--- +'@objectstack/spec': patch +--- + +docs(spec): `server.trustProxy`'s description names all three consumers of the client-address rule + +Clause-②: no (prose only: no shape, key, default or accepted value moves) + +The `.describe()` on `server.trustProxy` said the client-address rule had two consumers: the inbound rate limiter and the authentication plugin. There is a third. A declared endpoint (`apis:`) whose own `rateLimit` is enabled is metered by the endpoint policy chain, which keys anonymous callers by the same rule under the same `trustProxy`, whether or not `server.security.rateLimit.enabled` is set. The description and the TSDoc now name it. No key, type, default or accepted value changes. diff --git a/packages/spec/src/system/stack-server.zod.ts b/packages/spec/src/system/stack-server.zod.ts index c82ac67c894..9975481ce7d 100644 --- a/packages/spec/src/system/stack-server.zod.ts +++ b/packages/spec/src/system/stack-server.zod.ts @@ -210,19 +210,24 @@ export const StackServerConfigSchema = lazySchema(() => strictObject( * of authorship — the operator asserting "a proxy I control rewrites this * header on every request" — and never a default. * - * Both consumers apply one rule, `resolveClientAddress` from + * Three consumers apply one rule, `resolveClientAddress` from * `@objectstack/plugin-auth/client-address`: the runtime's inbound rate - * limiter, and `AuthPlugin` (passed this value by `objectstack serve`), - * whose ADR-0069 D5 IP allow-list and D7 `last_login_ip` audit use the - * address it establishes — so a header believed without such a proxy is - * an allow-list bypass as well as a rate-limit one. + * limiter; the endpoint policy chain's per-endpoint rate limiter, for + * every declared endpoint whose own `rateLimit` is enabled, whether or not + * the server-level budget is; and `AuthPlugin` (passed this value by + * `objectstack serve`), whose ADR-0069 D5 IP allow-list and D7 + * `last_login_ip` audit use the address it establishes — so a header + * believed without such a proxy is an allow-list bypass as well as a + * rate-limit one. */ trustProxy: z.boolean().default(false).describe( 'Believe `X-Forwarded-For` / `X-Real-IP` when identifying a caller. Declare `true` ONLY when a reverse proxy ' + 'you control overwrites those headers on every inbound request. Left `false` (the default) the caller IP is ' - + "the transport's own peer address, which a client cannot forge. One client-address rule, two consumers: the " - + 'inbound rate limiter when `server.security.rateLimit.enabled` is set, and the authentication plugin, whose IP ' - + 'allow-list (`allowed_ip_ranges`) judges and whose login audit stamps (`last_login_ip`) the same address.', + + "the transport's own peer address, which a client cannot forge. One client-address rule, three consumers: the " + + 'inbound rate limiter when `server.security.rateLimit.enabled` is set; the per-endpoint rate limiter of every ' + + 'declared endpoint (`apis:`) whose own `rateLimit` is enabled, whether or not the server-level one is; and the ' + + 'authentication plugin, whose IP allow-list (`allowed_ip_ranges`) judges and whose login audit stamps ' + + '(`last_login_ip`) the same address.', ), }, )); From 9aa6fdc4c8b6ca86101a1c8ec24d1f8f3da59c03 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Oct 2026 11:51:36 +0000 Subject: [PATCH 2/2] docs(spec): regenerate the stack-server reference for the trustProxy describe Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz Co-Authored-By: Claude --- content/docs/references/system/stack-server.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/references/system/stack-server.mdx b/content/docs/references/system/stack-server.mdx index 57756024dc6..d9756b0e458 100644 --- a/content/docs/references/system/stack-server.mdx +++ b/content/docs/references/system/stack-server.mdx @@ -101,7 +101,7 @@ const result = ServerRateLimitConfigSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **security** | `{ rateLimit?: object }` | optional | Server-level security configuration. Today: the global inbound rate limit. | -| **trustProxy** | `boolean` | optional (default: `false`) | Believe `X-Forwarded-For` / `X-Real-IP` when identifying a caller. Declare `true` ONLY when a reverse proxy you control overwrites those headers on every inbound request. Left `false` (the default) the caller IP is the transport's own peer address, which a client cannot forge. One client-address rule, two consumers: the inbound rate limiter when `server.security.rateLimit.enabled` is set, and the authentication plugin, whose IP allow-list (`allowed_ip_ranges`) judges and whose login audit stamps (`last_login_ip`) the same address. | +| **trustProxy** | `boolean` | optional (default: `false`) | Believe `X-Forwarded-For` / `X-Real-IP` when identifying a caller. Declare `true` ONLY when a reverse proxy you control overwrites those headers on every inbound request. Left `false` (the default) the caller IP is the transport's own peer address, which a client cannot forge. One client-address rule, three consumers: the inbound rate limiter when `server.security.rateLimit.enabled` is set; the per-endpoint rate limiter of every declared endpoint (`apis:`) whose own `rateLimit` is enabled, whether or not the server-level one is; and the authentication plugin, whose IP allow-list (`allowed_ip_ranges`) judges and whose login audit stamps (`last_login_ip`) the same address. | ### Nested Shape: `StackServerConfig.security`