Repository navigation
Commit 7d7babb
docs(permissions): state the allowed_ip_ranges save refusal, the live clear and the env recovery (#30382)
<!-- claude-projects-attribution: {"github_login":"marchtian"} -->
_Requested by **田騏** · [project
thread](https://claude.ai/code/project/chan_01VmuUhXU9SSUzqJYyXSmYSi?thread=cmsg_01VmuUhXU9SSUzqJYyXSmYSiNjyvCdXYygFqntMNR7S2dW)_
Fixes #30314
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:认证文档的 `allowed_ip_ranges`
一行加了一句话并链到一个短小节,写明三件事:保存一份不匹配自己当前地址的列表会被拒(400
`SETTINGS_VALIDATION`,提示里写明你的地址和救援办法);清空字段立即生效、回到部署自己声明的列表;已被锁在外面时把
`OS_AUTH_ALLOWED_IP_RANGES` 设为空值并重启。
- 为什么改:PR #30309 已经上线了这套行为,但文档一个字没提;管理员读文档时不知道保存会被拒、也不知道被锁后怎么进来。
- 风险与回滚:只改一页文档,不动代码;回滚即撤本 PR。
- 先看:`content/docs/permissions/authentication.mdx` 的 Network
表格那一行,以及紧跟在“How enforcement works”引用块后面的新小节 “Saving, clearing and
lockout recovery”。
- 两分钟验证:`pnpm check:doc-anchors`,预期 exit 0(行内链接指向的新小节锚点存在)。
- 需要你做的:无,席位落地。
## What changes
`content/docs/permissions/authentication.mdx` only. The
`allowed_ip_ranges` row gains one sentence that links a new short
subsection, **Saving, clearing and lockout recovery**, under
**Network**. Each statement was read from the code merged by PR #30309
(at `fdad1786e8`), not from its description:
- **The save refusal.** The settings save route `PUT
/api/settings/:namespace`
(`packages/services/service-settings/src/settings-routes.ts:320`, called
by the console at `ui/apps/console/src/pages/settings/api.ts:172`) hands
the request's peer address and headers to `setMany`
(`settings-routes.ts:354-368`). The write predicate registered for
`auth.allowed_ip_ranges`
(`packages/plugins/plugin-auth/src/auth-plugin.ts:2551-2556`) resolves
the caller's address with the auth manager's own `resolveClientAddress`
and matches it with the gate's own `ipMatchesRange`
(`packages/plugins/plugin-auth/src/allowed-ip-ranges.ts:148-152`). A
refusal becomes `SettingsValidationError`
(`packages/services/service-settings/src/settings-service.ts:2722`),
answered `400 SETTINGS_VALIDATION` with `details.fields`
(`settings-routes.ts:397`), field code `invalid_value` and
`constraint.clientAddress`. The message is the copy at
`allowed-ip-ranges.ts:85-123`, in the caller's language, ending with the
recovery sentence (`allowed-ip-ranges.ts:93`).
- **Clearing.** A cleared value resolves with source `default`;
`applySettings` then puts back the deployment's declared list, or none
(`auth-plugin.ts:2328-2334`). That list is captured after the
`auth:configure` hook (`auth-plugin.ts:750`, `:792`). The pass re-runs
on every auth settings change (`auth-plugin.ts:2569`), so no restart. A
clear is never refused (`allowed-ip-ranges.ts:145`).
- **Recovery.** `effectiveEnvOverride` treats any string as an override,
the empty string included (`settings-service.ts:1378-1379`); the key has
no default, so `coerceEnvValue` keeps `''`
(`settings-service.ts:3843-3857`,
`packages/services/service-settings/src/manifests/auth.manifest.ts:366-372`).
The value is then `source: 'env'`, so `isExplicit` holds and
`parseAllowedIpRanges('')` gives no list (`auth-plugin.ts:2094`,
`:2328-2329`). An override in force also locks the key against writes
(`settings-service.ts:2406-2408`, answered `409 SETTINGS_LOCKED` at
`settings-routes.ts:378-379`).
Where the code says more than, or something other than, the card's
Wanted lines:
1. **Wanted 1 says "leaves out" the caller's address; the code refuses a
list in which no entry *matches* it.** `ipMatchesRange` matches IPv4
CIDR and exact addresses only, so the page says "no entry matches". It
also states two cases the Wanted line does not: when the server cannot
establish the caller's address, any non-empty list is refused
(`allowed-ip-ranges.ts:152-156`); and only a change is judged, so saving
the list already in force again is not refused
(`settings-service.ts:2695`). Writes inside the server carry no request
and are not judged (`allowed-ip-ranges.ts:143`).
2. **The door is the settings API, not the console alone.** The check
sits on `PUT /api/settings/auth`. The console calls it, and so can any
HTTP client, which gets the same refusal.
3. **Wanted 3's recovery is not a permanent fix by itself.** While
`OS_AUTH_ALLOWED_IP_RANGES` is set, the stored list is kept but not
used, and a save to the field is refused with `409 SETTINGS_LOCKED`.
When the variable is removed, the stored list applies again at the next
restart. The page states this as it is.
## Tests
None: a docs-only change. No code, test or i18n file is touched. No
changeset: `content/docs/` ships in no published package, so this PR
carries `skip-changeset`.
## Acceptance notes
- **Placement.** The order asked for the subsection right after the
table. The table is followed by the hardening section's own "How
enforcement works" blockquote, which is about password expiry and MFA. A
heading placed before it would have put that blockquote under the new
heading. So the subsection sits right after the blockquote, still under
**Network**, and the row links to it.
- **No console path to clear a stored list while the env override is
set.** Follows from point 3 above: an admin who recovers with the empty
variable cannot then clear the stored list in the console, because the
key is locked. Removing the variable brings the list back. Named for the
seat; not filed.
- **"Changes take effect immediately" in the section intro.** The
hardening intro says every toggle applies without a restart. PR #30309's
acceptance notes measured that clearing `lockout_threshold` or
`max_concurrent_sessions_per_user` keeps the last stored value until a
restart. That family is the seat's (named there); this PR does not touch
the intro.
- **`check:liveness` is red on this tree, from `main`.** `pnpm --filter
@objectstack/spec run check:liveness` exits 1: the ledger entry
`query/searchFields` cites `packages/objectql/src/engine.ts`, which no
longer names the key after f672e0d moved its docblocks out. This diff
does not touch that file or the ledger.
## Verification
<details><summary>Derived gates: 45 derived, 45 run, 44 exit 0, 1 exit 1
(from main)</summary>
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`: 45 commands for the one changed path, at
head `e4fc7bccf3`.
- Each ran with its exit code captured before any pipe. 44 exit 0.
- First pass, 5 refused with `PREREQUISITE NOT MET` (exit 3:
`check:doc-formula-expressions`, `check:doc-security-posture`,
`check:skill-examples`, `check:docs-transcript-drift`) or a missing
`packages/spec/json-schema` (`check:docs`, exit 1). After building
`@objectstack/spec`, `@objectstack/formula`, `@objectstack/lint`, and
the `@objectstack/client` / `@objectstack/client-react` closures under
the verify lock (`VERDICT command-exit 0` both times), all five re-ran
to exit 0.
- `pnpm --filter @objectstack/spec run check:liveness`: exit 1, the
`main` finding above.
- `--ran` reconciliation: "45 derived famil(ies) accounted for — 45 run,
0 NOT-MEASURED".
- `git merge origin/main`: already up to date (`origin/main` =
`fdad1786e8`).
- NOT MEASURED here, CI's own: the `Build Docs` and `Plan Test Core
shards` jobs the paths schedule.
</details>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>1 parent dbf53ef commit 7d7babb
1 file changed
Lines changed: 34 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
619 | 619 | | |
620 | 620 | | |
621 | 621 | | |
622 | | - | |
| 622 | + | |
623 | 623 | | |
624 | 624 | | |
625 | 625 | | |
| |||
650 | 650 | | |
651 | 651 | | |
652 | 652 | | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
| 659 | + | |
| 660 | + | |
| 661 | + | |
| 662 | + | |
| 663 | + | |
| 664 | + | |
| 665 | + | |
| 666 | + | |
| 667 | + | |
| 668 | + | |
| 669 | + | |
| 670 | + | |
| 671 | + | |
| 672 | + | |
| 673 | + | |
| 674 | + | |
| 675 | + | |
| 676 | + | |
| 677 | + | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
653 | 686 | | |
654 | 687 | | |
655 | 688 | | |
| |||
0 commit comments