Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
55e8753
feat(shim): best-effort setcap cap_bpf+cap_perfmon to enable eBPF tra…
cole-rgb May 24, 2026
373b13d
ci(release): workflow_dispatch input for rookery ref (dev releases)
cole-rgb May 25, 2026
e55e6d7
feat(shim): install BPF rebuild toolchain on Linux runners
cole-rgb May 25, 2026
54eb2d4
ci(release): materialise tag for workflow_dispatch dev releases
cole-rgb May 25, 2026
82e625c
go.mod: wire commandrun/ebpf submodule (split out in rookery rc50)
cole-rgb May 25, 2026
9a1ec37
ci(release): go mod tidy against pinned rookery for dispatch flow
cole-rgb May 25, 2026
4287e16
ci(release): commit tidy result so goreleaser sees clean tree
cole-rgb May 25, 2026
b9c5b55
ci: downstream smoke for v1.0.5-rc1 — real consumer pattern
cole-rgb May 25, 2026
94a683e
ci(release): push materialised tag before goreleaser
cole-rgb May 25, 2026
ddd6c1b
ci(smoke): bump to v1.0.5-rc2 (tag now points at correct commit)
cole-rgb May 25, 2026
b7ecee5
ci(smoke): disable archivista upload — auth not configured in test repo
cole-rgb May 25, 2026
3db4399
feat(action): default fanotify=auto + require-zero-drops=true
cole-rgb May 25, 2026
a738757
ci(smoke): bump to v1.0.5-rc3 — first release with zero-drop defaults
cole-rgb May 25, 2026
e0965b3
ci(smoke): bump to v1.0.5-rc4 (rookery rc51 gate-fix)
cole-rgb May 25, 2026
5d56257
ci(smoke): fix stale path trigger after rename
cole-rgb May 25, 2026
3233109
ci(smoke): bump to v1.0.5-rc5 — rookery rc53 with multi-mount fanotify
cole-rgb May 25, 2026
9352fdf
ci(smoke): bump to v1.0.5-rc6 — rookery rc54 hard-vs-soft gate
cole-rgb May 25, 2026
20c6732
feat(action): products input + workdir default + no-products warning
cole-rgb May 25, 2026
bc39ca4
fix(action): anchor relative product paths to workingDir
cole-rgb May 25, 2026
adaa244
ci(smoke): heavy-products npm install workload
cole-rgb May 26, 2026
1f41d1b
ci(smoke): seed git repo before cilock attestation (npm-install)
cole-rgb May 26, 2026
007d68e
ci(smoke): two-step source→build chain-of-custody verification
cole-rgb May 26, 2026
f57ac91
ci(smoke): use the leaf sidecar cilock run already writes
cole-rgb May 26, 2026
7a18c1e
ci(smoke): hardcode action+rookery refs (GHA can't eval inputs in 'us…
cole-rgb May 26, 2026
709c140
ci(smoke): fix workingdir input + relative-path matching for chain build
cole-rgb May 26, 2026
5276276
ci(smoke): chain from source's material tree (the 'source observation…
cole-rgb May 26, 2026
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
81 changes: 81 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
rookery_ref:
description: 'rookery ref (branch / tag / SHA) — defaults to main'
type: string
required: false
default: ''
release_tag:
description: 'release tag to publish under (e.g. v1.0.5-rc1). Required for workflow_dispatch since there is no tag context. Ignored on tag-push triggers.'
type: string
required: false
default: ''

permissions:
contents: write
Expand All @@ -23,6 +35,12 @@ jobs:
uses: actions/checkout@v6
with:
repository: aflock-ai/rookery
# Defaults to main on tag pushes; workflow_dispatch input
# lets us cut a cilock-action dev release that embeds a
# specific rookery branch / tag (e.g. nk/ci-trace-mode-probe
# or v1.1.0-rc48) for testing new attestor code before
# merging to main.
ref: ${{ inputs.rookery_ref }}
path: rookery

- name: Setup Go
Expand All @@ -34,6 +52,64 @@ jobs:
run: npm install
working-directory: cilock-action/shim

# When rookery_ref pins a non-main rookery (dev RC flow),
# cilock-action's go.sum can lag behind whatever transitive
# deps the pinned rookery brings in (e.g. rc50 split out
# commandrun/ebpf as its own module and pulled cilium/ebpf
# in through that boundary). Run `go mod tidy` against the
# current rookery checkout so go.sum is consistent before
# goreleaser builds. CI-local only — never committed back.
- name: Refresh go.sum against pinned rookery
if: github.event_name == 'workflow_dispatch'
working-directory: cilock-action
env:
GOTOOLCHAIN: auto
run: |
go mod tidy
# goreleaser refuses to release with a dirty tree. Commit
# the tidy result locally (CI workspace only; never pushed).
if ! git diff --quiet go.mod go.sum; then
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add go.mod go.sum
git commit -m "ci: go mod tidy against rookery_ref=${{ inputs.rookery_ref }} (CI-local)"
echo "tidy: applied changes to go.mod/go.sum"
else
echo "tidy: go.mod/go.sum already in sync"
fi

# GoReleaser refuses to release unless the current HEAD has a
# semver tag pointing at it. On tag-push triggers GITHUB_REF_NAME
# is the tag itself; on workflow_dispatch (used for dev RCs that
# pin a non-main rookery_ref) we have to materialise one. Create
# a local tag at the dispatch ref and pass it via
# GORELEASER_CURRENT_TAG so goreleaser doesn't fall back to the
# stale v1 major-version tag and bail.
- name: Materialise release tag (workflow_dispatch only)
if: github.event_name == 'workflow_dispatch'
working-directory: cilock-action
run: |
TAG="${{ inputs.release_tag }}"
if [ -z "$TAG" ]; then
# Derive a snapshot-style tag from the rookery_ref so dev
# RCs are uniquely named.
ROOKERY="${{ inputs.rookery_ref }}"
ROOKERY="${ROOKERY//\//-}"
TAG="v0.0.0-dev-${ROOKERY:-snapshot}-$(git rev-parse --short HEAD)"
fi
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -fa "$TAG" -m "Dev release ${TAG} (rookery_ref=${{ inputs.rookery_ref }})"
# Push the tag BEFORE goreleaser so `uses: aflock-ai/cilock-action@$TAG`
# downstream actually fetches the post-tidy HEAD (with shim
# changes, action.yml, etc.). Without this push, GitHub's
# release-create API points the tag at the default branch's
# HEAD, breaking every downstream consumer of the action.
git push origin "refs/tags/$TAG"
echo "GORELEASER_CURRENT_TAG=$TAG" >> "$GITHUB_ENV"
echo "CILOCK_RELEASE_TAG=$TAG" >> "$GITHUB_ENV"
echo "Pushed tag $TAG to origin"

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
Expand All @@ -42,8 +118,13 @@ jobs:
workdir: cilock-action
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_CURRENT_TAG: ${{ env.GORELEASER_CURRENT_TAG }}

# Only roll the v1 major-version tag forward on real tag-push
# releases. Dev RCs (workflow_dispatch) get a uniquely-named
# release but shouldn't shift the v1 alias.
- name: Update v1 major version tag
if: github.event_name == 'push'
working-directory: cilock-action
run: |
git config user.name "github-actions[bot]"
Expand Down
242 changes: 242 additions & 0 deletions .github/workflows/smoke-multistep-chain.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
name: smoke — multi-step chain verification

# End-to-end two-step chain-of-custody smoke. Exercises every piece
# of the v0.3 chain pipeline:
#
# step 1 (source) — attest the source-tree state with cilock run
# step 2 (build) — attest the gh CLI build with cilock run
# chain proof — cilock prove-chain binds step 2's materials
# to step 1's signed Merkle root via per-leaf
# RFC 6962 inclusion proofs
# verify — cilock verify with a multi-step policy walks
# the chain: build's ArtifactsFrom=[source]
# resolves via the FilesystemChainSidecarSource,
# and every consumed material must either have
# an inclusion proof against source or match
# AllowedUntracked (toolchain reads)
#
# Negative case: tamper a source file between step 1 and step 2,
# rerun verify, must exit non-zero with a precise diagnostic
# naming the offending material.
#
# Requires cilock-action @ a release that includes the
# `cilock prove-chain` subcommand and policy.WithChainSidecarSource.

on:
workflow_dispatch:
inputs:
cilock_action_ref:
description: 'cilock-action tag (must include prove-chain + chain sidecar source)'
default: 'v1.1.0-rc1'
required: true
push:
paths:
- '.github/workflows/smoke-multistep-chain.yml'

permissions:
contents: read
id-token: write

jobs:
multistep-chain:
name: source → build chain (gh CLI)
runs-on: ubuntu-24.04
timeout-minutes: 25

steps:
- name: Checkout source under test (gh CLI)
uses: actions/checkout@v4
with:
repository: cli/cli
path: src

- name: Show runner info
run: |
uname -a
cat /proc/version

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: src/go.mod

# --- STEP 1: SOURCE ATTESTATION -------------------------------
# Attest the source-tree state. The 'source' step's products
# are the files under src/ at HEAD — every source file the
# build will later consume. The action's git attestor requires
# a .git directory at workingdir, so we point at src/ (where
# actions/checkout placed the .git).
- name: Attest source tree (step 1)
uses: aflock-ai/cilock-action@v1.0.5-rc14
with:
step: source
command: git rev-parse HEAD
# workingdir is the action's input name (not 'working-directory').
workingdir: src
# 'products: **' (anchored to workingdir=src) records every
# file in the source tree under the product Merkle root —
# gives step 2's chain-proof generator a leaf for any
# material the build reads.
products: |
**
outfile: /tmp/source.attestation.json
enable-archivista: 'false'

- name: Confirm source leaf sidecar exists
run: |
ls -la /tmp/source.attestation.material.tree.json
jq '{schemaVersion, source, merkleRoot, treeSize, leafCount: (.leaves | length)}' \
/tmp/source.attestation.material.tree.json

# --- STEP 2: BUILD ATTESTATION --------------------------------
# Build gh CLI under src/ (where its .git + go.mod live).
# Output bin/gh into src/bin/ so everything stays under the
# source workingdir.
- name: Attest gh CLI build (step 2)
uses: aflock-ai/cilock-action@v1.0.5-rc14
with:
step: build
workingdir: src
command: go build -o bin/gh ./cmd/gh
products: |
bin/gh
trace: 'true'
outfile: /tmp/build.attestation.json
enable-archivista: 'false'

# --- CHAIN PROOF GENERATION -----------------------------------
# Read the build attestation's traced materials list, filter to
# files under src/ (the source-step coverage), and emit a chain
# sidecar with per-material RFC 6962 inclusion proofs against
# step 1's signed Merkle root.
- name: Build chain sidecar (cilock prove-chain)
run: |
set -euo pipefail
# Install cilock from the release the action pinned.
curl -fsSL https://github.com/aflock-ai/rookery/releases/download/v1.1.0-rc65/install.sh | bash -s -- /usr/local/bin
cilock --version

# Extract materials from build attestation that live under src/.
# The build attestation's trace records ABSOLUTE paths; the
# source step's leaf sidecar stores paths RELATIVE to its
# workingdir (src/). Strip the workspace prefix so the
# 'path=sha256hex' tuples align with what BuildChainSidecar
# will look up.
PREFIX="$GITHUB_WORKSPACE/src/"
jq --arg prefix "$PREFIX" -r '
.payload | @base64d | fromjson
| .predicate.attestations[]?
| select(.type | test("command-run"))
| .attestation.processes[]?
| (.openedFiles // {}) | to_entries[]
| select(.value != null and (.value | type) == "object")
| select(.key | startswith($prefix))
| (.key | sub($prefix; "")) + "=" + (.value | to_entries[] | select(.key | test("sha256")) | .value)
' /tmp/build.attestation.json | sort -u > /tmp/consumed.list
echo "consumed material count: $(wc -l < /tmp/consumed.list)"
head -5 /tmp/consumed.list

# Build the chain sidecar. --consumed accepts repeated
# path=sha256hex args.
consumed_args=()
while IFS= read -r line; do
consumed_args+=(--consumed "$line")
done < /tmp/consumed.list

mkdir -p /tmp/chain-sidecars
cilock prove-chain \
--source-envelope /tmp/source.attestation.json \
--source-sidecar /tmp/source.attestation.material.tree.json \
--source-step source \
--domain rookery-material/v0.3 \
"${consumed_args[@]}" \
--outfile /tmp/chain-sidecars/build.chain.json

echo "=== chain sidecar summary ==="
jq '{
schemaVersion,
sourceStep,
proofCount: (.materialProofs | length)
}' /tmp/chain-sidecars/build.chain.json

# --- POLICY VERIFICATION (HAPPY PATH) -------------------------
- name: Write multi-step policy
run: |
cat > /tmp/policy.json <<'POLICY'
{
"expires": "2030-12-01T00:00:00Z",
"steps": {
"source": {
"name": "source",
"attestations": [
{"type": "https://aflock.ai/attestations/product/v0.3"}
]
},
"build": {
"name": "build",
"artifactsFrom": ["source"],
"attestations": [
{"type": "https://aflock.ai/attestations/product/v0.3"},
{"type": "https://aflock.ai/attestations/material/v0.3"}
],
"allowedUntracked": [
"/opt/hostedtoolcache/**",
"/usr/lib/**",
"/usr/include/**",
"/etc/**",
"/proc/**",
"/sys/**",
"/tmp/**"
]
}
}
}
POLICY
echo "policy written"

- name: Verify chain (must succeed)
run: |
cilock verify \
--policy /tmp/policy.json \
--attestation /tmp/source.attestation.json \
--attestation /tmp/build.attestation.json \
--chain-sidecar-dir /tmp/chain-sidecars \
--require-sidecar
echo "✓ multi-step chain verified end-to-end"

# --- NEGATIVE CASE: TAMPERED MATERIAL -------------------------
# Forge a chain sidecar with one tampered material digest. The
# cryptographic check inside VerifyChainSidecar must fail.
- name: Negative test — tampered chain sidecar must fail verify
run: |
set +e
# Flip a bit in the first proof's audit path.
jq '.materialProofs[0].auditPath[0] = "deadbeef00000000000000000000000000000000000000000000000000000000"' \
/tmp/chain-sidecars/build.chain.json > /tmp/chain-sidecars/build.chain.json.tampered
mv /tmp/chain-sidecars/build.chain.json.tampered /tmp/chain-sidecars/build.chain.json

cilock verify \
--policy /tmp/policy.json \
--attestation /tmp/source.attestation.json \
--attestation /tmp/build.attestation.json \
--chain-sidecar-dir /tmp/chain-sidecars \
--require-sidecar
rc=$?
if [ $rc -eq 0 ]; then
echo "::error::verify accepted a tampered chain sidecar — Merkle commitment broken"
exit 1
fi
echo "✓ tampered sidecar correctly rejected (exit $rc)"

- name: Upload artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: multistep-chain-attestations
path: |
/tmp/source.attestation.json
/tmp/source.leaves.sidecar.json
/tmp/build.attestation.json
/tmp/chain-sidecars/
/tmp/policy.json
if-no-files-found: warn
Loading
Loading