Skip to content

fix: confine asset uploads to verified regular files - #73587

Merged
pelikhan merged 2 commits into
mainfrom
pelikhan/asset-upload-symlink-safety
Nov 1, 2026
Merged

pelikhan merged 2 commits into
mainfrom
pelikhan/asset-upload-symlink-safety

Conversation

@pelikhan

@pelikhan pelikhan commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator

Motivation

Addresses code scanning alert 1039 (CWE-59). Agent-controlled upload paths could follow symlinks into files outside the allowed workspace or /tmp, and separate reads for hashing and copying allowed source replacement between validation and staging.

Changes

  • Reject source and parent-directory symlinks, dangling links, hard links, special files, and directory-prefix lookalikes.
  • Extend the shared symlink guard with descriptor-verified, bounded regular-file reads and exclusive no-follow destination creation. Linux pins directory traversal using /proc/self/fd; guarded asset access fails closed on macOS and Windows rather than using unsafe absolute-path revalidation.
  • Stage and publish the exact bytes used for SHA-256 computation rather than reopening the source. Apply the same protection to legacy and Claim-scoped publishers, including verification of existing publication destinations.
  • Tag policy, size-limit, and race rejections with ERR_VALIDATION, preserving that category in the upload handler while retaining ERR_SYSTEM for real I/O failures. Existing-destination verification failures include destination-specific diagnostics and abort publication before commit/push.
  • Add harmless external-sentinel, path-swap and swap-and-restore, platform-rejection, destination-link, size-mismatch, and snapshot-consistency regressions, and document file acceptance and platform requirements.

Validation

make agent-report-progress passed after the review fixes, including the gh-aw build, scoped lint, JavaScript typecheck, and 488 tests across four suites. Lint reported warnings but no errors.

Local validation ran on macOS. Tests model Linux descriptor-relative lookup with real file I/O; native Linux execution is not verified by that simulation. Native non-Linux asset access is intentionally unsupported and tested to fail closed. No workflow was dispatched and no scanning alert was dismissed.

Reject asset source and parent symlinks, hard links, special files, and prefix lookalikes. Pin directory traversal on Linux, bind reads to verified descriptors, and stage/publish the exact bounded bytes that were hashed using exclusive destination creation. Harden legacy and Claim publishers and add harmless sentinel and race regressions.

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

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

✅ Test Quality Sentinel completed test quality analysis.

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 · ~12 AIC · ⌖ ~16 AIC · ◇ ~5 AIC · ⊞ 12.7K · ◷

🧪 Test quality analysis by Test Quality Sentinel

@github-actions

github-actions Bot commented Oct 31, 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: opus55 medium · ~11 AIC · ⌖ ~1 AIC · ◇ ~1 AIC · ⊞ 7.7K · ◷

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions

github-actions Bot commented Oct 31, 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 · ~10 AIC · ⌖ ~6 AIC · ⊞ 15.1K · ◷

Generated by Ponytail Reviewer for #73587

@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

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

@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

✅ PR Code Quality Reviewer completed the code quality review.

🔎 Code quality review by PR Code Quality Reviewer

@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.

Request changes

The macOS implementation still performs writes through an unpinned absolute path, allowing an ancestor swap to create a file outside the permitted root before post-operation validation detects the change. See the inline comment on symlink_guard.cjs:106.

Static review only; tests were not run.

🔎 Code quality review by PR Code Quality Reviewer · copilot · gpt50 · ~2 AIC · ⌖ ~1 AIC · ⊞ 1.7K
Comment /review to run again

Comment thread actions/setup/js/symlink_guard.cjs Resolved

@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.

Skills-Based Review 🧠

Applied /diagnosing-bugs and /tdd (per pr-triage: bug_fix). This is a well-reasoned, well-tested CWE-59 symlink-following fix — the TOCTOU-safe primitives in symlink_guard.cjs (descriptor-pinned traversal, nlink===1 enforcement, O_NOFOLLOW/O_EXCL) and the regression suite covering symlinks, hardlinks, dangling links, and race-window swaps are solid. No blocking issues found; leaving a few non-blocking suggestions.

📋 Key Themes & Highlights

Key Themes

  • Error-code consistency: new symlink_guard.cjs throws raise bare Error(...) without the codebase's ERR_* prefix convention (error_codes.cjs), making them harder to classify at call sites/monitoring compared to other modules.
  • Edge-case test gap: the new existing-destination content check in upload_assets.cjs isn't wrapped in its own try/catch — worth confirming a size-mismatch failure there surfaces a clean message rather than an unhandled exception, and adding a regression test for it.
  • Possible Windows coverage gap: the PR description and tests only discuss Linux/macOS behavior for the new guarded-path primitives; I couldn't fully confirm Windows semantics or whether CI exercises this path there — flagged as a question, not a confirmed bug.

Positive Highlights

  • ✅ Excellent TOCTOU regression coverage (race-window file swaps, dangling/hard links, snapshot consistency)
  • ✅ The isWithin closure replacing the old startsWith path-prefix check in safe_outputs_handlers.cjs fixes a real path-prefix bypass (e.g. /workspace-sibling no longer false-matching /workspace) — nice incidental hardening
  • ✅ Existing-destination content verification in upload_assets.cjs closes a prior silent-acceptance gap

@copilot please address the review comments above.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · copilot · sonnet50 · ~165 AIC · ⌖ ~20 AIC · ⊞ 10.7K
Comment /matt to run again

Comment thread actions/setup/js/symlink_guard.cjs Resolved
Comment thread actions/setup/js/upload_assets.cjs Resolved
Comment thread actions/setup/js/symlink_guard.cjs Resolved

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.

🟡 Changes recommended

A macOS symlink swap-and-restore race can bypass directory verification, allowing guarded operations to read or create files outside the guarded directory.

2 open issues
What changed in this PR

This PR hardens gh-aw’s safe-output asset pipeline against unsafe file paths and source replacement during staging and publication.

Changes:

  • Add bounded, descriptor-verified regular-file reads and exclusive no-follow writes.
  • Use the verified bytes for hashing and staging, and guard both legacy and Claim-scoped publication.
  • Add security regression tests and document file acceptance requirements.
File Description
docs/​src/​content/​docs/​reference/​safe-outputs.md Documents accepted file types and snapshot protections.
actions/​setup/​js/​work_queue_upload_assets.cjs Uses guarded reads for Claim-scoped staged assets.
actions/​setup/​js/​work_queue_git_adapter_parity.test.cjs Tests rejection of linked Claim assets before publication.
actions/​setup/​js/​upload_assets.test.cjs Adds source, destination-link, and snapshot-consistency tests.
actions/​setup/​js/​upload_assets.cjs Guards publication and verifies existing destination content.
actions/​setup/​js/​symlink_guard.test.cjs Tests bounded reads, path swaps, exclusive writes, and descriptor cleanup.
actions/​setup/​js/​symlink_guard.cjs Adds directory traversal checks and guarded file-access helpers.
actions/​setup/​js/​safe_outputs_handlers.test.cjs Tests path boundaries, unsafe links, and staging consistency.
actions/​setup/​js/​safe_outputs_handlers.cjs Tightens workspace containment and stages verified file snapshots.

Review effort: Max (auto)

Comment thread actions/setup/js/symlink_guard.cjs Resolved
Fail closed outside Linux instead of using unpinned paths. Preserve coded validation failures, distinguish existing-destination I/O errors, and cover ancestor swap-and-restore and destination size mismatches.

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

pelikhan commented Nov 1, 2026

Copy link
Copy Markdown
Collaborator Author

Response to Test Quality Sentinel: Accepted as an informational status notice, not evidence of verification; no concrete test change is requested. The review fixes were independently validated by the final repository gate with 488 passing tests.

@pelikhan

pelikhan commented Nov 1, 2026

Copy link
Copy Markdown
Collaborator Author

Response to Design Decision Gate: Accepted as an informational status notice; this comment contains no ADR draft or requested design change. No additional code or documentation change is warranted by the notice.

@pelikhan

pelikhan commented Nov 1, 2026

Copy link
Copy Markdown
Collaborator Author

Response to Ponytail Reviewer: Accepted as an informational status notice, with the caveat that reported_no_action does not verify completion. No concrete change is requested here, so no additional implementation change was made.

@pelikhan

pelikhan commented Nov 1, 2026

Copy link
Copy Markdown
Collaborator Author

Response to Matt Pocock Skills Reviewer: Accepted as a completion notice requiring no separate code change. The substantive inline feedback was addressed in 944aa5d, with replies posted and the corresponding threads resolved.

@pelikhan

pelikhan commented Nov 1, 2026

Copy link
Copy Markdown
Collaborator Author

Response to PR Code Quality Reviewer: Accepted as a completion notice requiring no separate code change. The substantive ancestor-swap feedback was addressed in 944aa5d by failing closed outside Linux, and the inline thread was answered and resolved.

@pelikhan

pelikhan commented Nov 1, 2026

Copy link
Copy Markdown
Collaborator Author

The five original status notices already have individual linked responses. This repeated batch also includes those responses themselves; none introduces new review feedback.

  • 6551927542: Accepted as the existing Test Quality acknowledgement; no additional test change is requested.
  • 6551927725: Accepted as the existing Design Decision acknowledgement; no ADR or design change is requested.
  • 6551927913: Accepted as the existing Ponytail acknowledgement; no implementation change is requested.
  • 6551928125: Accepted as the existing Matt Pocock acknowledgement; the substantive inline feedback remains addressed in 944aa5d.
  • 6551928298: Accepted as the existing Code Quality acknowledgement; the ancestor-swap fix and resolved inline thread need no further change.

No new code changes, commits, or push are needed for this batch. Existing responses are reused for the original notices rather than duplicated, and these acknowledgements should not be treated as new actionable review requests.

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