Repository navigation
v0.4.0
Org policy and the app link-up. The Action can fetch an org policy (policy input) and upload the redacted run bundle
(upload input) to the pharn.dev app, a new policy mode reads agent settings and MCP configs (rules P01–P11), agent PRs
are detected (meta.isAgentPr), and check A7 scans agent configs for secrets. With no policy and no app the report is
the one 0.3.0 produced; nothing was removed or renamed. See Security for the new network surface and file reads.
Added
- Org policy rules P02–P06, and policy mode: with a policy in force the CLI and the Action read the tracked agent
settings files (.claude/settings.jsonand.claude/settings.local.json, root and nested) and, when the policy lists
MCP servers,.mcp.json,.cursor/mcp.jsonand.vscode/mcp.json, at HEAD. P02 flags an allow rule that overlaps
a forbidden permission,defaultModebypassPermissionsorauto, andadditionalDirectories; P03 a missing
required deny rule; P04 an MCP server that is not allowlisted; P05 a forbidden MCP server; P06 a missing required
hook. New message codesPOLICY_P02toPOLICY_P06andPOLICY_SETTINGS_INVALID(a file the rules could not fully
use, or did not read); P09 now judges those edits as a block, warn or info. Nothing read from a file is printed. meta.isAgentPrin the Action's report, withmeta.prAuthor,meta.prLabels,meta.coAuthorsandmeta.agentHint:
whether the pull request counts as an agent PR, from its author, its commits' authors, committers and
Co-authored-bytrailers, and its labels (agentandai-generatedby default). Seven known agents are recognized.
Evidence that cannot be read in full (an unknown author, unreadable commits, more than 250 commits) counts as an agent
PR. Metadata only: the verdict, the messages, the comment and the exit code do not change. In core:detectAgentPr,
parseCoAuthorTrailers,agentPrMetaFields,withAgentPrMeta,KNOWN_AGENTSandDEFAULT_AGENT_LABELS.schema/policy.v1.json: the JSON Schema (draft 2020-12) for version 1 of the policy document, the file that will
tell pharn-ci what "safe to merge" means for a repository, shared with the pharn.dev app. A pure validator in core
(validatePolicy, andparsePolicyJsonfor JSON text) checks a document against it: it never throws, never
repeats a value from the document in an error, and bounds its work on any input. The org policy entry below is
what reads it.- Org policy. The Action takes two inputs,
policy(auto,never,requiredorfile:<path>, defaultauto) and
app-url(defaulthttps://pharn.dev), and the CLI two flags,--policy-file <path>and--no-policy. With
policy: autoorrequiredand an OIDC token (permissions: id-token: write), the Action makes oneGETof the
pharn.dev policy endpoint, sending only the OIDC token and the repository name, and applies the served policy over
the workflow inputs and.pharn/ci.yml: it raises the threshold,setup.requireand the test-integrity mode,
replaces the exclusions and the coverage root, sets per-signal severities and the accept label, and adds
setup.expecttools. Afile:policy carried by the pull request can only tighten. Two new message codes:
POLICY_UNAVAILABLE(warn,errorunderpolicy: required) andPOLICY_OVERRIDE_IGNORED(warn). With no
token,policy: neveror no policy, the report is the one it was. - Check A7, secrets in agent configs. The CLI and the Action read the committed text at
HEADof the
machine-readable agent setup files (.mcp.json,.cursor/mcp.json,.vscode/mcp.json,.claude/settings*.json,
.codex/config.toml,.gemini/settings.json,opencode.json,.aider.conf.yml, the files under.claude/hooks/and
the.json,.yamland.ymlfiles under.continue/rules/; never the Markdown kinds) and report ten shapes of
secret (GitHub, Anthropic, OpenAI, Google, AWS, Slack, Stripe, private keys, URL passwords and tokens, and secret-like
values under secret-named keys). Twelve message codes,AGENT_CONFIG_SECRET_S01toAGENT_CONFIG_SECRET_S11and
AGENT_CONFIG_SECRET_UNSCANNED: S01 to S09 areerrorand fail the check, S10 iswarn, S11 isinfo, and files that
could not be scanned are named in the unscanned code (errorwhen a drop could hide a secret). Report v2 only gains
codes; the message-code description inschema/report.v2.jsonnames them. In the Action, the files are read only for
a diff from the local checkout; in PR-number mode and for a compare-API diff the message says nothing was read. - A redaction module in
@pharn-ci/core:findSecretSpans,redactSecrets,truncateUtf8,redactMessageand
MESSAGE_MAX_BYTES. Nothing outside check A7 uses it yet. - Upload of the run to pharn.dev. The Action takes an
uploadinput (autoornever, defaultauto) and sets a
run-urloutput. Withauto, a job that grantsid-token: writeand an organization that installed the Pharn
GitHub App get the redactedreport.jsonand a newmeta.jsonuploaded; the App then comments on the pull request,
so the Action posts no comment of its own, and a404from the app puts an install hint under the comment and the
summary. A failed upload never fails the check; a report over the limit adds theUPLOAD_TOO_LARGEmessage code
(warn).schema/upload-meta.v1.jsondescribesmeta.json. In core:buildUploadMeta,buildUploadBundle,
buildUploadTar,withUploadOutcomeandUPLOAD_REPORT_MAX_BYTES. - Org policy rules P01, P08, P09 and P10. A policy that sets
setup.requireorsetup.expect(P01) raises
NO_AGENT_SETUP_FILESandAGENT_SETUP_EXPECTED_MISSINGto the rule's severity; a tracked file that matches a
setup.forbidden_filespattern (P08) is a newPOLICY_P08error; a pull request that edits the policy file it is
checked against, the config or a workflow while it sets a value looser than the policy, or an agent settings file
the policy checks (P09) is a newPOLICY_P09error (a warning for an agent settings file); an unreadable policy
(P10) staysPOLICY_UNAVAILABLE.policy.rulessets each rule's severity, and afile:policy can only raise it.
The findings are in the report'spolicyobject and a new Policy section of the PR comment; without a policy the
report and the comment are the ones they were. - Org policy rule P11, an agent pull request that changes a protected path. A policy that lists
agents.protected_paths
makes every path of an agent PR's diff that matches one of the globs (a rename counts by both its paths) a P11
violation, and one newPOLICY_P11message names up to 20 paths and the evidence that made the pull request an agent
PR, as fixed words.agents.protected_paths_modeandpolicy.rules.P11set the severity (blockby default; a
file:policy always blocks); a person's pull request is never subject to it, and a missing diff fails closed. The
policy'sagents.labelsandagents.extra_loginsnow add to the default agent labels and logins. A pull request
that counts as an agent PR only because its evidence could not be read gets oneAGENT_PR_DETECTION_INCOMPLETE
message (warn) that says why. CLI runs have no pull request, so P11 never fires there. - Org policy rule P07, agent instruction files that disagree. Under a policy, when two instruction files of one
directory (CLAUDE.md,AGENTS.md,GEMINI.md,.cursor/rules,.claude/rules,.github/copilot-instructions.md
and the other instruction kinds) name different package managers (npm,pnpm,yarn,bun) or test runners
(vitest,jest) in inline code or shell blocks, each is a P07 violation with its path, its line and the tools as
fixed words, and one newPOLICY_P07message names up to 20 files (warnby default;setup.consistencyand
policy.rules.P07set the severity, and afile:policy can only raise it). Files that could not be read or compared
are reported too. Without a policy, or with P07 switched off, nothing is read and the report is the one it was.
Changed
- Agent PR detection runs on every pull request run, not only on runs whose report carries
meta, so the Action reads the
pull request's commits (GET /repos/{owner}/{repo}/pulls/{n}/commits) on every pull request run. On its own the flag
still changes no verdict, message, comment or exit code; under a policy with protected paths, P11 uses it.
Security
-
A
file:org policy that the pull request edits is also read at the merge base, from committed git objects only
(never the working tree, no symbolic link followed, at most 1 MiB), and its labels, logins and protected paths count
together with the pull request's own: a pull request cannot remove a protected path or an agent label by editing the
policy file. When the merge-base version cannot be read, an edited policy file makes the pull request an agent PR. -
The Action makes one new read-only GitHub API call,
GET /repos/{owner}/{repo}/pulls/{n}/commits(at most 3 pages of
100), and reads the text of the pull request's commit messages in memory for the agent PR detection. A message is
capped at 65,536 UTF-16 code units, read for at most 50 trailers in linear time, and never stored, logged or sent;
only fixed words, valid logins and the trailers of known agents reach the report.docs/THREAT-MODEL.mdhas the new
surface A5 and residual risks 22 and 23. -
No A7 message, and nothing else the report, the comment or the summary carries, names a secret's value, its key or
any other text of an agent config file: a message holds the shape id, a path in a code span and a line number. -
The upload is a new network surface of the Action: two
POSTrequests to the app origin and onePUTto a signed
storage URL, only withuploadnotneverand a job that grantsid-token: write. Only the redactedreport.json
andmeta.jsonleave the job, never file contents, diffs, coverage files or logs; the token and the signed URL are
masked and never logged, and nothing the app answers is quoted.docs/THREAT-MODEL.mdhas the new row D7 and residual
risk 27: a same-repository pull request runs its code in the job that can mint the upload token. -
Org policy rule P07 reads the committed text of the agent instruction files (the Markdown instruction kinds) at
HEAD
from git objects, under a policy with P07 in effect, in memory only, with the same caps as check A7 (200 files, 512 KiB
each, 16 MiB in all) and a linear reader. The text is never written, logged, uploaded or put in the report: a violation
holds only fixed words, the tool words, a path and a line number.docs/THREAT-MODEL.mdhas the new row C6 and
residual risk 35.