Skip to main content
View Markdown ↗

Copy this page

Select and copy the Markdown below, then paste it into your LLM.

On this page

Make a release approval expire

An approval that never lapses turns into a permanent exception. If a security team signs off releases one week at a time, the sign-off should stop working when the week is up, without anyone remembering to revoke it.

Every CI/lock policy carries an expires field, and it is part of what the policy owner signs. cilock verify refuses an expired policy before it looks at any evidence. In this session the prompt asks Claude to record a build, write a release policy that expires seven days out, show it admitting the build, and then show what a lapsed approval does, without touching the system clock.

How this was recorded

TestifySec wrote the prompt and sent it into the session. Claude Code and cilock ran unedited, and the recording was not changed afterwards.

A Claude Code session recording a Go build with cilock, signing a release policy that expires in seven days, and showing the same policy with a past expiry refused with policy expired

Download terminal recording (.cast)

What happens, step by step

  1. Two keys. keys/build.key signs the build evidence and keys/policy.key signs the policy. Whoever holds only the build key cannot approve a release; in the recording Claude holds both, so that separation exists only once the policy key goes to the approver.

  2. Record the build.

    cilock run --offline --step build -k keys/build.key -a environment,go-build \
      -o build.att.json -- go build -o bin/greet ./cmd/greet
  3. Write the policy. Claude writes a small script, make-policy.sh <expiry> <out>, so the same rules can be signed with any expiry. The build step must be signed by the build key, carry material, product, go-build and command-run records, and exit 0. cilock policy validate warns that the first exit-code rule would pass a record with no exit code (the rego modules deny nothing on an empty predicate); Claude rewrites it with object.get(input, "exitcode", -1) so a missing field is denied.

  4. This week's approval admits the build. Signed with an expiry seven days out (2026-10-23T07:18:57Z), the policy verifies bin/greet: Verification succeeded, exit 0.

  5. A lapsed approval refuses the same evidence. The same rules, signed by the same key with an expiry a day in the past:

    policy signature verified
    attestor policyverify failed: failed to verify policy: policy expired on 2026-10-15 02:18:57 -0500 CDT

    Exit 1. The signature is valid and the evidence is unchanged; only the date differs.

What the expiry is checked against

The expiry is compared with the verifier's own clock when cilock verify runs (attestation/policy/policy.go compares time.Now() with expires, allowing for a configured clock-skew tolerance). At the end of the session Claude suggests adding --timestamp-servers to rule out a clock set back; that is not right for this check. A trusted timestamp on the evidence does not change the comparison, so the guarantee is only as good as the clock on the machine that verifies: run the gate on infrastructure whose time you trust, such as a hosted CI runner, rather than on a laptop whose clock can be set back.

Renewing an approval means signing the policy again with a new expiry. Nothing about the build evidence has to change. See Policy verification for the other policy fields, and Release gates for a gate that adds Rego over the evidence.

Run it yourself

The recording used cilock built from main at commit 5b614726, with local ed25519 keys and no platform, so the signatures carry no timestamp.

Reference generated from the product documentation. Match commands and support details to your installed release.