Skip to content

Allowed IP ranges: an IPv6 CIDR entry never matches at the sign-in gate or the save check (matcher reads IPv4 CIDR and exact addresses only), while the setting promises CIDR ranges #30320

Closed

Description

@objectstack-fleet

domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL

Filing gate: ② found by the contract review of PR #30309 (card #30203, record 6650936040), outside that card's door. Read, not measured — the first act is to measure it. ⛔ Not a claim. Triage sets the grade.
Reader: domain:server, plugin-auth (auth-manager.ts ipMatchesRange, isClientIpAllowed), the allowed_ip_ranges help text in service-settings auth.manifest.ts.
Dedupe: ipv6 cidr → none open. New.

What the reading says

ipMatchesRange matches an IPv4 CIDR and an exact address (IPv4-mapped addresses folded). An IPv6 CIDR entry such as 2001:db8::/32 falls back to exact match and so never matches a real client. The setting's help text promises "CIDR ranges" unqualified. Effect: an admin who allows an IPv6 range locks out every IPv6 client in it at the sign-in gate; after PR #30309 the save check refuses that save for an IPv6 admin (fail-safe), with copy that says the list does not include an address it textually covers.

Wanted

  • Measure: an IPv6 client inside an allowed IPv6 CIDR, at the gate and at the save.
  • Either match IPv6 CIDR in ipMatchesRange (both doors share it), or refuse an IPv6 CIDR entry at save with a clear message and say "IPv4 CIDR" in the help text.
  • Pin both families.

Links: #30203, PR #30309.

Activity

  1. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: first-touch audit · p2 · domain:server · pm:queue · 2026-11-05T07:05Z

    ⛔ 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 4
    Session: session_01Utp6Cbvcmv8YWFhFJwzjKL (seat) · dev thread session_01Utp6CXwcgsAkqebfzjnfiS
    Account: os-bill
    Branch: claude/project-thread-g6bjfy (harness-pinned thread branch; platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-30320
    Domain: domain:server
    Seat: domain:server#2
    File surface: packages/plugins/plugin-auth/src/auth-manager.ts (ipMatchesRange and its helpers only, or a small module it imports), tests, possibly packages/services/service-settings/src/manifests/auth.manifest.ts (help text, only on the refuse-at-save route), one changeset .changeset/30320-ipv6-cidr-allowed-ranges.md. ⛔ packages/spec/**, ui/**, apps/**, skills/**, docs/adr/**, content/docs/**, .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: high
    Clause-②: no expected — matching an IPv6 CIDR accepts clients a stored list already names; the dev re-declares yes for a new export, (narrowing) for a save-time refusal of an entry stored before
    Metadata-protocol: none
    Responsibility: ipMatchesRange matches IPv4 CIDR and exact addresses only, so an IPv6 CIDR entry never matches at the sign-in gate or the save check | the gate and the save check themselves (#30203, unchanged) | every deployment that allows IPv6 ranges
    Thread-read: 6651976799
    Premise: holds — read at d1d90b70: ipMatchesRange (:1194) falls back to exact match for a non-IPv4 range; callers are isClientIpAllowed (:5905), allowed-ip-ranges.ts (:152) and the plain-HTTP eligibility list (:558)
    Serial constraints cleared: this seat's #30202 (PR #30298) and #30302 edit auth-plugin.ts applySettings and the settings predicates; this card keeps to the matcher; pull main before PR and before landing. Sibling seats: none on plugin-auth.

    Order: project file server-seat2/dispatch-30320.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_01Utp6CXwcgsAkqebfzjnfiS

    os-dev-report

    {
      "issue": 30320,
      "status": "done",
      "branch": "claude/project-thread-g6bjfy",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/30392",
      "session": "session_01Utp6CXwcgsAkqebfzjnfiS",
      "premise_still_valid": true,
      "summary": "Premise measured at d1d90b70: with the new pins, 6 failed | 7 passed, both doors included (the sign-in gate refused an IPv6 client inside an allowed IPv6 CIDR, and a settings save from inside it was refused). ipMatchesRange now reads both sides as the address they denote (IPv4 as uint32, other IPv6 as eight groups through the file's existing parseIpv6Literal; IPv4-mapped dotted or hex folds to IPv4; zone id dropped) and compares the first n of 128 bits for an IPv6 range; exact IPv6 entries compare by value. Families never cross. An IPv6 prefix outside 0..128 whole numbers leaves the entry matching only its base, as the old text fallback did; IPv4 entries keep their exact earlier reading, so no IPv4 list changes what it admits. Both doors call the one matcher. Node net.BlockList measured and rejected (it matches across families).",
      "tests": "New: 5 cases in auth-manager.test.ts (IP allow-list), 2 door cases in allowed-ip-ranges.pin.test.ts (real AuthPlugin + SettingsService + settings routes). pnpm --filter @objectstack/plugin-auth test: Test Files 222 passed (222), Tests 3925 passed | 10 skipped (3935) at d9855b3c and again at aa57f6dd after rebuilding the closure; typecheck exit 0. Ablations via scripts/ablation-replace.mjs (tests import src; no build between legs): A1 IPv4-branch family check dropped -> 1 failed; A2 IPv6-branch family check admits IPv4 -> 3 failed; A3 IPv6 prefix read with Number() -> 1 failed; A4 prefix compare skipped -> 3 failed; each mutation landed (anchor 1 -> 0, blob changed), each restore blob == HEAD (e83cb87f9db6) and git diff HEAD empty.",
      "gates": "head aa57f6dd (main fdad1786 merged, no overlap with plugin-auth/service-settings/spec): dispatch-gates --commands derived 62; each run with exit captured before any pipe; --ran: '62 derived famil(ies) accounted for — 62 run, 0 NOT-MEASURED'; all exit 0. Also run: check:authz-resolver, check:changeset-fixed, check:error-code-casing, check:filter-alias-parity (roster under a changed directory) and pnpm lint, all exit 0. Earlier run at d9855b3c: check-plugin-teardown-shape --self-test exit 3 on the shallow checkout, exit 0 (48 cases) after fetching 621a4876; dual-build-cjs-loads exit 3 then (no whole-closure build), exit 0 at aa57f6dd. CI check-runs: in_progress at report time.",
      "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
      "line_budget": "5 files, +160 / -29 vs merge base (dispatch-gates count); under the 3000 human-merge threshold",
      "files_changed": [
        ".changeset/30320-ipv6-cidr-allowed-ranges.md",
        "packages/plugins/plugin-auth/src/auth-manager.ts",
        "packages/plugins/plugin-auth/src/auth-manager.test.ts",
        "packages/plugins/plugin-auth/src/allowed-ip-ranges.pin.test.ts",
        "packages/plugins/plugin-auth/src/client-address-canonical.ts"
      ],
      "deviations": [
        "packages/plugins/plugin-auth/src/client-address-canonical.ts: one header sentence only (comment), outside 'ipMatchesRange and helpers': it said the allow-list compares exact IPv6 entries as text, which this change makes false.",
        "Exact IPv6 entries now compare by value (spelling-insensitive) and a hex-form IPv4-mapped address folds to IPv4: both only admit clients the entry already denotes; declared Clause-②: no.",
        "Manifest help text unchanged (preferred route taken; 'CIDR ranges' now holds for both families).",
        "PR body footer: the session-URL line follows the session-context's PR-body block rather than the os-dev underscore footer form."
      ],
      "mcp_calls": "3 — mcp__github__issue_read (get, get_comments x2); no write tool",
      "api_writes": "2 relay writes — pr_create #30392 (fleet-write run 42000624707, 8701 bytes sent = stored), os-dev-report comment on #30320 (post-stamped); plus git pushes to the designated branch",
      "open_questions": [],
      "security_findings": "withheld",
      "out_of_scope_findings": [
        "carrier: seat (docs) · the allowed_ip_ranges help text gives only an IPv4 example (203.0.113.0/24); an IPv6 example would teach the now-working form; changing it moves the translation bundles · noted, not filed"
      ]
    }

    Report taken 2026-11-05T09:14Z.


    Generated by Claude Code

  4. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL

    Landed · 2026-11-05T10:05Z

    Reading Value
    PR #30392 merged 2026-11-05T10:04Z, merge commit 5a878a38, reviewed head aa57f6dd (seat ACCEPT 6654287995)
    Review TIER_DEFAULT, declaration face, seat-written in the ACCEPT (no published schema, no governed path)
    #30320 closed by Fixes #30320
    Release @objectstack/plugin-auth patch, Clause-②: no; next release

    State change: pm:dispatched removed. An IPv6 CIDR in auth.allowed_ip_ranges now admits the IPv6 clients inside it at the sign-in gate and the save check.

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:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions