generated from coulomb/repo-seed
Permission enforcement on startup: enforce_permissions() checks store dir (700), user files (600), signing key, TLS key, audit.log, revoked.json. CLI and run_server() call it before any sensitive operation. New modules: security.py check_store(), enforce_permissions(), print_security_check() audit.py log_event() — append-only TSV audit log (mode 600) revoke.py revoke(jti), is_revoked(jti) — revocation list (mode 600) New CLI commands: security-check Print per-check pass/warn/fail report; exit 1 on failure revoke-token <jti|jwt> Add JTI to revocation list; accepts raw JTI or full JWT Serve integration: Audit log written for auth request, token issuance, and userinfo calls Revocation checked at /userinfo; revoked tokens return 401 Docs: security model section in LocalIdentity.md — threat model, assumptions, non-guarantees, SELinux/AppArmor guidance, revocation usage. 138 tests passing (34 new for Stage 4). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
53 lines
1.2 KiB
Python
53 lines
1.2 KiB
Python
"""
|
|
Token revocation list for local-identity OIDC serve.
|
|
|
|
Revoked JTIs (JWT IDs) are stored in ~/.local-identity/revoked.json (mode 600).
|
|
The list is append-only: JTIs are added but never removed.
|
|
|
|
Usage:
|
|
revoke(jti) Add a JTI to the revocation list.
|
|
is_revoked(jti) Return True if the JTI has been revoked.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from .store import _store_dir
|
|
|
|
|
|
def _revoked_path() -> Path:
|
|
return _store_dir() / "revoked.json"
|
|
|
|
|
|
def _load() -> set[str]:
|
|
path = _revoked_path()
|
|
if not path.exists():
|
|
return set()
|
|
try:
|
|
data = json.loads(path.read_text(encoding="utf-8"))
|
|
return set(data.get("revoked", []))
|
|
except (json.JSONDecodeError, OSError):
|
|
return set()
|
|
|
|
|
|
def _save(revoked: set[str]) -> None:
|
|
path = _revoked_path()
|
|
path.write_text(
|
|
json.dumps({"revoked": sorted(revoked)}, indent=2),
|
|
encoding="utf-8",
|
|
)
|
|
os.chmod(path, 0o600)
|
|
|
|
|
|
def is_revoked(jti: str) -> bool:
|
|
"""Return True if this JTI has been revoked. Defaults to False on read error."""
|
|
return jti in _load()
|
|
|
|
|
|
def revoke(jti: str) -> None:
|
|
"""Add a JTI to the revocation list."""
|
|
revoked = _load()
|
|
revoked.add(jti)
|
|
_save(revoked)
|