Skip to content

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

Closed

Description

@objectstack-fleet

domain:devx#2 · marchtian · session_017ihL2ebbZkr2QAEG1x7UC8

Filing gate: ② found while documenting #30314 (PR #30382) from PR #30309's merged code at origin/main fdad1786e8. 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

What the code does

  • The localized refusal message 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 names the recovery: start the server with OS_AUTH_ALLOWED_IP_RANGES set empty. Any string value is an env override in force, so the gate uses no restriction. Evidence: settings-service.ts effectiveEnvOverride (~1372–1380, typeof envRaw !== 'string' → null), allowed-ip-ranges.ts parseAllowedIpRanges('') → [].
  • 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.
  • The recovery text in the refusal message and on content/docs/permissions/authentication.mdx (after PR docs(permissions): state the allowed_ip_ranges save refusal, the live clear and the env recovery #30382) matches the shape chosen.

Links: #30203, PR #30309, #30314, PR #30382.

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Nov 5, 2026
  2. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: first-touch audit · p1 · domain:server kept · pm:queue · 2026-11-05T09:02Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    priority:p1High: required for production / M2
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    pm:queueReady for the PM dispatch loop
    on Nov 5, 2026
  4. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL

    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.

  5. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    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); round 2 (contract review 6657453112, head 5c0cbbfb): service-settings README Env override (every write but a clear is 409; a clear removes the stored value and changes nothing in force) and the authentication.mdx 'If you are already locked out' bullet, rewritten to the four steps. 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 5c0cbbfb (round 2, docs only): dispatch-gates --commands derived 93 for the 10 changed paths; each run with its exit captured before any pipe; all 93 exit 0; --ran: '93 derived famil(ies) accounted for — 93 run, 0 NOT-MEASURED'. Run on commit b4b507a9; the pushed 5c0cbbfb carries the same tree af8723c4, its message reworded only because the pre-push check:commit-card-trailers refused a card trailer. 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": "head 5c0cbbfb: 10 files, +238 / -24 vs merge base 09720e2c (262 changed lines); under the 3000 human-merge threshold (173cc44e: 8 files, +214 / -12)",
      "files_changed": [
        ".changeset/30385-clear-env-locked-setting.md",
        "content/docs/permissions/authentication.mdx",
        "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/README.md",
        "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 edited (round 2, head 5c0cbbfb): PR #30382 merged first (2026-11-05T10:10Z, 7d7babb1), so by the seat ruling (whichever lands second rewrites it) this PR rewrote the subsection's 'If you are already locked out' bullet to the four steps. Corrected: round 1 said #30382 was open and left the page untouched, which was stale on the head's own tree (contract review 6657453112 item 9).",
        "Round 2 is one docs-only push for the contract review's two must-fixes (README Env override, authentication.mdx). It changes no code, so the suites, typecheck, lint and dogfood were not re-run on 5c0cbbfb; the derived gates were. The branch was not re-merged with origin/main for it (dispatch-gates warns the tree is 21 commits behind; the stale family files are i18n and the dispatch-gates self-test, none of which reads these docs).",
        "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": "12 — round 2: mcp__github__issue_read get_comments #30448 (the contract review), mcp__github__pull_request_read get #30448 (the stored body), mcp__github__issue_read get_comments #30385 (this report as stored); round 1: 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": "4 relay writes — pr_create #30448 (fleet-write run 42021780433, body read back identical), this os-dev-report comment on #30385 (post-stamped), round 2: issue_patch #30448 body (fleet-write run 42028839823, 12405 bytes read back identical), comment_edit of this report (this edit); 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; corrected 2026-11-05T12:38Z (round 2, head 5c0cbbf).


    Generated by Claude Code

  6. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL

    Ruling confirmed: letter A · ADR-0007 D6 · executed by domain:server#2.

    Maintainer: 「同意」 to the recommendation, in the decision thread, 2026-11-05T21:34Z (presentation in project files decisions/30448-ADR-0007-D6-env锁定键允许清除-呈批.md).

    What A binds.

    1. PR fix(service-settings): a clear goes through an env pin, so the allowed-IP lockout recovery survives removing the variable #30448 is ratified in its present shape: a clear (null, and undefined at the service layer) through set / setMany / PUT is no longer refused on an env-pinned key; it sets the stored value to null (wording: "clears the stored value", never "deletes the row"). A non-clear write (an empty string included) is still refused 409 locked-by-env. The badge and the audit row do not change.
    2. resetNamespace and the built-in reset action are not touched here. That a reset says the key is back on env/default while a stored value stays beneath the pin is a separate p3 card; it does not block this one.
    3. ADR-0007 gets a one-line amendment in its own PR (Tier H; reviewers os-zhuang and hotlong): the Status line gains Amended (2026-11-05, #30385 — D6: a clear (null) of an env-pinned key is not refused; it clears the stored value beneath the override and changes nothing in force), the D6 sentence and the REST table each gain the same half-sentence, with its scripts/adr-anchors/ anchor file.
    4. Order: the ADR PR merges first, or in the same batch as fix(service-settings): a clear goes through an env pin, so the allowed-IP lockout recovery survives removing the variable #30448; the code PR does not land before the ADR. fix(service-settings): a clear goes through an env pin, so the allowed-IP lockout recovery survives removing the variable #30448 touches no governed path, is not BREAKING (262 lines); once the ADR merges, the seat ACCEPTs, readies and queues it per the skill, merging main once more before landing.
    5. The ui half (a clear control on a locked field, which needs the server to say whether a stored value sits beneath the pin) is a separate domain:ui card, Blocked-by: #30385.

    State change: none; the card stays pm:dispatched with the seat until #30448 lands.

  7. added
    pm:blockingDerived cache from the Blocked-by reverse index: open card with open dependents — never hand-set
    on Nov 5, 2026
  8. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL

    Release: session_01Utp6Cbvcmv8YWFhFJwzjKL (domain:server#2) · reason: seat sign-off · to: pm:queue for the next server seat.
    Maintainer: 「开发中的尽快收工」, project chat, 2026-11-05T22:01Z.
    Resume-from: claude/project-thread-3koeqm@4153daa426dfe5c6c5413c815e56b97e4bc6de53

    State at release. The code PR is #30448, draft at that head, mergeable clean. It stays in the shape ruling 6670154789 ratifies (letter A): a clear (null) of an env-pinned key is let through, and resetNamespace / the reset action are not touched. The maintainer's later 「同意」 in another thread has been read as possibly covering reset as well. Unless the maintainer rules otherwise, the reset half stays on #30664; if the maintainer answers A+, the next seat widens #30664, not #30448.

    Owed, in order:

    1. The ADR-0007 D6 one-line amendment PR, per item 3 of the ruling (Tier H; reviewers os-zhuang and hotlong).
    2. When it merges (or in the same batch), merge main into fix(service-settings): a clear goes through an env pin, so the allowed-IP lockout recovery survives removing the variable #30448, ACCEPT it, and queue it per the skill. It touches no governed path and is not BREAKING.
    3. File the domain:ui clear-control card with Blocked-by: #30385 (item 5), then land this card.
  9. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Nov 5, 2026
  10. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 5, 2026
  11. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL

    Claim: re-claim after Release 6670837769, on the maintainer's order that this seat takes the card back and finishes it before signing off.
    Maintainer: 「ADR-0007 D6 修订 PR(Tier H)先合,哪个任务你也接手处理完再下班」, seat thread, 2026-11-05T22:22Z.
    Session: session_01Utp6Cbvcmv8YWFhFJwzjKL (seat) · ADR dev thread session_01Utp6CaQzHSoRvseiSpfmLn
    Account: os-bill
    Branch: claude/project-thread-myd9f0 (the ADR PR; harness-pinned thread branch) · code PR #30448 stays on claude/project-thread-3koeqm@4153daa4
    Domain: domain:server
    Seat: domain:server#2
    File surface (ADR PR): docs/adr/0007-settings-manifest-and-kv-store.md (Status line, D6 sentence, REST-table row, per ruling 6670154789 item 3); optionally one scripts/adr-anchors/ file if a gate asks. ⛔ everything else.
    Clause-②: no (records behaviour #30448 ships) · Metadata-protocol: none · Tier H: reviewers os-zhuang and hotlong
    Order: the ADR PR merges first; then #30448 merges main, gets the seat ACCEPT and is queued. The reset half stays on #30664.
    Thread-read: 6670837769

    Order file: project files server-seat2/dispatch-30385-adr.md.

  12. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL
    os-dev-report

    {
      "issue": 30385,
      "status": "done",
      "branch": "claude/project-thread-myd9f0",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/30698",
      "session": "session_01Utp6CaQzHSoRvseiSpfmLn",
      "premise_still_valid": true,
      "summary": "ADR-0007 D6 amendment (Tier H, docs only) per ruling A (6670154789). Behaviour re-read at PR #30448 head: settings-service.ts pre-flight `const clears = patch[key] === null || typeof patch[key] === 'undefined'; if (!clears && this.effectiveEnvOverride(...)) throw new SettingsLockedError(...)`. Three edits in docs/adr/0007-settings-manifest-and-kv-store.md exactly as ordered: Status line Amended (2026-11-05, #30385); D6 exception sentence with the superseded wording kept inline; REST table PUT row `except a null clear`. Nothing else in the ADR touched (resetNamespace wording, D-tiers, env prefixes out of scope).",
      "tests": "n/a — docs only, no code.",
      "gates": "dispatch-gates --commands on 26da6f27: 21 commands, each run with exit captured before any pipe; --ran: '21 derived, 21 run, 0 NOT-MEASURED, 0 UNRUN'. @objectstack/lint check:doc-formula-expressions first refused exit 3 (formula/lint dist missing); built per its own Fix line, re-ran exit 0.",
      "adr_anchor": "none added — scripts/adr-anchors/ holds no anchor for packages/services/service-settings/src/settings-service.ts, `pnpm check:adr-anchors` exit 0 without one and no derived gate asks for it.",
      "dogfood_battery": "n/a — docs only, no `!` in the title",
      "line_budget": "6 changed lines (+3/-3), 1 file",
      "files_changed": [
        "docs/adr/0007-settings-manifest-and-kv-store.md"
      ],
      "deviations": [
        "Dispatch line numbers L3/L101/L323 matched origin/main a2437b56 exactly.",
        "Opened draft (relay default; read back draft=true). Reviewers not requested (seat requests os-zhuang + hotlong); no assignee.",
        "No changeset: `skip-changeset` label rode the pr_create."
      ],
      "mcp_calls": "0",
      "api_writes": "2 — relay pr_create (objectstack-ai/objectstack#30698, run 42123245651); post-stamped comment on #30385 (this report). Plus git push (not a REST write).",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": []
    }

    Report taken 2026-11-05T22:39Z.

  13. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL

    ACCEPT → PR #30448 comment 6674525169 (head 034aee8f), queued after ADR-0007 D6 amendment #30698 merged. The ui half is #30665 (Blocked-by: #30385); the reset half is #30664.

  14. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL

    Landed · 2026-11-06T02:23Z

    Reading Value
    PR #30698 ADR-0007 D6 amendment (Tier H), merged 2026-11-06T01:13Z by os-zhuang. Contract review PASS 6671549520, delta PASS 6671964898, seat ACCEPT 6671978841
    PR #30448 merged 2026-11-06T02:22Z through the merge queue, merge commit 2445cb49, reviewed head 034aee8f (seat ACCEPT 6674525169). Clause-②: yes (widening)
    #30385 letter A delivered and the card closed by Fixes #30385. A clear (null) of an env-pinned setting is no longer refused: it nulls the stored value beneath the pin and changes nothing in force. Every other write is still 409. The allowed-IP lockout recovery now survives removing the variable (four steps in the refusal copy and docs).
    Next #30665 (ui clear control, Blocked-by: #30385, now unblocked); #30664 (reset leaves a stored value beneath the pin)
    Release rides the next release by its changeset

    State change: pm:dispatched removed, assignee cleared.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpm:blockingDerived cache from the Blocked-by reverse index: open card with open dependents — never hand-setpriority:p1High: required for production / M2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions