# Make a release approval expire

Source: https://www.testifysec.com/docs/cilock/use-cases/expiring-approval/

A Claude Code session records a Go build with cilock and writes a signed release policy that expires in seven days, then shows the same policy signed with a past expiry refusing the same evidence with "policy expired".

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)](https://www.testifysec.com/media/docs/recordings/time-bound.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.**
   
    
   
   ```bash
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:
   
    
   
   ```text
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](https://www.testifysec.com/docs/cilock/concepts/policy-verification) for the other policy fields, and [Release gates](https://www.testifysec.com/docs/cilock/use-cases/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.
