GitHub Action that wraps a command (or another action) with cilock attestation. Records what ran, what files it touched, what containers it pulled, and what credentials it leaked — all into a signed in-toto statement.
- uses: aflock-ai/cilock-action@v1
with:
step: build
attestations: "environment git github sbom secretscan"
outfile: ${{ github.workspace }}/attestation.json
command: make buildSee action.yml for every input.
This trips up everyone the first time, including the people writing this README. The file at outfile: is a DSSE envelope:
{
"payloadType": "application/vnd.in-toto+json",
"payload": "<base64-encoded in-toto Statement>",
"signatures": [{ "keyid": "…", "sig": "…", "cert": "…" }]
}The actual attestation content — predicateType, subject, predicate.attestations[] — lives inside .payload, base64-encoded. Treating the envelope as the statement leads to OPA policies that silently match nothing because input.predicate doesn't exist at the envelope level.
# Inspect the inner Statement
jq -r '.payload' attestation.json | base64 -d | jq .
# Predicate type
jq -r '.payload' attestation.json | base64 -d | jq -r '.predicateType'
# → https://aflock.ai/attestation-collection/v0.1
# Per-attestor entries (this is where secretscan findings, trace data, etc. live)
jq -r '.payload' attestation.json | base64 -d \
| jq '.predicate.attestations[] | {type, attestation_keys: (.attestation | keys)}'OPA Rego policies that match on input.predicate.attestations[] need the decoded statement, not the envelope. The right shape:
jq -r '.payload' attestation.json | base64 -d > /tmp/statement.json
opa eval -d policy.rego -i /tmp/statement.json 'data.cilock.verify.deny' -f jsonIf your policy expects a specific attestor's data at the top level (e.g. trace data from command-run), drill in one more layer:
jq '.predicate.attestations[] | .attestation | select(has("processes"))' /tmp/statement.json \
> /tmp/trace-input.json
opa eval -d policy-trace.rego -i /tmp/trace-input.json 'data.cilock.verify.deny' -f jsonDSSE is what makes the signature meaningful — it covers the exact bytes of the payload (including payloadType), so downstream verifiers can prove what was signed without re-canonicalizing JSON. The base64 wrapping prevents whitespace and key-ordering changes from invalidating signatures.
The downside is the extra unwrap step. If you build CI checks against cilock attestations, write a small helper:
# decode_attestation.sh
jq -r '.payload' "$1" | base64 -d…and use it everywhere.
See action.yml. The most-used ones:
| Input | Purpose |
|---|---|
step |
Step name embedded in the attestation (required) |
command / action-ref |
What to wrap (exactly one required) |
attestations |
Space-separated attestor list |
outfile |
Path for the DSSE envelope. Format above. |
trace |
Enable command tracing (ptrace on Linux) |
enable-sigstore |
Sign with Fulcio (default true) |
enable-archivista |
Upload to Archivista (default true) |
cilock-args |
Pass-through args to attestors (see supported flags) |
cilock-action runs the rookery library in-process — it does not shell out to the cilock binary — so only flags that the action explicitly translates take effect. Currently supported:
--attestor-secretscan-fail-on-detection [bool]— exit non-zero when secretscan records findings--attestor-secretscan-max-decode-layers <int>— how many encoding layers to recursively decode (default 3)--attestor-secretscan-max-file-size <int-mb>— skip files larger than this--attestor-secretscan-config-path <path>— custom Gitleaks config
Unknown flags are silently ignored. File an issue or PR if you need another attestor option wired through.
- uses: aflock-ai/cilock-action@v1
with:
step: build
attestations: "environment git github secretscan"
outfile: ${{ github.workspace }}/attestation.json
cilock-args: "--attestor-secretscan-fail-on-detection"
command: make build- uses: aflock-ai/cilock-action@v1
with:
step: trivy-scan
attestations: "environment git github sbom"
outfile: ${{ github.workspace }}/scan.attestation.json
action-ref: aquasecurity/trivy-action@76071ef0d7ec1c61c8c2dc1a37a91019797d3c1b
action-inputs: |
{"scan-type": "fs", "scan-ref": "."}- uses: aflock-ai/cilock-action@v1
with:
step: build
outfile: ${{ github.workspace }}/attestation.json
command: make build
- name: Decode and verify
run: |
jq -r '.payload' attestation.json | base64 -d > /tmp/statement.json
opa eval -d policy.rego -i /tmp/statement.json \
'data.cilock.verify.deny' -f json | tee /tmp/opa-result.json
DENY=$(jq -r '.result[0].expressions[0].value | length // 0' /tmp/opa-result.json)
if [ "$DENY" -gt 0 ]; then
echo "::error::policy denied $DENY rules"
exit 1
fi@v1— major-version tag, auto-updated on eachv1.x.yrelease@v1.0.1— exact-tag pin@<40-hex-SHA>— SHA pin against the published-tag commit (recommended for supply-chain hygiene; the shim resolves the SHA to its release tag)
- cilock (the wrapped library): aflock-ai/rookery
- Docs: cilock.aflock.ai
- Supply-chain attack catalog with live detection demos: aflock-ai/supply-chain-attacks
- Commercial / managed: TestifySec Platform
Apache 2.0. Built and sponsored by TestifySec.