From a40a7432efb6105675440dc5accefce7e06a4c32 Mon Sep 17 00:00:00 2001 From: disqualifier Date: Mon, 29 Jun 2026 17:58:09 -0400 Subject: [PATCH] fix: clean error on OS-level write failures in config init and dispatch (v0.1.2) - config init catches OSError (read-only dir, ENOSPC, gone cwd) alongside CommandError and prints a clean [x] line; the main dispatch catches the full OSError family instead of only FileNotFoundError (L13) - document read_flag's fail-closed (non-dict -> not allowed) as a deliberate privilege- gate default (nit). Signed-off-by: disqualifier --- README.md | 6 +++--- pyproject.toml | 2 +- src/envelope_authorizer/__init__.py | 2 +- src/envelope_authorizer/cli.py | 8 ++++++-- src/envelope_authorizer/commands/__init__.py | 6 +++++- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 564b924..75a66d3 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.1 +envelope_authorizer @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_authorizer.git@v0.1.2 ``` Direct: ```bash -pip install "envelope_authorizer @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_authorizer.git@v0.1.1" +pip install "envelope_authorizer @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_authorizer.git@v0.1.2" ``` 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.1" +pip install "envelope_authorizer[mongo] @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_authorizer.git@v0.1.2" ``` Installing pulls `envelope_crypto` (and `mongo` with the extra). After install, diff --git a/pyproject.toml b/pyproject.toml index d44101f..24f7c87 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "envelope_authorizer" -version = "0.1.1" +version = "0.1.2" description = "CLI key-authorization manager for envelope_crypto" requires-python = ">=3.10" dependencies = [ diff --git a/src/envelope_authorizer/__init__.py b/src/envelope_authorizer/__init__.py index 485f44a..b3f4756 100644 --- a/src/envelope_authorizer/__init__.py +++ b/src/envelope_authorizer/__init__.py @@ -1 +1 @@ -__version__ = "0.1.1" +__version__ = "0.1.2" diff --git a/src/envelope_authorizer/cli.py b/src/envelope_authorizer/cli.py index 6a0ae17..b6dceb2 100644 --- a/src/envelope_authorizer/cli.py +++ b/src/envelope_authorizer/cli.py @@ -72,7 +72,9 @@ def main() -> int: try: config_init.run(None, None, args) return 0 - except CommandError as error: + except (CommandError, OSError) as error: + # config_init writes a file (cwd may be read-only, full, or gone) — an + # OSError must print a clean [✘] line, not a raw traceback return _fail(str(error)) parser.parse_args(["config", "--help"]) return 0 @@ -92,7 +94,9 @@ def main() -> int: return 0 except InvalidTag: return _fail("capability flag failed authentication — tampered or wrong DEK") - except (ConfigError, CommandError, RuntimeError, ValueError, FileNotFoundError) as error: + except (ConfigError, CommandError, RuntimeError, ValueError, OSError) as error: + # OSError covers the FileNotFoundError/PermissionError/IsADirectoryError family + # so an i/o failure prints a clean [✘] line instead of a traceback return _fail(str(error)) diff --git a/src/envelope_authorizer/commands/__init__.py b/src/envelope_authorizer/commands/__init__.py index 0fcf05f..25c97cd 100644 --- a/src/envelope_authorizer/commands/__init__.py +++ b/src/envelope_authorizer/commands/__init__.py @@ -22,7 +22,11 @@ def make_flag(crypto: EnvelopeCrypto, allowed: bool) -> dict: def read_flag(crypto: EnvelopeCrypto, blob: dict) -> bool: - """decrypt a capability flag; return the `allowed` bool""" + """decrypt a capability flag; return the `allowed` bool + + fails closed: a non-dict / unexpected plaintext reads as not-allowed rather than + raising — a privilege gate must default to deny on a malformed flag. + """ data = crypto.decrypt_data(blob) return bool(data.get("allowed", False)) if isinstance(data, dict) else False