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
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Preserve the exact bytes used by aeoess-aps fixture checksums.
integrations/aeoess-aps/fixtures/delegation-chain/*.json text eol=lf
integrations/aeoess-aps/fixtures/action-receipt/*.json text eol=lf

# Registry bytes; the tests check them against the digests Docker Hub serves.
integrations/docker-sandbox-kit/fixtures/*.json -text
59 changes: 59 additions & 0 deletions .github/workflows/docker-sandbox-kit-conformance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: docker-sandbox-kit conformance
on:
push:
paths:
- "integrations/docker-sandbox-kit/**"
- ".github/workflows/docker-sandbox-kit-conformance.yml"
pull_request:
paths:
- "integrations/docker-sandbox-kit/**"
- ".github/workflows/docker-sandbox-kit-conformance.yml"
schedule:
- cron: "0 6 * * 1"
workflow_dispatch:

permissions:
contents: read

jobs:
conformance:
strategy:
fail-fast: false
matrix:
python: ["3.11", "3.12", "3.13"]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: ${{ matrix.python }}
- name: Install released TRACE packages
working-directory: integrations/docker-sandbox-kit
run: python -m pip install -r requirements.txt
# Builds, signs and verifies records from two published Kits, and checks
# every refusal path.
- name: Integration tests
working-directory: integrations/docker-sandbox-kit
run: python -m pytest -q
- name: TRACE Level 0
working-directory: integrations/docker-sandbox-kit
run: |
python - <<'PY'
import json, pathlib
from agentrust_trace import generate_key, key_to_jwk, sign_record
from kit_to_trace import KitEvidence, build_from_kit
key = generate_key()
raw = pathlib.Path("fixtures/claude-acp-set-1.0.1.amd64.manifest.json").read_bytes()
record = build_from_kit(
KitEvidence.from_manifest(raw),
subject="spiffe://example.org/agent/claude-acp",
model_provider="anthropic",
model_id="claude-sonnet-4-6",
workload_digest="sha256:" + "e" * 64,
jwk=key_to_jwk(key),
kit_reference="docker.io/docker/sbx-kit-claude-acp-set",
)
pathlib.Path("signed-record.json").write_text(json.dumps(sign_record(record, key)))
PY
trace-tests verify --record signed-record.json --level 0
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ TRACE only works as a standard if it is genuinely neutral. Integrations are list
| [comply54](integrations/comply54/) | comply54 | trace | community |
| [ComputeID AgentPassport TRACE Adapter](integrations/computeid-agentpassport-trace/) | ComputeID | trace | community |
| [DecisionAssure](integrations/decisionassure/) | DecisionAssure (a1k7) | trace | community |
| [Docker Sandbox Kit](integrations/docker-sandbox-kit/) | agentrust-io | trace | community |
| [EPI Recorder](integrations/epilabs-epi-recorder/) | EPI Labs | trace, wcm | verified |
| [Google ADK](integrations/google-adk/) | agentrust-io | trace | verified |
| [LangChain](integrations/langchain/) | agentrust-io | trace | verified |
Expand Down
99 changes: 99 additions & 0 deletions integrations/docker-sandbox-kit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Docker Sandbox Kit to TRACE

A [Docker Sandbox Kit](https://github.com/docker/sandbox-kit-spec) is one OCI
image whose manifest annotation `vnd.docker.sandbox.kit.descriptor` lists what
the agent inside it asks to reach: network allow and deny rules by phase,
credentials, volumes, ports. This adapter takes a published Kit's platform
manifest and builds a TRACE Level 0 Trust Record whose `policy.bundle_hash` is
the digest of that descriptor, byte for byte as the frontend published it.

Written against [SPEC-v3](https://github.com/docker/sandbox-kit-spec/blob/main/docs/spec/SPEC-v3.md)
at commit `31791ea` (2026-10-01).

## Run it

Fetch the platform manifest exactly as the registry serves it. The digest is
over these bytes, so a pretty-printed copy will not match. For a public Docker
Hub Kit:

```bash
REPO=docker/sbx-kit-claude-acp-set
TOKEN=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:$REPO:pull" | jq -r .token)
curl -s -H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.oci.image.manifest.v1+json" \
"https://registry-1.docker.io/v2/$REPO/manifests/sha256:86d56a3b2ea714d55c2a48b55b9ca64e2145245f43583100dd944eccfb8fe78e" > kit.json
```

The platform digest comes from the Kit's image index, one entry per platform.

Then build the unsigned record:

```bash
pip install -r integrations/docker-sandbox-kit/requirements.txt
python integrations/docker-sandbox-kit/kit_to_trace.py kit.json \
--expected-digest sha256:86d56a3b2ea714d55c2a48b55b9ca64e2145245f43583100dd944eccfb8fe78e \
--kit-reference docker.io/docker/sbx-kit-claude-acp-set \
--subject spiffe://example.org/agent/claude-acp \
--model-provider anthropic --model-id claude-sonnet-4-6 \
--workload-digest sha256:<digest of the assembled image that ran> \
--jwk pubkey.jwk > record.json
```

Pass the result to `agentrust_trace.sign_record`. Signing is kept separate from
assembly.

## What the record claims

| TRACE field | Value |
|---|---|
| `policy.bundle_hash` | SHA-256 of the published descriptor annotation |
| `policy.enforcement_mode` | `declared`, always |
| `policy.policy_uri` | `oci://<repository>@<Kit manifest digest>`, when `--kit-reference` is given |
| `origin.kind` | `third-party-control-plane` |
| `origin.producer` | The frontend named in `vnd.docker.sandbox.kit.built-by`, else `docker/sandbox-kit` |
| `origin.source_event_id` | The Kit manifest digest |
| `build_provenance.digest` | The digest the caller supplies for the image that ran |
| `runtime.platform` | `software-only` |
| `appraisal` | `none`, stated by this adapter |

**Why `declared`.** The descriptor is a request. What a host granted is the
permission surface of the effective descriptor, with this installation's
create-phase args expanded, and it lives in the runtime's lock (SPEC-v3
sections 7.4 and 10). The image carries neither, so a record built from it can
name the policy the Kit asked for and claim nothing about enforcement. The
adapter takes no mode argument. A record claiming `enforce` needs evidence from
the runtime that it applied this descriptor.

**Why the Kit digest is not the workload digest.** SPEC-v3 section 10: "What
runs is never a published artifact." The assembler emits an ordinary image
identified by the lock, and that is the digest `build_provenance.digest`
needs. The Kit digest identifies where the policy came from.

## Failure behavior

The adapter builds no record when the input is an image index, an artifact
manifest, an image without the descriptor annotation, a v2 Kit, a `set`, a
descriptor that is not JSON, a schema version other than 3 or one that
disagrees with its annotation, or a `vnd.docker.sandbox.kit.capabilities`
index that does not mirror the descriptor's capability types. With
`--expected-digest`, it also refuses bytes that hash to anything else. The CLI
exits 2 and says which check failed.

## Tests and conformance

The fixtures are the registry bytes of `docker/sbx-kit-claude-acp-set` 1.0.1
and `docker/doodle` 2026 (linux/amd64), with the index for the refusal case.

```bash
pip install -r integrations/docker-sandbox-kit/requirements.txt
python -m pytest integrations/docker-sandbox-kit -q
```

The suite builds, schema-checks, signs and verifies a record from both Kits
with the released packages pinned in `requirements.txt`, and checks every
refusal above. A signed record from either Kit passes
`trace-tests verify --level 0`.

`agentrust-trace-adapters` 0.1.1 predates the `declared` mode, so the adapter
passes `build_record` a small policy object of its own. It switches to
`PolicyEvidence` once a release includes that mode.

Large diffs are not rendered by default.

Loading
Loading