refactor(local-identity): post-Stage4 cleanups and micro-fixes

- audit: chmod only on file creation, not every append (TOCTOU fix)
- jwt_utils: add extract_unverified_payload() helper
- cli: use extract_unverified_payload + JWTError instead of inline decode
- keys: extract _public_key_bytes() helper, import _b64url from jwt_utils
- security: FileNotFoundError try/except instead of path.exists() (TOCTOU fix)
- serve: cache JWK response at server init instead of per-request recompute

138 tests passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-02 08:25:21 +01:00
parent 3890dca25d
commit 52d44daec2
6 changed files with 37 additions and 20 deletions

View File

@@ -20,11 +20,11 @@ Environment:
"""
import argparse
import base64
import json
import sys
from .gecos import current_username, get_gecos_fullname
from .jwt_utils import JWTError, extract_unverified_payload
from .user import UserRecord, make_test_user
from . import audit
from . import export as export_mod
@@ -163,16 +163,12 @@ def cmd_revoke_token(args: argparse.Namespace) -> None:
if token_or_jti.count(".") == 2:
# Looks like a JWT — extract the JTI from the payload
try:
payload_b64 = token_or_jti.split(".")[1]
pad = (4 - len(payload_b64) % 4) % 4
payload = json.loads(
base64.urlsafe_b64decode(payload_b64 + "=" * pad)
)
payload = extract_unverified_payload(token_or_jti)
jti = payload.get("jti")
if not jti:
print("Error: JWT has no 'jti' claim.", file=sys.stderr)
sys.exit(1)
except Exception as exc:
except JWTError as exc:
print(f"Error decoding JWT: {exc}", file=sys.stderr)
sys.exit(1)
else: