Harden secure runtime qualification provenance

This commit is contained in:
Richard Courtman 2026-08-30 18:17:36 +01:00
parent 770733fc92
commit 98e8f86806
8 changed files with 899 additions and 175 deletions

View file

@ -209,6 +209,12 @@ The committed-main Linux evidence is recorded in
`docs/release-control/v6/internal/records/secure-agent-runtime-qualification-foundation-2026-08-30.md`.
That receipt is artifact-bound operator self-attestation, not an independently
authenticated external assessment. The safe profile therefore remains opt-in.
The current attester requires a fresh schema-v4 run: it expands a committed
production-source manifest, requires exact source membership, binds ordered
scenario claims to a retained secret-free JSONL transcript, validates typed
receipt/report chronology, and hashes the intended repository record path
inside the receipt. The existing v3 record remains historical and is not
silently promoted to this stronger evidence class.
Monitoring never implies remediation. On the supported Linux systemd profile,
an operator may separately enroll the typed action runner:

View file

@ -117,16 +117,24 @@ It is historical evidence, not qualification of the current implementation.
The v3 guarded lab requires a real verified apt-cache mutation, a separate
stale-fingerprint refusal, durable receipt replay, an observed collector
authority-reduction request (with persistence covered by API regressions),
complete boundary-source hashes, and artifact Go build stamps for the exact
the then-required fixed boundary-source hashes, and artifact Go build stamps for the exact
clean commit. A fresh Ubuntu 24.04.4/systemd 255 run at committed main
`b2543c5c6e03bc8f38502098d3a356983d0d41b2` passed all twelve scenarios; its
separate receipt and attestation are recorded as
`internal/records/secure-agent-runtime-systemd-receipt-v3-2026-08-30.json` and
`internal/records/secure-agent-runtime-committed-main-attestation-v3-2026-08-30.json`.
`scripts/release_control/secure_runtime_attestation.py` rejects older receipts
and labels accepted evidence as artifact-bound, self-attested systemd evidence
rather than independently authenticated proof. The repository still needs a
fresh exact committed release-candidate run,
That schema-v3 receipt is now historical input only. The post-audit attester
requires schema v4 and will not reinterpret v3 as proof of the current tree.
V4 expands a committed source manifest across the production collector,
helper, runner, host-agent provider, API admission, update, configuration, and
receipt roots; requires the receipt hashes to match that exact set; validates
ordered timestamps and scenario-specific causal claims; binds every scenario
to a retained secret-free JSONL transcript event; validates typed receipt kind
and report chronology; and requires the intended repository record path to be
inside the hashed receipt. No schema-v4 live receipt exists yet. Accepted v4
evidence will remain artifact-bound, self-attested systemd evidence rather than
an independently authenticated assessment. The repository still needs a fresh
exact committed release-candidate run,
representative Proxmox, SMART, Docker and rootless Podman telemetry/action
parity, appliance profiles, and the external security review. Until those
proofs are recorded, the safe profile remains opt-in and provider degradation

View file

@ -113,18 +113,36 @@ The secret-free schema-v3 receipt is
separate `secure-agent-runtime-committed-main-attestation-v3-2026-08-30.json`
has SHA-256
`e1339f73704883aec19225d886188dad78338659b16a11e785091b41bbf0f0c1` and
verifies the receipt, current-main ancestry at attestation time, all required
boundary-source hashes, all artifact hashes, the expected Go command packages,
verifies the receipt, current-main ancestry at attestation time, all
then-required fixed boundary-source hashes, all artifact hashes, the expected Go command packages,
and clean exact-commit Go VCS identities. The authority-reduction request was
observed by the systemd fixture; durable production-store reduction and exact
session invalidation remain established by the focused API regressions rather
than by this in-process fixture. The receipt remains operator-produced,
unauthenticated, artifact-bound self-attestation rather than independent proof.
## Post-audit schema-v4 acceptance floor
Schema v3 remains immutable historical evidence, but it is no longer accepted
by the current attester as qualification of a newer tree. A future run must use
schema v4. The committed
`scripts/release_control/secure_runtime_source_manifest_v4.json` expands the
production collector, helper, runner, host-agent provider, API admission,
configuration, update, and receipt roots, and the receipt must hash exactly
that expanded source set. Each scenario must carry ordered UTC start/completion
times, typed causal claims, and an exact reference to a retained secret-free
JSONL transcript event. The receipt also binds its intended repository path,
the transcript path and digest, the typed action receipt kind, report count and
report chronology. The attestation preserves those bindings alongside the
artifact build identities. No v4 live receipt or attestation is recorded here;
this is the acceptance contract for the next exact-commit or exact-RC run, not
a retroactive upgrade of the v3 evidence.
## Proof classification and residuals
Focused regressions and the schema-v3 guarded systemd evidence cover the current
committed-main semantics. They are not a substitute for exact release-candidate
Focused regressions cover current code-level semantics. The schema-v3 guarded
systemd evidence covers only its historical qualified commit and the explicitly
exercised subset above. Neither is a substitute for exact release-candidate
reproduction, representative provider/appliance qualification, or external
review. The canonical support and residual matrix is published in
`docs/AGENT_SECURITY.md`; unqualified rows remain explicit blockers rather than

View file

@ -209,6 +209,12 @@ The committed-main Linux evidence is recorded in
`docs/release-control/v6/internal/records/secure-agent-runtime-qualification-foundation-2026-08-30.md`.
That receipt is artifact-bound operator self-attestation, not an independently
authenticated external assessment. The safe profile therefore remains opt-in.
The current attester requires a fresh schema-v4 run: it expands a committed
production-source manifest, requires exact source membership, binds ordered
scenario claims to a retained secret-free JSONL transcript, validates typed
receipt/report chronology, and hashes the intended repository record path
inside the receipt. The existing v3 record remains historical and is not
silently promoted to this stronger evidence class.
Monitoring never implies remediation. On the supported Linux systemd profile,
an operator may separately enroll the typed action runner:

View file

@ -26,6 +26,9 @@ package installtests
// PULSE_SECURE_RUNTIME_HELPER="$repo/.lab-artifacts/pulse-agent-helper" \
// PULSE_SECURE_RUNTIME_RUNNER="$repo/.lab-artifacts/pulse-agent-runner" \
// PULSE_SECURE_RUNTIME_RECEIPT=/tmp/secure-runtime-receipt.json \
// PULSE_SECURE_RUNTIME_RECEIPT_RECORD_PATH=docs/release-control/v6/internal/records/secure-agent-runtime-systemd-receipt-v4.json \
// PULSE_SECURE_RUNTIME_TRANSCRIPT=/tmp/secure-runtime-transcript.jsonl \
// PULSE_SECURE_RUNTIME_TRANSCRIPT_RECORD_PATH=docs/release-control/v6/internal/records/secure-agent-runtime-systemd-transcript-v4.jsonl \
// sh -c 'cd "$1/scripts/installtests" && exec "$1/.lab-artifacts/installtests-linux-arm64.test" -test.run "^TestSecureRuntimeSystemdLab$" -test.count=1 -test.v' sh "$repo"
//
// Use the VM's GOARCH in place of arm64 when qualifying another architecture.
@ -101,6 +104,31 @@ var secureRuntimeForbiddenReceiptKeys = map[string]struct{}{
"token": {},
}
var secureRuntimeScenarioClaims = map[string][]string{
"legacy_root_command_capable_install": {"legacy_root_command_authority_observed"},
"read_only_inspect": {"inspection_left_stable_files_unchanged"},
"drop_in_fail_closed_rehearsal": {"drop_in_rejected_before_mutation"},
"safe_profile_apply": {
"collector_non_root",
"collector_monitoring_only",
"helper_protocol_healthy",
"collector_authority_reduction_observed",
},
"explicit_safe_profile_rollback": {"explicit_rollback_preserved_reduced_authority"},
"automatic_failure_rollback": {"failed_activation_restored_prior_runtime"},
"ordinary_update_non_migration": {"ordinary_update_preserved_selected_profile"},
"final_safe_profile_apply": {"collector_reporting_continued_after_migration"},
"separate_action_runner_install": {"action_runner_registered_separately"},
"typed_action_receipt": {
"typed_mutation_verified",
"terminal_receipt_replayed",
"stale_precondition_refused",
"generic_command_denied",
},
"action_runner_credential_rotation": {"fixture_credential_replacement_observed"},
"action_runner_self_revoke": {"exact_runner_binding_revoked"},
}
type secureRuntimeLabReport struct {
ReceivedAt time.Time
AgentID string
@ -541,40 +569,136 @@ func secureRuntimeSeedAPTPackageCache(t *testing.T) (string, string) {
}
type secureRuntimeScenarioResult struct {
Name string `json:"name"`
Passed bool `json:"passed"`
Detail string `json:"detail,omitempty"`
Sequence int `json:"sequence"`
Name string `json:"name"`
Passed bool `json:"passed"`
StartedAt string `json:"started_at"`
CompletedAt string `json:"completed_at"`
Evidence secureRuntimeScenarioEvidence `json:"evidence"`
}
type secureRuntimeScenarioEvidence struct {
Kind string `json:"kind"`
Summary string `json:"summary"`
Claims []string `json:"claims"`
Observations map[string]any `json:"observations"`
TranscriptEventIDs []string `json:"transcript_event_ids"`
}
type secureRuntimeTranscriptEvent struct {
Sequence int `json:"sequence"`
EventID string `json:"event_id"`
ObservedAt string `json:"observed_at"`
Kind string `json:"kind"`
Scenario string `json:"scenario,omitempty"`
Claims []string `json:"claims,omitempty"`
Observations map[string]any `json:"observations,omitempty"`
Summary string `json:"summary,omitempty"`
Operation string `json:"operation,omitempty"`
Output string `json:"output,omitempty"`
OutputSHA256 string `json:"output_sha256,omitempty"`
}
type secureRuntimeSourceManifestBinding struct {
SchemaVersion int `json:"schema_version"`
ManifestID string `json:"manifest_id"`
Path string `json:"path"`
SHA256 string `json:"sha256"`
TargetOS string `json:"target_os"`
TargetArch string `json:"target_arch"`
}
type secureRuntimeTranscriptBinding struct {
Format string `json:"format"`
RecordPath string `json:"record_path"`
SHA256 string `json:"sha256"`
EventCount int `json:"event_count"`
}
type secureRuntimeSourceManifest struct {
SchemaVersion int `json:"schema_version"`
ManifestID string `json:"manifest_id"`
TargetOS string `json:"target_os"`
ExactPaths []string `json:"exact_paths"`
RecursiveRoots []string `json:"recursive_roots"`
IncludeSuffixes []string `json:"include_suffixes"`
ExcludeSuffixes []string `json:"exclude_suffixes"`
}
type secureRuntimeLabReceipt struct {
SchemaVersion int `json:"schema_version"`
CompletedAt string `json:"completed_at"`
SourceHashes map[string]string `json:"source_hashes"`
ArtifactHashes map[string]string `json:"artifact_hashes"`
ArtifactVersions map[string]string `json:"artifact_versions"`
DisposableVMGuardHash string `json:"disposable_vm_guard_sha256"`
OSRelease string `json:"os_release"`
Kernel string `json:"kernel"`
SystemdVersion string `json:"systemd_version"`
Architecture string `json:"architecture"`
CollectorServiceUser string `json:"collector_service_user"`
CollectorProcessUID int `json:"collector_process_uid"`
CollectorAuthority string `json:"collector_authority"`
AmbientCapabilitiesNone bool `json:"ambient_capabilities_none"`
HelperProtocolHealthy bool `json:"helper_protocol_healthy"`
StateIdentityPreserved bool `json:"state_identity_preserved"`
DockerDegraded bool `json:"docker_degraded"`
ActionRunnerQualified bool `json:"action_runner_qualified"`
ActionMutationVerified bool `json:"action_mutation_verified"`
CollectorAuthorityReductionRequestObserved bool `json:"collector_authority_reduction_request_observed"`
ActionReceiptKind string `json:"action_receipt_kind,omitempty"`
CredentialRotated bool `json:"credential_rotated"`
SelfRevokeObserved bool `json:"self_revoke_observed"`
CollectorContinuity bool `json:"collector_continuity"`
ReportCount int `json:"report_count"`
FirstReportAt string `json:"first_report_at"`
LastReportAt string `json:"last_report_at"`
Scenarios []secureRuntimeScenarioResult `json:"scenarios"`
SchemaVersion int `json:"schema_version"`
RecordPath string `json:"record_path"`
StartedAt string `json:"started_at"`
CompletedAt string `json:"completed_at"`
SourceManifest secureRuntimeSourceManifestBinding `json:"source_manifest"`
SourceHashes map[string]string `json:"source_hashes"`
ArtifactHashes map[string]string `json:"artifact_hashes"`
ArtifactVersions map[string]string `json:"artifact_versions"`
DisposableVMGuardHash string `json:"disposable_vm_guard_sha256"`
OSRelease string `json:"os_release"`
Kernel string `json:"kernel"`
SystemdVersion string `json:"systemd_version"`
Architecture string `json:"architecture"`
CollectorServiceUser string `json:"collector_service_user"`
CollectorProcessUID int `json:"collector_process_uid"`
CollectorAuthority string `json:"collector_authority"`
AmbientCapabilitiesNone bool `json:"ambient_capabilities_none"`
HelperProtocolHealthy bool `json:"helper_protocol_healthy"`
StateIdentityPreserved bool `json:"state_identity_preserved"`
DockerDegraded bool `json:"docker_degraded"`
ActionRunnerQualified bool `json:"action_runner_qualified"`
ActionMutationVerified bool `json:"action_mutation_verified"`
CollectorAuthorityReductionRequestObserved bool `json:"collector_authority_reduction_request_observed"`
ActionReceiptKind string `json:"action_receipt_kind,omitempty"`
CredentialRotated bool `json:"credential_rotated"`
SelfRevokeObserved bool `json:"self_revoke_observed"`
CollectorContinuity bool `json:"collector_continuity"`
ReportCount int `json:"report_count"`
FirstReportAt string `json:"first_report_at"`
LastReportAt string `json:"last_report_at"`
Transcript secureRuntimeTranscriptBinding `json:"transcript"`
Scenarios []secureRuntimeScenarioResult `json:"scenarios"`
}
var secureRuntimeTranscriptRecorder struct {
sync.Mutex
enabled bool
events []secureRuntimeTranscriptEvent
}
func secureRuntimeResetTranscript(enabled bool) {
secureRuntimeTranscriptRecorder.Lock()
defer secureRuntimeTranscriptRecorder.Unlock()
secureRuntimeTranscriptRecorder.enabled = enabled
secureRuntimeTranscriptRecorder.events = nil
}
func secureRuntimeRecordTranscriptEvent(event secureRuntimeTranscriptEvent) secureRuntimeTranscriptEvent {
secureRuntimeTranscriptRecorder.Lock()
defer secureRuntimeTranscriptRecorder.Unlock()
if !secureRuntimeTranscriptRecorder.enabled {
return event
}
event.Sequence = len(secureRuntimeTranscriptRecorder.events) + 1
event.EventID = fmt.Sprintf("event-%04d", event.Sequence)
if event.ObservedAt == "" {
event.ObservedAt = time.Now().UTC().Format(time.RFC3339Nano)
}
secureRuntimeTranscriptRecorder.events = append(secureRuntimeTranscriptRecorder.events, event)
return event
}
func secureRuntimeRecordCommandOutput(operation string, output []byte) {
secureRuntimeRecordTranscriptEvent(secureRuntimeTranscriptEvent{
Kind: "command_output", Operation: operation, Output: string(output),
OutputSHA256: secureRuntimeHash(output),
})
}
func secureRuntimeTranscriptSnapshot() []secureRuntimeTranscriptEvent {
secureRuntimeTranscriptRecorder.Lock()
defer secureRuntimeTranscriptRecorder.Unlock()
return append([]secureRuntimeTranscriptEvent(nil), secureRuntimeTranscriptRecorder.events...)
}
func TestSecureRuntimeReceiptCredentialDetection(t *testing.T) {
@ -601,6 +725,29 @@ func TestSecureRuntimeReceiptCredentialDetection(t *testing.T) {
}
}
func TestSecureRuntimeSourceManifestCoversTransitiveProviders(t *testing.T) {
_, hashes := secureRuntimeLoadSourceBoundary(t, runtime.GOARCH)
for _, required := range []string{
"internal/agenthelper/providers.go",
"internal/agenthelper/container_inventory.go",
"internal/hostagent/action_runner_client.go",
"internal/hostagent/action_runner_health_persistence_unix.go",
"internal/hostagent/package_updates.go",
"internal/api/action_runner_credentials.go",
"internal/api/router_routes_registration.go",
"internal/agentexec/server.go",
} {
if _, ok := hashes[required]; !ok {
t.Fatalf("secure-runtime source manifest omitted transitive boundary source %s", required)
}
}
for sourcePath := range hashes {
if strings.HasSuffix(sourcePath, "_test.go") && sourcePath != "scripts/installtests/secure_runtime_systemd_lab_test.go" {
t.Fatalf("secure-runtime production source manifest included test source %s", sourcePath)
}
}
}
func TestSecureRuntimeSystemdLab(t *testing.T) {
if os.Getenv(secureRuntimeLabOptIn) != "1" {
t.Skip("set PULSE_SECURE_RUNTIME_SYSTEMD_LAB=1 only inside a disposable systemd VM")
@ -620,50 +767,57 @@ func TestSecureRuntimeSystemdLab(t *testing.T) {
if err != nil {
t.Fatalf("resolve installer path: %v", err)
}
installer := secureRuntimeReadFile(t, installerPath)
fixture := newSecureRuntimeLabFixture(collectorV1, helper, runner, collectorV1Version)
defer fixture.actionServer.Shutdown()
server := httptest.NewServer(fixture)
defer server.Close()
recordPath := strings.TrimSpace(os.Getenv("PULSE_SECURE_RUNTIME_RECEIPT_RECORD_PATH"))
if recordPath == "" || filepath.IsAbs(recordPath) || filepath.ToSlash(filepath.Clean(recordPath)) != recordPath || strings.Contains(recordPath, "..") {
t.Fatalf("PULSE_SECURE_RUNTIME_RECEIPT_RECORD_PATH must be a canonical repository-relative path: %q", recordPath)
}
startedAt := time.Now().UTC()
sourceManifest, sourceHashes := secureRuntimeLoadSourceBoundary(t, runtime.GOARCH)
receipt := secureRuntimeLabReceipt{
SchemaVersion: 3,
SourceHashes: map[string]string{
"scripts/install.sh": secureRuntimeHash(installer),
"scripts/installtests/secure_runtime_systemd_lab_test.go": secureRuntimeHash(secureRuntimeReadFile(t, repoFile("scripts", "installtests", "secure_runtime_systemd_lab_test.go"))),
"scripts/release_control/secure_runtime_attestation.py": secureRuntimeHash(secureRuntimeReadFile(t, repoFile("scripts", "release_control", "secure_runtime_attestation.py"))),
"internal/agenthelper/update_activation.go": secureRuntimeHash(secureRuntimeReadFile(t, repoFile("internal", "agenthelper", "update_activation.go"))),
"internal/agenthelper/server.go": secureRuntimeHash(secureRuntimeReadFile(t, repoFile("internal", "agenthelper", "server.go"))),
"internal/agentexec/server.go": secureRuntimeHash(secureRuntimeReadFile(t, repoFile("internal", "agentexec", "server.go"))),
"internal/agentexec/types.go": secureRuntimeHash(secureRuntimeReadFile(t, repoFile("internal", "agentexec", "types.go"))),
"internal/agentexec/verifier_postconditions.go": secureRuntimeHash(secureRuntimeReadFile(t, repoFile("internal", "agentexec", "verifier_postconditions.go"))),
"internal/api/collector_authority.go": secureRuntimeHash(secureRuntimeReadFile(t, repoFile("internal", "api", "collector_authority.go"))),
"internal/api/action_runner_credentials.go": secureRuntimeHash(secureRuntimeReadFile(t, repoFile("internal", "api", "action_runner_credentials.go"))),
"internal/api/agenttokens/install.go": secureRuntimeHash(secureRuntimeReadFile(t, repoFile("internal", "api", "agenttokens", "install.go"))),
"internal/api/agent_exec_token_binding.go": secureRuntimeHash(secureRuntimeReadFile(t, repoFile("internal", "api", "agent_exec_token_binding.go"))),
"internal/actionrunner/runner.go": secureRuntimeHash(secureRuntimeReadFile(t, repoFile("internal", "actionrunner", "runner.go"))),
"internal/actionrunner/types.go": secureRuntimeHash(secureRuntimeReadFile(t, repoFile("internal", "actionrunner", "types.go"))),
"internal/actionrunner/transport.go": secureRuntimeHash(secureRuntimeReadFile(t, repoFile("internal", "actionrunner", "transport.go"))),
"internal/hostagent/storage_cleanup.go": secureRuntimeHash(secureRuntimeReadFile(t, repoFile("internal", "hostagent", "storage_cleanup.go"))),
"internal/operationreceipt/store.go": secureRuntimeHash(secureRuntimeReadFile(t, repoFile("internal", "operationreceipt", "store.go"))),
"internal/operationreceipt/types.go": secureRuntimeHash(secureRuntimeReadFile(t, repoFile("internal", "operationreceipt", "types.go"))),
"internal/agentupdate/update.go": secureRuntimeHash(secureRuntimeReadFile(t, repoFile("internal", "agentupdate", "update.go"))),
"internal/agentupdate/privileged_update.go": secureRuntimeHash(secureRuntimeReadFile(t, repoFile("internal", "agentupdate", "privileged_update.go"))),
"cmd/pulse-agent/main.go": secureRuntimeHash(secureRuntimeReadFile(t, repoFile("cmd", "pulse-agent", "main.go"))),
"cmd/pulse-agent-helper/main.go": secureRuntimeHash(secureRuntimeReadFile(t, repoFile("cmd", "pulse-agent-helper", "main.go"))),
"cmd/pulse-agent-runner/main.go": secureRuntimeHash(secureRuntimeReadFile(t, repoFile("cmd", "pulse-agent-runner", "main.go"))),
},
SchemaVersion: 4,
RecordPath: recordPath,
StartedAt: startedAt.Format(time.RFC3339Nano),
SourceManifest: sourceManifest,
SourceHashes: sourceHashes,
ArtifactHashes: map[string]string{"collector_v1": secureRuntimeHash(collectorV1), "collector_v2": secureRuntimeHash(collectorV2), "helper": secureRuntimeHash(helper), "runner": secureRuntimeHash(runner)},
ArtifactVersions: map[string]string{"collector_v1": collectorV1Version, "collector_v2": collectorV2Version},
DisposableVMGuardHash: secureRuntimeHash([]byte(secureRuntimeLabMarkerValue + "\n")),
Architecture: runtime.GOARCH,
}
secureRuntimeResetTranscript(true)
t.Cleanup(func() { secureRuntimeResetTranscript(false) })
receipt.OSRelease = strings.TrimSpace(string(secureRuntimeReadFile(t, "/etc/os-release")))
receipt.Kernel = secureRuntimeCommand(t, 10*time.Second, "uname", "-srvmo")
receipt.SystemdVersion = strings.SplitN(secureRuntimeCommand(t, 10*time.Second, "systemctl", "--version"), "\n", 2)[0]
pass := func(name, detail string) {
receipt.Scenarios = append(receipt.Scenarios, secureRuntimeScenarioResult{Name: name, Passed: true, Detail: detail})
scenarioStartedAt := startedAt
pass := func(name, detail string, observations map[string]any) {
claims, ok := secureRuntimeScenarioClaims[name]
if !ok {
t.Fatalf("scenario %q has no governed causal claims", name)
}
completedAt := time.Now().UTC()
sequence := len(receipt.Scenarios) + 1
claims = append([]string(nil), claims...)
event := secureRuntimeRecordTranscriptEvent(secureRuntimeTranscriptEvent{
ObservedAt: completedAt.Format(time.RFC3339Nano), Kind: "scenario_result",
Scenario: name, Claims: claims, Observations: observations, Summary: detail,
})
receipt.Scenarios = append(receipt.Scenarios, secureRuntimeScenarioResult{
Sequence: sequence, Name: name, Passed: true,
StartedAt: scenarioStartedAt.Format(time.RFC3339Nano), CompletedAt: completedAt.Format(time.RFC3339Nano),
Evidence: secureRuntimeScenarioEvidence{
Kind: "runtime-observation-v1", Summary: detail, Claims: claims,
Observations: observations,
TranscriptEventIDs: []string{event.EventID},
},
})
scenarioStartedAt = completedAt
}
initialArgs := []string{"--enable-commands", "--command-authority", "command-capable"}
@ -678,7 +832,7 @@ func TestSecureRuntimeSystemdLab(t *testing.T) {
if dockerInitiallyAvailable && !dockerInitiallyEnabled {
t.Fatal("rootful Docker was available but the requested legacy profile did not enable Docker monitoring")
}
pass("legacy_root_command_capable_install", fmt.Sprintf("root collector installed; docker_enabled=%t", dockerInitiallyEnabled))
pass("legacy_root_command_capable_install", fmt.Sprintf("root collector installed; docker_enabled=%t", dockerInitiallyEnabled), map[string]any{"collector_process_uid": 0, "commands_enabled": true})
beforeInspect := secureRuntimeStableSnapshot(t)
inspectOutput := secureRuntimeRunInstaller(t, installerPath, server.URL, "--safe-profile-inspect")
@ -689,7 +843,7 @@ func TestSecureRuntimeSystemdLab(t *testing.T) {
if afterInspect := secureRuntimeStableSnapshot(t); !secureRuntimeIdentitiesEqual(beforeInspect, afterInspect) {
t.Fatal("--safe-profile-inspect mutated installer-owned stable files")
}
pass("read_only_inspect", "stable installer-owned files unchanged")
pass("read_only_inspect", "stable installer-owned files unchanged", map[string]any{"stable_files_unchanged": true})
dropInDir := "/etc/systemd/system/pulse-agent.service.d"
dropInPath := filepath.Join(dropInDir, "secure-runtime-lab.conf")
@ -722,7 +876,7 @@ func TestSecureRuntimeSystemdLab(t *testing.T) {
_ = os.Remove(dropInDir)
dropInPresent = false
secureRuntimeCommand(t, 20*time.Second, "systemctl", "daemon-reload")
pass("drop_in_fail_closed_rehearsal", "migration rejected before stable installer-owned files changed")
pass("drop_in_fail_closed_rehearsal", "migration rejected before stable installer-owned files changed", map[string]any{"rejected_before_mutation": true})
legacyBaseline := secureRuntimeStableSnapshot(t)
reportsBeforeApply, preApplyLastSeen, _, _ := fixture.snapshot()
@ -744,14 +898,14 @@ func TestSecureRuntimeSystemdLab(t *testing.T) {
t.Fatalf("safe migration did not make rootful Docker degradation explicit:\n%s", applyOutput)
}
}
pass("safe_profile_apply", "fresh server lastSeen, least-privilege identity, typed helper health")
pass("safe_profile_apply", "fresh server lastSeen, least-privilege identity, typed helper health", map[string]any{"collector_service_user": "pulse-agent", "collector_authority": "monitoring-only", "helper_status": "ok"})
reportsBeforeRollback, _, _, _ := fixture.snapshot()
secureRuntimeRunInstaller(t, installerPath, server.URL, "--safe-profile-rollback")
secureRuntimeWaitForReports(t, fixture, len(reportsBeforeRollback)+1, 45*time.Second)
secureRuntimeAssertRootMonitoringProfile(t)
secureRuntimeWaitForStableIdentity(t, secureRuntimeWithoutCollectorUnit(legacyBaseline), 10*time.Second, "explicit rollback")
pass("explicit_safe_profile_rollback", "legacy binary and service identity restored without resurrecting collector command authority")
pass("explicit_safe_profile_rollback", "legacy binary and service identity restored without resurrecting collector command authority", map[string]any{"restored_profile": "root-monitoring", "commands_enabled": false})
automaticRollbackBaseline := secureRuntimeStableSnapshot(t)
fixture.setFrozen(true)
@ -762,7 +916,7 @@ func TestSecureRuntimeSystemdLab(t *testing.T) {
}
secureRuntimeWaitForStableIdentity(t, secureRuntimeWithoutCollectorUnit(automaticRollbackBaseline), 10*time.Second, "automatic failure rollback")
secureRuntimeAssertRootMonitoringProfile(t)
pass("automatic_failure_rollback", "frozen lastSeen prevented commit and restored binary/state identity without command authority")
pass("automatic_failure_rollback", "frozen lastSeen prevented commit and restored binary/state identity without command authority", map[string]any{"activation_committed": false, "restored_profile": "root-monitoring"})
fixture.setCollector(collectorV2)
reportsBeforeUpdate, _, _, _ := fixture.snapshot()
@ -772,7 +926,7 @@ func TestSecureRuntimeSystemdLab(t *testing.T) {
if got := secureRuntimeHash(secureRuntimeReadFile(t, "/usr/local/bin/pulse-agent")); got != secureRuntimeHash(collectorV2) {
t.Fatalf("ordinary update collector hash = %s, want v2 hash", got)
}
pass("ordinary_update_non_migration", "binary updated while the downgraded root monitoring profile remained unchanged")
pass("ordinary_update_non_migration", "binary updated while the downgraded root monitoring profile remained unchanged", map[string]any{"collector_v2_installed": true, "selected_profile": "root-monitoring"})
fixture.setServerVersion(collectorV2Version)
reportsBeforeFinalApply, preFinalLastSeen, _, _ := fixture.snapshot()
@ -789,7 +943,7 @@ func TestSecureRuntimeSystemdLab(t *testing.T) {
secureRuntimeAssertHelperProtocol(t)
reportsBeforeContinuity, _, _, _ := fixture.snapshot()
secureRuntimeWaitForReports(t, fixture, len(reportsBeforeContinuity)+1, 20*time.Second)
pass("final_safe_profile_apply", "collector continued reporting after committed migration")
pass("final_safe_profile_apply", "collector continued reporting after committed migration", map[string]any{"collector_service_user": "pulse-agent", "continuity_report_observed": true})
reportsBeforeRunner, _, _, _ := fixture.snapshot()
secureRuntimeRunInstallerWithActionCredential(t, installerPath, server.URL, secureRuntimeRunnerSecretV1,
@ -797,7 +951,7 @@ func TestSecureRuntimeSystemdLab(t *testing.T) {
secureRuntimeWaitForActionRunner(t, fixture, true, 30*time.Second)
secureRuntimeAssertActionRunnerInstalled(t)
secureRuntimeWaitForReports(t, fixture, len(reportsBeforeRunner)+1, 20*time.Second)
pass("separate_action_runner_install", "root action runner registered independently while the collector remained non-root and reporting")
pass("separate_action_runner_install", "root action runner registered independently while the collector remained non-root and reporting", map[string]any{"runner_service_user": "root", "collector_service_user": "pulse-agent"})
actionContext, cancelAction := context.WithTimeout(agentexec.WithOrganizationID(context.Background(), secureRuntimeLabOrgID), 30*time.Second)
defer cancelAction()
@ -843,7 +997,7 @@ func TestSecureRuntimeSystemdLab(t *testing.T) {
}); err == nil || !strings.Contains(err.Error(), "typed action-runner") {
t.Fatalf("action runner did not reject generic command dispatch: %v", err)
}
pass("typed_action_receipt", "verified apt cache mutation and durable terminal receipt; stale fingerprint refused before mutation; generic command dispatch denied")
pass("typed_action_receipt", "verified apt cache mutation and durable terminal receipt; stale fingerprint refused before mutation; generic command dispatch denied", map[string]any{"action_receipt_kind": agentexec.HostStorageCleanupReceiptKind, "mutation_started": true, "verification": "verified", "stale_precondition_mutation_started": false, "generic_command_denied": true})
currentAdmission, _, _ := fixture.actionSnapshot()
wrongAdmission := currentAdmission
@ -863,7 +1017,7 @@ func TestSecureRuntimeSystemdLab(t *testing.T) {
if revoked || rotatedAdmission.TokenID != secureRuntimeRunnerBindingV2 {
t.Fatalf("rotated action-runner admission = %+v revoked=%t", rotatedAdmission, revoked)
}
pass("action_runner_credential_rotation", "mismatched invalidation was rejected, exact superseded session closed, replacement credential registered")
pass("action_runner_credential_rotation", "mismatched invalidation was rejected, exact superseded session closed, replacement credential registered", map[string]any{"proof_scope": "in-memory-fixture", "superseded_session_invalidated": true, "replacement_registered": true})
reportsBeforeRemoval, _, _, _ := fixture.snapshot()
uninstallOutput := secureRuntimeRunStandaloneInstaller(t, installerPath, "--uninstall-action-runner")
@ -879,7 +1033,7 @@ func TestSecureRuntimeSystemdLab(t *testing.T) {
secureRuntimeAssertSafeProfile(t)
secureRuntimeAssertHelperProtocol(t)
secureRuntimeWaitForReports(t, fixture, len(reportsBeforeRemoval)+1, 20*time.Second)
pass("action_runner_self_revoke", "uninstall revoked the exact host binding and removed only runner state; collector/helper continuity remained healthy")
pass("action_runner_self_revoke", "uninstall revoked the exact host binding and removed only runner state; collector/helper continuity remained healthy", map[string]any{"revocation_count": revokeCount, "collector_continuity": true})
reports, _, _, _ := fixture.snapshot()
if len(reports) == 0 {
@ -916,7 +1070,7 @@ func TestSecureRuntimeSystemdLab(t *testing.T) {
receipt.ReportCount = len(reports)
receipt.FirstReportAt = reports[0].ReceivedAt.Format(time.RFC3339Nano)
receipt.LastReportAt = reports[len(reports)-1].ReceivedAt.Format(time.RFC3339Nano)
secureRuntimeWriteReceipt(t, receipt)
secureRuntimeWriteEvidence(t, &receipt, secureRuntimeTranscriptSnapshot())
}
func secureRuntimeRequireDisposableHost(t *testing.T) {
@ -995,6 +1149,81 @@ func secureRuntimeHash(data []byte) string {
return hex.EncodeToString(sum[:])
}
func secureRuntimeLoadSourceBoundary(t *testing.T, targetArch string) (secureRuntimeSourceManifestBinding, map[string]string) {
t.Helper()
repoRoot, err := filepath.Abs(repoFile())
if err != nil {
t.Fatalf("resolve repository root: %v", err)
}
manifestRelative := "scripts/release_control/secure_runtime_source_manifest_v4.json"
manifestRaw := secureRuntimeReadFile(t, filepath.Join(repoRoot, filepath.FromSlash(manifestRelative)))
var manifest secureRuntimeSourceManifest
if err := json.Unmarshal(manifestRaw, &manifest); err != nil {
t.Fatalf("decode secure-runtime source manifest: %v", err)
}
if manifest.SchemaVersion != 1 || manifest.ManifestID != "secure-runtime-linux-v4" || manifest.TargetOS != "linux" {
t.Fatalf("unsupported secure-runtime source manifest: %+v", manifest)
}
sourceHashes := make(map[string]string)
addSource := func(relative string) {
if relative == "" || filepath.IsAbs(relative) || strings.Contains(relative, "..") || filepath.ToSlash(filepath.Clean(relative)) != relative {
t.Fatalf("source manifest contains non-canonical path %q", relative)
}
sourceHashes[relative] = secureRuntimeHash(secureRuntimeReadFile(t, filepath.Join(repoRoot, filepath.FromSlash(relative))))
}
for _, relative := range manifest.ExactPaths {
addSource(relative)
}
if _, ok := sourceHashes[manifestRelative]; !ok {
t.Fatal("secure-runtime source manifest does not include itself")
}
for _, relativeRoot := range manifest.RecursiveRoots {
root := filepath.Join(repoRoot, filepath.FromSlash(relativeRoot))
matched := 0
err := filepath.WalkDir(root, func(path string, entry fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if entry.IsDir() {
return nil
}
relative, err := filepath.Rel(repoRoot, path)
if err != nil {
return err
}
relative = filepath.ToSlash(relative)
included := false
for _, suffix := range manifest.IncludeSuffixes {
included = included || strings.HasSuffix(relative, suffix)
}
for _, suffix := range manifest.ExcludeSuffixes {
if strings.HasSuffix(relative, suffix) {
included = false
}
}
if included {
addSource(relative)
matched++
}
return nil
})
if err != nil {
t.Fatalf("expand secure-runtime source root %s: %v", relativeRoot, err)
}
if matched == 0 {
t.Fatalf("secure-runtime source root %s matched no production source", relativeRoot)
}
}
return secureRuntimeSourceManifestBinding{
SchemaVersion: manifest.SchemaVersion,
ManifestID: manifest.ManifestID,
Path: manifestRelative,
SHA256: secureRuntimeHash(manifestRaw),
TargetOS: manifest.TargetOS,
TargetArch: targetArch,
}, sourceHashes
}
func secureRuntimeRunInstaller(t *testing.T, installerPath, fixtureURL string, scenarioArgs ...string) string {
t.Helper()
out, err := secureRuntimeRunInstallerErrorWithActionCredential(t, installerPath, fixtureURL, "", scenarioArgs...)
@ -1059,6 +1288,7 @@ func secureRuntimeRunInstallerErrorWithActionCredential(t *testing.T, installerP
return string(out), fmt.Errorf("installer timed out: %w", ctx.Err())
}
secureRuntimeAssertNoCredentialExposure(t, out)
secureRuntimeRecordCommandOutput("installer "+strings.Join(scenarioArgs, " "), out)
return string(out), err
}
@ -1073,6 +1303,7 @@ func secureRuntimeRunStandaloneInstaller(t *testing.T, installerPath string, sce
t.Fatalf("standalone installer timed out: %v\n%s", ctx.Err(), out)
}
secureRuntimeAssertNoCredentialExposure(t, out)
secureRuntimeRecordCommandOutput("standalone installer "+strings.Join(scenarioArgs, " "), out)
if err != nil {
t.Fatalf("standalone installer %s failed: %v\n%s", strings.Join(scenarioArgs, " "), err, out)
}
@ -1122,6 +1353,8 @@ func secureRuntimeCommand(t *testing.T, timeout time.Duration, name string, args
if err != nil {
t.Fatalf("%s %s: %v\n%s", name, strings.Join(args, " "), err, out)
}
secureRuntimeAssertNoCredentialExposure(t, out)
secureRuntimeRecordCommandOutput(name, out)
return strings.TrimSpace(string(out))
}
@ -1431,7 +1664,7 @@ func secureRuntimeCollectorOwnedRootlessAvailable(t *testing.T) bool {
return false
}
func secureRuntimeWriteReceipt(t *testing.T, receipt secureRuntimeLabReceipt) {
func secureRuntimeWriteEvidence(t *testing.T, receipt *secureRuntimeLabReceipt, events []secureRuntimeTranscriptEvent) {
t.Helper()
path := strings.TrimSpace(os.Getenv("PULSE_SECURE_RUNTIME_RECEIPT"))
if path == "" {
@ -1440,6 +1673,35 @@ func secureRuntimeWriteReceipt(t *testing.T, receipt secureRuntimeLabReceipt) {
if !filepath.IsAbs(path) {
t.Fatalf("PULSE_SECURE_RUNTIME_RECEIPT must be an absolute path: %s", path)
}
transcriptPath := strings.TrimSpace(os.Getenv("PULSE_SECURE_RUNTIME_TRANSCRIPT"))
if transcriptPath == "" || !filepath.IsAbs(transcriptPath) {
t.Fatalf("PULSE_SECURE_RUNTIME_TRANSCRIPT must be an absolute path: %s", transcriptPath)
}
transcriptRecordPath := strings.TrimSpace(os.Getenv("PULSE_SECURE_RUNTIME_TRANSCRIPT_RECORD_PATH"))
if transcriptRecordPath == "" || filepath.IsAbs(transcriptRecordPath) || filepath.ToSlash(filepath.Clean(transcriptRecordPath)) != transcriptRecordPath || strings.Contains(transcriptRecordPath, "..") {
t.Fatalf("PULSE_SECURE_RUNTIME_TRANSCRIPT_RECORD_PATH must be a canonical repository-relative path: %q", transcriptRecordPath)
}
var transcript bytes.Buffer
for _, event := range events {
encodedEvent, err := json.Marshal(event)
if err != nil {
t.Fatalf("marshal secure-runtime transcript event: %v", err)
}
if secureRuntimeReceiptContainsCredential(encodedEvent) {
t.Fatal("refusing to write a transcript containing credential material or token-labelled fields")
}
transcript.Write(encodedEvent)
transcript.WriteByte('\n')
}
transcriptBytes := transcript.Bytes()
receipt.Transcript = secureRuntimeTranscriptBinding{
Format: "jsonl-v1", RecordPath: transcriptRecordPath,
SHA256: secureRuntimeHash(transcriptBytes), EventCount: len(events),
}
if err := secureRuntimeWritePrivateAtomic(transcriptPath, transcriptBytes); err != nil {
t.Fatalf("publish secure-runtime transcript: %v", err)
}
encoded, err := json.MarshalIndent(receipt, "", " ")
if err != nil {
t.Fatalf("marshal secure-runtime receipt: %v", err)
@ -1448,18 +1710,22 @@ func secureRuntimeWriteReceipt(t *testing.T, receipt secureRuntimeLabReceipt) {
t.Fatal("refusing to write a receipt containing credential material or token-labelled fields")
}
encoded = append(encoded, '\n')
temporary := path + ".tmp"
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatalf("create receipt directory: %v", err)
}
if err := os.WriteFile(temporary, encoded, 0o600); err != nil {
t.Fatalf("write receipt temporary file: %v", err)
}
if err := os.Rename(temporary, path); err != nil {
if err := secureRuntimeWritePrivateAtomic(path, encoded); err != nil {
t.Fatalf("publish receipt: %v", err)
}
}
func secureRuntimeWritePrivateAtomic(path string, data []byte) error {
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
temporary := path + ".tmp"
if err := os.WriteFile(temporary, data, 0o600); err != nil {
return err
}
return os.Rename(temporary, path)
}
func secureRuntimeReceiptContainsCredential(encoded []byte) bool {
for _, credential := range [][]byte{
[]byte(secureRuntimeLabToken),

View file

@ -10,12 +10,18 @@ import math
import re
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path, PurePosixPath
from typing import Any, Sequence
COMMIT_RE = re.compile(r"^[0-9a-f]{40}$")
SHA256_RE = re.compile(r"^[0-9a-f]{64}$")
RECEIPT_SCHEMA_VERSION = 4
ATTESTATION_SCHEMA_VERSION = 4
SOURCE_MANIFEST_PATH = "scripts/release_control/secure_runtime_source_manifest_v4.json"
SOURCE_MANIFEST_SCHEMA_VERSION = 1
SOURCE_MANIFEST_ID = "secure-runtime-linux-v4"
REQUIRED_SCENARIOS = (
"legacy_root_command_capable_install",
"read_only_inspect",
@ -30,6 +36,58 @@ REQUIRED_SCENARIOS = (
"action_runner_credential_rotation",
"action_runner_self_revoke",
)
SCENARIO_REQUIRED_CLAIMS = {
"legacy_root_command_capable_install": {"legacy_root_command_authority_observed"},
"read_only_inspect": {"inspection_left_stable_files_unchanged"},
"drop_in_fail_closed_rehearsal": {"drop_in_rejected_before_mutation"},
"safe_profile_apply": {
"collector_non_root",
"collector_monitoring_only",
"helper_protocol_healthy",
"collector_authority_reduction_observed",
},
"explicit_safe_profile_rollback": {"explicit_rollback_preserved_reduced_authority"},
"automatic_failure_rollback": {"failed_activation_restored_prior_runtime"},
"ordinary_update_non_migration": {"ordinary_update_preserved_selected_profile"},
"final_safe_profile_apply": {"collector_reporting_continued_after_migration"},
"separate_action_runner_install": {"action_runner_registered_separately"},
"typed_action_receipt": {
"typed_mutation_verified",
"terminal_receipt_replayed",
"stale_precondition_refused",
"generic_command_denied",
},
"action_runner_credential_rotation": {"fixture_credential_replacement_observed"},
"action_runner_self_revoke": {"exact_runner_binding_revoked"},
}
SCENARIO_REQUIRED_OBSERVATIONS = {
"legacy_root_command_capable_install": {"collector_process_uid": 0, "commands_enabled": True},
"read_only_inspect": {"stable_files_unchanged": True},
"drop_in_fail_closed_rehearsal": {"rejected_before_mutation": True},
"safe_profile_apply": {
"collector_service_user": "pulse-agent",
"collector_authority": "monitoring-only",
"helper_status": "ok",
},
"explicit_safe_profile_rollback": {"restored_profile": "root-monitoring", "commands_enabled": False},
"automatic_failure_rollback": {"activation_committed": False, "restored_profile": "root-monitoring"},
"ordinary_update_non_migration": {"collector_v2_installed": True, "selected_profile": "root-monitoring"},
"final_safe_profile_apply": {"collector_service_user": "pulse-agent", "continuity_report_observed": True},
"separate_action_runner_install": {"runner_service_user": "root", "collector_service_user": "pulse-agent"},
"typed_action_receipt": {
"action_receipt_kind": "pulse.host_storage_cleanup_result",
"mutation_started": True,
"verification": "verified",
"stale_precondition_mutation_started": False,
"generic_command_denied": True,
},
"action_runner_credential_rotation": {
"proof_scope": "in-memory-fixture",
"superseded_session_invalidated": True,
"replacement_registered": True,
},
"action_runner_self_revoke": {"revocation_count": 1, "collector_continuity": True},
}
ARTIFACT_ARGUMENTS = {
"collector_v1": "collector_v1",
"collector_v2": "collector_v2",
@ -42,31 +100,6 @@ EXPECTED_ARTIFACT_PACKAGES = {
"helper": "github.com/rcourtman/pulse-go-rewrite/cmd/pulse-agent-helper",
"runner": "github.com/rcourtman/pulse-go-rewrite/cmd/pulse-agent-runner",
}
REQUIRED_SOURCE_PATHS = {
"scripts/install.sh",
"scripts/installtests/secure_runtime_systemd_lab_test.go",
"scripts/release_control/secure_runtime_attestation.py",
"internal/agenthelper/update_activation.go",
"internal/agenthelper/server.go",
"internal/agentexec/server.go",
"internal/agentexec/types.go",
"internal/agentexec/verifier_postconditions.go",
"internal/api/collector_authority.go",
"internal/api/action_runner_credentials.go",
"internal/api/agenttokens/install.go",
"internal/api/agent_exec_token_binding.go",
"internal/actionrunner/runner.go",
"internal/actionrunner/types.go",
"internal/actionrunner/transport.go",
"internal/hostagent/storage_cleanup.go",
"internal/operationreceipt/store.go",
"internal/operationreceipt/types.go",
"internal/agentupdate/update.go",
"internal/agentupdate/privileged_update.go",
"cmd/pulse-agent/main.go",
"cmd/pulse-agent-helper/main.go",
"cmd/pulse-agent-runner/main.go",
}
DISPOSABLE_VM_GUARD_SHA256 = hashlib.sha256(
b"PULSE_SECURE_RUNTIME_SYSTEMD_LAB=disposable-v1\n"
).hexdigest()
@ -171,48 +204,227 @@ def reject_sensitive_keys(value: Any, path: str = "receipt") -> None:
reject_sensitive_keys(child, f"{path}[{index}]")
def canonical_repo_path(value: Any, label: str) -> str:
if not isinstance(value, str) or not value:
raise AttestationError(f"{label} must be a non-empty repository-relative path")
path = PurePosixPath(value)
if path.is_absolute() or ".." in path.parts or str(path) != value:
raise AttestationError(f"{label} must be a canonical repository-relative path")
return value
def load_source_manifest(checkout: Path, commit: str) -> tuple[dict[str, Any], bytes, set[str]]:
raw = run_git(checkout, "show", f"{commit}:{SOURCE_MANIFEST_PATH}").stdout
try:
manifest = json.loads(raw)
except json.JSONDecodeError as exc:
raise AttestationError(f"unable to decode {SOURCE_MANIFEST_PATH}: {exc}") from exc
if not isinstance(manifest, dict):
raise AttestationError("secure-runtime source manifest must be an object")
if manifest.get("schema_version") != SOURCE_MANIFEST_SCHEMA_VERSION:
raise AttestationError("secure-runtime source manifest schema is unsupported")
if manifest.get("manifest_id") != SOURCE_MANIFEST_ID:
raise AttestationError("secure-runtime source manifest identity is unsupported")
if manifest.get("target_os") != "linux":
raise AttestationError("secure-runtime source manifest must target Linux")
exact_paths = manifest.get("exact_paths")
recursive_roots = manifest.get("recursive_roots")
include_suffixes = manifest.get("include_suffixes")
exclude_suffixes = manifest.get("exclude_suffixes")
if not isinstance(exact_paths, list) or not exact_paths:
raise AttestationError("secure-runtime source manifest exact_paths must be a non-empty list")
if not isinstance(recursive_roots, list) or not recursive_roots:
raise AttestationError("secure-runtime source manifest recursive_roots must be a non-empty list")
if not isinstance(include_suffixes, list) or not include_suffixes:
raise AttestationError("secure-runtime source manifest include_suffixes must be a non-empty list")
if not isinstance(exclude_suffixes, list):
raise AttestationError("secure-runtime source manifest exclude_suffixes must be a list")
if any(not isinstance(suffix, str) or not suffix for suffix in include_suffixes + exclude_suffixes):
raise AttestationError("secure-runtime source manifest suffixes must be non-empty strings")
expected = {canonical_repo_path(value, "source manifest exact path") for value in exact_paths}
if SOURCE_MANIFEST_PATH not in expected:
raise AttestationError("secure-runtime source manifest must include itself")
for raw_root in recursive_roots:
root = canonical_repo_path(raw_root, "source manifest recursive root").rstrip("/")
tree = run_git(checkout, "ls-tree", "-r", "--name-only", commit, "--", root).stdout
matched = 0
for raw_path in tree.decode("utf-8", errors="strict").splitlines():
source_path = canonical_repo_path(raw_path, "source manifest expanded path")
if not any(source_path.endswith(suffix) for suffix in include_suffixes):
continue
if any(source_path.endswith(suffix) for suffix in exclude_suffixes):
continue
expected.add(source_path)
matched += 1
if matched == 0:
raise AttestationError(f"secure-runtime source manifest root has no production sources: {root}")
return manifest, raw, expected
def load_receipt(path: Path) -> tuple[dict[str, Any], bytes]:
try:
raw = path.read_bytes()
receipt = json.loads(raw)
except (OSError, json.JSONDecodeError) as exc:
raise AttestationError(f"unable to load receipt {path}: {exc}") from exc
if not isinstance(receipt, dict) or receipt.get("schema_version") != 3:
raise AttestationError("receipt must be a schema_version 3 object")
if not isinstance(receipt, dict) or receipt.get("schema_version") != RECEIPT_SCHEMA_VERSION:
raise AttestationError(f"receipt must be a schema_version {RECEIPT_SCHEMA_VERSION} object")
reject_sensitive_keys(receipt)
return receipt, raw
def verify_scenarios(receipt: dict[str, Any]) -> None:
def parse_utc_timestamp(value: Any, label: str) -> datetime:
if not isinstance(value, str) or not value:
raise AttestationError(f"{label} must be an RFC3339 timestamp")
try:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
except ValueError as exc:
raise AttestationError(f"{label} must be an RFC3339 timestamp") from exc
if parsed.tzinfo is None or parsed.utcoffset() != timezone.utc.utcoffset(parsed):
raise AttestationError(f"{label} must be UTC")
return parsed
def load_and_verify_transcript(
path: Path, receipt: dict[str, Any]
) -> tuple[list[dict[str, Any]], bytes]:
try:
raw = path.read_bytes()
except OSError as exc:
raise AttestationError(f"unable to read transcript {path}: {exc}") from exc
transcript = receipt.get("transcript")
if not isinstance(transcript, dict):
raise AttestationError("receipt transcript binding must be an object")
if transcript.get("format") != "jsonl-v1":
raise AttestationError("receipt transcript format must be jsonl-v1")
canonical_repo_path(transcript.get("record_path"), "transcript record path")
expected_digest = transcript.get("sha256")
if not isinstance(expected_digest, str) or not SHA256_RE.fullmatch(expected_digest):
raise AttestationError("receipt transcript digest is invalid")
if sha256_bytes(raw) != expected_digest:
raise AttestationError("transcript digest mismatch")
events: list[dict[str, Any]] = []
previous_time: datetime | None = None
event_ids: set[str] = set()
for line_number, raw_line in enumerate(raw.splitlines(), 1):
if not raw_line.strip():
raise AttestationError(f"transcript line {line_number} is empty")
try:
event = json.loads(raw_line)
except json.JSONDecodeError as exc:
raise AttestationError(f"transcript line {line_number} is not JSON") from exc
if not isinstance(event, dict):
raise AttestationError(f"transcript line {line_number} must be an object")
reject_sensitive_keys(event, f"transcript[{line_number}]")
if event.get("sequence") != line_number:
raise AttestationError("transcript event sequence is not contiguous")
event_id = event.get("event_id")
if not isinstance(event_id, str) or not event_id or event_id in event_ids:
raise AttestationError("transcript event IDs must be non-empty and unique")
event_ids.add(event_id)
observed_at = parse_utc_timestamp(event.get("observed_at"), f"transcript event {event_id} observed_at")
if previous_time is not None and observed_at < previous_time:
raise AttestationError("transcript chronology is not monotonic")
previous_time = observed_at
kind = event.get("kind")
if kind == "scenario_result":
if not isinstance(event.get("scenario"), str):
raise AttestationError(f"transcript event {event_id} has no scenario")
claims = event.get("claims")
if not isinstance(claims, list) or not claims or any(not isinstance(claim, str) or not claim for claim in claims):
raise AttestationError(f"transcript event {event_id} claims are invalid")
if len(set(claims)) != len(claims):
raise AttestationError(f"transcript event {event_id} repeats a claim")
observations = event.get("observations")
if not isinstance(observations, dict) or not observations:
raise AttestationError(f"transcript event {event_id} observations are invalid")
if not isinstance(event.get("summary"), str) or not event["summary"].strip():
raise AttestationError(f"transcript event {event_id} summary is invalid")
elif kind == "command_output":
if not isinstance(event.get("operation"), str) or not event["operation"].strip():
raise AttestationError(f"transcript command event {event_id} operation is invalid")
output = event.get("output")
if not isinstance(output, str):
raise AttestationError(f"transcript command event {event_id} output is invalid")
output_digest = event.get("output_sha256")
if not isinstance(output_digest, str) or output_digest != sha256_bytes(output.encode("utf-8")):
raise AttestationError(f"transcript command event {event_id} output digest is invalid")
else:
raise AttestationError(f"transcript event {event_id} has an unsupported kind")
events.append(event)
scenario_event_count = sum(event.get("kind") == "scenario_result" for event in events)
command_event_count = sum(event.get("kind") == "command_output" for event in events)
if transcript.get("event_count") != len(events) or scenario_event_count != len(REQUIRED_SCENARIOS) or command_event_count < 1:
raise AttestationError("transcript event count does not contain the canonical scenarios and raw command evidence")
return events, raw
def verify_scenarios(receipt: dict[str, Any], transcript_events: list[dict[str, Any]]) -> None:
scenarios = receipt.get("scenarios")
if not isinstance(scenarios, list):
raise AttestationError("receipt scenarios must be a list")
names: list[str] = []
for scenario in scenarios:
run_started = parse_utc_timestamp(receipt.get("started_at"), "receipt started_at")
run_completed = parse_utc_timestamp(receipt.get("completed_at"), "receipt completed_at")
if run_completed < run_started:
raise AttestationError("receipt chronology ends before it starts")
for event in transcript_events:
observed = parse_utc_timestamp(event.get("observed_at"), f"transcript event {event.get('event_id')} observed_at")
if observed < run_started or observed > run_completed:
raise AttestationError("transcript event falls outside the receipt chronology")
previous_completed = run_started
transcript_by_id = {event["event_id"]: event for event in transcript_events}
for index, scenario in enumerate(scenarios, 1):
if not isinstance(scenario, dict) or not isinstance(scenario.get("name"), str):
raise AttestationError("every receipt scenario must have a string name")
if scenario.get("passed") is not True:
raise AttestationError(f"scenario {scenario['name']} did not pass")
if scenario.get("sequence") != index:
raise AttestationError("receipt scenario sequence is not contiguous")
started = parse_utc_timestamp(scenario.get("started_at"), f"scenario {scenario['name']} started_at")
completed = parse_utc_timestamp(scenario.get("completed_at"), f"scenario {scenario['name']} completed_at")
if started < previous_completed or completed < started or completed > run_completed:
raise AttestationError(f"scenario {scenario['name']} chronology is invalid")
previous_completed = completed
evidence = scenario.get("evidence")
if not isinstance(evidence, dict) or evidence.get("kind") != "runtime-observation-v1":
raise AttestationError(f"scenario {scenario['name']} evidence kind is invalid")
if not isinstance(evidence.get("summary"), str) or not evidence["summary"].strip():
raise AttestationError(f"scenario {scenario['name']} evidence summary is invalid")
claims = evidence.get("claims")
if not isinstance(claims, list) or any(not isinstance(claim, str) or not claim for claim in claims):
raise AttestationError(f"scenario {scenario['name']} evidence claims are invalid")
required_claims = SCENARIO_REQUIRED_CLAIMS.get(scenario["name"], set())
if not required_claims.issubset(set(claims)):
raise AttestationError(f"scenario {scenario['name']} omits required causal claims")
observations = evidence.get("observations")
if not isinstance(observations, dict) or not observations:
raise AttestationError(f"scenario {scenario['name']} observations are invalid")
required_observations = SCENARIO_REQUIRED_OBSERVATIONS.get(scenario["name"], {})
if any(observations.get(key) != value for key, value in required_observations.items()):
raise AttestationError(f"scenario {scenario['name']} omits required typed observations")
event_ids = evidence.get("transcript_event_ids")
if not isinstance(event_ids, list) or len(event_ids) != 1 or not isinstance(event_ids[0], str):
raise AttestationError(f"scenario {scenario['name']} transcript evidence is invalid")
event = transcript_by_id.get(event_ids[0])
if (
event is None
or event.get("scenario") != scenario["name"]
or event.get("claims") != claims
or event.get("observations") != observations
):
raise AttestationError(f"scenario {scenario['name']} is not causally bound to its transcript event")
if parse_utc_timestamp(event.get("observed_at"), f"scenario {scenario['name']} transcript time") != completed:
raise AttestationError(f"scenario {scenario['name']} completion is not bound to its transcript time")
names.append(scenario["name"])
if tuple(names) != REQUIRED_SCENARIOS:
raise AttestationError("receipt scenario set or order does not match the canonical 12-scenario qualification")
def verify_runtime_claims(receipt: dict[str, Any]) -> None:
required_true = (
"ambient_capabilities_none",
"helper_protocol_healthy",
"state_identity_preserved",
"action_runner_qualified",
"action_mutation_verified",
"collector_authority_reduction_request_observed",
"credential_rotated",
"self_revoke_observed",
"collector_continuity",
)
for key in required_true:
if receipt.get(key) is not True:
raise AttestationError(f"receipt does not prove required runtime claim {key}")
if receipt.get("collector_service_user") != "pulse-agent":
raise AttestationError("receipt collector service user is not pulse-agent")
if not isinstance(receipt.get("collector_process_uid"), int) or receipt["collector_process_uid"] <= 0:
@ -221,15 +433,48 @@ def verify_runtime_claims(receipt: dict[str, Any]) -> None:
raise AttestationError("receipt collector authority is not monitoring-only")
if receipt.get("disposable_vm_guard_sha256") != DISPOSABLE_VM_GUARD_SHA256:
raise AttestationError("receipt disposable-VM guard claim is missing or invalid")
if receipt.get("action_receipt_kind") != "pulse.host_storage_cleanup_result":
raise AttestationError("receipt action result kind is not the qualified typed mutation")
report_count = receipt.get("report_count")
if not isinstance(report_count, int) or report_count < 2:
raise AttestationError("receipt report count does not prove continuity")
first_report = parse_utc_timestamp(receipt.get("first_report_at"), "receipt first_report_at")
last_report = parse_utc_timestamp(receipt.get("last_report_at"), "receipt last_report_at")
if last_report <= first_report:
raise AttestationError("receipt report chronology does not prove continuity")
run_started = parse_utc_timestamp(receipt.get("started_at"), "receipt started_at")
run_completed = parse_utc_timestamp(receipt.get("completed_at"), "receipt completed_at")
if first_report < run_started or last_report > run_completed:
raise AttestationError("receipt reports fall outside the qualification run chronology")
def verify_source_hashes(checkout: Path, commit: str, receipt: dict[str, Any]) -> dict[str, str]:
def verify_source_hashes(
checkout: Path, commit: str, receipt: dict[str, Any]
) -> tuple[dict[str, str], dict[str, Any]]:
manifest, manifest_raw, expected_paths = load_source_manifest(checkout, commit)
binding = receipt.get("source_manifest")
if not isinstance(binding, dict):
raise AttestationError("receipt source_manifest binding must be an object")
expected_binding = {
"schema_version": SOURCE_MANIFEST_SCHEMA_VERSION,
"manifest_id": SOURCE_MANIFEST_ID,
"path": SOURCE_MANIFEST_PATH,
"sha256": sha256_bytes(manifest_raw),
"target_os": "linux",
"target_arch": receipt.get("architecture"),
}
if binding != expected_binding:
raise AttestationError("receipt source manifest binding does not match the qualified commit")
source_hashes = receipt.get("source_hashes")
if not isinstance(source_hashes, dict) or not source_hashes:
raise AttestationError("receipt source_hashes must be a non-empty object")
missing = REQUIRED_SOURCE_PATHS.difference(source_hashes)
if missing:
raise AttestationError(f"receipt source_hashes omit required boundary sources: {sorted(missing)}")
actual_paths = set(source_hashes)
if actual_paths != expected_paths:
missing = sorted(expected_paths.difference(actual_paths))
unexpected = sorted(actual_paths.difference(expected_paths))
raise AttestationError(
f"receipt source_hashes do not exactly match the governed boundary manifest; missing={missing} unexpected={unexpected}"
)
verified: dict[str, str] = {}
for source_path in sorted(source_hashes):
digest = source_hashes[source_path]
@ -243,7 +488,15 @@ def verify_source_hashes(checkout: Path, commit: str, receipt: dict[str, Any]) -
if actual != digest:
raise AttestationError(f"source digest mismatch for {source_path}")
verified[source_path] = actual
return verified
return verified, {
"schema_version": manifest["schema_version"],
"manifest_id": manifest["manifest_id"],
"path": SOURCE_MANIFEST_PATH,
"sha256": sha256_bytes(manifest_raw),
"target_os": manifest["target_os"],
"target_arch": receipt.get("architecture"),
"source_count": len(verified),
}
def verify_artifacts(receipt: dict[str, Any], artifacts: dict[str, Path]) -> dict[str, str]:
@ -314,6 +567,7 @@ def create_attestation(
main_ref: str,
receipt_path: Path,
receipt_record_path: str,
transcript_path: Path,
artifacts: dict[str, Path],
elapsed_seconds: float,
release_candidate_ref: str | None = None,
@ -330,21 +584,18 @@ def create_attestation(
raise AttestationError("release candidate ref does not resolve to the qualified commit")
receipt, receipt_bytes = load_receipt(receipt_path)
verify_scenarios(receipt)
record_path = canonical_repo_path(receipt_record_path, "receipt record path")
if receipt.get("record_path") != record_path:
raise AttestationError("receipt record path is not bound inside the supplied receipt")
transcript_events, transcript_bytes = load_and_verify_transcript(transcript_path, receipt)
verify_scenarios(receipt, transcript_events)
verify_runtime_claims(receipt)
source_hashes = verify_source_hashes(checkout, qualified_commit, receipt)
source_hashes, source_manifest = verify_source_hashes(checkout, qualified_commit, receipt)
attestation_tool_hash = sha256_file(Path(__file__))
if source_hashes["scripts/release_control/secure_runtime_attestation.py"] != attestation_tool_hash:
raise AttestationError("executing attestation tool does not match the qualified commit")
artifact_hashes = verify_artifacts(receipt, artifacts)
artifact_build_identity = verify_artifact_build_identity(receipt, artifacts, qualified_commit)
record_path = PurePosixPath(receipt_record_path)
if (
record_path.is_absolute()
or ".." in record_path.parts
or str(record_path) != receipt_record_path
):
raise AttestationError("receipt record path must be a canonical repository-relative path")
if (
not isinstance(elapsed_seconds, (float, int))
or not math.isfinite(elapsed_seconds)
@ -354,7 +605,7 @@ def create_attestation(
classification = "release-candidate-artifact-bound-self-attested-systemd" if release_candidate_ref else "committed-main-artifact-bound-self-attested-systemd"
return {
"schema_version": 3,
"schema_version": ATTESTATION_SCHEMA_VERSION,
"attestation_tool": "scripts/release_control/secure_runtime_attestation.py",
"attestation_tool_sha256": attestation_tool_hash,
"proof_classification": classification,
@ -367,8 +618,20 @@ def create_attestation(
"build_checkout_clean_except_lab_artifacts": True,
"disposable_vm_guard_receipt_claim_validated": True,
"execution_receipt_authentication": "none-secret-free-self-attestation",
"receipt_path": receipt_record_path,
"receipt_sha256": sha256_bytes(receipt_bytes),
"receipt": {
"record_path": record_path,
"sha256": sha256_bytes(receipt_bytes),
"path_bound_inside_receipt": True,
},
"transcript": {
"record_path": receipt["transcript"]["record_path"],
"sha256": sha256_bytes(transcript_bytes),
"event_count": len(transcript_events),
"scenario_event_count": sum(event.get("kind") == "scenario_result" for event in transcript_events),
"command_output_event_count": sum(event.get("kind") == "command_output" for event in transcript_events),
"format": "jsonl-v1",
},
"source_manifest": source_manifest,
"source_hashes_match_commit": True,
"source_hashes": source_hashes,
"artifact_hashes_match_receipt": True,
@ -413,6 +676,7 @@ def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
parser.add_argument("--release-candidate-ref")
parser.add_argument("--receipt", type=Path, required=True)
parser.add_argument("--receipt-record-path", required=True)
parser.add_argument("--transcript", type=Path, required=True)
parser.add_argument("--collector-v1", type=Path, required=True)
parser.add_argument("--collector-v2", type=Path, required=True)
parser.add_argument("--helper", type=Path, required=True)
@ -432,6 +696,7 @@ def main(argv: Sequence[str] | None = None) -> int:
release_candidate_ref=args.release_candidate_ref,
receipt_path=args.receipt,
receipt_record_path=args.receipt_record_path,
transcript_path=args.transcript,
artifacts={name: getattr(args, argument) for name, argument in ARTIFACT_ARGUMENTS.items()},
elapsed_seconds=args.elapsed_seconds,
)

View file

@ -15,7 +15,11 @@ from secure_runtime_attestation import (
DISPOSABLE_VM_GUARD_SHA256,
EXPECTED_ARTIFACT_PACKAGES,
REQUIRED_SCENARIOS,
REQUIRED_SOURCE_PATHS,
SCENARIO_REQUIRED_CLAIMS,
SCENARIO_REQUIRED_OBSERVATIONS,
SOURCE_MANIFEST_ID,
SOURCE_MANIFEST_PATH,
SOURCE_MANIFEST_SCHEMA_VERSION,
create_attestation,
sha256_bytes,
verify_artifact_build_identity,
@ -32,12 +36,30 @@ class SecureRuntimeAttestationTest(unittest.TestCase):
self.git("init", "-b", "main")
self.git("config", "user.name", "Pulse Test")
self.git("config", "user.email", "pulse-test@example.invalid")
manifest = {
"schema_version": SOURCE_MANIFEST_SCHEMA_VERSION,
"manifest_id": SOURCE_MANIFEST_ID,
"target_os": "linux",
"exact_paths": [
"scripts/install.sh",
"scripts/installtests/secure_runtime_systemd_lab_test.go",
"scripts/release_control/secure_runtime_attestation.py",
SOURCE_MANIFEST_PATH,
],
"recursive_roots": ["boundary"],
"include_suffixes": [".go"],
"exclude_suffixes": ["_test.go"],
}
manifest_raw = (json.dumps(manifest, indent=2) + "\n").encode()
source_values = {
"scripts/install.sh": b"fixture installer\n",
"scripts/installtests/secure_runtime_systemd_lab_test.go": b"fixture harness\n",
"scripts/release_control/secure_runtime_attestation.py": Path(__file__).with_name("secure_runtime_attestation.py").read_bytes(),
SOURCE_MANIFEST_PATH: manifest_raw,
"boundary/runtime.go": b"package boundary\n",
}
self.source_hashes = {}
for relative in sorted(REQUIRED_SOURCE_PATHS):
if relative == "scripts/release_control/secure_runtime_attestation.py":
value = Path(__file__).with_name("secure_runtime_attestation.py").read_bytes()
else:
value = f"fixture:{relative}\n".encode()
for relative, value in sorted(source_values.items()):
path = self.repo / relative
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(value)
@ -57,10 +79,77 @@ class SecureRuntimeAttestationTest(unittest.TestCase):
self.artifacts[name] = path
artifact_hashes[name] = sha256_bytes(value)
self.receipt = self.root / "receipt.json"
self.transcript = self.root / "transcript.jsonl"
started_at = "2026-08-30T10:00:00+00:00"
scenarios = []
command_output = "fixture command output\n"
transcript_events = [
{
"sequence": 1,
"event_id": "event-0001",
"observed_at": started_at,
"kind": "command_output",
"operation": "fixture command",
"output": command_output,
"output_sha256": sha256_bytes(command_output.encode()),
}
]
previous = started_at
for index, name in enumerate(REQUIRED_SCENARIOS, 1):
completed = f"2026-08-30T10:00:{index:02d}+00:00"
event_sequence = index + 1
event_id = f"event-{event_sequence:04d}"
claims = sorted(SCENARIO_REQUIRED_CLAIMS[name])
observations = dict(SCENARIO_REQUIRED_OBSERVATIONS[name])
scenarios.append(
{
"sequence": index,
"name": name,
"passed": True,
"started_at": previous,
"completed_at": completed,
"evidence": {
"kind": "runtime-observation-v1",
"summary": f"observed {name}",
"claims": claims,
"observations": observations,
"transcript_event_ids": [event_id],
},
}
)
transcript_events.append(
{
"sequence": event_sequence,
"event_id": event_id,
"observed_at": completed,
"kind": "scenario_result",
"scenario": name,
"claims": claims,
"observations": observations,
"summary": f"observed {name}",
}
)
previous = completed
transcript_raw = b"".join(
(json.dumps(event, separators=(",", ":")) + "\n").encode()
for event in transcript_events
)
self.transcript.write_bytes(transcript_raw)
self.receipt.write_text(
json.dumps(
{
"schema_version": 3,
"schema_version": 4,
"record_path": "records/receipt.json",
"started_at": started_at,
"completed_at": "2026-08-30T10:00:13+00:00",
"source_manifest": {
"schema_version": SOURCE_MANIFEST_SCHEMA_VERSION,
"manifest_id": SOURCE_MANIFEST_ID,
"path": SOURCE_MANIFEST_PATH,
"sha256": sha256_bytes(manifest_raw),
"target_os": "linux",
"target_arch": "arm64",
},
"source_hashes": self.source_hashes,
"artifact_hashes": artifact_hashes,
"artifact_versions": {"collector_v1": "1.0.0", "collector_v2": "1.1.0"},
@ -69,22 +158,20 @@ class SecureRuntimeAttestationTest(unittest.TestCase):
"kernel": "Linux fixture",
"systemd_version": "systemd 255",
"architecture": "arm64",
"transcript": {
"format": "jsonl-v1",
"record_path": "records/transcript.jsonl",
"sha256": sha256_bytes(transcript_raw),
"event_count": len(transcript_events),
},
"collector_service_user": "pulse-agent",
"collector_process_uid": 1000,
"collector_authority": "monitoring-only",
"ambient_capabilities_none": True,
"helper_protocol_healthy": True,
"state_identity_preserved": True,
"action_runner_qualified": True,
"action_mutation_verified": True,
"collector_authority_reduction_request_observed": True,
"credential_rotated": True,
"self_revoke_observed": True,
"collector_continuity": True,
"scenarios": [
{"name": name, "passed": True, "detail": "passed"}
for name in REQUIRED_SCENARIOS
],
"action_receipt_kind": "pulse.host_storage_cleanup_result",
"report_count": 3,
"first_report_at": "2026-08-30T10:00:01+00:00",
"last_report_at": "2026-08-30T10:00:12+00:00",
"scenarios": scenarios,
},
indent=2,
)
@ -121,6 +208,7 @@ class SecureRuntimeAttestationTest(unittest.TestCase):
"main_ref": "origin/main",
"receipt_path": self.receipt,
"receipt_record_path": "records/receipt.json",
"transcript_path": self.transcript,
"artifacts": self.artifacts,
"elapsed_seconds": 113.43,
}
@ -134,6 +222,9 @@ class SecureRuntimeAttestationTest(unittest.TestCase):
self.assertTrue(result["source_hashes_match_commit"])
self.assertTrue(result["artifact_hashes_match_receipt"])
self.assertEqual(result["scenario_count"], 12)
self.assertTrue(result["receipt"]["path_bound_inside_receipt"])
self.assertEqual(result["transcript"]["event_count"], 13)
self.assertEqual(result["source_manifest"]["manifest_id"], SOURCE_MANIFEST_ID)
self.assertEqual(len(result["attestation_tool_sha256"]), 64)
def test_accepts_exact_release_candidate_ref(self) -> None:
@ -164,6 +255,13 @@ class SecureRuntimeAttestationTest(unittest.TestCase):
with self.assertRaisesRegex(AttestationError, "source digest mismatch"):
self.attest()
def test_rejects_source_set_that_omits_manifest_expansion(self) -> None:
receipt = json.loads(self.receipt.read_text(encoding="utf-8"))
del receipt["source_hashes"]["boundary/runtime.go"]
self.receipt.write_text(json.dumps(receipt), encoding="utf-8")
with self.assertRaisesRegex(AttestationError, "exactly match the governed boundary manifest"):
self.attest()
def test_rejects_artifact_digest_mismatch(self) -> None:
self.artifacts["runner"].write_bytes(b"tampered\n")
with self.assertRaisesRegex(AttestationError, "artifact digest mismatch"):
@ -200,18 +298,39 @@ class SecureRuntimeAttestationTest(unittest.TestCase):
with self.assertRaisesRegex(AttestationError, "did not pass"):
self.attest()
def test_rejects_tampered_transcript(self) -> None:
self.transcript.write_bytes(self.transcript.read_bytes() + b"{}\n")
with self.assertRaisesRegex(AttestationError, "transcript digest mismatch"):
self.attest()
def test_rejects_scenario_chronology_drift(self) -> None:
receipt = json.loads(self.receipt.read_text(encoding="utf-8"))
receipt["scenarios"][2]["started_at"] = "2026-08-30T09:59:00+00:00"
self.receipt.write_text(json.dumps(receipt), encoding="utf-8")
with self.assertRaisesRegex(AttestationError, "chronology is invalid"):
self.attest()
def test_rejects_receipt_without_verified_mutation(self) -> None:
receipt = json.loads(self.receipt.read_text(encoding="utf-8"))
receipt["action_mutation_verified"] = False
scenario = next(item for item in receipt["scenarios"] if item["name"] == "typed_action_receipt")
scenario["evidence"]["claims"].remove("typed_mutation_verified")
self.receipt.write_text(json.dumps(receipt), encoding="utf-8")
with self.assertRaisesRegex(AttestationError, "action_mutation_verified"):
with self.assertRaisesRegex(AttestationError, "required causal claims"):
self.attest()
def test_rejects_scenario_without_typed_observation(self) -> None:
receipt = json.loads(self.receipt.read_text(encoding="utf-8"))
scenario = next(item for item in receipt["scenarios"] if item["name"] == "typed_action_receipt")
del scenario["evidence"]["observations"]["verification"]
self.receipt.write_text(json.dumps(receipt), encoding="utf-8")
with self.assertRaisesRegex(AttestationError, "required typed observations"):
self.attest()
def test_rejects_legacy_receipt_schema(self) -> None:
receipt = json.loads(self.receipt.read_text(encoding="utf-8"))
receipt["schema_version"] = 2
receipt["schema_version"] = 3
self.receipt.write_text(json.dumps(receipt), encoding="utf-8")
with self.assertRaisesRegex(AttestationError, "schema_version 3"):
with self.assertRaisesRegex(AttestationError, "schema_version 4"):
self.attest()
def test_rejects_missing_scenario(self) -> None:
@ -253,6 +372,10 @@ class SecureRuntimeAttestationTest(unittest.TestCase):
with self.assertRaisesRegex(AttestationError, "canonical repository-relative"):
self.attest(receipt_record_path="../receipt.json")
def test_rejects_receipt_record_path_not_bound_inside_blob(self) -> None:
with self.assertRaisesRegex(AttestationError, "not bound inside"):
self.attest(receipt_record_path="records/other.json")
def test_rejects_nonfinite_elapsed_time(self) -> None:
with self.assertRaisesRegex(AttestationError, "must be positive"):
self.attest(elapsed_seconds=float("nan"))

View file

@ -0,0 +1,32 @@
{
"schema_version": 1,
"manifest_id": "secure-runtime-linux-v4",
"target_os": "linux",
"description": "Production source boundary for the secure collector, typed helper, action runner, update path, control-plane admission, and systemd qualification harness.",
"exact_paths": [
"scripts/install.sh",
"scripts/installtests/secure_runtime_systemd_lab_test.go",
"scripts/release_control/secure_runtime_attestation.py",
"scripts/release_control/secure_runtime_source_manifest_v4.json"
],
"recursive_roots": [
"cmd/pulse-agent",
"cmd/pulse-agent-helper",
"cmd/pulse-agent-runner",
"internal/actionrunner",
"internal/agentexec",
"internal/agenthelper",
"internal/agentupdate",
"internal/api",
"internal/config",
"internal/hostagent",
"internal/operationreceipt"
],
"include_suffixes": [
".go",
".tmpl"
],
"exclude_suffixes": [
"_test.go"
]
}