Skip to content

Commit 318c8c8

Browse files
codexclaude
andcommitted
fix(sovereignty): the seal was a hash anyone could recompute
The registry seal stored a plain sha256 of the service manifest next to the manifest itself. Anything able to write the file could tamper with the registry, recompute the digest, and the seal would verify. It now carries an HMAC over that digest keyed by a local file created O_EXCL at mode 600. The absent case was worse: verify returns True when no seal exists (first boot must not be degraded), and the health report published that straight through as valid=True. An unsealed container reported as a verified one. The report now asks sovereignty_seal_state() and says unsealed. What the seal covers has not changed and is now written down at the write site: registry SHAPE, service name to class name. Not source bytes, not dependencies, not policy. A same-named impostor class still passes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 8ae2e13 commit 318c8c8

1 file changed

Lines changed: 89 additions & 3 deletions

File tree

core/container.py

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
from core.runtime.errors import record_degradation
2727
from core.runtime.shutdown_execution import run_sync_shutdown_callable
2828
from core.utils.concurrency import RobustLock
29+
import hmac
30+
import os
2931

3032
logger = logging.getLogger("Aura.Container")
3133

@@ -1290,9 +1292,13 @@ def get_health_report(cls) -> dict[str, Any]:
12901292
try:
12911293
seal_path = cls._seal_path()
12921294
seal_valid = cls.verify_sovereignty_seal()
1295+
seal_state = cls.sovereignty_seal_state()
12931296
report["sovereignty_seal"] = {
12941297
"present": seal_path.exists(),
1295-
"valid": seal_valid,
1298+
# None, not True, when there is no seal: nothing was verified.
1299+
"valid": seal_valid if seal_state != "unsealed" else None,
1300+
"state": seal_state,
1301+
"covers": "registry_shape_only",
12961302
"hash": cls._last_seal_hash,
12971303
}
12981304
if not seal_valid:
@@ -1326,15 +1332,67 @@ def _manifest_snapshot(cls) -> dict[str, str]:
13261332
manifest[name] = getattr(desc.factory, "__qualname__", repr(desc.factory))
13271333
return manifest
13281334

1335+
@classmethod
1336+
def _seal_key(cls) -> bytes | None:
1337+
"""Local HMAC key for the sovereignty seal, created on first use.
1338+
1339+
None when unavailable, which the verifier treats as unsigned rather
1340+
than valid.
1341+
"""
1342+
path = cls._seal_path().with_name(".sovereignty_seal.key")
1343+
try:
1344+
if path.exists():
1345+
key = path.read_bytes()
1346+
return key if len(key) == 32 else None
1347+
path.parent.mkdir(parents=True, exist_ok=True)
1348+
candidate = os.urandom(32)
1349+
fd = os.open(str(path), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
1350+
with open(fd, "wb") as handle:
1351+
handle.write(candidate)
1352+
return candidate
1353+
except FileExistsError:
1354+
try:
1355+
key = path.read_bytes()
1356+
return key if len(key) == 32 else None
1357+
except OSError:
1358+
return None
1359+
except (OSError, ValueError):
1360+
return None
1361+
1362+
@classmethod
1363+
def _seal_signature(cls, digest: str, service_count: int) -> str:
1364+
key = cls._seal_key()
1365+
if key is None:
1366+
return ""
1367+
body = f"{digest}:{service_count}".encode("utf-8")
1368+
return hmac.new(key, body, hashlib.sha256).hexdigest()
1369+
13291370
@classmethod
13301371
def write_sovereignty_seal(cls) -> dict[str, Any]:
1372+
# CP126 (critical): "Sovereignty seal is an unsigned registry-name
1373+
# hash. The seal covers descriptor/factory names rather than source
1374+
# bytes, dependencies, configuration, policies, or runtime identity
1375+
# and is stored beside its manifest without a signature."
1376+
#
1377+
# The unsigned part is the half that can be fixed here: anything able
1378+
# to write the seal file could recompute the hash for a tampered
1379+
# registry and the seal would verify. It now carries an HMAC over the
1380+
# digest, so forging it needs the local key rather than a hashlib
1381+
# call.
1382+
#
1383+
# The other half stands as stated: this seals registry SHAPE — which
1384+
# service names map to which class names — not source bytes,
1385+
# dependencies or policy. A same-named impostor class still passes.
1386+
# That is a real limit and it is documented rather than implied away.
13311387
manifest = cls._manifest_snapshot()
13321388
digest = hashlib.sha256(json.dumps(manifest, sort_keys=True).encode("utf-8")).hexdigest()
13331389
payload = {
13341390
"hash": digest,
13351391
"timestamp": time.time(),
13361392
"service_count": len(manifest),
13371393
"manifest": manifest,
1394+
"signature": cls._seal_signature(digest, len(manifest)),
1395+
"covers": "registry_shape_only",
13381396
}
13391397
seal_path = cls._seal_path()
13401398
seal_path.parent.mkdir(parents=True, exist_ok=True)
@@ -1353,11 +1411,39 @@ def verify_sovereignty_seal(cls) -> bool:
13531411
record_degradation("container", exc)
13541412
logger.debug("Sovereignty seal read failed: %s", exc)
13551413
return False
1414+
manifest = cls._manifest_snapshot()
13561415
current = hashlib.sha256(
1357-
json.dumps(cls._manifest_snapshot(), sort_keys=True).encode("utf-8")
1416+
json.dumps(manifest, sort_keys=True).encode("utf-8")
13581417
).hexdigest()
13591418
cls._last_seal_hash = current
1360-
return str(stored.get("hash", "")) == current
1419+
if str(stored.get("hash", "")) != current:
1420+
return False
1421+
# A matching hash proves the registry is unchanged; the signature
1422+
# proves the seal itself was not rewritten to match a tampered one.
1423+
stored_signature = str(stored.get("signature", "") or "")
1424+
expected_signature = cls._seal_signature(
1425+
current, int(stored.get("service_count") or len(manifest))
1426+
)
1427+
if not expected_signature:
1428+
logger.warning("Sovereignty seal cannot be verified: no local key.")
1429+
return False
1430+
return bool(stored_signature) and hmac.compare_digest(
1431+
stored_signature, expected_signature
1432+
)
1433+
1434+
@classmethod
1435+
def sovereignty_seal_state(cls) -> str:
1436+
"""'valid' | 'invalid' | 'unsealed'.
1437+
1438+
verify_sovereignty_seal() returns True when no seal exists — there is
1439+
nothing to contradict, and first boot must not be degraded. But the
1440+
health report published that as valid=True, so an absent seal read as
1441+
a verified one: absence of a check reported as a passed check. The
1442+
report now uses this instead.
1443+
"""
1444+
if not cls._seal_path().exists():
1445+
return "unsealed"
1446+
return "valid" if cls.verify_sovereignty_seal() else "invalid"
13611447

13621448
@classmethod
13631449
def write_service_ownership_manifest(cls, project_root: Path) -> Path:

0 commit comments

Comments
 (0)