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: the documented env recovery cannot clear the stored list, so removing OS_AUTH_ALLOWED_IP_RANGES locks the admin out again at the next restart #30385
Filing gate: ② found while documenting #30314 (PR #30382) from PR #30309's merged code at origin/mainfdad1786e8. Read from code, not measured on a live boot. ⛔ Not a claim. Triage sets lane and grade.
Reader: domain:server (packages/services/service-settings/**, packages/plugins/plugin-auth/**).
Dedupe (open AND closed): OS_AUTH_ALLOWED_IP_RANGES → #30203 (closed, the save refusal + live clear), #30313 (console shows the caller's address), #30314 (docs). None covers clearing the stored value while the env override is in force. New.
reach: an administrator who stored a list that now excludes them (a changed office address, a proxy change, a list saved before #30309's refusal).
type: Bug
While the override is in force, the key is locked. A save to allowed_ip_ranges (including an empty one) is refused with 409 SETTINGS_LOCKED (settings-routes.ts ~375–380). The stored list stays.
So the admin who got back in cannot clear or fix the stored list in Setup → Authentication. When they remove the variable, the stored list applies again at the next restart and locks them out again.
Wanted
An admin recovering through the env override can clear or correct the stored allowed_ip_ranges without leaving the variable set for good. The owning lane picks the shape. For example, a clear (an empty save, or restore-default) is allowed on a locked key, or the refusal names a supported way to drop the stored value.
Claim: PM loop round 6
Session: session_01Utp6Cbvcmv8YWFhFJwzjKL (seat) · dev thread session_01Utp6CZjAtpVuoYZBzB9n5i
Account: os-bill
Branch: claude/project-thread-3koeqm (harness-pinned thread branch; platform exception to claude/issue-<n>-<slug>)
Worktree: objectstack-issue-30385
Domain: domain:server
Seat: domain:server#2
File surface: packages/services/service-settings/src/settings-service.ts (the env-lock branch only), settings-routes.ts / settings-service.types.ts only as needed, tests; packages/plugins/plugin-auth/src/allowed-ip-ranges.ts (refusal copy) and its locale strings; content/docs/permissions/authentication.mdx and docs/HARDENING.md (the recovery sentence only); one changeset .changeset/30385-clear-env-locked-setting.md. ⛔ packages/spec/**, ui/**, apps/**, skills/**, docs/adr/**, .claude/**, content/docs/releases/** (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 clear of an env-pinned key that was refused 409 is stored; the dev re-declares no if neither the published service-settings surface nor its documented error contract moves; reviewed at CONTRACT_REVIEW_TIER either way
Metadata-protocol: none
Responsibility: an env override in force locks the key against every write, clears included, so the stored allowed-IP list survives the documented env recovery | the env override and the Phase-2 row locks themselves (unchanged) | every administrator recovering from an allowed-IP lockout
Thread-read: 6653992606
Premise: read, to be measured first — at 0e4dfbe2: settings-service.ts ~:2406 throws SettingsLockedError whenever effectiveEnvOverride is in force, for any patch value; settings-routes.ts ~:379 answers 409 SETTINGS_LOCKED
Serial constraints cleared: sibling seat 3 yielded this card (takes #30370, no overlap); this seat's #30312 queued behind it (same applySettings area); PR #30392 (auth-manager.ts, queued) out of surface; PR #30382 (docs) checked before the same lines; pull main before PR and before landing.
Order: project file server-seat2/dispatch-30385.md, delivered as the card thread's opening message.
{
"issue": 30385,
"status": "done",
"branch": "claude/project-thread-3koeqm",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30448",
"session": "session_01Utp6CZjAtpVuoYZBzB9n5i",
"premise_still_valid": true,
"summary": "Premise measured at base 0e4dfbe2 on the real AuthPlugin + SettingsService + settings routes: with a stored allow-list refusing ::1, OS_AUTH_ALLOWED_IP_RANGES='' opens the gate, but '' and null through PUT /api/settings/auth both answer 409 SETTINGS_LOCKED (locked-by-env), resetNamespace skips the key (it resolves env), and removing the variable locks ::1 out again at the next boot. Fix (seat ruling, assumption 2): in setMany's pre-flight the env-lock branch now refuses every write but a clear (null/undefined); the clear goes on to the Phase 2 row locks, the platform-rung rule and the write predicates, then nulls the stored row as every clear does, so the key falls back to its default once the override is removed; the answer still reads source env, locked true. Every other write, '' included, stays 409. The allowed-IP refusal's recovery sentence (en, zh-CN, ja-JP, es-ES) now names the four steps that work, through the settings API because the console renders a pinned field read-only. Docs: HARDENING.md Network row, config-resolution.mdx write-error table (seat-widened surface). Depends on an ADR-0007 D6 amendment, which the seat has put to the maintainer.",
"tests": "head 173cc44e: service-settings test Test Files 68 passed (68), Tests 1161 passed (1161), typecheck exit 0; plugin-auth test Test Files 224 passed (224), Tests 3955 passed | 10 skipped (3965), typecheck (tsc + examples + check:test-typecheck) exit 0. New pins: settings-service.test.ts top-level describe, 5 cases (clear under a pin: answer env/locked, reset event, default after the pin lifts; undefined is a clear beside an ordinary write; every other write incl. '' refused locked-by-env, batch stores nothing; upper-rung locked row refuses a clear locked-by-global; own-rung locked row refuses a non-platform clear, platform clear goes through); allowed-ip-ranges.pin.test.ts top-level describe, 1 end-to-end case (stored list refuses ::1, env '' admits, '' is 409 with exact details, null is 200 env/locked, remove env + restart admits ::1). Ablations via scripts/ablation-replace.mjs on settings-service.ts (both suites resolve service-settings from src, no build between; each anchor 1 -> 0, blob changed, restore blob == HEAD, git diff HEAD empty): A clear exemption removed -> service-settings 4 failed | 1 passed, plugin-auth pin 1 failed | 9 passed; B every write through the pin -> 1 failed | 4 passed, pin 1 failed | 9 passed; C clear skips Phase 2 locks -> 2 failed | 3 passed, pin 10 passed.",
"gates": "head 173cc44e: dispatch-gates --commands derived 92; each run with its exit captured before any pipe; all 92 exit 0; --ran: '92 derived famil(ies) accounted for — 92 run, 0 NOT-MEASURED'. First round (head c1c43e27) had check:dual-build-cjs-loads and check-plugin-teardown-shape --self-test at exit 3 (no whole-workspace build; shallow checkout); after building the dogfood closure and fetching 621a4876 both exit 0. pnpm lint exit 0. Changeset gates: check-adr-0087-registration, check-empty-changeset, check-changeset-no-major exit 0. CI check-runs: in_progress at report time.",
"dogfood_battery": "173cc44e · os-verify-lock VERDICT command-exit 0 · Test Files 419 passed | 3 skipped (422) · Tests 3492 passed | 36 skipped (3528) · no failures (run because the order asks it; os-dev owes it only on a ! title or a narrowing arm)",
"line_budget": "8 files, +214 / -12 vs merge base (226 changed lines, dispatch-gates count); under the 3000 human-merge threshold",
"files_changed": [
".changeset/30385-clear-env-locked-setting.md",
"content/docs/protocol/kernel/config-resolution.mdx",
"docs/HARDENING.md",
"packages/plugins/plugin-auth/src/allowed-ip-ranges.pin.test.ts",
"packages/plugins/plugin-auth/src/allowed-ip-ranges.ts",
"packages/services/service-settings/src/settings-service.test.ts",
"packages/services/service-settings/src/settings-service.ts",
"packages/services/service-settings/src/settings-service.types.ts"
],
"deviations": [
"content/docs/protocol/kernel/config-resolution.mdx (one table row) added to the file surface by the seat in the card thread: the row said an override in force always refuses a write, which this change makes false.",
"content/docs/permissions/authentication.mdx not edited: PR #30382 (open, another lane) adds the recovery subsection on the same lines; seat ruling: whichever of the two lands second rewrites its last bullet to the four steps.",
"settings-service.ts edits beyond the env-lock branch are doc comments only: set() and setMany() docblocks (the documented error contract); resetNamespace's docblock left as it was, per the seat (seat 4's card edits nearby functions).",
"Full dogfood and pnpm lint run locally because the order asks; os-dev assigns the first to ! / narrowing PRs and the second to CI.",
"Commit trailers are the model-free pair AGENTS.md prescribes, not the harness's model-named Co-Authored-By line."
],
"mcp_calls": "9 — mcp__github__issue_read x3 (get #30385, get_comments #30385 x2), mcp__github__issue_read get_comments #30203, mcp__github__pull_request_read x5 (get #30309, get_comments #30309, get #30382, get_diff #30382, get #30448); no write tool",
"api_writes": "2 relay writes — pr_create #30448 (fleet-write run 42021780433, body read back identical), this os-dev-report comment on #30385 (post-stamped); plus git pushes to the designated branch",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"class: a · reach: POST /api/settings/auth/reset (built-in reset action, resetNamespace) answers 'Cleared N saved value(s); environment/default configuration is back in effect' while a row stored beneath an OS_* pin survives and applies again when the variable is removed; measured in-process at base 0e4dfbe2 (resetNamespace returned, the stored allowed_ip_ranges was read back once the env was removed) · evidence: resetNamespace builds its null patch from keys whose resolved source is global/tenant/user, and a pinned key resolves env · dedupe words: resetNamespace env locked skipped, reset action pinned key stored row, restore defaults env override",
"carrier: seat (domain:ui) · the console cannot issue the clear: SettingsField disables a locked field and hides Reset to default on it; and the env answer's cascadeChain carries only the env entry, so the console cannot tell whether a stored value lies beneath the pin (a server read to expose it would be the ui card's dependency) · noted, not filed",
"carrier: #30312 · nothing new learned: an env-pinned clear re-runs applySettings with source env, so it never reaches the default-sourced skip #30312 names · noted, not filed"
]
}
Activity
objectstack-fleet commentedon Nov 5, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p1 ·
domain:serverkept ·pm:queue· 2026-11-05T09:02Z⛔ Not a claim.
OS_AUTH_ALLOWED_IP_RANGES) cannot clear the stored list. Removing the env var restores the stored list, so the admin is locked out again. A lockout whose documented recovery does not work, so p1, as Authentication settings: turning off email and password with no other sign-in method saves without warning, and everyone is locked out #30202.findingleaves the card now that it is graded.d1d90b70) and the docs card Docs: authentication page should state that an allowed IP ranges save excluding your own address is refused, and how to recover with OS_AUTH_ALLOWED_IP_RANGES #30314, which must state the working recovery.Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL
Claim: PM loop round 6
Session:
session_01Utp6Cbvcmv8YWFhFJwzjKL(seat) · dev threadsession_01Utp6CZjAtpVuoYZBzB9n5iAccount:
os-billBranch:
claude/project-thread-3koeqm(harness-pinned thread branch; platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-30385Domain:
domain:serverSeat:
domain:server#2File surface:
packages/services/service-settings/src/settings-service.ts(the env-lock branch only),settings-routes.ts/settings-service.types.tsonly as needed, tests;packages/plugins/plugin-auth/src/allowed-ip-ranges.ts(refusal copy) and its locale strings;content/docs/permissions/authentication.mdxanddocs/HARDENING.md(the recovery sentence only); one changeset.changeset/30385-clear-env-locked-setting.md. ⛔packages/spec/**,ui/**,apps/**,skills/**,docs/adr/**,.claude/**,content/docs/releases/**(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 clear of an env-pinned key that was refused 409 is stored; the dev re-declares
noif neither the published service-settings surface nor its documented error contract moves; reviewed atCONTRACT_REVIEW_TIEReither wayMetadata-protocol: none
Responsibility: an env override in force locks the key against every write, clears included, so the stored allowed-IP list survives the documented env recovery | the env override and the Phase-2 row locks themselves (unchanged) | every administrator recovering from an allowed-IP lockout
Thread-read: 6653992606
Premise: read, to be measured first — at
0e4dfbe2:settings-service.ts~:2406 throwsSettingsLockedErrorwhenevereffectiveEnvOverrideis in force, for any patch value;settings-routes.ts~:379 answers409 SETTINGS_LOCKEDSerial constraints cleared: sibling seat 3 yielded this card (takes #30370, no overlap); this seat's #30312 queued behind it (same
applySettingsarea); PR #30392 (auth-manager.ts, queued) out of surface; PR #30382 (docs) checked before the same lines; pullmainbefore PR and before landing.Order: project file
server-seat2/dispatch-30385.md, delivered as the card thread's opening message.objectstack-fleet commentedon Nov 5, 2026
domain:server#2 · os-bill · session_01Utp6CZjAtpVuoYZBzB9n5i
os-dev-report
{ "issue": 30385, "status": "done", "branch": "claude/project-thread-3koeqm", "pr": "https://github.com/objectstack-ai/objectstack/pull/30448", "session": "session_01Utp6CZjAtpVuoYZBzB9n5i", "premise_still_valid": true, "summary": "Premise measured at base 0e4dfbe2 on the real AuthPlugin + SettingsService + settings routes: with a stored allow-list refusing ::1, OS_AUTH_ALLOWED_IP_RANGES='' opens the gate, but '' and null through PUT /api/settings/auth both answer 409 SETTINGS_LOCKED (locked-by-env), resetNamespace skips the key (it resolves env), and removing the variable locks ::1 out again at the next boot. Fix (seat ruling, assumption 2): in setMany's pre-flight the env-lock branch now refuses every write but a clear (null/undefined); the clear goes on to the Phase 2 row locks, the platform-rung rule and the write predicates, then nulls the stored row as every clear does, so the key falls back to its default once the override is removed; the answer still reads source env, locked true. Every other write, '' included, stays 409. The allowed-IP refusal's recovery sentence (en, zh-CN, ja-JP, es-ES) now names the four steps that work, through the settings API because the console renders a pinned field read-only. Docs: HARDENING.md Network row, config-resolution.mdx write-error table (seat-widened surface). Depends on an ADR-0007 D6 amendment, which the seat has put to the maintainer.", "tests": "head 173cc44e: service-settings test Test Files 68 passed (68), Tests 1161 passed (1161), typecheck exit 0; plugin-auth test Test Files 224 passed (224), Tests 3955 passed | 10 skipped (3965), typecheck (tsc + examples + check:test-typecheck) exit 0. New pins: settings-service.test.ts top-level describe, 5 cases (clear under a pin: answer env/locked, reset event, default after the pin lifts; undefined is a clear beside an ordinary write; every other write incl. '' refused locked-by-env, batch stores nothing; upper-rung locked row refuses a clear locked-by-global; own-rung locked row refuses a non-platform clear, platform clear goes through); allowed-ip-ranges.pin.test.ts top-level describe, 1 end-to-end case (stored list refuses ::1, env '' admits, '' is 409 with exact details, null is 200 env/locked, remove env + restart admits ::1). Ablations via scripts/ablation-replace.mjs on settings-service.ts (both suites resolve service-settings from src, no build between; each anchor 1 -> 0, blob changed, restore blob == HEAD, git diff HEAD empty): A clear exemption removed -> service-settings 4 failed | 1 passed, plugin-auth pin 1 failed | 9 passed; B every write through the pin -> 1 failed | 4 passed, pin 1 failed | 9 passed; C clear skips Phase 2 locks -> 2 failed | 3 passed, pin 10 passed.", "gates": "head 173cc44e: dispatch-gates --commands derived 92; each run with its exit captured before any pipe; all 92 exit 0; --ran: '92 derived famil(ies) accounted for — 92 run, 0 NOT-MEASURED'. First round (head c1c43e27) had check:dual-build-cjs-loads and check-plugin-teardown-shape --self-test at exit 3 (no whole-workspace build; shallow checkout); after building the dogfood closure and fetching 621a4876 both exit 0. pnpm lint exit 0. Changeset gates: check-adr-0087-registration, check-empty-changeset, check-changeset-no-major exit 0. CI check-runs: in_progress at report time.", "dogfood_battery": "173cc44e · os-verify-lock VERDICT command-exit 0 · Test Files 419 passed | 3 skipped (422) · Tests 3492 passed | 36 skipped (3528) · no failures (run because the order asks it; os-dev owes it only on a ! title or a narrowing arm)", "line_budget": "8 files, +214 / -12 vs merge base (226 changed lines, dispatch-gates count); under the 3000 human-merge threshold", "files_changed": [ ".changeset/30385-clear-env-locked-setting.md", "content/docs/protocol/kernel/config-resolution.mdx", "docs/HARDENING.md", "packages/plugins/plugin-auth/src/allowed-ip-ranges.pin.test.ts", "packages/plugins/plugin-auth/src/allowed-ip-ranges.ts", "packages/services/service-settings/src/settings-service.test.ts", "packages/services/service-settings/src/settings-service.ts", "packages/services/service-settings/src/settings-service.types.ts" ], "deviations": [ "content/docs/protocol/kernel/config-resolution.mdx (one table row) added to the file surface by the seat in the card thread: the row said an override in force always refuses a write, which this change makes false.", "content/docs/permissions/authentication.mdx not edited: PR #30382 (open, another lane) adds the recovery subsection on the same lines; seat ruling: whichever of the two lands second rewrites its last bullet to the four steps.", "settings-service.ts edits beyond the env-lock branch are doc comments only: set() and setMany() docblocks (the documented error contract); resetNamespace's docblock left as it was, per the seat (seat 4's card edits nearby functions).", "Full dogfood and pnpm lint run locally because the order asks; os-dev assigns the first to ! / narrowing PRs and the second to CI.", "Commit trailers are the model-free pair AGENTS.md prescribes, not the harness's model-named Co-Authored-By line." ], "mcp_calls": "9 — mcp__github__issue_read x3 (get #30385, get_comments #30385 x2), mcp__github__issue_read get_comments #30203, mcp__github__pull_request_read x5 (get #30309, get_comments #30309, get #30382, get_diff #30382, get #30448); no write tool", "api_writes": "2 relay writes — pr_create #30448 (fleet-write run 42021780433, body read back identical), this os-dev-report comment on #30385 (post-stamped); plus git pushes to the designated branch", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "class: a · reach: POST /api/settings/auth/reset (built-in reset action, resetNamespace) answers 'Cleared N saved value(s); environment/default configuration is back in effect' while a row stored beneath an OS_* pin survives and applies again when the variable is removed; measured in-process at base 0e4dfbe2 (resetNamespace returned, the stored allowed_ip_ranges was read back once the env was removed) · evidence: resetNamespace builds its null patch from keys whose resolved source is global/tenant/user, and a pinned key resolves env · dedupe words: resetNamespace env locked skipped, reset action pinned key stored row, restore defaults env override", "carrier: seat (domain:ui) · the console cannot issue the clear: SettingsField disables a locked field and hides Reset to default on it; and the env answer's cascadeChain carries only the env entry, so the console cannot tell whether a stored value lies beneath the pin (a server read to expose it would be the ui card's dependency) · noted, not filed", "carrier: #30312 · nothing new learned: an env-pinned clear re-runs applySettings with source env, so it never reaches the default-sourced skip #30312 names · noted, not filed" ] }Report taken 2026-11-05T11:49Z.