adhoc: push:restricted adversarial pentest (#827)
* adhoc: push:restricted adversarial pentest
enumerates the 16 attack vectors the deep audit identified as load-bearing
for `push: restricted`. used to drive e2e verification against the preview
repo's pullfrog.yml; also runnable via pnpm runtest locally.
validator only asserts that the repo's default branch SHA didn't move —
the per-attack outputs are the deliverable for human review (the test
exists to feed adversarial runs, not to be a CI guard).
* wipe runner leak surface before agent spawn
the GHA runner persists credentials inside $RUNNER_TEMP that an MCP-shell
agent can grep — _runner_file_commands/set_output_* (from any composite
step that called core.setOutput, e.g. pullfrog/pullfrog/get-installation-token
which leaks a ghs_… installation token), <uuid>.sh rendered step scripts
(whose run: | body embeds ${{ ... }} expressions literally before write),
and git-credentials-*.config from actions/checkout@v6.
snapshot-and-delete that surface at action startup, after our own token is
in memory and before setupGit. preserves $GITHUB_OUTPUT, $GITHUB_ENV, and
$GITHUB_STATE so pullfrog's result output and post: hook still work.
setupGit's existing removeIncludeIfEntries call strips the matching
dangling includeIf.gitdir:....path entries from the user's .git/config.
does not tighten isGitCommand — that's a UX guard, not a security
boundary, and trivially bypassable via bash -c, absolute paths, symlinks,
python subprocess. the security boundary is the absence of credentials on
disk for those bypassed shells to authenticate with.
verified end-to-end by re-firing action/test/adhoc/pushRestrictedAdversarial
against pullfrog/preview-827-push-restricted-pentest.
* preserve all runner file-command paths from wipe
addresses pullfrog review on f7f5143b: GITHUB_STEP_SUMMARY also lives at
$RUNNER_TEMP/_runner_file_commands/step_summary_<uuid> and is read by the
runner AFTER our step exits to render the job summary in the GH UI. wiping
it silently broke pullfrog's job summary output. preserve GITHUB_PATH too
for symmetry — it's the same allocation pattern, and a step or post hook
that appends a directory expects the file to exist.
set of file-command env vars enumerated in @actions/core:
GITHUB_ENV, GITHUB_OUTPUT, GITHUB_PATH, GITHUB_STATE, GITHUB_STEP_SUMMARY
This commit is contained in:
committed by
pullfrog[bot]
parent
b6c57547ca
commit
fd2c67ab50
@@ -43,7 +43,7 @@ import {
|
||||
} from "./utils/runLifecycle.ts";
|
||||
import { logRunStartup } from "./utils/runStartupLog.ts";
|
||||
import { setEnvAllowlist } from "./utils/secrets.ts";
|
||||
import { createTempDirectory, setupGit } from "./utils/setup.ts";
|
||||
import { createTempDirectory, setupGit, wipeRunnerLeakSurface } from "./utils/setup.ts";
|
||||
import { killTrackedChildren } from "./utils/subprocess.ts";
|
||||
import { resolveTimeoutMs, TIMEOUT_DISABLED } from "./utils/time.ts";
|
||||
import { Timer } from "./utils/timer.ts";
|
||||
@@ -145,6 +145,13 @@ export async function main(): Promise<MainResult> {
|
||||
// resolve tokens first — acquireNewToken needs OIDC env vars for token exchange
|
||||
await using tokenRef = await resolveTokens({ push: payload.push });
|
||||
|
||||
// wipe the GHA runner's known credential leak surface inside $RUNNER_TEMP
|
||||
// before the agent spawns. our installation token is already in memory
|
||||
// (tokenRef above), and setupGit's includeIf strip handles the matching
|
||||
// dangling references in the user's .git/config. see wipeRunnerLeakSurface
|
||||
// for the leak inventory and threat model.
|
||||
wipeRunnerLeakSurface();
|
||||
|
||||
// stash OIDC credentials in memory before wiping from process.env
|
||||
// the agent's shell commands can't access JS variables, so this is safe
|
||||
const oidcCredentials: OidcCredentials | null =
|
||||
|
||||
Reference in New Issue
Block a user