Skip to content

Audit the environment scope the deploy credentials live in - #22

Merged
ptr727 merged 3 commits into
developfrom
feature/audit-environment-scope
Aug 4, 2026
Merged

Audit the environment scope the deploy credentials live in#22
ptr727 merged 3 commits into
developfrom
feature/audit-environment-scope

Conversation

@ptr727

@ptr727 ptr727 commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Why

The secrets audit read repository scope only. That is where the merge bot's credentials live, and it is correct for them, but the deploy's credentials are environment-scoped. So the audit passed cleanly over a completely unconfigured deploy, and this is the only repo in the fleet where that gap exists.

configure.sh check does not reach environment scope either, and it says so: it defers the secrets question to a manual verification.

What it now asserts

Both scopes, separately. The environment half carries three assertions, and the third is the one presence-checking misses:

  • Every environments.secrets and environments.variables name is present in every environment.
  • Every environmentSecrets.<env> name is present in that environment.
  • A scoped name is absent from an environment that does not list it. production holding a Pangolin access token is a finding rather than a harmless extra, because production answers unauthenticated and a token there means a check could pass through a gate production is not supposed to have.

A finding from writing it

spec/secrets.json declared DEPLOY_ROOT as an environment variable. Neither environment has it and the workflow never reads it, because the rsync destination is anchored at the deploy key's confinement root and names an environment rather than a host path. A declared-but-unread name is drift an audit cannot distinguish from a missing one, so it is no longer declared.

A second finding, from running it

My first version of the check used .environmentSecrets where the key is .environments.environmentSecrets. The path resolved to null, the loop ran zero times, and the check reported clean. That is the third false pass of this shape here, so every lookup is now jq -e and the pattern is recorded in the traps.

Verification

Run against the live configuration: staging carries the token pair, production correctly does not, and all shared names are present in both.

🤖 Generated with Claude Code

The secrets audit read repository scope alone, which is where the merge
bot's credentials live and not where the deploy's do, so an unconfigured
deploy passed it. It now asserts both, including that a scoped credential
is absent from the environment that must not carry it.

The deploy root is no longer declared, since the rsync destination is
anchored at the key's confinement root and a declared-but-unread name is
drift an audit cannot tell from a missing one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 4, 2026 23:15

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR tightens the repo’s operational audit guidance by explicitly covering environment-scoped deploy credentials (in addition to repository-scoped secrets), and updates the secrets manifest to remove a previously declared-but-unread deploy-root variable.

Changes:

  • Extend AUDIT.md to describe and enumerate environment-scoped secrets/variables and the required assertions.
  • Update spec/secrets.json to stop declaring DEPLOY_ROOT and document why it is intentionally absent.
  • Add a “silent clean pass from empty query results” trap reminder to TODO.md.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
TODO.md Adds a reminder about empty query results falsely reading as “clean”.
spec/secrets.json Removes DEPLOY_ROOT from declared environment variables and adds an explanatory note.
AUDIT.md Adds environment-scope audit guidance and example commands for enumerating environment secrets/variables.

Comment thread AUDIT.md Outdated
Comment thread spec/secrets.json
A guard written in a for header never fires, because command substitution
discards the exit status, so the lookup is assigned before it is iterated.

The manifest note described the environment split as carrying a deploy root
the manifest no longer declares.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 4, 2026 23:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (1)

AUDIT.md:62

  • The Environment scope section says gh secret list does not reach environment-scoped credentials, but gh secret list --env <env> (and gh variable list --env <env>) can list them. Also, the gh api calls as written will only fetch the first page of secrets/variables, which can silently truncate results and undermine the “assert against spec” goal if the list ever exceeds the default page size.
`configure.sh check` does not reach these, and neither does `gh secret list`. Assert them against `spec/secrets.json`:

```sh
# Read the lists first, so a moved key fails here rather than emptying the loop below.
envs=$(jq -e -r '.environments.names[]' spec/secrets.json) || exit 1

The section claimed a listing could not reach environment scope, which it
can with an environment flag, and the raw API calls read one page.

The listing prints every variable's value unless names are requested, so an
audit run wrote the deploy endpoint into its own output.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ptr727

ptr727 commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

Both halves correct. Fixed in fd8f8b0.

gh secret list does reach environment scope, with --env. I asserted it could not, and verified only that configure.sh check does not. The section now says what is actually true: configure.sh check defers the secrets question to a manual verification, and the listing is how that verification is done.

Pagination was a real risk, and the fix removes the question rather than answering it. gh secret list --env and gh variable list --env page for themselves, so the raw gh api calls are gone.

Checking that also surfaced something neither of us raised: the bare gh variable list prints every value. An audit runbook that says never to print a value would, run as written, have written the deploy host, user, and known-hosts entry into whatever captured its output. Variables are not secrets, but an audit that leaks its own configuration is the wrong shape. --json name is now there for that reason and the text says so.

Verified by running the documented commands verbatim: staging 3 secrets and 4 variables, production 1 and 4.

Copilot AI review requested due to automatic review settings August 4, 2026 23:28

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@ptr727
ptr727 merged commit 496c0df into develop Aug 4, 2026
5 checks passed
@ptr727
ptr727 deleted the feature/audit-environment-scope branch August 4, 2026 23:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants