S14: NPE fail-closed adapter; npe mode never falls back to lab-xor
Some checks are pending
ci / python (push) Waiting to run
ci / go (push) Waiting to run

This commit is contained in:
George Lambert 2026-09-15 22:22:56 -04:00
parent d67509e470
commit 9eed0b1942
4 changed files with 112 additions and 2 deletions

View file

@ -75,8 +75,10 @@ def seal(
raise ValueError("lab_key required for lab-xor")
key = hashlib.sha256(lab_key).digest() # 32-byte key
ct = _xor(key, raw).hex() # hex ct
elif mode == "npe": # production — sidecar not in this module
raise NotImplementedError("npe: attach NPE sidecar; do not HPKE here")
elif mode == "npe": # production — sidecar only
from .npe_adapter import seal_npe # fail-closed import
ct = seal_npe(to, body) # never lab-xor here
else: # unknown
raise ValueError("unknown crypto.mode " + mode)
token = hashlib.sha256(nonce + b"error-token").hexdigest()[:32] # return-path token stub

View file

@ -0,0 +1,40 @@
"""NPE sidecar adapter (fail-closed).
Production ``crypto.mode=npe`` must not fall back to lab-xor.
If the ``npe`` binary is missing, raise NpeRequired.
Live ns1 must not set this until CI review (see UserReview.MD).
"""
from __future__ import annotations # annotations as strings
import json # encode sidecar request
import shutil # look up npe on PATH
import subprocess # run sidecar
from typing import Any, Dict, Mapping # types
class NpeRequired(RuntimeError):
"""Raised when NPE is required but the sidecar is not usable."""
def npe_bin() -> str:
"""Return path to npe or raise."""
path = shutil.which("npe") # PATH lookup
if not path: # missing
raise NpeRequired("npe binary not on PATH; attach sidecar") # fail closed
return path # found
def seal_npe(to: str, body: Mapping[str, Any]) -> str:
"""Ask sidecar to HPKE-seal body for mailbox ``to``. Returns hex ct."""
raw = json.dumps(body, sort_keys=True, separators=(",", ":")).encode() # canonical body
proc = subprocess.run( # npe CLI contract: stdin body, arg dest
[npe_bin(), "seal", "--to", to],
input=raw,
capture_output=True,
timeout=15,
check=False,
)
if proc.returncode != 0: # sidecar failed
raise NpeRequired("npe seal failed: " + proc.stderr.decode(errors="replace")[:200])
return proc.stdout.strip().decode() # hex or token from sidecar