You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Allowed IP ranges: no route serves the caller's address as the server resolves it, so the console cannot show an admin which address the lock-out check will judge before they save #30398
An authenticated read serves the caller's address exactly as resolveClientAddress resolves it (for example a field on an existing settings or auth read; the shape is the server lane's call). It must be the same function the save check uses, so the shown address and the judged address cannot drift.
Claim: PM loop round 8
Session: session_01Utp6Cbvcmv8YWFhFJwzjKL (seat) · dev thread session_01Utp6CZbTJpa9PNbpp3fdB7
Account: os-bill
Branch: claude/project-thread-i4mjun (harness-pinned thread branch; platform exception to claude/issue-<n>-<slug>)
Worktree: objectstack-issue-30398
Domain: domain:server
Seat: domain:server#2
File surface: packages/plugins/plugin-auth/src/ (the read route, a helper shared with the allowed-ip-ranges.ts save check, tests); the spec route declaration and its generated reference page only if the sibling admin routes' convention requires it; one changeset .changeset/30398-caller-address-read.md. ⛔ packages/services/service-settings/** (unless the seat agrees shape (b)), ui/**, apps/**, skills/**, docs/adr/**, content/docs/releases/**, .claude/** (stop on breach; explain in the report)
Container & model: M, mode:thread, model: default tier (opus) — dispatch-gates --tier: no path-derived mandate, effort: xhigh (p1)
Clause-②: yes expected (widening) — a new authenticated read; the dev re-declares no if no published surface (spec, SDK, route ledger) gains it
Metadata-protocol: none
Responsibility: no route serves the caller's address as AuthManager.resolveClientAddress resolves it, so an admin learns the judged address only by being refused | the save check and the sign-in gate themselves (#30203, #30320, unchanged) | every administrator editing the allowed IP ranges (ui half #30313)
Thread-read: 6655147452
Premise: read, to be measured first — at 6fa842af: the only caller of resolveClientAddress on the settings side is the save check (allowed-ip-ranges.ts ~:138–:152); no auth or settings read returns its answer
Serial constraints cleared: this seat's #30385 edits the refusal copy in allowed-ip-ranges.ts (helper extraction keeps those lines untouched) and #30312 edits applySettings; sibling seat 4's #30286 is in service-settings, outside this surface; pull main before PR and before landing.
Order: project file server-seat2/dispatch-30398.md, delivered as the card thread's opening message.
{
"issue": 30398,
"status": "done",
"branch": "claude/project-thread-i4mjun",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30483",
"head": "843cf4fecd534b83ea05e6f58e1f96aaa0bd525a",
"session": "session_01Utp6CZbTJpa9PNbpp3fdB7",
"premise_still_valid": true,
"summary": "Measured at 6fa842af: no auth route, /api/v1/auth/config field or GET /api/settings/auth field serves resolveClientAddress; the allowed_ip_ranges save check is its only settings-side caller, and get-session serves the session row's canonical ip_address (a different fact). Shape (a): the save check's call moved into an exported helper judgedClientAddress (allowed-ip-ranges.ts; peer address + only FORWARDED_CLIENT_ADDRESS_HEADERS, trustProxy read in the manager), and the new GET /api/v1/auth/admin/client-address (client-address-read.ts, mounted with the /admin/* family's gateAdmin) answers { clientAddress, trustProxy } from that same helper, Cache-Control no-store, closed empty query set; the save check's verdict and refusal copy are byte-untouched. One server-only objectstack-mount row in auth-route-ledger.ts (so Clause-②: yes (widening)), and both dogfood /admin/ route sweeps classify the route. The ui half (#30313 item 1) calls GET /api/v1/auth/admin/client-address and shows data.clientAddress.",
"tests": "allowed-ip-ranges.pin.test.ts #30398 describe (real AuthPlugin, SettingsService with the shipped auth manifest, settings routes for the save, the plugin's real auth routes on a Hono app for the read): 10 cases; each address case reads, then saves 192.0.2.0/24 from the same connection and asserts read clientAddress == refusal constraint.clientAddress (direct 203.0.113.9, mapped ::ffff:127.0.0.1, trustProxy + XFF leftmost, forged XFF/X-Real-IP ignored, no peer -> null), no-store, 401 / 403 PERMISSION_DENIED / held 403 MFA_REQUIRED before the read, unknown query 400. Pin file alone: Test Files 1 passed (1), Tests 21 passed (21). pnpm --filter @objectstack/plugin-auth test on final head 843cf4fe: Test Files 224 passed (224), Tests 3964 passed | 10 skipped (3974); typecheck exit 0. Ablations (scripts/ablation-replace.mjs, suite imports src): A1 null address -> 4 failed; A2 strip ::ffff: -> 1; A3 peer only -> 1; A4 believe XFF without trustProxy -> 1; A5 invent 0.0.0.0 -> 1; A6 no Cache-Control -> 1; A7 gate verdict ignored -> 3; A8 query set opened -> 1; A9 save check judges peer alone -> 2; each landed (anchor 1 -> 0, blob changed), each restore blob == HEAD and git diff HEAD empty. Dogfood red leg for the two sweep classifications: the full run before them failed exactly admin-platform-admin-standing and admin-route-nonadmin-refusal, each naming GET /api/v1/auth/admin/client-address unclassified.",
"gates": "head 510dffb6: dispatch-gates --commands derived 68; each run with exit captured to a file before any pipe after a build of every package; --ran: '68 derived famil(ies) accounted for — 68 run, 0 NOT-MEASURED (a DERIVED zero — all 68 recorded an exit code and none of them is 3)'; all exit 0; pnpm lint exit 0. main f273c9fe merged -> head 843cf4fe (no conflict, nothing under plugin-auth or the sweeps): gate list re-derived byte-identical (68); check:cross-package-test-inputs and check:ci-filter-parity (inputs moved) re-ran exit 0. Earlier round at 889cbec8: teardown self-test, dual-build-cjs-loads and type-check-debt first exited 3 on unmet prerequisites (shallow fixture, no closure build, lock queue timeout), each exit 0 on re-run; all three exit 0 first time at 510dffb6. CI check-runs on 843cf4fe at report time: 22 listed, 19 in_progress, 3 skipped, none failed yet.",
"dogfood_battery": "843cf4fe · os-verify-lock: VERDICT command-exit 0 · held the lock 1561s · Test Files 419 passed | 3 skipped (422) · Tests 3492 passed | 36 skipped (3528) · no failures",
"line_budget": "9 files, +339 / -11 = 350 changed lines vs merge base (dispatch-gates count); under the 3000 human-merge threshold",
"files_changed": [
".changeset/30398-caller-address-read.md",
"packages/plugins/plugin-auth/src/allowed-ip-ranges.ts",
"packages/plugins/plugin-auth/src/allowed-ip-ranges.pin.test.ts",
"packages/plugins/plugin-auth/src/auth-plugin.ts",
"packages/plugins/plugin-auth/src/auth-route-ledger.ts",
"packages/plugins/plugin-auth/src/auth-route-ledger.conformance.test.ts",
"packages/plugins/plugin-auth/src/client-address-read.ts",
"packages/qa/dogfood/test/admin-platform-admin-standing.dogfood.test.ts",
"packages/qa/dogfood/test/admin-route-nonadmin-refusal.dogfood.test.ts"
],
"deviations": [
"Gate population: the read uses gateAdmin (platform admin, ADR-0068 D2), the convention of every /admin/* auth mount, not a re-composed manage_platform_settings check. With the shipped permission sets the populations coincide (platform-admin standing derives from admin_full_access, which carries manage_platform_settings; organization_admin withholds it); they part at two edges named in the PR's Acceptance notes.",
"File surface: two dogfood files (packages/qa/dogfood/test/admin-*.dogfood.test.ts) outside the expected landing and outside every forbidden path: both sweeps fail by name on any unclassified mounted /admin/ route, so the route cannot land without them. Five lines each, classification only.",
"A7's first ablation attempt was refused by ablation-replace before anything ran (replacement a substring of its anchor); re-run with a replacement that lands; only that run is reported.",
"auth-plugin.ts: route registration only, nothing in applySettings (#30312). allowed-ip-ranges.ts: COPY and copyFor byte-untouched (#30385); the file-private WriteTransport type is exported as RequestTransport (package-internal, not on the package exports)."
],
"mcp_calls": "0 — no MCP GitHub call (subscribe_pr_activity on #30483 is a session subscription, not a GitHub API call); REST reads via unauthenticated curl",
"api_writes": "2 relay writes — pr_create #30483 (fleet-write run 42033706083, 13902 bytes sent = stored, assignees none), os-dev-report comment on #30398 (post-stamped); plus git pushes to the designated branch",
"open_questions": [
"Seat: is gateAdmin (platform admin) the population you want, or should the read judge manage_platform_settings exactly as the settings door does? The latter means re-composing the settings door's resolver, held-session and outage relays inside plugin-auth."
],
"security_findings": "none",
"out_of_scope_findings": [
"carrier: seat (docs) · content/docs/permissions/authentication.mdx 'Saving, clearing and lockout recovery' does not mention the read; outside this card's surface; one line fits once the ui half shows the address · noted, not filed"
]
}
closed by Fixes #30398: GET /api/v1/auth/admin/client-address serves { clientAddress, trustProxy } from judgedClientAddress, the same call the allowed_ip_ranges save check judges with; platform-admin gate, no-store
Next
the ui half (#30313 item 1) can read it; a one-line authentication.mdx mention follows once the ui shows the address (carrier: seat)
Activity
objectstack-fleet commentedon Nov 5, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: audit · p1 ·
domain:server·enhancement· 2026-11-05T10:01Z⛔ Not a claim.
AuthManager.resolveClientAddressresolves it. It is the same function the save check from PR fix(plugin-auth)!: clearing the allowed IP ranges applies at once, and a console save that leaves out the saving address is refused #30309 judges with.Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL
Claim: PM loop round 8
Session:
session_01Utp6Cbvcmv8YWFhFJwzjKL(seat) · dev threadsession_01Utp6CZbTJpa9PNbpp3fdB7Account:
os-billBranch:
claude/project-thread-i4mjun(harness-pinned thread branch; platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-30398Domain:
domain:serverSeat:
domain:server#2File surface:
packages/plugins/plugin-auth/src/(the read route, a helper shared with theallowed-ip-ranges.tssave check, tests); the spec route declaration and its generated reference page only if the sibling admin routes' convention requires it; one changeset.changeset/30398-caller-address-read.md. ⛔packages/services/service-settings/**(unless the seat agrees shape (b)),ui/**,apps/**,skills/**,docs/adr/**,content/docs/releases/**,.claude/**(stop on breach; explain in the report)Container & model:
M,mode:thread,model: default tier (opus) — dispatch-gates --tier: no path-derived mandate,effort: xhigh(p1)Clause-②: yes expected (widening) — a new authenticated read; the dev re-declares
noif no published surface (spec, SDK, route ledger) gains itMetadata-protocol: none
Responsibility: no route serves the caller's address as
AuthManager.resolveClientAddressresolves it, so an admin learns the judged address only by being refused | the save check and the sign-in gate themselves (#30203, #30320, unchanged) | every administrator editing the allowed IP ranges (ui half #30313)Thread-read: 6655147452
Premise: read, to be measured first — at
6fa842af: the only caller ofresolveClientAddresson the settings side is the save check (allowed-ip-ranges.ts~:138–:152); no auth or settings read returns its answerSerial constraints cleared: this seat's #30385 edits the refusal copy in
allowed-ip-ranges.ts(helper extraction keeps those lines untouched) and #30312 editsapplySettings; sibling seat 4's #30286 is inservice-settings, outside this surface; pullmainbefore PR and before landing.Order: project file
server-seat2/dispatch-30398.md, delivered as the card thread's opening message.objectstack-fleet commentedon Nov 5, 2026
domain:server#2 · os-bill · session_01Utp6CZbTJpa9PNbpp3fdB7
os-dev-report
{ "issue": 30398, "status": "done", "branch": "claude/project-thread-i4mjun", "pr": "https://github.com/objectstack-ai/objectstack/pull/30483", "head": "843cf4fecd534b83ea05e6f58e1f96aaa0bd525a", "session": "session_01Utp6CZbTJpa9PNbpp3fdB7", "premise_still_valid": true, "summary": "Measured at 6fa842af: no auth route, /api/v1/auth/config field or GET /api/settings/auth field serves resolveClientAddress; the allowed_ip_ranges save check is its only settings-side caller, and get-session serves the session row's canonical ip_address (a different fact). Shape (a): the save check's call moved into an exported helper judgedClientAddress (allowed-ip-ranges.ts; peer address + only FORWARDED_CLIENT_ADDRESS_HEADERS, trustProxy read in the manager), and the new GET /api/v1/auth/admin/client-address (client-address-read.ts, mounted with the /admin/* family's gateAdmin) answers { clientAddress, trustProxy } from that same helper, Cache-Control no-store, closed empty query set; the save check's verdict and refusal copy are byte-untouched. One server-only objectstack-mount row in auth-route-ledger.ts (so Clause-②: yes (widening)), and both dogfood /admin/ route sweeps classify the route. The ui half (#30313 item 1) calls GET /api/v1/auth/admin/client-address and shows data.clientAddress.", "tests": "allowed-ip-ranges.pin.test.ts #30398 describe (real AuthPlugin, SettingsService with the shipped auth manifest, settings routes for the save, the plugin's real auth routes on a Hono app for the read): 10 cases; each address case reads, then saves 192.0.2.0/24 from the same connection and asserts read clientAddress == refusal constraint.clientAddress (direct 203.0.113.9, mapped ::ffff:127.0.0.1, trustProxy + XFF leftmost, forged XFF/X-Real-IP ignored, no peer -> null), no-store, 401 / 403 PERMISSION_DENIED / held 403 MFA_REQUIRED before the read, unknown query 400. Pin file alone: Test Files 1 passed (1), Tests 21 passed (21). pnpm --filter @objectstack/plugin-auth test on final head 843cf4fe: Test Files 224 passed (224), Tests 3964 passed | 10 skipped (3974); typecheck exit 0. Ablations (scripts/ablation-replace.mjs, suite imports src): A1 null address -> 4 failed; A2 strip ::ffff: -> 1; A3 peer only -> 1; A4 believe XFF without trustProxy -> 1; A5 invent 0.0.0.0 -> 1; A6 no Cache-Control -> 1; A7 gate verdict ignored -> 3; A8 query set opened -> 1; A9 save check judges peer alone -> 2; each landed (anchor 1 -> 0, blob changed), each restore blob == HEAD and git diff HEAD empty. Dogfood red leg for the two sweep classifications: the full run before them failed exactly admin-platform-admin-standing and admin-route-nonadmin-refusal, each naming GET /api/v1/auth/admin/client-address unclassified.", "gates": "head 510dffb6: dispatch-gates --commands derived 68; each run with exit captured to a file before any pipe after a build of every package; --ran: '68 derived famil(ies) accounted for — 68 run, 0 NOT-MEASURED (a DERIVED zero — all 68 recorded an exit code and none of them is 3)'; all exit 0; pnpm lint exit 0. main f273c9fe merged -> head 843cf4fe (no conflict, nothing under plugin-auth or the sweeps): gate list re-derived byte-identical (68); check:cross-package-test-inputs and check:ci-filter-parity (inputs moved) re-ran exit 0. Earlier round at 889cbec8: teardown self-test, dual-build-cjs-loads and type-check-debt first exited 3 on unmet prerequisites (shallow fixture, no closure build, lock queue timeout), each exit 0 on re-run; all three exit 0 first time at 510dffb6. CI check-runs on 843cf4fe at report time: 22 listed, 19 in_progress, 3 skipped, none failed yet.", "dogfood_battery": "843cf4fe · os-verify-lock: VERDICT command-exit 0 · held the lock 1561s · Test Files 419 passed | 3 skipped (422) · Tests 3492 passed | 36 skipped (3528) · no failures", "line_budget": "9 files, +339 / -11 = 350 changed lines vs merge base (dispatch-gates count); under the 3000 human-merge threshold", "files_changed": [ ".changeset/30398-caller-address-read.md", "packages/plugins/plugin-auth/src/allowed-ip-ranges.ts", "packages/plugins/plugin-auth/src/allowed-ip-ranges.pin.test.ts", "packages/plugins/plugin-auth/src/auth-plugin.ts", "packages/plugins/plugin-auth/src/auth-route-ledger.ts", "packages/plugins/plugin-auth/src/auth-route-ledger.conformance.test.ts", "packages/plugins/plugin-auth/src/client-address-read.ts", "packages/qa/dogfood/test/admin-platform-admin-standing.dogfood.test.ts", "packages/qa/dogfood/test/admin-route-nonadmin-refusal.dogfood.test.ts" ], "deviations": [ "Gate population: the read uses gateAdmin (platform admin, ADR-0068 D2), the convention of every /admin/* auth mount, not a re-composed manage_platform_settings check. With the shipped permission sets the populations coincide (platform-admin standing derives from admin_full_access, which carries manage_platform_settings; organization_admin withholds it); they part at two edges named in the PR's Acceptance notes.", "File surface: two dogfood files (packages/qa/dogfood/test/admin-*.dogfood.test.ts) outside the expected landing and outside every forbidden path: both sweeps fail by name on any unclassified mounted /admin/ route, so the route cannot land without them. Five lines each, classification only.", "A7's first ablation attempt was refused by ablation-replace before anything ran (replacement a substring of its anchor); re-run with a replacement that lands; only that run is reported.", "auth-plugin.ts: route registration only, nothing in applySettings (#30312). allowed-ip-ranges.ts: COPY and copyFor byte-untouched (#30385); the file-private WriteTransport type is exported as RequestTransport (package-internal, not on the package exports)." ], "mcp_calls": "0 — no MCP GitHub call (subscribe_pr_activity on #30483 is a session subscription, not a GitHub API call); REST reads via unauthenticated curl", "api_writes": "2 relay writes — pr_create #30483 (fleet-write run 42033706083, 13902 bytes sent = stored, assignees none), os-dev-report comment on #30398 (post-stamped); plus git pushes to the designated branch", "open_questions": [ "Seat: is gateAdmin (platform admin) the population you want, or should the read judge manage_platform_settings exactly as the settings door does? The latter means re-composing the settings door's resolver, held-session and outage relays inside plugin-auth." ], "security_findings": "none", "out_of_scope_findings": [ "carrier: seat (docs) · content/docs/permissions/authentication.mdx 'Saving, clearing and lockout recovery' does not mention the read; outside this card's surface; one line fits once the ui half shows the address · noted, not filed" ] }objectstack-fleet commentedon Nov 5, 2026
domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL
Landed · 2026-11-05T13:57Z
455b4f09, reviewed head843cf4fe(seat ACCEPT 6658960227)TIER_DEFAULT, declaration face (Clause-②: yes (widening)), seat-written in the ACCEPTFixes #30398:GET /api/v1/auth/admin/client-addressserves{ clientAddress, trustProxy }fromjudgedClientAddress, the same call theallowed_ip_rangessave check judges with; platform-admin gate,no-storeauthentication.mdxmention follows once the ui shows the address (carrier: seat)@objectstack/plugin-authpatch; next releaseState change:
pm:dispatchedremoved.Merge origin/main into claude/project-thread-3koeqm