From 2d01805427a32de9a7a91b6865a5690208975122 Mon Sep 17 00:00:00 2001 From: disqualifier Date: Sun, 28 Jun 2026 17:18:28 -0400 Subject: [PATCH] docs: correct capability-flag threat-model boundary; add detection guidance the docs claimed 'you cannot grant yourself authority without already having it', which is false in the shared-DEK model: a DEK-holder with write access can copy a sealed True flag onto its own doc. replaced with the honest boundary (the flag is unforgeable WITHOUT the DEK, but is not a defense against a malicious DEK-holder, which is out of scope by design) and added operational guidance to detect a self-grant by auditing authorization state. no code or storage-format change. Signed-off-by: disqualifier --- README.md | 45 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 4fd3175..564b924 100644 --- a/README.md +++ b/README.md @@ -13,20 +13,20 @@ authorization system and the key-document schema; the crypto primitives live in ## Install ``` -envelope_authorizer @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_authorizer.git@v0.1.0 +envelope_authorizer @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_authorizer.git@v0.1.1 ``` Direct: ```bash -pip install "envelope_authorizer @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_authorizer.git@v0.1.0" +pip install "envelope_authorizer @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_authorizer.git@v0.1.1" ``` The base install uses a local JSON file for storage (stdlib only). For shared dev→server storage, install the mongo extra: ```bash -pip install "envelope_authorizer[mongo] @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_authorizer.git@v0.1.0" +pip install "envelope_authorizer[mongo] @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_authorizer.git@v0.1.1" ``` Installing pulls `envelope_crypto` (and `mongo` with the extra). After install, @@ -47,12 +47,39 @@ Each key carries an **encrypted capability flag** — `meta.authorizer` = - **Servers** are authorized with `allowed: False` → they can boot and use the DEK (decrypt project data) but **cannot authorize anything else**. -The flag is GCM-sealed under the DEK on purpose: a server **cannot flip its own -flag in the database to escalate**, because forging a valid flag requires already -holding the DEK, and editing the stored ciphertext breaks the auth tag. The -guarantee is precisely: *you cannot grant yourself authority without already -having it.* (A legitimate authorizer can of course mint new flags — that is its -job. This stops passive DB tampering, not an authorizer.) +The flag is GCM-sealed under the DEK on purpose. What that buys, stated honestly: + +- **Without the DEK, the flag is inert.** A stolen database at rest cannot be + escalated — the flags are ciphertext, unforgeable and unflippable without the key + (editing the stored ciphertext breaks the GCM auth tag). This stops passive DB + tampering. +- **It does not defend against a party that already holds the DEK and can write + storage.** The flag is sealed under the *shared* DEK and is **not bound to key + identity**, so such a party can copy a valid sealed `True` flag from an + authorizer's doc onto its own and self-grant authority. This is **out of scope by + design**: the trust model assumes DEK-holders are trusted. A DEK-holder already + has full data access (DEK + write access is game over), so self-granting authority + exposes no additional data. We deliberately do not add per-key signing to prevent + it — the residual is handled operationally (below). + +So the precise boundary is: **the capability flag is unforgeable without the DEK; it +is not a defense against a malicious DEK-holder.** (A legitimate authorizer can of +course mint new flags — that is its job.) + +### Operational guidance (the residual control) + +Because the DEK-holder case above is **not** prevented cryptographically in the +shared-DEK model, the intended mitigation is **detection, not crypto**. Consumers +should periodically audit the authorization state in storage: + +- enumerate every key doc's capability flag (`list` shows authorizer status), +- compare the set of `allowed: True` keys against a known-good list of expected + authorizers, +- alert on any unexpected authorizer. + +An unexpected `allowed: True` appearing in storage is the signal that a DEK-holder +self-granted — catch it by review, then revoke and re-authorize from a trusted +machine. `revoke` is **bookkeeping, not rotation**: it deletes the record so the key can no longer unwrap at boot, but it does not rotate the DEK or scrub it from a machine