Skip to content

fix: keep staged safe-output assets runner-owned - #74171

Merged
pelikhan merged 2 commits into
mainfrom
pelikhan/asset-publication-investigation
Nov 2, 2026
Merged

pelikhan merged 2 commits into
mainfrom
pelikhan/asset-publication-investigation

Conversation

@pelikhan

@pelikhan pelikhan commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Seven scheduled workflows failed asset publication because safeoutputs created private staged PNGs as the container's default root user. The host artifact uploader then failed with EACCES during ZIP creation; missing-artifact and Push assets failures were downstream cascades. This addresses the ownership mismatch exposed by the guarded staging change in #73587 without weakening its protections.

Extend the existing built-in MCP runner-identity policy to safeoutputs, alongside agenticworkflows, before gateway configuration serialization. Both default to the runner's UID:GID and a writable, container-local HOME. Explicit user and HOME overrides, other backends, credentials, mounts, and restrictive 0600/0700 modes remain unchanged.

Add regression coverage for both built-ins, startup wiring, explicit overrides, and guarded asset/Claim-directory readability. Document the identity defaults and update Safe Outputs to version 1.29.15 and the compiler threat specification to version 1.0.91, with synchronized histories. The specifications define the private staging/access contract, retain asset guards, distinguish producer failures from publication cascades, and map implementation and evidence limits. This is a supporting runtime boundary, not a new compiler threat rule.

No workflow source or generated lock changes are needed because the fix is in the shared runtime launcher.

Validation

  • make fmt-cjs && make lint-cjs passed.
  • npm run typecheck --prefix actions/setup/js --silent passed.
  • TMPDIR=<canonical private session directory> make agent-report-progress passed: 212 startup tests passed; the Linux-only filesystem regression was skipped on macOS.
  • cd actions/setup/js && npm run test:js -- --no-file-parallelism symlink_guard.test.cjs safe_outputs_handlers.test.cjs upload_assets.test.cjs passed: 466 tests.
  • Specification versions, histories, reciprocal anchors, and implementation/test mappings checked.
  • Read-only security review of the runtime fix: PASS; no confirmed vulnerabilities in the reviewed changes.
  • bash scripts/check-safe-outputs-conformance.sh reports pre-existing dispatch-token conformance failures under SEC-012, plus existing low/medium diagnostics. The two failing permission/authentication predicates were confirmed against the committed baseline; those implementations are unchanged by this PR.

The canonical TMPDIR avoids an existing macOS /var symlink-sensitive test failure. Docker/Colima were unavailable, so Linux container-to-host behavior and live Actions recovery remain unverified. No workflows were dispatched or rerun.

Apply the existing built-in MCP runner identity policy to safeoutputs so private staged assets remain readable by host artifact uploads. Preserve restrictive modes, guarded file access and explicit user overrides; cover startup wiring and private Claim directories.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@pelikhan
pelikhan marked this pull request as ready for review November 2, 2026 15:43
Copilot AI auto review requested due to automatic review settings November 2, 2026 15:43
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

βœ… Ponytail Reviewer completed successfully!

Ponytail Reviewer

Outcome: reported_no_action

The agent completed and reported no action. A no-op output alone does not verify that all required work was completed or that publication was possible.

Agent messages are omitted from this comment. The category uses structured run status, not interpretation of agent prose.

Generated from Ponytail Reviewer Β· gpt56 Β· ~12 AIC Β· βŒ– ~6 AIC Β· ⊞ 15.1K Β· β—·

Generated by Ponytail Reviewer for #74171

@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

βœ… Design Decision Gate πŸ—οΈ completed the design decision gate check. See the comment below for the result and any generated ADR draft.

Design Decision Gate πŸ—οΈ

Outcome: reported_no_action

The agent completed and reported no action. A no-op output alone does not verify that all required work was completed or that publication was possible.

Agent messages are omitted from this comment. The category uses structured run status, not interpretation of agent prose.

Generated from Design Decision Gate πŸ—οΈ Β· routed: gpt56 max Β· ~1 AIC Β· βŒ– ~2 AIC Β· ⊞ 7.7K Β· β—·

πŸ—οΈ ADR gate enforced by Design Decision Gate πŸ—οΈ

@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. βœ…

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer

@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

βœ… Test Quality Sentinel completed test quality analysis.

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

Test Quality Sentinel

Outcome: reported_no_action

The agent completed and reported no action. A no-op output alone does not verify that all required work was completed or that publication was possible.

Agent messages are omitted from this comment. The category uses structured run status, not interpretation of agent prose.

Generated from Test Quality Sentinel Β· sonnet50 Β· ~15 AIC Β· ⊞ 12.7K Β· β—·

πŸ§ͺ Test quality analysis by Test Quality Sentinel

@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

βœ… PR Code Quality Reviewer completed the code quality review.

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

πŸ”Ž Code quality review by PR Code Quality Reviewer

Version the safe outputs and compiler threat specifications, define runner-owned private asset staging and retained guards, and map runtime regression evidence without adding a compiler rejection or lock migration.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

Comment Memory

Peek at saved memory (pr-code-quality-reviewer)
reviewed_at: 2026-11-02T15:47:40Z
review_event: REQUEST_CHANGES
top_themes: [safe-outputs alias not covered by runner identity policy]
files_reviewed: [actions/setup/js/start_mcp_gateway.cjs, actions/setup/js/start_mcp_gateway.test.cjs, docs/src/content/docs/reference/mcp-gateway.md]
comment_count: 1

Note

This comment is managed by comment memory.

Expand the saved memory block to view or edit the persistent context for this thread.
Edit only the text inside the backtick fences; workflow metadata and the footer are regenerated automatically.

Learn more about comment memory

πŸ”Ž Code quality review by PR Code Quality Reviewer Β· copilot Β· gpt50 Β· ~2 AIC Β· ⊞ 1.7K Β· β—·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

Request changes

The runner-identity policy does not cover the accepted safe-outputs backend alias. Configurations using that key can still create root-owned staged assets and hit the EACCES failure this PR targets. I left an inline comment with the exact fix and requested regression coverage.

πŸ”Ž Code quality review by PR Code Quality Reviewer Β· copilot Β· gpt50 Β· ~2 AIC Β· ⊞ 1.7K
Comment /review to run again

});
if (!servers || typeof servers !== "object") return;
for (const [name, server] of Object.entries(servers)) {
if (name !== "agenticworkflows" && name !== "safeoutputs") continue;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The identity policy misses the safe-outputs alias that this launcher already accepts, so configurations using that key still run without --user/HOME=/tmp and can reproduce the root-owned staged-asset EACCES failure this PR is meant to fix.

πŸ’‘ Handle both accepted names

waitForSafeOutputsReadiness recognizes both safeoutputs and safe-outputs (line 269), but this allow-list recognizes only safeoutputs. Include safe-outputs here, or normalize the server name once before applying the policy, and add a regression test for the alias.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

πŸ”΅ Needs a closer look

The container identity changes still need native Linux/Docker validation of non-root backend startup and host artifact readability/upload, since macOS validation skipped the filesystem regression.

No open issues πŸŽ‰

What changed in this PR

This PR extends gh-aw’s runner-identity policy to the safeoutputs backend so host artifact uploads can read privately staged assets without weakening the existing file guards.

Changes:

  • Defaults both built-in backends to the runner’s UID:GID and container-local HOME, preserving explicit overrides.
  • Adds identity-policy, startup-wiring, and Linux filesystem regression coverage.
  • Updates specifications and documentation to define the ownership contract and its verification limits; live container-to-host recovery remains unverified.
File Description
specs/​compiler-threat-detection-spec.md Defines the runtime staging ownership boundary and maps supporting evidence.
specs/​compiler-threat-detection-changelog.md Records the ownership contract in version 1.0.91.
docs/​src/​content/​docs/​specs/​safe-outputs-specification.md Specifies private staging access and transport requirements in version 1.29.15.
docs/​src/​content/​docs/​reference/​mcp-gateway.md Documents built-in backend identity and HOME defaults.
actions/​setup/​js/​start_mcp_gateway.test.cjs Covers both built-ins, overrides, startup wiring, and private asset readability.
actions/​setup/​js/​start_mcp_gateway.cjs Applies the shared runner-identity policy to safeoutputs before configuration serialization.

Review effort: Balanced (auto)

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

Skills-Based Review 🧠

Applied /diagnosing-bugs, /tdd, and /codebase-design. Requesting changes because this breaks Windows gateway startup for windows.lock.yml.

πŸ“‹ Key Themes and Highlights

Key Themes

  • Windows regression (blocking): applyBuiltinMCPRunnerIdentity now covers safeoutputs. On windows-latest, process.getuid and process.getgid are undefined, so the gateway throws Runner identity is required for the safeoutputs container. windows.lock.yml renders a safeoutputs container in its agent job on windows-latest.
  • Test gap: the rejection table never covers a platform with no UID or GID, which is the path the Windows regression hits.
  • Linux filesystem test scope: it checks mode bits under the runner UID and GID, not the container-to-host ownership boundary the PR targets.
  • Docs: docs/src/content/docs/reference/tools.md (about line 203) still describes the default only for agentic-workflows. It is outside this diff, so it was not commented inline. Update it in this PR or a follow-up.

Positive Highlights

  • The rename to applyBuiltinMCPRunnerIdentity is consistent across source, tests, and exports.
  • Explicit container --user and configured HOME values are preserved, and -u parsing covers Docker attached-username forms.

Verification Limits

  • npm ci failed in this sandbox with a TLS error, so vitest was not run. Findings come from static analysis and a simulated non-POSIX process, which reproduced the throw.

@copilot please address the review comments above.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · copilot · routed: haiku55 xhigh · ~7 AIC · ⊞ 5.4K
Comment /matt to run again

])("rejects invalid runner identities (%s, %s)", (uid, gid) => {
const backend = { container: "alpine:3.24" };
expect(() => applyAgenticWorkflowsRunnerIdentity({ mcpServers: { agenticworkflows: backend } }, uid, gid)).toThrow("Runner identity is required");
expect(() => applyBuiltinMCPRunnerIdentity({ mcpServers: { [name]: backend } }, uid, gid)).toThrow("Runner identity is required");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] The rejection table covers NaN, Infinity, and negative IDs, but not the case where the platform provides no IDs. That is the path the new safeoutputs default reaches on Windows. Passing undefined does not exercise it, because default parameters replace it with the real process.getuid?.() value.

πŸ’‘ Suggested test

Stub process.getuid and process.getgid to undefined (for example with vi.spyOn, restored in afterEach), then call applyBuiltinMCPRunnerIdentity with a safeoutputs container. Assert the outcome matches the fix chosen for the Windows regression, and that the backend config is not partially mutated.

@copilot please address this.

expect(safeoutputs.env).toEqual({ HOME: "/tmp" });
});

it.skipIf(process.platform !== "linux")("keeps guarded assets and private Claim directories readable by the host uploader", () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/codebase-design] This test checks the mode bits that writeGuardedFile produces, but it runs as a child process under the runner UID and GID. It does not exercise the container-to-host ownership boundary this PR is meant to fix, so passing here does not prove the host uploader can read the assets.

πŸ’‘ Suggested clarification

State this limit in the test name or a short comment so future readers do not assume container ownership is covered. Where Docker is available, add a Docker-backed check for the container-owned path.

@copilot please address this.

// Docker parses any suffix after -u as a username or UID, including -uroot.
if (args.some(arg => arg === "--user" || arg === "-u" || arg.startsWith("--user=") || (arg.startsWith("-u") && !arg.startsWith("--")))) continue;
if (!Number.isSafeInteger(uid) || !Number.isSafeInteger(gid) || uid === undefined || gid === undefined || uid < 0 || gid < 0) {
throw codedError(`Runner identity is required for the ${name} container`, ERR_CONFIG);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/diagnosing-bugs] Windows regression: applyBuiltinMCPRunnerIdentity now applies to safeoutputs. On windows-latest, process.getuid and process.getgid are undefined, so this throws Runner identity is required for the safeoutputs container. windows.lock.yml renders a safeoutputs container in its agent job on windows-latest, so that workflow now fails at gateway startup. Before this PR only agenticworkflows reached this branch, and that container never appears in Windows lock files.

πŸ’‘ Suggested fix

When uid or gid is unavailable (non-POSIX), skip the identity default for safeoutputs instead of throwing, or gate the default on process.platform !== "win32". Keep the throw for explicitly invalid IDs so the existing rejection tests still pass. Add a regression test that removes process.getuid and process.getgid and asserts the Windows path does not throw.

@copilot please address this.

@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

PR Finisher

Pushed a fix for review feedback: the runner-identity policy now also covers the safe-outputs alias, and skips (instead of throwing) when the platform provides no UID/GID (Windows). Added tests for the alias and the no-ID case.

Validation: node --check only. JS dependencies could not be installed in this sandbox (self-signed cert on npm), so vitest/prettier did not run. Please re-trigger CI.

Remaining: the container-to-host ownership test (thread on test line 484) needs Linux Docker and is left for a human; threads to be resolved after the push is published.

Generated by 🏁 PR Finisher Β· copilot Β· auto Β· ~21 AIC Β· ⊞ 10.3K Β· β—·

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

πŸŽ‰ This pull request is included in a new release.

Release: v1.2.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants