Skip to main content

What is cilock?

Cilock is a pipeline observer for CI/CD. It wraps each pipeline step, records cryptographic evidence of what actually executed, and verifies that evidence against signed policy before any artifact ships.

Cilock builds on Witness (originated at TestifySec, donated to the CNCF in-toto ecosystem) and is bidirectionally interoperable with it. It implements the "pipeline observer" pattern recommended in the CNCF Software Supply Chain Best Practices v2 guide and the strategies in NIST SP 800-204D (Strategies for the Integration of Software Supply Chain Security in DevSecOps CI/CD Pipelines; TestifySec contributed to this work).

What it isโ€‹

What it doesWraps a pipeline step and records evidence about the source, environment, command, inputs, outputs, and optional security analysis performed during that step.
Why teams use itLogs tell you what the runner printed. Cilock gives you signed evidence of what actually ran, plus policy enforcement to reject what shouldn't have.
What it is notIt is not your build tool, test framework, container builder, or deployment engine. It sits around those tools and records trustworthy facts about them.

What can you do with cilock?โ€‹

  • Promote releases with confidence: only ship artifacts when you can verify they came from the expected branch, workflow, build command, and signing identity. โ†’ Release promotion gate
  • Prove security checks actually ran: record signed evidence that tests, SAST, SBOM generation, or secret scanning were executed for a given release candidate. โ†’ SBOM and SARIF evidence
  • Reduce audit reconstruction work: instead of collecting screenshots and CI logs by hand, present structured evidence tied to specific artifacts and releases. โ†’ Store attestations in Archivista
  • Make pipeline guardrails machine-checkable: turn "our release process requires X, Y, and Z" into policy verification instead of relying on reviewers to remember every rule. โ†’ Policy verification
  • Standardize provenance across CI systems: capture similar evidence from GitHub Actions, GitLab CI, or other environments without redesigning your trust model each time. โ†’ GitHub Actions ยท GitLab CI
  • Link artifacts back to source and pipeline context: answer the operational question behind many incidents and audits. What exactly produced this binary, image, or package? โ†’ Attestations

Why cilock existsโ€‹

Two supply-chain attacks in March 2026 made the case in five days:

  • March 19, 2026: an attacker force-pushed 75 of 76 version tags in aquasecurity/trivy-action. Every pipeline referencing the action by tag silently ran malicious code on its next trigger. Credentials were scraped from /proc/<pid>/mem and /proc/*/environ, encrypted with AES-256-CBC + RSA-4096, and exfiltrated to a typosquat domain.
  • March 24, 2026: litellm==1.82.7 and 1.82.8 shipped to PyPI with a credential stealer hidden in a .pth file. It executed on every Python interpreter startup (no import litellm required), sweeping SSH keys, cloud credentials, Kubernetes tokens, and shell history.

Both attacks used the same playbook: harvest, encrypt, exfiltrate to a typosquat. The shared root cause was structural: the CI pipeline trusted code it shouldn't have, with credentials it shouldn't have had, and there was no signed record of what actually ran. Cilock addresses the structural problem.

Three layers of defenseโ€‹

SHA pinning alone isn't enough. The action you pinned to could itself be compromised by a maintainer, a stolen credential, or a typo-squat. Cilock catches supply-chain attacks at three independent layers, so an attacker has to bypass all three to succeed.

Layer 1: Prevention (don't run untrusted code)โ€‹

Restrict actions to an approved catalog (internal forks, Chainguard Actions, GitHub's official actions/* namespace), then enforce it with policy. The cilock-action records whether each action ref is pinned to a 40-character commit SHA (refpinned: true|false) and emits a GitHub Actions annotation when it isn't. A signed Rego policy then decides whether to allow the build:

# policy-source-restrict.rego (verbatim from cilock-trivy-detection-test)
package cilock.verify

deny[msg] {
ref := input.actionref
not startswith(ref, "actions/")
not startswith(ref, "chainguard-dev/")
msg := sprintf("Action from untrusted source: %s", [ref])
}

deny[msg] {
not input.refpinned
msg := sprintf("Action ref not pinned to SHA: %s", [input.actionref])
}

Layer 2: Content detection (catch credential leakage)โ€‹

If prevention fails (a trusted source is compromised, a human overrides a check), cilock's secretscan attestor catches credential patterns in the command output. It runs Gitleaks pattern detection on stdout and recursively decodes base64, hex, and URL-encoded content through multiple layers. The default decode depth is 3 layers (configurable via --attestor-secretscan-max-decode-layers).

secretscan: Gitleaks patterns, recursive decode, env-var matching, encoded-secret detection. Each layer catches what the previous one missed

--attestor-secretscan-fail-on-detection blocks the build when any finding fires.

Layer 3: Behavioral detection (catch what the attacker does)โ€‹

The --trace flag (Linux only) enables ptrace-based syscall capture. Every file each process opens is recorded as openedfiles in the commandrun attestation, along with suspicious syscalls (ptrace, memfd_create, mount, clone). OPA Rego policies match the credential-harvesting filesystem fingerprint:

# policy-trace-behavioral.rego (adapted from cilock-trivy-detection-test)
package cilock.verify

import rego.v1

deny contains msg if {
some proc in input.processes
some file in object.keys(proc.openedfiles)
startswith(file, "/tmp/runner_collected")
msg := sprintf("Suspicious file access: process %s (PID %d) opened %s, matches credential harvesting pattern",
[proc.program, proc.processid, file])
}

deny contains msg if {
some proc in input.processes
some file in object.keys(proc.openedfiles)
file == "/proc/self/environ"
msg := sprintf("Suspicious file access: process %s (PID %d) read /proc/self/environ, environment variable harvesting indicator",
[proc.program, proc.processid])
}

These are the exact Rego rules used in the cilock-trivy-detection-test repo to catch the covert variant of the TeamPCP credential stealer. Trace overhead measured on an npm install workload: roughly 36% (5.1s โ†’ 6.9s).

For a full walkthrough of how these layers stop the Trivy and LiteLLM attacks, see Defending against supply-chain attacks.

Without cilock vs. with cilockโ€‹

What the supply-chain attacks of 2026 looked like, with and without a pipeline observer in place:

Without cilock: attacker pushes tag โ†’ CI executes blindly โ†’ secrets exfiltrated. With cilock: SHA pinned โ†’ secretscan detects payload โ†’ attestation signed and stored โ†’ policy blocks release

Without cilockWith cilock
A compromised action or package enters the pipelineSource policy blocks unapproved actions and packages before they run
CI executes blindly with no source or hash verificationSHA and hash pinning are enforced; tag rewrites and package swaps have no effect
Credentials are harvested at runtime (SSH keys, cloud creds, tokens, shell history)Secretscan catches credential patterns in output, including payloads hidden under layered base64, hex, or URL encoding
Covert variants write to files instead of stdout, evading log inspectionTrace + OPA catches the credential-sweep filesystem access pattern
Encrypted bundle exfiltrated to an attacker-controlled domainSigned attestation creates a tamper-evident record of what ran and what was accessed
No forensic trail of what executed, what was touched, or what was stolenPolicy enforcement blocks the release before any compromised artifact ships

Mental model: a notary for pipeline stepsโ€‹

If a normal pipeline says "trust me, I built version 1.2.3," cilock says "here is signed evidence of the exact commit, command, environment, inputs, outputs, and supporting scan artifacts behind that claim." Evidence is structured and signed instead of scattered across logs, artifacts, and screenshots, and the build provenance can travel with the artifact instead of staying trapped in one CI tool.

Honest limitationsโ€‹

Cilock is forensic and policy-driven, not a runtime IPS. It catches attacks at release time, not in real time:

  • Detection is post-execution. If a step exfiltrates secrets during execution, the exfiltration has already happened. Cilock blocks the release and provides forensic evidence; it cannot prevent the initial exfiltration.
  • No network egress monitoring. Cilock doesn't see HTTPS POST traffic from inside a step. StepSecurity Harden-Runner covers that gap.
  • --trace requires opt-in and is Linux-only. Without it, behavioral detection is off. Covert file-based attacks may evade content scanning alone.
  • Novel exfiltration techniques can evade pattern matching. Behavioral detection (filesystem patterns) covers many of these; both layers together catch most known playbooks.
  • Cilock operates in CI/CD only. It does not protect developer laptops or production servers.

How it worksโ€‹

  1. Cilock wraps a step. You run a build, test, scan, or packaging command through cilock, often in CI but sometimes locally. Each invocation is named via --step and represents one step in the supply-chain lifecycle.
  2. Attestors collect facts. Plugins gather facts in five lifecycle phases (pre-material โ†’ material โ†’ execute โ†’ product โ†’ post-product): git state, CI metadata, environment details, file digests of inputs and outputs, SBOMs, SARIF, and more.
  3. Evidence is bundled and signed. The run's attestations are bundled into a Collection and wrapped in a DSSE envelope, then cryptographically signed.
  4. Evidence is stored or shipped. The signed result can be saved as a file, pushed into Archivista, attached to an OCI image, or attached to downstream release workflows.
  5. Policies verify the release story. A verifier checks the evidence against a signed policy that lists required attestations, trusted functionaries, and embedded OPA Rego rules.

Mediaโ€‹

More context on cilock and the supply-chain landscape it addresses:

Where to nextโ€‹