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

Open

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. 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

  2. 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.

  3. 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). 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.

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpm:dispatchedpriority: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