2626from core .runtime .errors import record_degradation
2727from core .runtime .shutdown_execution import run_sync_shutdown_callable
2828from core .utils .concurrency import RobustLock
29+ import hmac
30+ import os
2931
3032logger = 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