跳到正文
DEV Community· Ventse·· 3 小時前AI 評分55

in-toto 與 SLSA 的構建來源證明如何啟發 AI 智能體紀錄設計

in-toto and SLSA for AI agents: what build provenance teaches us about agent records

AI 導讀

文章以 in-toto attestation 與 SLSA provenance 為設計參照,分析 AI 智能體執行紀錄如何記錄來源、區分信任邊界並防止偽造。

正文

Software supply-chain security has spent the last few years answering a question that AI agents now raise again: given an artifact, how does someone who was not in the room find out how it was produced, and how far should they believe the answer? For builds, the answer the industry has converged on is an attestation, usually an in-toto attestation carrying SLSA provenance. This piece reads those two specifications as a design guide for recording what an agent did, and is explicit about where the analogy holds and where it does not.

What an in-toto attestation is

The in-toto attestation framework defines an attestation as "authenticated metadata about one or more software artifacts". The current specification (v1.2) splits it into layers, each with one job:

  • Predicate: "arbitrary metadata about a subject artifact, with a type-specific schema". Provenance, a test result or a vulnerability scan are all predicates.
  • Statement: "binds the attestation to a particular subject and unambiguously identifies the types of the predicate".
  • Envelope: "handles authentication and serialization", in other words the signature.
  • Bundle: a way of grouping several attestations together.

A v1 Statement has four fields: _type, always https://in-toto.io/Statement/v1; subject, a list of artifacts, each of which "MUST have digest set"; predicateType, a URI naming the schema of the predicate; and predicate itself. Two rules in that document matter for anyone thinking about agents. Subjects "are assumed to be immutable", and they "are matched purely by digest, regardless of content type". The statement is about specific bytes, not about a file name or a version label.

The envelope: DSSE and why it refuses canonical JSON

in-toto attestations are normally signed with DSSE, the Dead Simple Signing Envelope. The protocol signs a pre-authentication encoding of the payload rather than the payload alone:

PAE(type, body) = "DSSEv1" + SP + LEN(type) + SP + type + SP + LEN(body) + SP + body
SIGNATURE = Sign(PAE(UTF8(PAYLOAD_TYPE), SERIALIZED_BODY))

Two design choices are worth understanding. First, the payload type is inside the signature. The design notes explain that without an authenticated type, a signer and a verifier may interpret the same bytes differently, and give the example of a message produced as CBOR being read as protobuf. Second, DSSE deliberately avoids canonicalisation. The same document says a signing scheme "SHOULD avoid depending on canonicalization for security", warns that "two semantically different payloads could have the same canonical encoding", and prefers to "transmit the encoded byte stream exactly as it was signed" so that verification happens before any parsing.

That second point is a direct critique of the approach our own receipts take. The Traceseal receipt specification signs the canonical JSON encoding of the execution and provenance objects, which means a verifier has to parse the receipt and re-encode it before checking the signature. We mitigate this by keeping every field a string (the spec encodes ok as "true" rather than a JSON boolean for that reason) and by publishing a reference encoder, but the DSSE authors' objection is a fair one and it applies to us. If you are designing an agent record format from scratch today, signing the exact bytes with a typed envelope is the safer default.

What SLSA provenance records

SLSA describes provenance as "the verifiable information about software artifacts describing where, when and how something was produced", and models it as "an attestation that a particular build platform produced a set of software artifacts". It is an in-toto predicate with type https://slsa.dev/provenance/v1, and it has two halves:

  • buildDefinition, the input to the build: a buildType, the externalParameters "under external control, such as those set by a user or tenant", the internalParameters under the platform's control, and the resolvedDependencies pulled in at build time.
  • runDetails, this particular execution: a builder.id, metadata including invocationId, startedOn and finishedOn, and any byproducts.

Map that onto an agent run and most of it carries over with little strain. The task prompt and the user's instructions are external parameters. The model identifier, system prompt and tool configuration chosen by the operator are internal parameters. Retrieved documents, tool definitions and the code of any skill the agent loaded are resolved dependencies. The harness that ran the agent is the builder. The files it wrote, the messages it sent and the commits it made are subjects, identified by digest.

The useful idea: who is trusted for which field

The most transferable part of SLSA is not the schema but the way it assigns trust to each field. The provenance specification says external parameters "are untrusted; they MUST be included in the provenance and MUST be verified downstream", while internal parameters "are trusted because the platform is trusted". It also states that "the accuracy and completeness" of the build definition "are implied by runDetails.builder.id", and that consumers "MUST accept only specific signer-builder pairs".

Put plainly: a provenance document is only as good as the thing that wrote it, and the format makes that dependency explicit rather than hiding it. Agent logs rarely do this. A transcript mixes what the user asked for, what the model decided, what a tool returned and what the harness observed, all written by the same process and all presented with the same apparent authority. Separating "values supplied from outside, to be checked" from "values recorded by the platform, trusted if the platform is" is a small change in a record format, and it tells a reader exactly what they are being asked to believe.

Build levels, read as agent levels

The SLSA v1.0 build track grades how hard the provenance is to fake:

  • Build L1: provenance exists. It "can be used to prevent mistakes but is trivial to bypass or forge", and it may be "incomplete and/or unsigned".
  • Build L2: the build runs on a hosted platform, "not an individual's workstation", and the provenance is signed by it. Forging it "requires an explicit 'attack', though this may be easy to perform".
  • Build L3: hardened builds. The platform must "prevent secret material used to sign the provenance from being accessible to the user-defined build steps", and runs must not be able to influence one another.

The L3 requirement is the one agent systems most often get wrong. In a build, the user-defined steps are the untrusted part. In an agent run, the untrusted part is the agent itself: its behaviour depends on a model and on whatever content it reads, and indirect prompt injection means that content can steer it. If the agent process can reach the key that signs its own record, the record is at L1 in SLSA's terms no matter how good the cryptography is, because the thing being described can write the description. The OWASP agentic guidance reaches the same conclusion from the other direction.

Our own tooling follows this rule. Alibi runs each agent inside a bubblewrap sandbox and signs receipts on the host after the contained processes have exited; the key is never mounted inside the sandbox. Our sandboxing article covers the profile, including a DNS leak we found when we tested it.

A quick test for any agent record: could the agent, if it misbehaved, have produced a record that verifies but says something false? If the answer is yes, the signature is protecting the record from everyone except the party you most need it to be protected from.

Where the analogy breaks

Build provenance can rely on three properties that agent runs do not have, and it is worth being exact about them.

  • Builds aim to be repeatable; agent runs are not. A verifier who distrusts build provenance can, in principle, rebuild from the declared inputs and compare digests. An agent run against a hosted model, with live tool results, cannot be replayed to the same outcome. The record is the only evidence there will ever be, which raises the stakes on getting the signer right.
  • Build inputs are artifacts; agent inputs are often conversations. A resolved dependency has a digest. A web page the agent read at 14:02, or the response of an API that has since changed, can be hashed, but the hash only helps if someone kept the bytes.
  • The subject of a build is the output; the subject of an agent run is often the action. An email sent, a payment initiated or a ticket closed is not an immutable artifact with a digest in the sense the in-toto Statement assumes. You can hash a description of the action, but then you are attesting to the description, and the question becomes who wrote it.

None of this makes the build world's tools wrong for agents. It means the trust model has to be carried over, not just the field names. And SLSA provenance, like our receipts, is an attestation and not a proof. Our specification says it directly: a receipt "proves that the operator's key signed the attestation. It does NOT prove the operator is honest." The same is true of any signed provenance. What the format buys you is that the claim is attributable, tamper-evident and checkable by a third party, which a log file is not.

What to take from this

  • Identify subjects by digest. Record the hash of every file an agent wrote and every payload it sent, not only a path or a name.
  • Split external from internal parameters. Mark what came from the user or from content the agent read separately from what the operator configured.
  • Keep the signing key out of the agent's reach. This is the single requirement that decides whether a record is evidence or self-description.
  • Sign exact bytes and the payload type if you are designing a format now. DSSE's reasons are sound.
  • Get an outside party involved in time and ordering. A signature does not fix when something happened; see our piece on trusted timestamps and on transparency log witnesses.

The receipt format, verifier and log are open: the spec, the verifier on PyPI, and the public log.


Originally published at traceseal.io.

來源:DEV Community · dev.to