docs(security): narrow x-no-account-id guidance and JWT notes

This commit is contained in:
Scott Idem
2026-05-01 13:59:07 -04:00
parent d5e5cb7ada
commit 19822c4eaf
4 changed files with 34 additions and 2 deletions

View File

@@ -86,6 +86,18 @@ site_access_code_kv: {
}
```
### `x-no-account-id` — Narrow Transport Exception
`x-no-account-id` is a transport-level escape hatch that strips account context before the request leaves the frontend. It is not a permission grant and it is not a replacement for JWT or `x-account-id`.
Use it only when the request truly cannot be made account-scoped. Current legitimate cases should stay narrow:
1. Bootstrap / site-domain discovery before the account is known.
2. Explicit public or guest endpoints that do not have an account context.
3. Helper paths that intentionally need a global-default fallback.
If a request already has a valid account context, prefer `x-account-id` and let the JWT carry session identity. Treat any new `x-no-account-id` use as temporary until it is reviewed and either replaced or justified.
---
## Utility Functions
@@ -154,6 +166,11 @@ Returns `1` if `level_a` is higher, `-1` if lower, `0` if equal. Useful for thre
- Security model: API key is one layer; JWT + `x-account-id` scoping provides the primary auth.
- Do not introduce new usages. Prefer `PUBLIC_AE_BOOTSTRAP_KEY` for unauthenticated lookups.
### JWT usage guidance
- JWTs are the preferred proof of an established session. Keep them attached to authenticated flows instead of leaning on transport-level bypasses.
- If a route or helper can work with a JWT and an account ID, it should not need `x-no-account-id`.
- If a helper still needs the bypass today, document the reason and add a removal target.
### Email Display
Non-trusted users must never see a full email address. Obscure using:
```typescript

View File

@@ -29,7 +29,7 @@ running in Docker. The frontend talks to it exclusively via the V3 REST API.
| Editors | CodeMirror 6 (primary), Edra/TipTap (secondary) |
| Native | Electron app for onsite launcher (`src/lib/electron/electron_relay.ts`) |
| Backend | FastAPI + MariaDB, V3 API (`/v3/crud/`, `/v3/lookup/`) |
| Auth | Custom headers: `x-aether-api-key` + `x-account-id` (no Bearer tokens) |
| Auth | Custom headers: `x-aether-api-key` + `x-account-id`; JWT Bearer is auto-injected when a session exists |
---
@@ -294,7 +294,9 @@ These are real incidents — know them before you start.
10. **Using query `key` as a proxy for bypass stripped `x-account-id`** — this caused
valid account-scoped requests to lose account context and 403. `key` can be a valid
endpoint/business param, but it is not equivalent to `x-no-account-id: bypass`.
endpoint/business param, but it is not equivalent to `x-no-account-id: bypass`. Keep
`x-no-account-id` usage narrow and temporary; do not expand it without a documented
allowlist case.
11. **Pre-stringifying `*_json` fields before passing to API wrappers** — the API wrappers
(`api_post__crud_obj.ts` for V3, `api.ts` for legacy CRUD) automatically serialize any

View File

@@ -19,12 +19,16 @@ Required for any non-public data (Journals, Badges, Users, etc.).
* **Header:** `x-account-id: <account_id>`
2. **Administrative Bypass**: For authorized scripts needing global access.
* **Header:** `x-no-account-id: bypass`
* **Scope:** Narrow escape hatch only. Keep it limited to allowlisted bootstrap/public/global-default paths and prefer `x-account-id` or JWT-backed requests everywhere else.
3. **Token Access**: Provide a **JWT** in the query string.
* **Query Param:** `?jwt=<token>`
4. **Important Distinction:** A query parameter named `key` is **not** an account-context bypass signal.
* `key` may be used by specific endpoints/business logic, but it must **not** cause the frontend to remove `x-account-id`.
* Only explicit `x-no-account-id: bypass` should strip account context.
> [!NOTE]
> The `x-no-account-id` path should continue to shrink over time. If you need a new use, document why `x-account-id` or JWT cannot cover it and mark the use as temporary unless it is a hard bootstrap/global-default requirement.
> [!CAUTION]
> **UNSUPPORTED HEADERS:** The header `x-aether-api-token` is **NOT recognized** by the V3 API. If you send it, the backend will treat you as a guest and block access to private data.

View File

@@ -83,6 +83,15 @@ This gives session expiry without a network call on every page load.
**Note:** The backend fixes described below have been implemented and tested in the `aether_api_fastapi` repository (the `/authenticate_passcode` endpoint now uses explicit role priority, returns a full passcode JWT with `auth_type: 'passcode'`, applies per-role TTLs, and validates passcode length). Frontend changes can proceed once the backend deployment with these fixes is available.
### Backend Agent Follow-Up
If the backend team revisits this area, keep the next round focused on narrowing escape hatches rather than adding new ones:
1. Audit every `x-no-account-id` use and decide whether it is still required for bootstrap, public delivery, or a global-default fallback.
2. Prefer JWT-backed auth once a session exists; do not add new transport-level bypass paths for authenticated UI flows.
3. Mark any remaining bypass-only helper as temporary and add a removal target.
4. Plan the eventual removal of `access_code_kv_json` from public bootstrap payloads once passcode auth is fully deployed.
**Phase 2 status:** Not started — removing `access_code_kv_json` from the public site model remains pending.
**File:** `aether_api_fastapi/app/routers/api.py`