Skip to content

Attestation

WCP defines two levels of attestation: registry record attestation (§5.10 artifact hash) and full-package attestation (worker code + deps + manifest). Both use SHA-256 and deterministic serialization. Both detect modification after the fact.

The Unit of Trust

The unit of attestation in WCP is the complete worker package — not just the code file. A worker package includes code, dependencies, configuration schema, and a signed manifest. All of it is hashed together.

worker-package/
code/
worker_logic.py
bootstrap.py
requirements.lock
config.schema.json
manifest.json ← signed manifest (excluded from hash input)

Trust is bound to namespace-key authorization, not personal authorship. The trust statement embedded in every manifest reads:

Package attested by namespace <ns> at <UTC>; package hash sha256:<hash>.

The Supply Chain Threat

Without attestation, the following attack is silent:

1. Attacker gains access to worker package after enrollment
2. Attacker modifies worker_logic.py to exfiltrate payloads
3. Worker retains original worker_id and capabilities
4. Hall dispatches it normally — valid capability ID, valid tenant
5. Data exfiltrated. Evidence receipt shows nothing wrong. Attack is invisible.

Full-package attestation closes this attack surface at runtime. The moment any file in the package diverges from what was manifested, the next verification call fails closed.

Registry Record Attestation

Every registry record carries an artifact_hash — a SHA-256 over all other fields, serialized deterministically. This detects tampering with the enrollment document itself.

import hashlib
import json
def compute_artifact_hash(record: dict) -> str:
record_without_hash = {k: v for k, v in record.items() if k != 'artifact_hash'}
canonical = json.dumps(record_without_hash, sort_keys=True, separators=(',', ':'))
return 'sha256:' + hashlib.sha256(canonical.encode()).hexdigest()

This logic runs internally inside the Hall server (hall_api/server.py) at enrollment time. It is not a public import from pyhall.registry — do not call it directly. If you need to pre-compute the hash before enrolling, use the same pattern shown above inline.

At enrollment, the Hall re-computes the hash and compares it to the submitted artifact_hash field. A mismatch means the record was modified after it was generated — enrollment is rejected.

Full-Package Attestation

Full-package attestation is implemented by PackageAttestationVerifier. It verifies the entire worker package at runtime before execution.

Deny Codes

PackageAttestationVerifier is fail-closed. Any check failure returns a deny code with no silent fallback:

CodeCause
ATTEST_MANIFEST_MISSINGmanifest.json does not exist or is unreadable
ATTEST_MANIFEST_ID_MISMATCHManifest worker_id / worker_species_id does not match the declared worker identity
ATTEST_HASH_MISMATCHRecomputed package hash does not match manifest.package_hash
ATTEST_SIGNATURE_MISSINGNo signature in manifest, or WCP_ATTEST_HMAC_KEY env var is not set
ATTEST_SIG_INVALIDHMAC-SHA256 signature does not verify
ATTEST_BANNED_HASHPackage hash matches a banned hash in the registry’s banned_hashes set

All six deny codes are exported from pyhall directly:

from pyhall import (
PackageAttestationVerifier,
ATTEST_MANIFEST_MISSING,
ATTEST_MANIFEST_ID_MISMATCH,
ATTEST_HASH_MISMATCH,
ATTEST_SIGNATURE_MISSING,
ATTEST_SIG_INVALID,
ATTEST_BANNED_HASH,
)

canonical_package_hash

canonical_package_hash(package_root) computes a deterministic SHA-256 over the full package content. Hash input is one record per file, sorted lexicographically by relative POSIX path:

<relative_posix_path>\n<size_bytes>\n<sha256_hex(file_content)>\n

Files excluded from the hash: manifest.json, manifest.sig, manifest.tmp, .git/, __pycache__/, .DS_Store, *.pyc.

from pathlib import Path
from pyhall import canonical_package_hash
pkg_hash = canonical_package_hash(Path("/opt/workers/my-worker"))
# returns a 64-character lowercase hex SHA-256 digest

build_manifest and write_manifest

build_manifest() computes the canonical package hash, assembles the manifest dict, and signs it with HMAC-SHA256. write_manifest() writes the signed manifest to disk.

from pathlib import Path
from pyhall import build_manifest, write_manifest
manifest = build_manifest(
package_root=Path("./my-worker"),
worker_id="org.acme.summarizer.instance-1",
worker_species_id="wrk.doc.summarizer",
worker_version="1.0.0",
signing_secret="your-namespace-hmac-key", # or read from env
build_source="ci", # "local" | "ci" | "agent"
)
write_manifest(manifest, Path("./my-worker/manifest.json"))

The signing secret is the namespace key. Set it via the WCP_ATTEST_HMAC_KEY environment variable in production. The manifest includes a trust_statement field with the canonical namespace-key trust claim.

scaffold_package

scaffold_package() creates a minimal worker package directory layout with stub files:

from pathlib import Path
from pyhall import scaffold_package
scaffold_package(
package_root=Path("./my-worker"),
worker_logic_file=Path("./my_logic.py"), # optional — stub written if None
overwrite=False,
)

Layout created:

my-worker/
code/
bootstrap.py
worker_logic.py
requirements.lock
config.schema.json

After scaffolding, add your logic to code/worker_logic.py, pin dependencies in requirements.lock, then run build_manifest to sign the package.

PackageAttestationVerifier

PackageAttestationVerifier verifies a worker package at runtime. Use it at worker startup — before any execution — to confirm the package is attested and unchanged.

from pathlib import Path
from pyhall import PackageAttestationVerifier
verifier = PackageAttestationVerifier(
package_root=Path("/opt/workers/my-worker"),
manifest_path=Path("/opt/workers/my-worker/manifest.json"),
worker_id="org.acme.summarizer.instance-1",
worker_species_id="wrk.doc.summarizer",
# secret_env defaults to "WCP_ATTEST_HMAC_KEY"
)
ok, deny_code, meta = verifier.verify()
if not ok:
raise SystemExit(f"Attestation denied: {deny_code}")
# On success, meta contains:
# meta["trust_statement"] — canonical namespace-key trust claim
# meta["package_hash"] — verified hash for embedding in evidence receipts
# meta["verified_at_utc"] — UTC ISO 8601
# meta["attested_at_utc"] — when the manifest was built

The verification sequence:

  1. manifest.json must exist and be parseable → ATTEST_MANIFEST_MISSING
  2. Manifest worker_id and worker_species_id must match the declared identity → ATTEST_MANIFEST_ID_MISMATCH
  3. Recomputed canonical_package_hash must match manifest.package_hashATTEST_HASH_MISMATCH; if the hash matches a banned hash in the registry’s banned_hashes set → ATTEST_BANNED_HASH
  4. signature_hmac_sha256 must be present and WCP_ATTEST_HMAC_KEY must be set → ATTEST_SIGNATURE_MISSING
  5. HMAC signature must verify → ATTEST_SIG_INVALID

All checks must pass. No silent fallback.

Registry Record Verification

import hashlib
import json
def compute_artifact_hash(record: dict) -> str:
record_without_hash = {k: v for k, v in record.items() if k != 'artifact_hash'}
canonical = json.dumps(record_without_hash, sort_keys=True, separators=(',', ':'))
return 'sha256:' + hashlib.sha256(canonical.encode()).hexdigest()
record = {
"worker_id": "org.acme.summarizer",
"worker_species_id": "wrk.doc.summarizer",
"capabilities": ["cap.doc.summarize"],
# ... all other fields
}
record["artifact_hash"] = compute_artifact_hash(record)
# Verify: recompute and compare
expected = compute_artifact_hash(record)
if expected != record["artifact_hash"]:
raise ValueError("Registry record has been tampered with")

Compliance Requirements

Attestation is off by default (require_worker_attestation: false). WCP-Full compliance requires it enabled in production:

SettingDefaultWCP-Full production requirement
require_worker_attestationfalsetrue
require_signatoryfalsetrue

Both work together: signatory validation proves who is requesting; full-package attestation proves the worker that executes is exactly what was enrolled. Combined with the cryptographic evidence receipt and correlation chain, they establish an auditable chain of custody that survives post-incident review.