PR 2005 removes repository credentials from branch-selected CI validation and places every fnox or Fly credential consumer behind a protected GitHub environment. Production and development jobs now require the main ref and check out its exact event SHA. The setup path refuses to install the fnox key unless each environment has exactly one deployment policy, the main branch. The live environments already contain the environment-scoped credential copies and main-only policies; repository-level copies remain temporarily until merge so the current default-branch workflows continue to function, then must be deleted to complete the cutover.
The branch-controlled validation job contains no secrets context. Secret-bearing production and development jobs require refs/heads/main, use exact-SHA checkout, and name environments whose live custom policies allow main alone. Fnox validation remains in the production release job, so a future reviewer rule requires one approval rather than two. The remaining cutover action is deleting the two repository-level credentials immediately after merge.
The external cutover is intentionally two phase
Environment-scoped FNOX_AGE_KEY and FLY_API_TOKEN copies are staged now, and both live environments allow only main. The repository-level copies cannot be deleted before merge because the current default-branch workflows still consume them. Deletion immediately after merge is part of the security fix, not optional cleanup. GitHub rejected a required-reviewer rule with HTTP 422 because this private repository's current billing plan does not support that protection.
Claim map
Evidence coverage
Each material changed behavior or state maps to direct evidence or an explicit gap.
Behavior or claim
Evidence
Status
Arbitrary-ref CI validation receives no repository credential
A contract test rejects every secrets context reference in the validation job, and the workflow diff moves fnox validation into Release production
Covered
Production and development credentials are available only from main
Exact ref guards and SHA checkouts in both workflows, plus observed live environment policy responses containing only branch main
Covered
Credential setup cannot silently widen the environment policy
The setup script requires the complete policy array to equal one branch main entry; a focused behavioral test rejects branch wildcards and main plus wildcard tags
Covered
No repository-level fnox or Fly credential remains
Environment copies are verified, but repository copies must remain until the new consumers merge and are scheduled for immediate post-merge deletion
Gap
Development deployment requires an independent reviewer
GitHub returned HTTP 422: the current billing plan does not support required reviewers for this private repository
Gap
Observed change
Review evidence
Evidence is grouped by the reviewer question each item addresses.
Credential boundary
Table
Each sensitive surface moves behind policy outside branch code
The environment branch policy is controlled by repository settings, so a selected branch cannot remove it by editing workflow YAML.
Surface
Before
Proposed and staged state
CI validation
Selected-ref checkout later received repository FNOX_AGE_KEY
Selected-ref validation contains no secrets context; fnox validation runs in protected Production
Production release
Every non-PR event qualified
Requires refs/heads/main and checks out github.sha inside Production
Development release
Any workflow_dispatch ref qualified and checkout followed it
Requires refs/heads/main, checks out github.sha, and live development policy allows main alone
Fnox key setup
Installed one repository secret
Installs only environment secrets after exact main-only policy verification
Live credential storage
Repository FNOX_AGE_KEY and FLY_API_TOKEN
Production and development copies staged; repository copies deleted immediately after merge
Production trust boundary
Code
The secret-bearing release job requires main before entering Production
The environment and ref guard are job-level policy inputs, and checkout is pinned to the event SHA after the job qualifies.
Proposed CI workflow at implementation commit 776374e03.After
Setup accepts exactly one branch policy and no tag policy
The complete policy array must be one branch named main. A main branch plus an attacker-controlled tag fails the check.
Proposed fnox setup at implementation commit 776374e03.After
scripts/setup-github-fnox-age.sh:25-46
require_main_only_environment() {
local environment="$1"
local custom_policy
local main_only
custom_policy="$(
gh api "repos/$repository/environments/$environment" \
--jq '.deployment_branch_policy.custom_branch_policies == true'
)"
main_only="$(
gh api "repos/$repository/environments/$environment/deployment-branch-policies" \
--jq '[.branch_policies[] | {type, name}] == [{type: "branch", name: "main"}]'
)"
if [ "$custom_policy" != "true" ] || [ "$main_only" != "true" ]; then
exit 1
fi
}
Live GitHub settings
Table
Protected copies are ready before the default-branch cutover
Observed through GitHub's environment and secret-name APIs on August 25, 2026. Secret values were never printed.
Environment
Deployment policies
Relevant secret names
Reviewer rule
Production
branch main only
FLY_API_TOKEN, FNOX_AGE_KEY, VERCEL_TOKEN
Unavailable on current plan
development
branch main only
FLY_API_TOKEN, FNOX_AGE_KEY
HTTP 422 on attempted configuration
Confidence
Validation
Pass
Repository Actions checks
bun run actions:lint passed actionlint, shell syntax, 128 focused workflow and deployment tests with 748 assertions, pinned-action validation, and all existing PR evidence validation
Pass
Focused secret-boundary contract
The focused suite passed validation isolation, main and exact-SHA production and development guards, protected environment installation, and rejection of wildcard branch or tag policy shapes
Pass
Independent security review
The reviewer identified tag-policy filtering and duplicate environment approvals. The implementation now compares the complete policy array and keeps fnox validation inside the one production release job. The reviewer also required the explicit post-merge repository-secret deletion recorded here.
Pass
Live environment boundary
GitHub API responses show exactly one branch main policy for Production and development, and secret-name listings show the required environment copies without exposing values
Warn
Required reviewer
GitHub rejected the development reviewer rule with HTTP 422 because the billing plan does not support it for this private repository
Limits
Known issues, risks, and gaps
The code and branch-policy boundary are ready, but final revocation occurs only after merge
Repository credentials still exist until cutover Deleting them before merge would break the workflows currently on main. Delete FNOX_AGE_KEY and FLY_API_TOKEN at repository scope immediately after merge, then verify only environment copies remain.
Required reviewers need a billing-plan change Main-only branch policies are active. The stronger independent-approval rule cannot be enabled for this private repository on its current plan, and no required review was bypassed.
A main push can still trigger deployment This change closes arbitrary branch and tag refs. Branch protection and rulesets are also unavailable on the current private-repository plan, so direct write access to main remains an organization access-control concern.