fix: a stored key doc with meta:null no longer crashes authorize/revoke

find_by_friendly, authorize.py's existing-target check, and revoke.py's
success-print all did doc.get("meta", {}).get(...) directly - the {} default
only applies when the key is ABSENT, not when its value is null (plausible in
shared mongo or a hand-edited JSON store), so a null meta raised AttributeError.
revoke's crash landed AFTER storage.delete() already succeeded, so a completed
revoke was reported as a raw traceback instead of a clean exit. list_keys._meta
already coerced correctly; that fix is now hoisted into commands/__init__.py as
doc_meta, the single source every meta-reading call site goes through. cli.py's
catch tuple also gains AttributeError as defense in depth for any future
unguarded meta access.

Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
2026-07-06 00:16:52 -04:00
parent 764bcb6146
commit e5c3547c51
5 changed files with 24 additions and 14 deletions
+5 -2
View File
@@ -99,9 +99,12 @@ def main() -> int:
return _fail("capability flag failed authentication - tampered or wrong DEK")
except PyMongoError as error:
return _fail(f"storage backend error: {error}")
except (ConfigError, CommandError, RuntimeError, ValueError, OSError, KeyError, TypeError) as error:
except (
ConfigError, CommandError, RuntimeError, ValueError, OSError, KeyError, TypeError, AttributeError,
) as error:
# covers a malformed config/doc/flag (incl. KeyError/TypeError from unguarded
# indexing) - all print a clean [✘] line, not a raw traceback
# indexing, AttributeError from a null field a caller assumed was a dict) -
# all print a clean [✘] line, not a raw traceback
return _fail(str(error))
+13 -1
View File
@@ -68,9 +68,21 @@ def boot_local(config, storage) -> Tuple[EnvelopeCrypto, dict]:
return crypto, doc
def doc_meta(doc: dict) -> dict:
"""the doc's meta block as a dict, coercing a missing/null/malformed one to {}
a stored doc's `meta` can be null (shared mongo, a hand-edited JSON file) - the
dict.get(key, {}) default only applies when the key is ABSENT, not when its
value is null, so every meta-reading call site must go through this rather than
`doc.get("meta", {})` directly or it raises AttributeError on a null meta.
"""
meta = doc.get("meta")
return meta if isinstance(meta, dict) else {}
def find_by_friendly(storage, friendly: str) -> Optional[dict]:
"""return the doc whose meta.friendly matches, or None"""
for doc in storage.get_all():
if doc.get("meta", {}).get("friendly") == friendly:
if doc_meta(doc).get("friendly") == friendly:
return doc
return None
@@ -9,6 +9,7 @@ from . import (
CommandError,
boot_local,
build_doc,
doc_meta,
find_by_friendly,
local_fingerprint,
make_flag,
@@ -35,7 +36,7 @@ def run(config, storage, args) -> None:
)
existing = storage.get(new_fp)
if existing:
existing_friendly = existing.get("meta", {}).get("friendly", "?")
existing_friendly = doc_meta(existing).get("friendly", "?")
raise CommandError(
f"target key is already authorized as '{existing_friendly}'; "
f"authorize would silently replace that record - revoke it first "
@@ -7,7 +7,7 @@ the wrapped key or DEK.
from datetime import datetime, timezone
from . import boot_local, read_flag
from . import boot_local, doc_meta, read_flag
def _can_authorize(crypto, doc) -> str:
@@ -18,12 +18,6 @@ def _can_authorize(crypto, doc) -> str:
return "?"
def _meta(doc) -> dict:
"""the doc's meta block as a dict, coercing a missing/null/malformed one to {}"""
meta = doc.get("meta")
return meta if isinstance(meta, dict) else {}
def _created(meta: dict) -> str:
"""format created_at as a UTC timestamp string, or '-' if absent/unparseable"""
raw = meta.get("created_at")
@@ -44,7 +38,7 @@ def run(config, storage, args) -> None:
print(header)
print("-" * len(header))
for doc in docs:
meta = _meta(doc)
meta = doc_meta(doc)
print(
f"{doc.get('_id', '')[:16]:<18} "
f"{str(meta.get('friendly', '-')):<16} "
+2 -2
View File
@@ -7,7 +7,7 @@ rotate the DEK or scrub it from a machine that already unwrapped it.
from envelope_crypto import EnvelopeCrypto
from . import CommandError, find_by_friendly, local_fingerprint
from . import CommandError, doc_meta, find_by_friendly, local_fingerprint
_WARNING = (
"[!] Revoke removes the record only. It does NOT rotate the DEK or scrub it\n"
@@ -47,6 +47,6 @@ def run(config, storage, args) -> None:
raise CommandError("refusing to revoke the local key")
storage.delete(doc["_id"])
friendly = doc.get("meta", {}).get("friendly", "?")
friendly = doc_meta(doc).get("friendly", "?")
print(f"[✔] Revoked: {friendly} ({doc['_id'][:16]})")
print(_WARNING)