Skip to content

Harden safe-output field validation - #51133

Merged
pelikhan merged 28 commits into
mainfrom
security/safe-output-field-allowlist
Aug 8, 2026
Merged

Harden safe-output field validation#51133
pelikhan merged 28 commits into
mainfrom
security/safe-output-field-allowlist

Conversation

@pelikhan

@pelikhan pelikhan commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

Hardens security-sensitive parts of the safe-output pipeline: strict field allowlisting during output normalization, a trusted allowlist for reusing existing PR/issue comments via comment_id, and re-derivation of sensitive metadata (base commit SHA, upload filenames, patch sizes/filenames) from trusted runtime state instead of agent-supplied values.

Key changes

  • Strict field validation (safe_output_type_validator.cjs, collect_ndjson_output.cjs): normalized items now start with only the type field and add back only declared/validated fields — undeclared fields supplied by the agent are stripped rather than passed through. Validation now errors if a value is accepted but produces no normalized output (previously undetected class of bug).
  • Trusted comment_id allowlist (add_comment.cjs, add_comment.go, safe_outputs_handlers.cjs, safe_outputs_handler_registry.go, safe_outputs_validation_config.go): agents may only reuse an existing comment ID when target: "*" and the ID is present in a new allows-comment-ids / AllowedCommentIDs workflow-configured allowlist. Existing comment targets are fetched and validated before update, closing a path where an agent could edit/hide arbitrary comments.
  • Trusted base commit derivation (commit_sha_helpers.cjs, generate_git_patch.cjs, create_pull_request.cjs, push_to_pull_request_branch.cjs): base commit SHA is now embedded in and extracted from the generated patch header (extractPatchBaseCommit) rather than taken from the agent-controlled safe-output message (base_commit/normalizeCommitSHA removed).
  • Filename-quoting bypass fix (push_to_pull_request_branch.cjs): git diff/pre-apply file listings switched to NUL-delimited output so non-ASCII/special-character filenames (e.g., résumé.txt) can't bypass size/protection checks via shell quoting; patch size validation simplified to always check actual patch file size (dropped diff_size/bundle-size branching) plus new post-apply content size enforcement.
  • Upload asset hardening (upload_assets.cjs): staged files are now keyed by a hash of the source path to prevent overwrite collisions; target filenames/metadata are re-derived rather than trusted from agent input; added buildAssetUrl() for GitHub.com/GHES URL generation.
  • Schema/spec updates: schemas/safe-outputs.json and docs/.../safe-outputs-specification.md (bumped to v1.28.2) document the comment_id/allows-comment-ids contract and that downstream payloads contain only declared fields. New job-type validation configs added in pkg/safe-output/validation.go for upload_artifact, push_repo_memory, create_check_run, plus temporary_id support (pattern ^#?aw_[A-Za-z0-9_]{3,12}$) across update_project/create_project/upload_artifact, and a duplicate_of field on create_pull_request_review_comment.
  • Minor/unrelated cleanup: maps.Copy() refactor in codemod_bash_allowlist_unsupported_engine.go; comment/formatting/typo fixes in awf_command_builder.go, awf_env.go, and associated test files.

Impact

  • Security-relevant, high impact: field allowlisting, comment_id allowlist, base-commit trust boundary, filename NUL-delimiting, and upload asset re-derivation all close paths where agent-supplied (untrusted) data could influence privileged validation/processing decisions.
  • Behavior change: safe-output normalized items no longer pass through undeclared fields; consumers relying on previously-leaked extra fields will need to declare them in the tool schema.
  • Broad test coverage added across all touched handlers (add_comment, collect_ndjson_output, safe_output_type_validator, safe_outputs_handlers, upload_assets, push_to_pull_request_branch, generate_git_patch, commit_sha_helpers).

Changed files (~29 files across Go and JS, all 7 diff chunks analyzed)

Full file list
  • .changeset/patch-harden-safe-output-field-validation.md
  • actions/setup/js/add_comment.cjs + test
  • actions/setup/js/collect_ndjson_output.cjs + test
  • actions/setup/js/commit_sha_helpers.cjs + test
  • actions/setup/js/create_pull_request.cjs + test
  • actions/setup/js/generate_git_patch.cjs + test
  • actions/setup/js/push_to_pull_request_branch.cjs + integration test + test
  • actions/setup/js/safe_output_type_validator.cjs + test
  • actions/setup/js/safe_outputs_handlers.cjs + test
  • actions/setup/js/safe_outputs_tools.json
  • actions/setup/js/upload_assets.cjs + test
  • actions/setup/setup.sh
  • schemas/safe-outputs.json
  • docs/src/content/docs/reference/safe-outputs.md
  • docs/src/content/docs/specs/safe-outputs-specification.md
  • pkg/cli/codemod_bash_allowlist_unsupported_engine.go
  • pkg/workflow/add_comment.go
  • pkg/workflow/awf_command_builder.go + test
  • pkg/workflow/awf_env.go + test
  • pkg/workflow/awf_feature_flags_test.go
  • pkg/workflow/js/safe_outputs_tools.json
  • pkg/workflow/safe_output_validation_config_test.go
  • pkg/workflow/safe_outputs_config_generation_test.go
  • pkg/workflow/safe_outputs_handler_registry.go
  • pkg/workflow/safe_outputs_validation_config.go
  • pkg/safe-output/validation.go
> Generated by [PR Description Updater](https://github.com/github/gh-aw/actions/runs/31230236568) for #51133 · auto · 112.7 AIC · ⌖ 3.04 AIC · ⊞ 6.9K · [◷](https://github.com/search?q=repo%3Agithub%2Fgh-aw+%22gh-aw-workflow-call-id%3A+github%2Fgh-aw%2Fpr-description-caveman%22&type=pullrequests)

Allow only schema-declared fields through safe-output validation and move trusted transport metadata out of agent-controlled NDJSON.

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

Copilot-Session: 5c72be66-bc49-4422-8e20-5567e99015f1
@pelikhan
pelikhan marked this pull request as ready for review August 7, 2026 18:42
Copilot AI balanced review requested due to automatic review settings August 7, 2026 18:42
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for Test Quality Sentinel. Review the logs for details.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for Matt Pocock Skills Reviewer. Review the logs for details.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

⚠️ PR Code Quality Reviewer failed during code quality review.

Warning

Threat Detection Engine Failure — The analysis engine could not complete. 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.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for Design Decision Gate 🏗️. Review the logs for details.

No ADR enforcement needed: PR does not have the 'implementation' label and has 96 new lines of code in business logic directories (≤100 threshold).

@pelikhan

pelikhan commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

@copilot use w3c-spec-writer to update the safe output specification

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.

Pull request overview

Hardens safe-output validation and moves privileged metadata derivation away from agent-controlled fields.

Changes:

  • Strips undeclared fields and removes agent-controlled comment_id.
  • Re-derives upload and patch metadata in privileged processing.
  • Adds schema-consistency and security regression tests.
Show a summary per file
File Description
pkg/workflow/safe_outputs_validation_config.go Expands validation contracts.
pkg/workflow/safe_output_validation_config_test.go Tests schema/config coverage.
pkg/workflow/js/safe_outputs_tools.json Removes comment_id.
actions/setup/js/upload_assets.test.cjs Tests trusted upload metadata.
actions/setup/js/upload_assets.cjs Re-derives asset metadata.
actions/setup/js/safe_outputs_tools.json Removes runtime comment_id.
actions/setup/js/safe_outputs_handlers.test.cjs Verifies hashed staging names.
actions/setup/js/safe_outputs_handlers.cjs Prevents basename collisions.
actions/setup/js/safe_output_type_validator.test.cjs Tests undeclared-field stripping.
actions/setup/js/safe_output_type_validator.cjs Whitelists configured fields.
actions/setup/js/push_to_pull_request_branch.test.cjs Tests trusted patch metadata and sizing.
actions/setup/js/push_to_pull_request_branch.cjs Uses patch-embedded base metadata.
actions/setup/js/generate_git_patch.cjs Embeds base commit in patches.
actions/setup/js/create_pull_request.test.cjs Tests patch-embedded base handling.
actions/setup/js/create_pull_request.cjs Reads trusted patch base metadata.
actions/setup/js/commit_sha_helpers.test.cjs Tests metadata extraction.
actions/setup/js/commit_sha_helpers.cjs Adds patch-header extraction.
actions/setup/js/collect_ndjson_output.test.cjs Tests custom-field filtering.
actions/setup/js/collect_ndjson_output.cjs Whitelists custom job inputs.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 19/19 changed files
  • Comments generated: 4
  • Review effort level: Balanced

Comment thread actions/setup/js/upload_assets.cjs Outdated
Comment on lines +128 to +132
const size = fileContent.length;
const githubServer = process.env.GITHUB_SERVER_URL || "https://github.com";
const repo = process.env.GITHUB_REPOSITORY || "owner/repo";
const url = `${githubServer}/${repo}/blob/${normalizedBranchName}/${targetFileName}?raw=true`;
processedAssets.push({ fileName, sha: computedSha, size, targetFileName, url });

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.

Fixed in 5be57975a0: upload_assets.cjs now uses the same host-specific URL construction as the producer, preserving GHES /raw/ URLs.

Comment on lines +529 to +532
// Key the staged file by its declared source path so same-basename assets
// cannot overwrite each other before the privileged publishing job.
const stagedFileName = `${crypto.createHash("sha256").update(filePath).digest("hex")}${fileExt}`;
const targetPath = path.join(assetsDir, stagedFileName);

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.

Fixed in 5be57975a0: repeated upload_asset calls for the same resolved source path are now rejected before staging, preventing same-path snapshot collisions.

Comment on lines 435 to +452
@@ -455,21 +448,8 @@ async function main(config = {}) {
}
const bundleSizeKb = Math.ceil(bundleSizeBytes / 1024);

const diffSizeBytesRaw = message.diff_size;
const haveDiffSize = typeof diffSizeBytesRaw === "number" && diffSizeBytesRaw >= 0;

let sizeForCheckBytes;
let sizeLabel;
if (haveDiffSize) {
sizeForCheckBytes = diffSizeBytesRaw;
sizeLabel = "Incremental diff size";
} else if (hasBundleFile) {
sizeForCheckBytes = bundleSizeBytes;
sizeLabel = "Bundle size";
} else {
sizeForCheckBytes = patchSizeBytes;
sizeLabel = "Patch size";
}
const sizeForCheckBytes = patchSizeBytes;
const sizeLabel = "Patch size";

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.

Fixed in 5be57975a0: after applying changes, the privileged checkout now enforces max_patch_size against the expanded changed file content size before pushing.

"draft_title": {Type: "string", Sanitize: true, MaxLength: 256},
"draft_body": {Type: "string", Sanitize: true, MaxLength: MaxBodyLength},
"fields": {Type: "object"},
"project": {Required: true, Type: "string", Sanitize: true, MaxLength: 512, Pattern: "^https://[^/]+/(orgs|users)/[^/]+/projects/\\d+", PatternError: "must be a full GitHub project URL (e.g., https://github.com/orgs/myorg/projects/42)"},

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.

Fixed in 5be57975a0: update_project.project validation now accepts temporary project IDs while retaining GHES-compatible project URL support.

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

This PR correctly addresses agent-controlled field injection by rebuilding normalizedItem from only declared contract fields and moving base_commit to a trusted patch header. The approach is sound and the security boundary is well-considered.

Field stripping (safe_output_type_validator.cjs): normalizedItem = { type: item.type } + allowlist rebuild ensures undeclared fields never reach downstream handlers. The fieldValue fallback for declared fields that pass validation but produce no normalizedValue is correct.

Trusted patch header: base_commit is now embedded at patch-generation time and validated via normalizeCommitSHA on extraction, removing agent control over this value entirely.

Asset staging hardening: SHA256 path-keying prevents same-basename file collisions before the privileged publish job. The targetFileName !== path.basename(targetFileName) path-traversal guard is correct.

Size-check simplification: Always using uncompressed patch size removes agent influence over diff_size and makes the check deterministic.

Tests are comprehensive and verify the new strip-by-default behavior. One minor nit filed inline. ✅

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 90.4 AIC · ⊞ 5.5K

Comments that could not be inline-anchored

actions/setup/js/generate_git_patch.cjs:297

Nit: validate baseCommitSha before embedding

The value is injected into the patch header without passing through normalizeCommitSHA. extractPatchBaseCommit validates on the receiving end so this is not a security gap, but a malformed value (e.g. containing a newline) would silently produce an unparseable header and cause a silent fallback instead of an early warning at generation time.

Consider validating in embedBaseCommit before injecting:

function embedBaseCommit(patchCon…

</details>

@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 /improve-codebase-architecture and /tdd — requesting changes on a few targeted issues.

📋 Key Themes & Highlights

Key Themes

  • Implicit caller–callee contracts: The removal of the null-guard on validateItemWithSafeJobConfig is correct in intent, but the call site no longer documents/enforces the invariant that safeJobConfig is always non-null by the time it reaches the helper.
  • Partial field-copy in the validator: The new else if (fieldValue !== undefined) branch in safe_output_type_validator.cjs may silently copy raw values for fields whose validators return { isValid: true } without a normalizedValue — the undeclared-field gate is now open to that edge case.
  • embedBaseCommit missing SHA validation: The function trusts the caller to pass a hex SHA; a non-hex value would be written verbatim into the patch header.
  • Asset URL computed before upload succeeds: processedAssets is populated before the git push, so a failed or skipped upload produces a dangling URL in the job summary.
  • Test coverage gaps for custom-schema stripping and CRLF patches: The Slack test doesn't assert that the extra channel field was stripped; extractPatchBaseCommit has no CRLF line-ending test.

Positive Highlights

  • ✅ Excellent privilege separation: metadata (SHA, size, targetFileName, base commit) is now re-derived or embedded by the privileged job, not trusted from agent output.
  • ✅ Staging collision prevention via SHA256(declaredPath) is a clean solution.
  • comment_id removal is well-justified and the PR description explains the ownership gap clearly.
  • extractPatchBaseCommit is a clean, testable unit with good boundary tests.
  • ✅ Test suite (729 JS, focused Go) gives strong regression confidence for the happy paths.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 79.4 AIC · ⊞ 7.1K
Comment /matt to run again

Comments that could not be inline-anchored

actions/setup/js/collect_ndjson_output.cjs:392

[/improve-codebase-architecture] The old if (safeJobConfig &amp;&amp; safeJobConfig.inputs) guard allowed items without a declared schema to bypass normalisation. Removing it is correct — but now validateItemWithSafeJobConfig(item, undefined, i+1) can be called when safeJobConfig is null/undefined, relying silently on the helper's early-return. The caller–callee contract is implicit and fragile.

<details>
<summary>💡 Suggestion</summary>

Add a guard to make the invariant explicit and prev…

actions/setup/js/safe_output_type_validator.cjs:624

[/improve-codebase-architecture] The new else if (fieldValue !== undefined) branch silently copies any non-undefined field value that didn't produce a normalizedValue from the validator. This means fields that fail validation with result.isValid === true but return no normalizedValue (e.g. no-op validators) still pass through the raw, unvalidated fieldValue. Verify that every field validator either sets result.normalizedValue or explicitly returns isValid: false; otherwise t…

actions/setup/js/commit_sha_helpers.cjs:121

[/tdd] extractPatchBaseCommit uses .split((r/redacted)?\n\r?\n/, 1)[0] to extract the first block, but the tests only cover Unix (\n\n) line endings. A CRLF-terminated patch (\r\n\r\n block separator) should be tested since the regex allows it in the line splitter but split on \r?\n\r?\n would match \r\n\r\n as a two-character pair — confirm the test covers this edge case.

<details>
<summary>💡 Suggested test</summary>

it(&#39;handles CRLF line endings in patch header&#39;, ()

</details>

<details><summary>actions/setup/js/upload_assets.cjs:833</summary>

**[/improve-codebase-architecture]** The path traversal guard `targetFileName !== path.basename(targetFileName)` correctly rejects names containing `/`, but it only guards the *final* `targetFileName`. The `targetFileName` is computed as `asset.targetFileName || generatedTargetFileName` — if `asset.targetFileName` is agent-supplied and contains a directory component, the guard fires. However if `asset.targetFileName` is an empty string `&quot;&quot;`, it falls back to `generatedTargetFileName` (safe) wit…

</details>

<details><summary>actions/setup/js/upload_assets.cjs:841</summary>

**[/improve-codebase-architecture]** The URL is constructed from `GITHUB_SERVER_URL` and `GITHUB_REPOSITORY` with a hard-coded `blob/&lt;branch&gt;/&lt;targetFileName&gt;` path before the file has been committed to the branch. If the upload later fails or is skipped (`missingAssetCount++`), `processedAssets` still contains a URL pointing to a file that may not exist. The summary is written after the upload loop, but a partial failure leaves dangling URLs in the job summary.

&lt;details&gt;
&lt;summary&gt;💡 Suggestio…

</details>

<details><summary>actions/setup/js/generate_git_patch.cjs:297</summary>

**[/tdd]** `embedBaseCommit` injects the `X-GH-AW-Base-Commit` header by inserting after the first newline. If `baseCommitSha` contains characters outside `[0-9a-fA-F]` (e.g. because the caller passes an unvalidated value), the injected header could contain unexpected content. There is no test asserting that `embedBaseCommit` rejects non-hex SHA values before writing them into the patch.

&lt;details&gt;
&lt;summary&gt;💡 Suggested test&lt;/summary&gt;

```js
it(&#39;does not embed a non-hex baseCommitSha&#39;, () =&gt; {


</details>

<details><summary>actions/setup/js/collect_ndjson_output.cjs:114</summary>

**[/tdd]** `validateItemWithSafeJobConfig` now seeds `normalizedItem` with only `{ type: item.type }`  but there is no test asserting that a **custom** safe-job config item (non-built-in schema, via `GH_AW_SAFE_OUTPUTS` env config) also strips undeclared fields. The Slack test at line 264 now passes `channel` in the input but only asserts on `text`; it doesn&#39;t confirm that `channel` is absent from the output. Add an explicit assertion to prevent regression.

&lt;details&gt;
&lt;summary&gt;💡 Suggestion&lt;/s

</details>

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report — PR #51133

Score: 92/100Excellent

Summary

This PR demonstrates exceptional test quality with 193+ test cases covering validation schemas, field enforcement, error handling, and complex integration scenarios. Tests are behavioral-focused, extensively cover edge cases, and maintain a healthy test-to-production code ratio of 1.73:1 (well under the 2:1 threshold).


Key Metrics

Metric Value Status
Design Tests 93.3% (180/193) ✅ Excellent
Edge Case Coverage 82.9% (160/193) ✅ Strong
Test Inflation Ratio 1.73:1 ✅ Healthy
Assertion Count 295+ (226 JS + 69 Go) ✅ Comprehensive
Mock Usage Vitest + Go stdlib only ✅ No forbidden libraries
Build Tags All Go tests tagged ✅ Compliant

Test Coverage by Domain

Input Validation (commit_sha_helpers.test.cjs)
  • ✅ Valid SHA trimming and format handling
  • ✅ Invalid references (branch names, git injection attempts, spaces)
  • ✅ Patch metadata extraction with header block scope validation
  • Count: 5 test cases, 100% design tests
Field Validation Engine (safe_output_type_validator.test.cjs)
  • ✅ Required field enforcement across 30+ tool types
  • ✅ Type and enum validation (case-insensitive normalization)
  • ✅ Length constraints: minLength, maxLength, truncation logic
  • ✅ Nested structured data: labels with confidence/rationale/suggest
  • ✅ Sanitization: @mentions converted to backticks
  • ✅ Data schema enforcement (custom JSON schema validation)
  • ✅ Error stripping policies: x-strip-on-error tags for optional fields
  • ✅ Custom validation rules: requiresOneOf patterns
  • Count: 102 test cases, 226 assertions, ~88% design tests
Validation Configuration Schema (safe_output_validation_config_test.go)
  • ✅ All 34 tool types present in config
  • ✅ Filtering: type lists return correct subsets
  • ✅ Field marshaling with omitempty JSON tags
  • ✅ Custom validation consistency (12 rule patterns validated)
  • ✅ Issue intent fields: rationale, confidence, suggest presence/absence
  • ✅ Label mutations: field definitions aligned across add_labels, remove_labels, update_issue
  • ✅ Tool input schema coverage: every tool input mapped to config field
  • ✅ MinLength/MaxLength consistency: min ≤ max for all fields
  • ✅ Cache key sorting: buildValidationConfigCacheKey produces deterministic output
  • Count: 21 test functions, 69 error assertions, 90% design tests
Asset Upload (upload_assets.test.cjs)
  • ✅ Environment validation: GH_AW_ASSETS_BRANCH requirement
  • ✅ Empty items handling: correct output count
  • ✅ Branch name normalization: special characters → hyphens
  • ✅ Orphan branch creation when upstream doesn't exist
  • ✅ Git operations: fetch, checkout --orphan, push mocking
  • ✅ File hashing and metadata propagation
  • Count: 20+ test cases, 100% design tests
Patch Application (push_to_pull_request_branch.test.cjs)
  • ✅ GitHub Actions context: pull_request, issue_comment, schedule, workflow_dispatch
  • ✅ Repository states: new pushes, base branch updates, force pushes
  • ✅ Empty commit policies: warn (default), error, ignore modes
  • ✅ Fork PR detection and early failure
  • ✅ Error scenarios: git failures, oversized patches, invalid content
  • ✅ Transport path isolation (temp file cleanup)
  • Count: 25+ test cases (comprehensive test matrix), 100% design tests

Quality Checks

Guideline Result Notes
No mock libraries ✅ Pass Vitest mocks for I/O; Go stdlib only (no gomock)
All Go tests tagged ✅ Pass (go/redacted):build !integration on line 1 of .go file
Error coverage ✅ Pass Error cases, invalid inputs, edge conditions in all test suites
Happy-path only ✅ Pass Tests include error paths, state transitions, fork scenarios
Test inflation ✅ Pass 1.73:1 ratio ≪ 2:1 threshold
Assertion messages ✅ Pass Go tests use Errorf, Fatalf with context; JS tests have descriptive error checks
No duplicates ✅ Pass Test patterns vary by scenario (table-driven vs. individual vs. nested)
Assertions present ✅ Pass 295+ total assertions; no "empty" test cases

Score Breakdown

Design tests (180/193 = 93.3%):  0.933 × 40 = 37.3 pts
Edge cases (160/193 = 82.9%):    0.829 × 30 = 24.9 pts
Duplicates (0 clusters):          20 pts
No inflation (1.73:1 < 2:1):      10 pts
─────────────────────────────────────────────────
Total Score:                      92/100 ✅

Recommendation

APPROVE — Test quality exceeds expectations. Design-focused tests with comprehensive error and edge-case coverage. All guidelines met. Implementation ratio 6.7% (threshold: ≤30%).

🧪 Test quality analysis by Test Quality Sentinel · haiku45 · 47.7 AIC · ⊞ 7.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.

✅ Test Quality Sentinel: 92/100. 93.3% design tests (threshold: ≥70%), 6.7% implementation tests (threshold: ≤30%). All guidelines met. Comprehensive behavioral coverage with excellent edge-case testing.

@pelikhan

pelikhan commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

@copilot run pr-finisher skill

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

@copilot use w3c-spec-writer to update the safe output specification

Addressed in 80b0655dd7 by using the W3C specification writer to update the Safe Outputs MCP Gateway specification for the hardened field-validation behavior and trusted metadata handling.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot Please address the remaining review feedback, refresh the branch if needed, and run the pr-finisher skill before handing back to maintainers.

Outstanding review items:

  • github-actions requested changes on validator allowlist edge cases, trusted metadata handling, and missing regression tests.

Failed checks:

Branch refresh was requested via GitHub update-branch.
Run context: https://github.com/github/gh-aw/actions/runs/31209372433

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 10.6 AIC · ⊞ 8.4K ·
Comment /souschef to run again

Copilot AI and others added 2 commits August 7, 2026 19:11
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

@copilot run pr-finisher skill

Ran the PR finisher pass. Addressed the in-scope review findings in 5be57975a0 and replied to each review thread. Local validation passed with make agent-report-progress; CodeQL timed out and instructed not to rerun. CI on the agent-pushed HEAD is stale and needs a maintainer re-trigger before merge.

@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 — requesting changes on two correctness issues.

📋 Key Themes & Highlights

Key Themes

  • Undeclared-field stripping (core fix): normalizedItem = { type: item.type } correctly closes the passthrough hole — excellent change.
  • Path-mode upload security: staging via sha256-keyed filenames and re-deriving metadata server-side are solid hardening steps.
  • comment_id allowlist: the parseAllowedCommentIds + validateAllowedAddCommentId pattern is well-structured, but two issues need fixing (see inline comments).
  • Test coverage: the new regression suite is comprehensive; two missing edge cases flagged inline.

Positive Highlights

  • ✅ Field stripping via allowlist is clean and the diagnostic error for unnormalized fields aids future maintenance.
  • uploadedAssetPaths Set prevents duplicate source-path uploads within a single session.
  • buildAssetUrl correctly handles GHES vs. github.com URL shapes.
  • ✅ Parameterised it.each for undeclared-field stripping gives good coverage with minimal boilerplate.

Issues Found

  1. validateAllowedAddCommentId mutates entry directly — side-effectful validation is fragile (safe_outputs_handlers.cjs:226).
  2. Path-mode uploads still accept agent-supplied targetFileName — weakens the metadata-derivation hardening (upload_assets.cjs:141).
  3. Missing tests for comment_id rejection paths — non-* target and empty allowlist not covered (safe_outputs_handlers.test.cjs).

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 110.8 AIC · ⊞ 7.1K
Comment /matt to run again

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Smoke Test Results:
Test 1: ✅
Test 2: ✅
Test 3: ❌
Test 4: ❌
Test 5: ✅
Overall: FAIL
Run: https://github.com/github/gh-aw/actions/runs/31222503776
@pelikhan

📰 BREAKING: Report filed by Smoke Copilot - AOAI (apikey) · o40mini · 22.6 AIC · ⌖ 2.31 AIC · ⊞ 20.2K ·
Comment /smoke-copilot-aoai-apikey to run again
Add label smoke to run again

@pelikhan

pelikhan commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

@copilot review and apply reviews in #51133 (review)

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Smoke Test: Copilot Engine

PR: Harden safe-output field validation (#51133)

✅ github/gh-proxy, mcpscripts PR query, playwright, web-fetch, file/bash, discussion comment, build, artifact upload, discussion create, workflow dispatch, PR review tools, comment memory, check run, LSP function count
❌ Serena find_symbol (timeout), file-summarizer sub-agent (no response)

Overall: FAIL (2/16 failed)

Author: @pelikhan · Assignees: none

Warning

Firewall blocked 6 domains

The following domains were blocked by the firewall during workflow execution:

  • accounts.google.com
  • android.clients.google.com
  • clients2.google.com
  • contentautofill.googleapis.com
  • safebrowsingohttpgateway.googleapis.com
  • www.google.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "accounts.google.com"
    - "android.clients.google.com"
    - "clients2.google.com"
    - "contentautofill.googleapis.com"
    - "safebrowsingohttpgateway.googleapis.com"
    - "www.google.com"

See Network Configuration for more information.

📰 BREAKING: Report filed by Smoke Copilot · auto · 50.9 AIC · ⌖ 2.5 AIC · ⊞ 8.6K ·
Comment /smoke-copilot to run again
Add label smoke 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.

Smoke test automated review pass.

Warning

Firewall blocked 6 domains

The following domains were blocked by the firewall during workflow execution:

  • accounts.google.com
  • android.clients.google.com
  • clients2.google.com
  • contentautofill.googleapis.com
  • safebrowsingohttpgateway.googleapis.com
  • www.google.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "accounts.google.com"
    - "android.clients.google.com"
    - "clients2.google.com"
    - "contentautofill.googleapis.com"
    - "safebrowsingohttpgateway.googleapis.com"
    - "www.google.com"

See Network Configuration for more information.

📰 BREAKING: Report filed by Smoke Copilot · auto · 50.9 AIC · ⌖ 2.5 AIC · ⊞ 8.6K
Comment /smoke-copilot to run again
Add label smoke to run again

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.

Review details

Suppressed comments (2)

pkg/workflow/js/safe_outputs_tools.json:338

  • The PR description explicitly says the agent-facing comment_id is removed, but this schema continues to expose it for the agent to select from an allowlist. The implementation and documentation consistently describe allowlisted reuse, so either update the PR description to state that narrower contract or remove this field to match the stated security behavior.
    actions/setup/js/upload_assets.cjs:13
  • The new path-only entry shape is valid ({ type, path }), but this typedef still marks fileName, sha, size, and targetFileName as required. That misstates the contract used below and will give incorrect static-analysis/IDE results for the newly supported input.
  • Files reviewed: 192/192 changed files
  • Comments generated: 1
  • Review effort level: Balanced

return await createProtectedFilesFallbackIssue(postApplyProtection.files);
}

const changedBlobSizeBytes = getChangedBlobSizeBytes(baseGitOpts, actualFiles);

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.

Fixed in 9c43ee37d4: getBundlePreApplyFiles and the post-apply diff now use git diff --name-only -z and split on NUL without trimming, so filenames with leading/trailing whitespace or C-quoted characters are measured correctly instead of being silently dropped to zero size.

Co-authored-by: gh-aw-bot <4175913+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

@copilot Quick triage nudge for this PR.

Please address the latest reviewer feedback, refresh the branch if needed, run the pr-finisher skill, and push follow-up fixes....

Addressed in the latest commit:

  • push_to_pull_request_branch.cjs:1245: fixed the quoting bypass — git diff --name-only now uses -z (NUL-delimited) output for both the post-apply and bundle pre-apply file listings, so filenames git would otherwise C-style quote (non-ASCII, special characters) resolve correctly instead of silently falling into the "deleted" (0-byte) fallback path. Added a regression test with a non-ASCII filename.
  • safe_outputs_tools.json:338: this isn't a regression — it's the intentional design from the target: "*" + allows-comment-ids request. comment_id is agent-supplied but only honored when the trusted allows-comment-ids allowlist contains that exact ID; the PR description/spec changelog (23a10f0) documents this as a reintroduction, not a contradiction with the original unconditional removal.

golangci-lint run ./cmd/... ./pkg/..., full JS suite (390 files / 12801 tests), and make agent-report-progress all pass.

…e re-derivation

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

@copilot review and apply reviews in #51133 (review)

Addressed in 0efed9c: validateAllowedAddCommentId no longer mutates its entry argument (returns a result the caller applies explicitly), path-mode upload_asset uploads always re-derive targetFileName from the trusted staged content hash, and added regression tests for comment_id rejection (non-* target, empty allowlist). The filename-quoting bypass in the post-apply diff was already fixed concurrently in 9c43ee3.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot Quick triage nudge for this PR.

Please address the outstanding changes-requested feedback, update the safe output specification as requested, refresh the branch if needed, and run the pr-finisher skill before handing back to maintainers.

Run: https://github.com/github/gh-aw/actions/runs/31225438989

Generated by 👨🍳 PR Sous Chef
Comment /souschef to run again

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 39.7 AIC · ⊞ 8.5K ·
Comment /souschef to run again

Copilot AI and others added 2 commits August 7, 2026 23:07
…field-allowlist

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

@copilot Quick triage nudge for this PR.

Please address the outstanding changes-requested feedback, update the safe output specification as requested, refresh the branch if needed, and run the `pr-fi...

All review threads already have fixes applied and replied to in prior commits (5be57975a0, d909c0635e, 178ff3136d, 9c43ee37d4, 0efed9c, c16df32), and the spec update was completed in 23a10f0. Refreshed the branch with main in the latest commit — merge was clean with no conflicts. golangci-lint run ./cmd/... ./pkg/... is clean, full JS suite (390 files / 12803 tests) passes, focused Go safe-output tests pass, and all 283 workflows recompile successfully.

@pelikhan
pelikhan merged commit 766e774 into main Aug 8, 2026
38 checks passed
@pelikhan
pelikhan deleted the security/safe-output-field-allowlist branch August 8, 2026 00:29
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.

4 participants