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.

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.

Profiles and noise control

core (default) scans agent context files with the agent-specific rules. extended swaps in the generic rules and widens discovery to source and dependency files. Thresholds, a config file, an ignore file, baselines, and inline cairnlock:ignore comments keep large repositories usable. The full breakdown lives in the README's noise-controls section.

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.6.0
        with:
          base-ref: ${{ github.event.pull_request.base.ref }}
          profile: core
          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.
InputDefaultPurpose
path.Directory to scan
outputcairnlock.sarifSARIF output file
failtrueFail when the threshold is exceeded
fail-onlowlow, medium, high, or never
profilecorecore or extended
base-refemptyRun verify --base against this ref
allow-lock-changefalsePermit lockfile changes vs base-ref

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.