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.
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.
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.
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
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.
| Input | Default | Purpose |
|---|---|---|
path | . | Directory to scan |
output | cairnlock.sarif | SARIF output file |
fail | true | Fail when the threshold is exceeded |
fail-on | low | low, medium, high, or never |
profile | core | core or extended |
base-ref | empty | Run verify --base against this ref |
allow-lock-change | false | Permit lockfile changes vs base-ref |
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.