cairnlock

Guide

The lockfile workflow, base-branch verification, and CI setup.

Lockfile workflow

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.

CommandPurposeExit codes
initCreate the lockfile from the current tree0, or 1 if it exists (use --force)
verifyFail on unreviewed changes0 clean, 1 changes, 2 usage or environment error
diffList changes and run rules inside themalways 0
approve Record a reviewed change0, or 1 for unknown files

Verify against a base ref

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.

SituationExit code
Nothing differs from the base lockfile0
Tracked file changed, added, or deleted1
Lockfile differs from base (without --allow-lock-change)1
No lockfile at the base ref2, with a hint to run cairnlock init on the base branch
Ref does not resolve, or the clone is shallow2; 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.

Protect the lockfile with CODEOWNERS

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.

GitHub Action

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
Use 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.

Pre-commit hook

cp hooks/pre-commit .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit

The hook runs cairnlock verify before every commit.

Publishing these docs

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.