Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,20 @@ node dist/cli.js passport .tmp/demo/run.bundle.json \
--oasf-media-type "$OASF_MEDIA_TYPE" \
--execution-evidence .tmp/demo/stagefabric-binding.json \
--output .tmp/demo/execution-passport.json
node dist/cli.js verify-passport .tmp/demo/execution-passport.json \
--bundle .tmp/demo/run.bundle.json \
--report .tmp/demo/conformance-report.json \
--oasf-record /path/to/validated-oasf-record.json \
--execution-evidence-resource /path/to/stagefabric-evidence.json
```

The output file contains exact RFC 8785 Statement bytes suitable for an external DSSE/Sigstore adapter. Repeat `--execution-evidence` only with strict v2 bindings whose `runIdDigest` matches the Passport run and whose authority is fixed to `observation-only`.
`passport` writes exact RFC 8785 Statement bytes suitable for an external
DSSE/Sigstore adapter. `verify-passport` is the consumer-side check: it rebuilds
the expected subject and predicate bindings from the supplied bundle, report,
and OASF record, then requires the exact bytes of every referenced execution
artifact. It proves internal consistency, not signer authenticity or OASF
semantics. Repeat `--execution-evidence` and
`--execution-evidence-resource` once per corresponding binding and artifact.

`bind-stagefabric` accepts only the exact canonical JSON plus trailing LF emitted by the StageFabric evidence writer. Pretty-printed JSON, YAML, or a reserialized object is rejected because the resulting descriptor digest identifies the retrievable file bytes, not merely an equivalent object.

Expand Down Expand Up @@ -168,6 +179,7 @@ Read [Architecture](docs/architecture.md), [Contracts and conformance](docs/cont
- Run status and conformance status are separate: a failed run can still have an intact, conformant evidence record, but it is never explained as a completed outcome.
- Reports bind the evaluator name, package version, semantic revision, selected rule set, and effective profile configuration. The evaluator digest identifies the declared implementation revision; it is not a code signature.
- An unsigned Execution Passport proves deterministic integrity and cross-artifact binding only. Authenticity requires an externally verified DSSE envelope or equivalent trusted channel, and consumers must make that requirement explicit so removing a signature cannot silently downgrade policy.
- Consumer verification requires the complete supplied subject set and exact bytes for every execution-evidence resource; validating the Passport schema alone does not verify those bindings.
- The OASF subject digest proves which opaque record was bound; it does not prove that the record is semantically valid OASF. Validate it before passport creation with the specification owner’s tooling.
- Execution-placement evidence enters the predicate only as a strict, run-bound, observation-only descriptor with a normalized observation time and no payload or `content` field. A StageFabric descriptor hashes the exact canonical JSON file bytes, including its trailing LF. Descriptor metadata must come from a trusted producer and be redacted or pseudonymized when sensitive; its optional stable URI may be omitted. Full provider result objects are outside the Passport contract.
- The StageFabric adapter follows its finalized `0.7.0-alpha.1` successful-run contract: trace events are either completed placements or pre-output retries with status `429`, `502`, `503`, or `504`. Terminal failures, arbitrary HTTP codes, missing retry codes, and codes attached to completed events fail closed.
Expand Down
6 changes: 6 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ Passport creation is a deterministic packaging step after conformance evaluation

Optional execution-placement evidence is reduced to a `ResourceDescriptor` with no payload or `content` field. AgenticStrata admits only the descriptor, producer identity, fixed role, canonical run-identifier digest, normalized observation time, `observation-only` authority, and `content-free` disclosure marker. A StageFabric adapter validates the source artifact, its successful-run-only trace (`completed` or a pre-output retry with `429`, `502`, `503`, or `504`), timestamp grammar, producer seal, and exact canonical-JSON-plus-LF writer bytes before reduction. Descriptor metadata must be trusted and redacted or pseudonymized when sensitive, and its URI may be omitted. A placement observation never grants authority; a descriptor from another run or after the report is rejected.

The consumer path accepts the Passport together with all three subject documents
and the exact bytes of every execution-evidence resource. It reconstructs the
expected Statement, checks the complete bundle/report relationship again, and
requires a one-to-one digest match for external evidence. Schema validation by
itself is deliberately insufficient.

The core emits an unsigned Statement. A replaceable external adapter may wrap its exact RFC 8785 bytes in DSSE and use Sigstore or another trust system. Signature verification, signer identity, transparency-log policy, and trusted time remain consumer-owned controls.

## Safe semantic caching
Expand Down
9 changes: 9 additions & 0 deletions docs/contracts-and-conformance.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ OASF input is accepted only as an opaque I-JSON object and canonicalized for its

The Passport is created after assessment and is not required by a conformance rule. Making it a prerequisite of the report it contains would create a circular dependency.

Consumer verification is a separate fail-closed operation. Given a v2 Passport,
the complete run bundle, report, opaque OASF record, and every referenced
execution-evidence file, `verifyExecutionPassport` reconstructs the expected
Statement and compares all subject and predicate bindings. Evidence files are
matched one-to-one by SHA-256 over their exact bytes; missing, duplicated, or
unreferenced files fail verification. The CLI exposes the same operation as
`verify-passport`. This verifies an unsigned artifact set, not producer identity,
OASF semantics, trusted time, or an external DSSE envelope.

## Evidence, not file detection

A conformance run consumes a `RunBundle`. It does not infer architecture from directory names, imports, or keywords. This avoids declaring success simply because a project contains a policy file or telemetry package. Conversely, a source scanner remains useful before runtime evidence exists; its result should be treated as a discovery signal rather than runtime proof.
Expand Down
20 changes: 20 additions & 0 deletions docs/spec/attestations/execution-passport/v2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,26 @@ A conforming builder fails closed unless:

These checks establish deterministic internal consistency. They do not authenticate a producer or prevent wholesale replacement by an actor able to construct another self-consistent set.

## Consumer verification

A consumer must not treat schema validation as verification of the external
subjects. `verifyExecutionPassport` and the `verify-passport` CLI require the
Passport plus the complete RunBundle, ConformanceReport, opaque OASF record, and
the exact bytes of every execution-evidence resource. The verifier:

1. validates the strict v2 Statement;
2. repeats every creation-time bundle, report, receipt, run, status, and time check;
3. reconstructs and compares the three ordered subject descriptors and predicate;
4. matches every external evidence descriptor to exactly one supplied file by
SHA-256 over its complete bytes; and
5. rejects missing, duplicated, or unreferenced evidence files.

This is unsigned artifact-set verification. Producer-contract semantics are
validated by the adapter that creates a binding; OASF semantics remain with its
owner. Authenticity, signer identity, trusted time, revocation, and protection
against wholesale replacement still require an externally verified envelope or
trusted store.

## OASF, DSSE, and signing boundaries

OASF input is an opaque I-JSON object. Validate it with the owning ecosystem before Passport creation; its subject digest proves which canonical bytes were bound, not semantic OASF validity.
Expand Down
1 change: 1 addition & 0 deletions docs/threat-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Model output, retrieved content, external messages, tool descriptions, and impor
| A report is replayed under ambiguous evaluator semantics | Reports bind evaluator name, package version, semantic revision, rule IDs, and effective configuration by digest. | The declared evaluator digest is not a code signature; trusted distribution or executable attestation is required for stronger provenance. |
| Imported agent definition becomes mutable | Reference plus digest; embedded redefinition is rejected. | Availability and signature verification belong to the artifact resolver. |
| A Passport mixes a run with another report or manifest | Creation recomputes canonical bundle and manifest digests, verifies the complete report and evaluator seals, and rejects mismatched run identity or terminal state. | Hashes do not establish who produced otherwise self-consistent artifacts. |
| A consumer validates only the Passport schema while receiving different or missing subjects | `verify-passport` reconstructs the Statement from the supplied bundle, report, and OASF record, then requires one exact-byte SHA-256 match for every referenced execution artifact. | An unsigned, wholly replaced but self-consistent artifact set still requires an authenticated envelope or trusted channel. |
| A Passport is interpreted as proof of success | The predicate carries separate explicit run and conformance statuses and permits failed or incomplete states. | Consumers must still enforce the statuses appropriate to their decision. |
| Raw execution data leaks through placement evidence | The schema and CLI reject payload/content fields, inline or local URI schemes, credentialed HTTPS references, query strings, fragments, and oversized URIs. | Descriptor names, producer identifiers, media types, HTTPS paths, and URNs remain metadata channels; accept them only from trusted producers and redact, pseudonymize, or omit optional URIs. |
| Valid placement evidence is replayed onto another run or treated as permission | V2 binds each descriptor to `SHA256(RFC8785(runId))`, fixes its authority to `observation-only`, and rejects mismatches before Passport creation. | A compromised producer can still create false but internally consistent observations; authenticate and qualify producers externally. |
Expand Down
28 changes: 27 additions & 1 deletion scripts/package-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,38 @@ try {
temporary
);
run(executable, ["validate", passportOutput], temporary);
const verification = JSON.parse(
run(
executable,
[
"verify-passport",
passportOutput,
"--bundle",
join(cliOutput, "run.bundle.json"),
"--report",
join(cliOutput, "conformance-report.json"),
"--oasf-record",
oasfRecord,
"--execution-evidence-resource",
stageFabricEvidence
],
temporary
)
);
if (
verification.valid !== true ||
verification.verifiedSubjects !== 3 ||
verification.verifiedExecutionEvidenceResources !== 1
) {
throw new Error("Installed CLI did not verify the complete Passport artifact set.");
}
console.log(
JSON.stringify({
cliVersion: version,
cliDemo: "pass",
cliStageFabricBinding: "pass",
cliPassport: "pass"
cliPassport: "pass",
cliPassportVerification: "pass"
})
);
} finally {
Expand Down
13 changes: 9 additions & 4 deletions src/adapters/documents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,23 @@ import { canonicalize } from "../core/canonical.js";

export const MAX_DOCUMENT_BYTES = 16 * 1024 * 1024;

function readSource(path: string): string {
export function readResourceBytes(path: string): Uint8Array {
const metadata = statSync(path);
if (!metadata.isFile()) {
throw new Error("Contract input must be a regular file.");
throw new Error("Input must be a regular file.");
}
if (metadata.size > MAX_DOCUMENT_BYTES) {
throw new Error(`Contract input exceeds the ${MAX_DOCUMENT_BYTES}-byte limit.`);
throw new Error(`Input exceeds the ${MAX_DOCUMENT_BYTES}-byte limit.`);
}
const bytes = readFileSync(path);
if (bytes.byteLength > MAX_DOCUMENT_BYTES) {
throw new Error(`Contract input exceeds the ${MAX_DOCUMENT_BYTES}-byte limit.`);
throw new Error(`Input exceeds the ${MAX_DOCUMENT_BYTES}-byte limit.`);
}
return bytes;
}

function readSource(path: string): string {
const bytes = readResourceBytes(path);
try {
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
} catch {
Expand Down
55 changes: 54 additions & 1 deletion src/cli-program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Command, InvalidArgumentError } from "commander";
import {
readDocument,
readJsonDocumentWithSource,
readResourceBytes,
writeCanonicalJson,
writeJson,
writeText
Expand All @@ -20,6 +21,7 @@ import type {
ConformanceProfile,
ConformanceReport,
ExecutionEvidenceBinding,
ExecutionPassport,
RunBundle,
ValidationResult
} from "./contracts/types.js";
Expand All @@ -28,7 +30,7 @@ import { reportIssues, runConformance } from "./conformance/engine.js";
import { lintManifest } from "./conformance/manifest.js";
import { canonicalize } from "./core/canonical.js";
import { explainRun } from "./core/explain.js";
import { createExecutionPassport } from "./core/passport.js";
import { createExecutionPassport, verifyExecutionPassport } from "./core/passport.js";
import { evidenceIndex, verifyTraceChain } from "./core/receipts.js";
import { runDemo } from "./demo/demo.js";

Expand Down Expand Up @@ -84,6 +86,16 @@ function requireExecutionEvidence(path: string): ExecutionEvidenceBinding {
return document as ExecutionEvidenceBinding;
}

function requirePassport(path: string): ExecutionPassport {
const document = readDocument(path);
const validation = validateAs("ExecutionPassport", document);
if (!validation.valid) {
printValidation(validation);
throw new Error("The input is not a valid Execution Passport v2.");
}
return document as ExecutionPassport;
}

interface PassportCommandOptions {
report: string;
oasfRecord: string;
Expand Down Expand Up @@ -219,6 +231,47 @@ export function createCliProgram(): Command {
}
});

program
.command("verify-passport")
.description(
"Verify an unsigned Passport against its complete subjects and execution-evidence files."
)
.argument("<passport>")
.requiredOption("--bundle <file>", "RunBundle subject")
.requiredOption("--report <file>", "ConformanceReport subject")
.requiredOption("--oasf-record <file>", "opaque OASF record subject")
.option(
"--execution-evidence-resource <file>",
"complete evidence artifact referenced by the Passport; repeat for multiple resources",
collectPath,
[]
)
.action(
(
file: string,
options: {
bundle: string;
report: string;
oasfRecord: string;
executionEvidenceResource: string[];
}
) => {
const result = verifyExecutionPassport({
passport: requirePassport(resolve(file)),
bundle: requireBundle(resolve(options.bundle)),
report: requireReport(resolve(options.report)),
oasfRecord: readDocument(resolve(options.oasfRecord)),
executionEvidenceResources: options.executionEvidenceResource.map((path) =>
readResourceBytes(resolve(path))
)
});
console.log(JSON.stringify(result, null, 2));
if (!result.valid) {
process.exitCode = 2;
}
}
);

program
.command("replay")
.description("Replay the hash-linked runtime receipt chain and detect tampering or missing evidence.")
Expand Down
Loading
Loading