The lockfile workflow, base-branch verification, and CI setup.
cairnlock.lock stores a SHA-256 hash for every tracked
context file. Commit it. Once it is committed, verify fails on
any edit, addition, or deletion that has not been approved.
| Command | Purpose | Exit codes |
|---|---|---|
init | Create the lockfile from the current tree | 0, or 1 if it exists (use --force) |
verify | Fail on unreviewed changes | 0 clean, 1 changes, 2 usage or environment error |
diff | List changes and run rules inside them | always 0 |
approve | Record a reviewed change | 0, or 1 for unknown files |
A plain verify compares the working tree against the
lockfile on disk. That can be defeated: a pull request can edit
AGENTS.md and rehash cairnlock.lock in the same
commit, and verify passes. Base verification closes that hole.
cairnlock verify --base origin/main
This reads cairnlock.lock as it exists at the base ref with
git show, not from the working tree, then compares the working
tree against it. If the lockfile itself differs from the base version, that
is reported as its own finding:
lockfile modified in this change
modified: AGENTS.md
verify failed: 2 findings vs base 'origin/main'
Pass --allow-lock-change when the lockfile change is the
whole point of the branch. The tracked-file comparison still runs against
the base lockfile either way.
| Situation | Exit code |
|---|---|
| Nothing differs from the base lockfile | 0 |
| Tracked file changed, added, or deleted | 1 |
Lockfile differs from base (without --allow-lock-change) | 1 |
| No lockfile at the base ref | 2, with a hint to run cairnlock init on the base branch |
| Ref does not resolve, or the clone is shallow | 2; shallow clones get a hint about fetch-depth: 0 |
Ref starts with - | 2, rejected before git runs |
All git calls use an argument list, never a shell. cairnlock never makes network calls; base verification only reads from the local clone.
Treat cairnlock.lock like a dependency lockfile. Add a
CODEOWNERS file so edits to it require review from a designated owner:
# .github/CODEOWNERS
/cairnlock.lock @your-org/security-team
Combined with verify --base in CI, a pull request cannot
silently rewrite which agent files count as approved.
The repository ships a composite action. It verifies the lockfile and writes SARIF for Code Scanning.
name: cairnlock
on:
push:
branches: [main]
pull_request:
permissions:
security-events: write
contents: read
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pennacle-security/cairnlock@v0.3.0
with:
base-ref: ${{ github.event.pull_request.base.ref }}
output: cairnlock.sarif
fail: "false"
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: cairnlock.sarif
fetch-depth: 0 when you set base-ref. A
shallow checkout cannot resolve the base ref, and verify exits 2 with a
message that says exactly this.
Action inputs: path, output,
fail, base-ref,
allow-lock-change.
cp hooks/pre-commit .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
The hook runs cairnlock verify before every commit.
The docs/ directory is plain HTML with no build step. In the
repository settings, enable GitHub Pages from the main branch with the
/docs folder as the source to publish it.