Frontier Scout
Does this PR stay inside its declared scope?
Check the diff against the base commit's policy. Compile the same policy into Claude Code permissions and hooks.
Status: research preview. 2.2.0 is the current release and repairs the verifier. v2.1.0 and earlier have known verification defects. Release details
Published regression cases (matrix):
Outside allowed paths -> FAIL
Protected path + unsigned receipt -> UNVERIFIED
Used here: our own PRs exercise the Action in advisory CI. The PR can edit that workflow; it is not a trusted merge gate.
Engineering: 40-test regression matrix · Action isolation tests Hosted source-install check · Optional attestations
Boundary: unsigned receipts cannot authenticate approval. Verified attestations identify a workflow.
Set up the pinned Action · Limits
Quickstart: the GitHub Action
Add the verifier to any repo with a frontier-scout.policy.json (one policy init away —
see full setup).
Release status. The repaired verifier and Action described in this section ship in release 2.2.0 (
ajaysurya1221/frontier-scout@v2.2.0;frontier-scout==2.2.0on PyPI). v2.1.0 and earlier behave differently and have known defects (see the previous release below). Keep thepull_requestexample below advisory: the PR can edit that workflow, so it is not a merge gate.
Repaired verifier (2.2.0)
name: Frontier Scout verify
on:
pull_request:
permissions:
contents: read
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # the verifier diffs against the base ref
persist-credentials: false
- uses: ajaysurya1221/frontier-scout@v2.2.0
with:
advisory: "true" # report only: a pull_request workflow is editable by the PR
evidence-artifact: "frontier-scout-evidence"
The Action runs agent verify-pr against the base branch. The policy and lock come from
the base commit, so a PR cannot widen its own scope. Every path the PR changes is checked,
including both ends of a rename, deletions, and mode-only and binary changes. The verdict is
one of three:
- FAIL: a path outside
allowed_file_globs; a protected path with no action record; a missing, malformed or drifted policy or lock; a malformed, unbound or stale record; or a change made despite adeny. - UNVERIFIED: a protected path changed and only unsigned records mention it (receipts cannot authenticate an approval, so a human approves it outside the tool), or the diff could not be collected.
- PASS: none of the above.
Merge the policy and lock before relying on the check. They are read from the base branch,
so the PR that introduces them is reported as POLICY_LOCK_MISSING.
UNVERIFIED is never reported as a pass. Every finding carries a reason code, and the output
reports scope and approval provenance separately. The Action annotates the PR, writes a step
summary, and emits a machine-readable evidence JSON. The defect matrix behind this behaviour
is in docs/evaluation/verifier-2026-10-06.md.
The Action never runs Python from the PR checkout: every step runs from $RUNNER_TEMP with
isolated Python (python -I), and the repository reaches the verifier as an absolute
--repo path, so a pip.py, json.py or frontier_scout/ at the PR root cannot replace
the installer, the verifier or the output step.
On pull_request, GitHub runs the workflow file from the PR, so a PR can edit the verify step
itself. A check that must gate merges has to run from a workflow the PR cannot edit, for
example a required workflow in an organization ruleset pinned to a trusted ref. That is a
prerequisite for gating; installing a repaired release does not by itself turn the
pull_request example above into a gate.
Inspect the same workflow this repository runs
On pull requests, the dogfood workflow runs both the CLI verifier and the composite Action against the base branch. The Action uploads an evidence JSON artifact. Both jobs are advisory: inspect the recorded verdict, because a successful job does not mean the scope verdict passed.
A team requiring merge enforcement must place the check in a workflow the candidate PR cannot modify. Local receipts remain unsigned observations.
The previous release (v2.1.0)
ajaysurya1221/frontier-scout@v2.1.0 and frontier-scout==2.1.0 on PyPI predate the repair
shipped in 2.2.0:
verify-prhas five false acceptance paths: unenforcedallowed_file_globs, any receipt counted as approval, unbound receipts, PR-side policy and receipts, and lossy diff parsing.- Its Action runs Python from the PR checkout's working directory, so a PR can ship a
pip.pyorjson.pythat replaces the installer or rewrites the verdict, exit code and evidence before anything is uploaded or attested.
If you use it, use it only as an advisory signal on pull_request with contents: read:
keep advisory: "true", do not set attest: "true", and do not run it from
pull_request_target or any workflow that holds secrets or write permissions.
Signed evidence (optional)
With attest: "true", the evidence JSON is signed via GitHub artifact attestations
(Sigstore) — Frontier Scout deliberately rides GitHub's signing rail rather than inventing
a receipt protocol. This needs the repaired Action above (2.2.0 or later): on v2.1.0, PR-supplied Python can run before the evidence is signed.
permissions:
contents: read
id-token: write
attestations: write
steps:
- uses: ajaysurya1221/frontier-scout@v2.2.0
with:
attest: "true"
evidence-artifact: "frontier-scout-evidence"
Anyone can then verify the evidence independently:
gh attestation verify frontier-scout-evidence.json --owner <org-or-user> \
--predicate-type https://github.com/ajaysurya1221/frontier-scout/predicate/verify-pr/v1
If attestation is requested and cannot be produced, the Action fails — it never silently degrades to unsigned evidence. (Not available to fork PRs; OIDC.) Attestation is off by default, and this repository's own dogfood workflow does not request it.
What's verified vs what's claimed
Honesty model, load-bearing:
| Artifact | Status |
|---|---|
| Scope check: every changed path (both rename ends, deletions, mode and binary changes) against the base commit's policy and lock | Checked in CI. The diff and the policy come from git, not from the PR's own files |
| Approval of a protected-path change | Not authenticated. FAIL with no receipt, UNVERIFIED with one, never PASS |
Evidence JSON with a passing gh attestation verify |
Verified provenance of the JSON. Signed by the workflow's Sigstore identity, mode (enforcing/advisory) and verdict carried in the predicate. It does not show who approved a change |
| Evidence JSON without attestation | Supporting claim |
| Local action records (receipts written by the agent-side hook) | Unauthenticated observation. Written on the same machine the agent controls; never treated as approval; flagged RECEIPT_IN_PR when committed in the PR |
UNVERIFIED (no diff, or a protected change backed only by receipts) |
Never rendered as a pass, in either mode; non-zero exit when enforcing |
In the documented README comparison, Notari describes signed scope approval; Frontier Scout does not authenticate approval. The comparison is pinned to 2026-10-05 source revisions and does not report execution tests.
Status: research preview, maintained; one maintainer; no adoption claims. Claude Code first (Codex/Cursor/Copilot are roadmap, not built). The pre-registered demand gates and their day-90 evaluation are public: KILL_CRITERIA.md.
Full setup (policy + local hooks)
python3 -m venv .venv
source .venv/bin/activate
python -m pip install frontier-scout==2.2.0
cd your-repo
frontier-scout agent policy init # conservative frontier-scout.policy.json from a scan
frontier-scout agent compile --target claude --repo . --out .
frontier-scout doctor # confirm policy/lock/hooks/workflow are in place
compile writes:
| Artifact | Purpose |
|---|---|
.claude/settings.json |
permissions (allow/deny/ask) + hook wiring |
.claude/hooks/pre_tool_use.py · post_tool_use.py |
decide allow/deny/ask, write action records |
.claude/hooks/_fs_guard.py |
self-contained (stdlib-only) decision + record logic |
policy.lock.json |
sha256 binding action records to this exact policy |
managed-settings.json |
admin/MDM MCP allow/deny fragment |
.github/workflows/frontier-scout-verify.yml |
the PR verifier check |
compile pins the generated workflow to the version that compiled it
(frontier-scout==2.2.0 here). A workflow compiled by 2.1.0 or earlier installs the latest
release unpinned, so it picks up 2.2.0 and its stricter verdicts on its next run; recompile it
to pin the version. 2.1.0 itself has the defects listed above.
Run Claude Code normally — the hook gates each tool call and writes redacted local action
records to .frontier-scout/receipts/. The CI verifier then checks the PR diff against the
base commit's policy and lock, and reports those records as unsigned observations. Receipts
you commit under frontier-scout-receipts/ are part of the PR: either include that directory
in allowed_file_globs, or pass receipts from outside the PR with --receipts. The CLI
equivalent of the Action:
frontier-scout agent verify-pr --repo . --base "origin/main" \
--receipts "frontier-scout-receipts/*.json" --json-out evidence.json
See examples/demo-walkthrough.md for a step-by-step
demo, and examples/sample-repo/ for the end-to-end
fixture.
The policy
frontier-scout.policy.json is a typed schema (not a new language), compiled to native
config — four dimensions plus approval gates:
{
"allowed_shell_commands": ["pytest", "git status"],
"blocked_shell_commands": ["rm -rf", "git push --force"],
"allowed_file_globs": ["src/**", "tests/**"],
"protected_file_globs": ["**/migrations/**", ".github/workflows/**", "**/.env"],
"mcp_server_allowlist": ["github"],
"required_checks": ["pytest"],
"approval_gates": ["network", "shell", "credential", "write", "protected-path"]
}
Decisions are fail-closed: anything not provably safe escalates to ask; off-allowlist
MCP servers and blocked commands hard-deny.
Optional: a decision model inside the hook
Off by default, and the compiled artifacts are identical whether or not you use it. A policy may opt in to a System One decision model (TypeSafe's Jev) as a second opinion on Bash calls, consulted by the hook after the static decision:
{
"decision_model": {
"provider": "typesafe",
"model": "jev-1.13.0",
"key_env": "TYPESAFE_API_KEY",
"timeout_seconds": 3.0,
"relax_ask_to_allow_at": 0.95,
"deny_at": 0.9,
"ask_at": 0.5
}
}
The hook asks four literal questions about the command (main effect; destructive; exposes
secrets; escalates privilege) and may only tighten an allow to ask when the model
rates the call possibly risky (ask_at) or an allow/ask to deny when it is confident
the call is risky (deny_at), or relax an ask to allow when it is confident the call
is read-only or build/test and not risky. A static deny is never relaxed. No key, a
timeout, a malformed or wrong-model answer, or a low-confidence answer leaves the static
decision in force, and the receipt says so (decision_model.applied: tightened,
tightened-to-ask, relaxed, abstained or unavailable). The key is read from the environment at hook time
and never written. Thresholds are yours to set from your own data. The default thresholds were
evaluated on the maintainer-labelled command corpus in
docs/evaluation/decision-model/. Those results do not
establish general permission calibration or native-session enforcement.
CLI
| Command | What it does |
|---|---|
agent verify-pr |
PR scope check: the real diff against the base commit's policy and lock; receipts are reported, never treated as approval (--json, --json-out) |
agent compile |
Compile the policy into Claude Code native controls + CI verifier |
agent scan |
Static repo agent-risk scan (secret-likely files by name only) |
agent policy init | explain |
Generate / read a conservative policy |
agent check "<task>" |
Static pre-check of a proposed task (executes nothing) |
agent receipts list | show |
Inspect local action records |
agent export agents-md | pr-checklist |
Advisory policy snippets |
doctor |
Offline agent-readiness check |
Safety model
- Static + read-only. The scan reads file names, never secret contents. The only
subprocesses are read-only git calls (
rev-parse,diff,ls-tree,cat-fileinverify-pr;rev-parsefor receipt metadata). - Keyless and offline by default. The compiler, verifier, doctor and hooks make no
network call unless a policy opts into the decision model,
which is advisory and fail-closed: it can never relax a static
deny, and any failure leaves the static decision in force. - Emit, don't enforce. Frontier Scout writes native config; Claude Code's hook/permission system enforces locally and GitHub Actions enforces in CI.
- Fail-closed where it can be. In the hook, a missing or malformed policy denies by
default and every dangerous capability escalates to approval.
verify-prnever passes what it cannot establish (see the verdicts). It does not authenticate approvals. - Redacted. Every persisted/emitted string is scrubbed of secret-shaped tokens, including
file paths in
verify-prfindings and annotations; undecodable file-name bytes are emitted as\xNNmarkers. - Honest. It is control evidence, not a guarantee. Local hooks are not a complete enforcement boundary — they are paired with the CI diff verifier on purpose, and unsigned evidence is always labeled a claim (see verified vs claimed). Correctness comes from deterministic compile output, action records, and CI verification — Frontier Scout does not rely on optional Claude Code conveniences like hook input-rewriting or mid-session settings reload (even where current Claude Code supports them).
What we don't build
Frontier Scout writes local action records for PR scope verification and signs evidence through GitHub's attestation rail. It is not a signed receipt protocol, signing daemon, MCP proxy, SDK, dashboard, or ledger. It deliberately reuses wheels that already exist:
- Claude Code — runtime hooks, permissions, and managed settings (local enforcement).
- GitHub Actions — the PR check (CI enforcement).
- GitHub artifact attestations / Sigstore — evidence signing and verification.
- MCP clients / gateways — tool transport.
- Existing receipt / provenance projects (for example, Agent Receipts or Pipelock) — for any future portable receipt format; integrate, don't reinvent.
Roadmap
P0 (shipped): the GitHub Action with signed evidence, the Claude compiler + local action records, the CI verifier (its scope-check repair shipped in 2.2.0; see above). P1 was demand-gated and the gates were not met at the day-90 evaluation: platform-evidence ingestion, Codex adapter, scanner findings as policy inputs stay unbuilt unless someone with a real use case asks. See ROADMAP.md.
Contributing
See CONTRIBUTING.md, AGENTS.md, the design records in
docs/adrs/, SECURITY.md (threat model, reporting) and the
changelog. Tests: PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python -m pytest -q.
Lint/type: make lint, make type. Figures: python3 docs/assets/src/make_figures.py --write.
Maintained by Ajay Surya Senthilrajan, with AI pair-programming recorded in commit trailers. See the tests, design records and release evidence linked here.
License
MIT — see LICENSE.
Metadata
Release files for frontier-scout 2.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| frontier_scout-2.2.0.tar.gz | 99.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| frontier_scout-2.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 162.7 kB
Release files / frontier_scout-2.2.0.tar.gz
| Download URL | frontier_scout-2.2.0.tar.gz |
|---|---|
| Size | 99.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5e7302fe2e956306a047fc33aa0d980dc628c57ca4b023ba1851286ba1c604ae
|
|
BLAKE2b-256 checksum How to use checksums |
f85a98a9894378d2c6711dfec645359e203034c9819f0f936bd4f7344df6819b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.
Transparency logRelease files / frontier_scout-2.2.0-py3-none-any.whl
| Download URL | frontier_scout-2.2.0-py3-none-any.whl |
|---|---|
| Size | 63.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
79ebd9d677c3f7a8939c331805f421e2f08951fdc0bde607872929a0d46bf09d
|
|
BLAKE2b-256 checksum How to use checksums |
710dd1aa20ef7f8c371aaa4c5631628bf833f7db96b2c6c926cefb5939e509d5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.
Transparency log