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
plugin-auth: GET /api/v1/auth/client-address answers a revoked or expired API key with 500 INTERNAL_ERROR instead of its own refusal #31259
Follow-up of #31030 (PR #31258), should-fix S1 of its contract review (6712531628 on PR #31258), filed by domain:server#6 so the narrowing PR lands without re-opening its review. ⛔ Not a claim. Suggested-grade: p3 (fail-closed, no disclosure; a wrong status class and a spurious error log line).
Observed (read in the code at PR #31258 head 78ac3b00): the new organization-administrator read GET /api/v1/auth/client-address (auth-plugin.ts) resolves its caller with credentials: 'api-key-and-session', so an API key reaches it. resolveRequestExecutionContext propagates an ApiKeyLifecycleRefusalError for a revoked, expired or inactive key. The route's catch relays only AuthzStoreUnavailableError (503); every other error falls to 500 INTERNAL_ERROR with an error log line.
Expected: the route relays an API-key lifecycle refusal with the error's own status, code and message, as the settings door does (packages/services/service-settings/src/settings-routes.ts, isApiKeyLifecycleRefusalError), beside the 503 arm. One reader of the classifier, never a copy.
Pins owed: a revoked key answers its lifecycle refusal (status + code), red on the PR #31258 head, green after; the 503 store-outage arm and the 401 / 403 gate arms unchanged.
Claim: PM loop round 1 · 2026-11-08T02:34Z
Session: session_01FEkQ5kvEQydxRtYLVRTfvo (card thread, dev) · seat session_01FEkQ5o8PE7ZtVjcor51wBZ
Account: os-project-manager
Branch: claude/project-thread-jjonmc
Worktree: objectstack-issue-31259
Domain: domain:server
Seat: domain:server#5
File surface: packages/plugins/plugin-auth/src/** (the GET /api/v1/auth/client-address route's catch + its test), root .changeset/
Container & model: S, mode:thread, model: default tier (dispatch-gates --tier: no path-derived mandate), effort: thread default
Clause-②: no
Metadata-protocol: none
Responsibility: plugin-auth organization client-address read (objectstack) | the catch relays only the 503 store outage; an API-key lifecycle refusal falls to 500 | an API-key caller of that read whose key is revoked, expired or inactive
Thread-read: 6712917904
Premise: holds (read, not run): PR #31258 merged as f5e23e2f; the one classifier isApiKeyLifecycleRefusalError is exported by @objectstack/core (security/api-key.ts) and already relayed this way by plugin-sharing and core's own-row delete routes; the dev re-measures the 500 red on main first
Serial constraints cleared: cleared — PR #31258 merged; no open PR touches packages/plugins/plugin-auth/src/auth-plugin.ts
Scope: Fixes #31259. One classifier, beside the 503 arm; 401/403/503 arms unchanged. Order: /mnt/project-files/pm-orders/server/order-31259.md. Summons: the maintainer's 「server 再开两个座位」「并发都是5」 (this project, 2026-11-07); grading per 「定级你可以自己决定」 is triage's here (graded on the card).
os-dev-report
{
"issue": 31259,
"status": "done",
"branch": "claude/project-thread-jjonmc",
"pr": "#31345",
"session": "session_01FEkQ5kvEQydxRtYLVRTfvo",
"premise_still_valid": true,
"summary": "GET /api/v1/auth/client-address (auth-plugin.ts) now relays an API key's lifecycle refusal beside the 503 store-outage arm: the error's own status (403), code (API_KEY_REVOKED / API_KEY_EXPIRED / API_KEY_INACTIVE) and located message, with no error log line. The classifier is the one @objectstack/core exports (isApiKeyLifecycleRefusalError), no copy and no new dependency edge (plugin-auth already depends on core). The 503, 401/403 admission, 400 closed-query and admitted 200 arms are unchanged. Head e85053f; merged origin/main once before opening (a no-op: origin/main was still f5e23e2). Files: auth-plugin.ts (route catch + one import), allowed-ip-ranges.pin.test.ts (new top-level describe + a 'resolved' caller option on the rig's orgRead), .changeset/31259-client-address-api-key-lifecycle-refusal.md (patch).",
"tests": "New describe in the route's shared harness allowed-ip-ranges.pin.test.ts drives the REAL resolveOrganizationSettingsCaller (rig option caller: 'resolved') over a stubbed data engine holding one sys_api_key row: revoked -> 403 API_KEY_REVOKED, expired (expires_at 2000-01-01) -> 403 API_KEY_EXPIRED, each with the refusal's first sentence, no data, the address never read, logger.error never called. Red on main's route (pin commit dc35c35): 2 failed, each answering status 500 INTERNAL_ERROR 'Your client address could not be read.' with one logger.error call. Green on e85053f: vitest run src/allowed-ip-ranges.pin.test.ts exit 0, Tests 46 passed (46). Ablation via scripts/ablation-replace.mjs on the committed fix: anchor 'if (isApiKeyLifecycleRefusalError(error)) {' -> 'if (false && isApiKeyLifecycleRefusalError(error)) {' (anchor 1 -> 0, blob 5d15e7c4 -> 575d2eaf): Tests 2 failed | 44 passed (46), same 500 answer; restored blob == HEAD 5d15e7c4, git diff HEAD empty. The test imports ./auth-plugin.js from source, so no build sits between mutation and run. Full suite on e85053f: pnpm --filter @objectstack/plugin-auth test VERDICT command-exit 0, Test Files 240 passed (240), Tests 4248 passed, 10 skipped. pnpm --filter @objectstack/plugin-auth typecheck (tsc, tsconfig.examples.json, check:test-typecheck) after building plugin-auth: VERDICT command-exit 0.",
"gates": "On e85053f: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 75 commands; each run with stdin /dev/null, exit captured before any pipe: 75 exit 0. Three first answered exit 3 (prerequisite, nothing measured) and were re-run to 0: check-plugin-teardown-shape.mjs --self-test (after git fetch --depth=1 of its pinned fixture commit), check:dual-build-cjs-loads (after a full turbo run build --concurrency=2 under the lock, VERDICT command-exit 0; then 122 entry points / 72 packages load), check:type-check-debt (lock queue timeout while that build held the lock). --ran: '75 derived famil(ies) accounted for - 75 run, 0 NOT-MEASURED (a DERIVED zero - all 75 recorded an exit code and none of them is 3)', exit 0. Ran-file: pm-orders/server/31259-gates-ran.txt. Lint: pnpm exec eslint packages/plugins/plugin-auth --no-inline-config exit 0, no output. CI check-runs on PR #31345: in_progress at report time (not awaited, per os-dev.md).",
"dogfood_battery": "n/a - no ! in the PR title and Clause-② is no with no (narrowing) arm",
"mcp_calls": "6 - reads only: issue_read x3 (card #31259 get, get_comments x2), pull_request_read x1 (PR #31258 get), pull_request_read x1 (PR #31345 get, read-back), ToolSearch-loaded schemas not counted; no MCP write tool",
"api_writes": "3 - git push (branch claude/project-thread-jjonmc, 3 pushes: pin, fix; merge was a no-op), relay pr_create (POST /repos/objectstack-ai/objectstack/dispatches -> fleet-write run 42442212194, PR #31345, 7735 bytes sent = stored, no labels: the order names none and the changeset is present so skip-changeset does not apply; the relay refuses an empty list, so the key was omitted; no assignees), this os-dev-report comment via post-stamped",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"carrier: none - nothing found outside the card's scope. The platform-admin twin GET /api/v1/auth/admin/client-address reads its caller through gateAdmin (session only, no API-key door), so it has no lifecycle arm to relay; checked, not a finding."
]
}
Activity
objectstack-fleet commentedon Nov 7, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: audit · p3 ·
domain:server· confirmed · 2026-11-07T20:56Z⛔ Not a claim.
GET /api/v1/auth/client-addresstakes an API key, and a revoked or expired key's lifecycle refusal is not relayed with its own status and code, as the settings door does. It fails closed with no disclosure; the cost is a wrong status class and a spurious log line.domain:server,pm:queue. Take it after PR feat(plugin-auth)!: an organization administrator reads their judged client address; the console's retired MFA notices are gone #31258 (open, draft) lands.Generated by Claude Code
objectstack-fleet commentedon Nov 8, 2026
domain:server#5 · os-project-manager · session_01FEkQ5o8PE7ZtVjcor51wBZ
Claim: PM loop round 1 · 2026-11-08T02:34Z
Session:
session_01FEkQ5kvEQydxRtYLVRTfvo(card thread, dev) · seatsession_01FEkQ5o8PE7ZtVjcor51wBZAccount:
os-project-managerBranch:
claude/project-thread-jjonmcWorktree:
objectstack-issue-31259Domain:
domain:serverSeat:
domain:server#5File surface:
packages/plugins/plugin-auth/src/**(theGET /api/v1/auth/client-addressroute's catch + its test), root.changeset/Container & model: S,
mode:thread,model: default tier(dispatch-gates --tier: no path-derived mandate),effort: thread defaultClause-②: no
Metadata-protocol: none
Responsibility:
plugin-authorganization client-address read (objectstack) | the catch relays only the 503 store outage; an API-key lifecycle refusal falls to 500 | an API-key caller of that read whose key is revoked, expired or inactiveThread-read: 6712917904
Premise: holds (read, not run): PR #31258 merged as
f5e23e2f; the one classifierisApiKeyLifecycleRefusalErroris exported by@objectstack/core(security/api-key.ts) and already relayed this way by plugin-sharing and core's own-row delete routes; the dev re-measures the 500 red on main firstSerial constraints cleared: cleared — PR #31258 merged; no open PR touches
packages/plugins/plugin-auth/src/auth-plugin.tsScope: Fixes #31259. One classifier, beside the 503 arm; 401/403/503 arms unchanged. Order:
/mnt/project-files/pm-orders/server/order-31259.md. Summons: the maintainer's 「server 再开两个座位」「并发都是5」 (this project, 2026-11-07); grading per 「定级你可以自己决定」 is triage's here (graded on the card).objectstack-fleet commentedon Nov 8, 2026
domain:server#5 · os-project-manager · session_01FEkQ5kvEQydxRtYLVRTfvo
os-dev-report
{
"issue": 31259,
"status": "done",
"branch": "claude/project-thread-jjonmc",
"pr": "#31345",
"session": "session_01FEkQ5kvEQydxRtYLVRTfvo",
"premise_still_valid": true,
"summary": "GET /api/v1/auth/client-address (auth-plugin.ts) now relays an API key's lifecycle refusal beside the 503 store-outage arm: the error's own status (403), code (API_KEY_REVOKED / API_KEY_EXPIRED / API_KEY_INACTIVE) and located message, with no error log line. The classifier is the one @objectstack/core exports (isApiKeyLifecycleRefusalError), no copy and no new dependency edge (plugin-auth already depends on core). The 503, 401/403 admission, 400 closed-query and admitted 200 arms are unchanged. Head e85053f; merged origin/main once before opening (a no-op: origin/main was still f5e23e2). Files: auth-plugin.ts (route catch + one import), allowed-ip-ranges.pin.test.ts (new top-level describe + a 'resolved' caller option on the rig's orgRead), .changeset/31259-client-address-api-key-lifecycle-refusal.md (patch).",
"tests": "New describe in the route's shared harness allowed-ip-ranges.pin.test.ts drives the REAL resolveOrganizationSettingsCaller (rig option caller: 'resolved') over a stubbed data engine holding one sys_api_key row: revoked -> 403 API_KEY_REVOKED, expired (expires_at 2000-01-01) -> 403 API_KEY_EXPIRED, each with the refusal's first sentence, no data, the address never read, logger.error never called. Red on main's route (pin commit dc35c35): 2 failed, each answering status 500 INTERNAL_ERROR 'Your client address could not be read.' with one logger.error call. Green on e85053f: vitest run src/allowed-ip-ranges.pin.test.ts exit 0, Tests 46 passed (46). Ablation via scripts/ablation-replace.mjs on the committed fix: anchor 'if (isApiKeyLifecycleRefusalError(error)) {' -> 'if (false && isApiKeyLifecycleRefusalError(error)) {' (anchor 1 -> 0, blob 5d15e7c4 -> 575d2eaf): Tests 2 failed | 44 passed (46), same 500 answer; restored blob == HEAD 5d15e7c4, git diff HEAD empty. The test imports ./auth-plugin.js from source, so no build sits between mutation and run. Full suite on e85053f: pnpm --filter @objectstack/plugin-auth test VERDICT command-exit 0, Test Files 240 passed (240), Tests 4248 passed, 10 skipped. pnpm --filter @objectstack/plugin-auth typecheck (tsc, tsconfig.examples.json, check:test-typecheck) after building plugin-auth: VERDICT command-exit 0.",
"gates": "On e85053f: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 75 commands; each run with stdin /dev/null, exit captured before any pipe: 75 exit 0. Three first answered exit 3 (prerequisite, nothing measured) and were re-run to 0: check-plugin-teardown-shape.mjs --self-test (after git fetch --depth=1 of its pinned fixture commit), check:dual-build-cjs-loads (after a full turbo run build --concurrency=2 under the lock, VERDICT command-exit 0; then 122 entry points / 72 packages load), check:type-check-debt (lock queue timeout while that build held the lock). --ran: '75 derived famil(ies) accounted for - 75 run, 0 NOT-MEASURED (a DERIVED zero - all 75 recorded an exit code and none of them is 3)', exit 0. Ran-file: pm-orders/server/31259-gates-ran.txt. Lint: pnpm exec eslint packages/plugins/plugin-auth --no-inline-config exit 0, no output. CI check-runs on PR #31345: in_progress at report time (not awaited, per os-dev.md).",
"dogfood_battery": "n/a - no ! in the PR title and Clause-② is no with no (narrowing) arm",
"mcp_calls": "6 - reads only: issue_read x3 (card #31259 get, get_comments x2), pull_request_read x1 (PR #31258 get), pull_request_read x1 (PR #31345 get, read-back), ToolSearch-loaded schemas not counted; no MCP write tool",
"api_writes": "3 - git push (branch claude/project-thread-jjonmc, 3 pushes: pin, fix; merge was a no-op), relay pr_create (POST /repos/objectstack-ai/objectstack/dispatches -> fleet-write run 42442212194, PR #31345, 7735 bytes sent = stored, no labels: the order names none and the changeset is present so skip-changeset does not apply; the relay refuses an empty list, so the key was omitted; no assignees), this os-dev-report comment via post-stamped",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"carrier: none - nothing found outside the card's scope. The platform-admin twin GET /api/v1/auth/admin/client-address reads its caller through gateAdmin (session only, no API-key door), so it has no lifecycle arm to relay; checked, not a finding."
]
}