Skip to content

Detect and replace heredocs in generated workflow YAML - #53183

Merged
pelikhan merged 5 commits into
mainfrom
copilot/add-go-linter-for-heredoc
Aug 16, 2026
Merged

Detect and replace heredocs in generated workflow YAML#53183
pelikhan merged 5 commits into
mainfrom
copilot/add-go-linter-for-heredoc

Conversation

Copilot AI commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Generated YAML heredocs expose workflow content to shell injection. This adds enforcement against new heredocs and begins migrating file generation to JavaScript.

Changes

  • Static enforcement

    • Add generatedyamlheredoc, a Go analyzer detecting heredocs embedded in generated workflow shell.
    • Register and document the analyzer.
    • Explicitly suppress existing sites to track migration debt.
  • Safe file rendering

    • Add create_files.cjs for writing environment-provided content without shell evaluation or base64 encoding.
    • Constrain output paths to the configured runner directory.
    • Reject traversal and symlink escapes.
    • Write files with 0600 permissions.
  • Initial migration

    • Replace safe-outputs config heredoc generation with the JavaScript renderer.
    • Pass config content directly through GH_AW_SAFE_OUTPUTS_CONFIG.
    • Regenerate compiled workflow locks.


✨ PR Review Safe Output Test - Run 31974687259> [!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
  • www.google.com
  • www.gstatic.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"
    - "www.google.com"
    - "www.gstatic.com"

See Network Configuration for more information.

💥 [THE END] — Illustrated by Smoke Claude · sonnet46 · 80.4 AIC · ⌖ 17.9 AIC · ⊞ 6.6K ·
Comment /smoke-claude to run again

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title Add generated YAML heredoc linter and file renderer Detect and replace heredocs in generated workflow YAML Aug 16, 2026
Copilot AI requested a review from pelikhan August 16, 2026 18:46
@pelikhan
pelikhan marked this pull request as ready for review August 16, 2026 19:15
Copilot AI balanced review requested due to automatic review settings August 16, 2026 19:15

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.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Ponytail Reviewer completed successfully!

pr-diff.patch is empty (0 bytes) — no diff content available to review for over-engineering. Skipping Ponytail review.

Generated by Ponytail Reviewer for #53183

@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

Test Quality Sentinel skipped because pre-fetch PR data was unavailable: unable to fetch PR file list

🧪 Test quality analysis by Test Quality Sentinel

@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions

github-actions Bot commented Aug 16, 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 commented Aug 16, 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 Aug 16, 2026

Copy link
Copy Markdown
Contributor

Comment Memory

reviewed_at: 2026-08-16T19:17:00Z
review_event: REQUEST_CHANGES
top_themes:
  - incomplete heredoc migration in early comment-memory bootstrap
  - enforcement linter allows existing live generated-shell heredoc sites
files_reviewed:
  - pkg/linters/generatedyamlheredoc/generatedyamlheredoc.go
  - pkg/linters/generatedyamlheredoc/generatedyamlheredoc_test.go
  - actions/setup/js/create_files.cjs
  - actions/setup/js/create_files.test.cjs
  - pkg/workflow/mcp_setup_safe_outputs.go
  - pkg/workflow/compiler_yaml_runtime_setup.go
  - pkg/workflow/safe_outputs_dynamic_allowed_repos_test.go
  - pkg/linters/registry.go
  - pkg/linters/README.md
comment_count: 0
Still logs trace the quiet shore
Night compilers breathe in green light
One branch wakes at dawn

Note

This comment is managed by comment memory.

It stores persistent context for this thread in the code block at the top of this comment.
Edit only the text inside the backtick fences; workflow metadata and the footer are regenerated automatically.

Learn more about comment memory

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
  • www.google.com
  • www.gstatic.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"
    - "www.google.com"
    - "www.gstatic.com"

See Network Configuration for more information.

🔮 The oracle has spoken through Smoke Codex · gpt54 · 41.7 AIC · ⊞ 12.1K ·
Comment /smoke-codex 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.

Request changes

This migration is headed in the right direction, but it still leaves an unfixed heredoc write in the early comment-memory bootstrap path, so the new enforcement is incomplete and the attack surface you described is still present in generated workflow shell.

Blocking themes
  • compiler_yaml_runtime_setup.go still writes config.json via a shell heredoc for comment-memory bootstrap.
  • The new analyzer only prevents new occurrences and explicitly suppresses existing ones, so that remaining path is not theoretical debt — it is still live generated shell.
  • Until that bootstrap write is migrated to the same JS file renderer approach, the PR does not fully deliver the promised hardening.

🔎 Code quality review by PR Code Quality Reviewer · gpt54 · 22 AIC · ⌖ 6.3 AIC · ⊞ 6.9K
Comment /review to run again

Documents the decision to introduce a Go linter (generatedyamlheredoc)
and JavaScript file renderer (create_files.cjs) as a replacement for
shell-evaluated heredocs in generated workflow YAML.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (206 new lines in business-logic directories) but did not have a linked Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/53183-detect-replace-heredocs-generated-workflow-yaml.md — review and complete it before merging.

🔒 This PR cannot merge until an ADR is linked in the PR body.

📋 What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR description
  2. Complete the missing sections — add context the AI couldn't infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-53183: Detect and Replace Heredocs in Generated Workflow YAML

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

❓ Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 0042-use-postgresql.md for PR #42).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · sonnet46 · 58.8 AIC · ⌖ 24.8 AIC · ⊞ 9K ·
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.

This PR makes solid progress on a real security concern — heredocs in generated YAML expose content to shell evaluation, and the JavaScript renderer (create_files.cjs) is the right long-term direction. The linter + nolint tracking is a good incremental approach.

Five issues flagged inline, two of which are blocking:

Severity File Issue
🔴 Blocking create_files.cjs:72 O_NOFOLLOW || 0 silently disables symlink protection when flag is unavailable
🟡 Non-blocking create_files.cjs:103 TOCTOU gap between realpathSync and writeFile for parent directory symlinks
🟡 Non-blocking mcp_setup_safe_outputs.go:46 GH_AW_FILE_ROOT emitted unquoted via %s — inconsistent with writeYAMLEnv pattern
🟡 Non-blocking generatedyamlheredoc.go:91 Linter only catches cat <<; tee <<, node <<, read << are undetected
🟡 Non-blocking create_files.test.cjs:57 No test for symlinked-parent-directory escape path

The blocking item is the O_NOFOLLOW || 0 silent degradation — if the flag is undefined on a platform, the symlink guard is silently removed, which undermines the primary security guarantee of this new renderer.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • proxy.golang.org

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

network:
  allowed:
    - defaults
    - "proxy.golang.org"

See Network Configuration for more information.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 107.3 AIC · ⌖ 8.71 AIC · ⊞ 5.6K

*/
function writeFile(filePath, content) {
const flags = fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_TRUNC | (fs.constants.O_NOFOLLOW || 0);
const fd = fs.openSync(filePath, flags, 0o600);

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.

Security: O_NOFOLLOW silently degrades when unavailable

The expression (fs.constants.O_NOFOLLOW || 0) is a silent fallback — if O_NOFOLLOW is undefined (e.g., on certain platforms), the flag is dropped and the symlink guard disappears without warning. Since this module's core purpose is to prevent path-escape attacks, a missing flag should be a hard failure:

const O_NOFOLLOW = fs.constants.O_NOFOLLOW;
if (!O_NOFOLLOW) {
  throw new Error(`${ERR_SYSTEM}: O_NOFOLLOW flag is not available on this platform; cannot write files safely`);
}
const flags = fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_TRUNC | O_NOFOLLOW;

This makes the protection explicit and non-optional.

@copilot please address this.

makeDirectory(root);
const resolvedRoot = fs.realpathSync(root);

for (const directory of config.directories || []) {

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.

Potential TOCTOU between assertPathWithin and writeFile

The code calls makeDirectory(parentPath), then assertPathWithin(resolvedRoot, fs.realpathSync(parentPath)), and then writeFile(filePath, ...). Between the realpathSync check and the openSync, a symlink could be swapped in at parentPath (race window).

O_NOFOLLOW on the final file open protects against the file being a symlink, but not against the parent directory being replaced by a symlink after the realpathSync check. To close this gap, the assertPathWithin check on the parent should happen before makeDirectory, not after:

const filePath = resolveRelativePath(resolvedRoot, item.path);
const parentPath = path.dirname(filePath);
// Assert path is safe before creating anything on disk.
const expectedParent = path.resolve(resolvedRoot, path.dirname(item.path));
assertPathWithin(resolvedRoot, expectedParent);
makeDirectory(parentPath);
// Now recheck after creation in case a symlink was raced in:
assertPathWithin(resolvedRoot, fs.realpathSync(parentPath));
writeFile(filePath, env[item.content_env] || "");

@copilot please address this.

Comment thread pkg/workflow/mcp_setup_safe_outputs.go Outdated
yaml.WriteString(" - name: Generate Safe Outputs Config\n")
fmt.Fprintf(yaml, " uses: %s\n", getCachedActionPin("actions/github-script", workflowData))
yaml.WriteString(" env:\n")
fmt.Fprintf(yaml, " GH_AW_FILE_ROOT: %s\n", constants.GhAwRootDir)

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.

Inconsistent YAML quoting for GH_AW_FILE_ROOT

This line emits GH_AW_FILE_ROOT unquoted using %s, while all other env vars in this block go through writeYAMLEnv which uses %q (double-quoted YAML scalar). The current constant value (${{ runner.temp }}/gh-aw) is safe unquoted, but using an inconsistent pattern is fragile if the constant ever changes.

Prefer the existing helper for consistency:

writeYAMLEnv(yaml, "          ", "GH_AW_FILE_ROOT", constants.GhAwRootDir)

Note: %q wraps the value in "..." which is valid YAML and does not interfere with GitHub Actions expression evaluation.

@copilot please address this.

beforeIsWord := catIndex > 0 && isShellWordByte(line[catIndex-1])
afterIndex := catIndex + len("cat")
afterIsWord := afterIndex < len(line) && isShellWordByte(line[afterIndex])
if beforeIsWord || afterIsWord {

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.

Linter misses tee <<, append (>><<), and process-substitution heredocs

The lineContainsShellHeredoc function only recognises the cat command. Other patterns that embed content via heredocs in generated YAML are not caught:

  • tee << 'EOF' (common alternative to cat)
  • node script.js << 'EOF' (piping stdin via heredoc to a process)
  • read VAR << 'EOF'

If any of these patterns appear in future generated YAML they will pass undetected. Consider generalising the detection to match << (the heredoc operator) after any shell word, not only after cat:

func lineContainsShellHeredoc(line string) bool {
    _, after, found := strings.Cut(line, "<<")
    if !found {
        return false
    }
    // Skip <<< (here-string) and <<EOF without space (bit-shift)
    return !strings.HasPrefix(after, "<") && len(after) > 0
}

@copilot please address this.

fs.writeFileSync(filePath, "old", { mode: 0o666 });
fs.chmodSync(filePath, 0o666);

renderFiles({ files: [{ path: "config.json", content_env: "CONTENT" }] }, { CONTENT: "new" }, root);

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.

Missing test: symlink escape via symlinked parent directory

The test suite checks traversal (../secret) and missing env vars, but there is no test for the case where a symlink directory (rather than a symlink file) is placed inside the root after makeDirectory. The O_NOFOLLOW flag only protects against the final file target being a symlink — it does not protect against a parent directory being replaced by a symlink between the realpathSync check and the openSync call.

A test that:

  1. Creates the root
  2. Creates an intermediate dir that is a symlink pointing outside the root
  3. Calls renderFiles with a path that traverses through the symlink

...would verify the defence is effective (or expose the TOCTOU gap noted in the adjacent inline comment).

@copilot please address this.

@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, /tdd, and /codebase-design — requesting changes on correctness and coverage gaps.

📋 Key Themes & Highlights

Key Themes

  • Silent security degradation: O_NOFOLLOW || 0 drops the symlink guard on platforms without that constant, with no error or warning.
  • Linter coverage gap: lineContainsShellHeredoc detects only cat <<; other heredoc forms (tee <<, node <<) pass undetected — the linter name implies broader coverage than it provides.
  • Hardcoded JSON schema: the GH_AW_FILE_CONFIG value is a raw Go string literal that will drift if the FileRenderConfig schema evolves.
  • 19 (nolint/redacted) suppression sites: well-documented as migration debt — the tracking approach is sound. The "early config" suppression (compiler_yaml_runtime_setup.go) has a clear architectural reason.

Positive Highlights

  • ✅ Layered path-traversal and symlink defences in create_files.cjs (resolveRelativePath + realpathSync post-mkdir) are thorough.
  • ✅ Go linter test data covers all intended patterns (herestring <<<, arithmetic shifts, word-boundary false positives) cleanly.
  • ✅ Replacing the heredoc with an actions/github-script step that reads content from env vars is architecturally correct — no base64 encoding, no shell evaluation.
  • ✅ Test suite for create_files.cjs covers the most important security property (shell injection canary) and permission restriction.
  • writeYAMLEnv using %q for the sanitized config value is a good defence against YAML structure injection.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 129.2 AIC · ⌖ 9.14 AIC · ⊞ 7.7K
Comment /matt to run again

Comment thread actions/setup/js/create_files.cjs Outdated
* @param {string} content
*/
function writeFile(filePath, content) {
const flags = fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_TRUNC | (fs.constants.O_NOFOLLOW || 0);

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] O_NOFOLLOW || 0 silently drops the symlink guard on platforms where fs.constants.O_NOFOLLOW is undefined (e.g. Windows). A successful openSync with flags 0 provides no symlink protection; the security property evaporates without any error.

💡 Suggestion

Assert the flag is available rather than silently zeroing it:

const O_NOFOLLOW = fs.constants.O_NOFOLLOW;
if (O_NOFOLLOW === undefined) {
  throw new Error(`${ERR_SYSTEM}: O_NOFOLLOW is not available on this platform`);
}
const flags = fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_TRUNC | O_NOFOLLOW;

If Windows runners are explicitly out-of-scope, a one-line comment stating that assumption prevents future readers from softening the guard further.

@copilot please address this.

return false
}

func lineContainsShellHeredoc(line string) bool {

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] lineContainsShellHeredoc detects only cat << patterns. Other valid heredoc forms — tee <<, node renderer.cjs <<, bare bash -c ... << — would pass undetected. The linter is named generatedyamlheredoc (not catamlheredoc), so callers expect broader coverage.

💡 Suggestion

Either narrow the name/doc to cat-only and make that explicit in the diagnostic message, or extend detection to any <word> << pattern (anything that is a non-empty token immediately before << that is not itself <<):

// lineContainsShellHeredoc returns true for any shell heredoc of the form:
//   <cmd> << or << (standalone redirect with no preceding word)
func lineContainsShellHeredoc(line string) bool {
    idx := strings.Index(line, "<<")
    for idx >= 0 {
        after := line[idx+2:]
        if strings.HasPrefix(after, "<") { // skip here-string <<<
            line = after
            idx = strings.Index(line, "<<")
            continue
        }
        // Anything before << that is a non-space token is a heredoc command
        pre := strings.TrimRight(line[:idx], " \t")
        if pre != "" {
            return true
        }
        line = after
        idx = strings.Index(line, "<<")
    }
    return false
}

This also removes the special cat-word-boundary logic, simplifying the function.

@copilot please address this.

Comment thread actions/setup/js/create_files.cjs Outdated
const parentPath = path.dirname(filePath);
makeDirectory(parentPath);
assertPathWithin(resolvedRoot, fs.realpathSync(parentPath));
writeFile(filePath, env[item.content_env] || "");

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] env[item.content_env] || "" conflates two distinct cases: (a) the env var is set to an empty string (valid, write empty file) and (b) the env var is undefined (already caught on line 116). Because the hasOwnProperty check on line 116 already guards against missing keys, the || "" here is unreachable for undefined, but it will silently convert false, 0, or other falsy values passed through process.env to "" rather than their string form.

💡 Suggestion

process.env values are always strings or undefined, so || "" is safe in practice — but it obscures that invariant. Prefer the explicit cast:

writeFile(filePath, String(env[item.content_env] ?? ""));

or simply:

writeFile(filePath, env[item.content_env]);

since by this point the key is guaranteed to exist. A brief comment noting the invariant would help future readers.

@copilot please address this.

continue
}
value, err := strconv.Unquote(lit.Value)
if err != nil || !containsShellHeredoc(value) {

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 linter skips files matching filecheck.ShouldSkipFilename, which excludes Go test files (_test.go). But the primary production callsites being guarded are inside pkg/workflow/*.go — none of which are test files. The existing Go analysis test covers the testdata fixture well.

However, there is no regression test asserting that a new cat << inserted into pkg/workflow/ (without a (nolint/redacted)) is actually caught and the CI lint step fails. The 19 suppression sites are tracked correctly, but the linter's effectiveness against future regressions (someone accidentally adding a heredoc in a new file) is not directly exercised at the integration level.

💡 Suggestion

Add a note in the linter's README.md entry explaining how to verify the linter fires (e.g. gh aw lint or the relevant make lint target), so that reviewers of future heredoc-adjacent PRs know how to confirm the guard is active.

@copilot please address this.

Comment thread pkg/workflow/mcp_setup_safe_outputs.go Outdated
fmt.Fprintf(yaml, " uses: %s\n", getCachedActionPin("actions/github-script", workflowData))
yaml.WriteString(" env:\n")
fmt.Fprintf(yaml, " GH_AW_FILE_ROOT: %s\n", constants.GhAwRootDir)
writeYAMLEnv(yaml, " ", "GH_AW_FILE_CONFIG", `{"files":[{"path":"safeoutputs/config.json","content_env":"GH_AW_SAFE_OUTPUTS_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.

[/codebase-design] The GH_AW_FILE_CONFIG value is a hardcoded JSON string literal inside Go source. If the FileRenderConfig schema grows (e.g. to support file modes, symlinks, or encoding options), this literal will need updating in two separate places: the Go string here and the JavaScript parseConfig schema.

💡 Suggestion

Consider marshalling a Go struct matching FileRenderConfig rather than maintaining a raw JSON string:

type fileRenderItem struct {
    Path       string `json:"path"`
    ContentEnv string `json:"content_env"`
}
type fileRenderConfig struct {
    Files []fileRenderItem `json:"files"`
}
config, _ := json.Marshal(fileRenderConfig{
    Files: []fileRenderItem{{Path: "safeoutputs/config.json", ContentEnv: "GH_AW_SAFE_OUTPUTS_CONFIG"}},
})
writeYAMLEnv(yaml, "          ", "GH_AW_FILE_CONFIG", string(config))

This keeps the schema DRY and makes compile-time typos impossible.

@copilot please address this.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot this PR is ready for the next finishing pass.

Please address these items, newest first:

  • Review thread from Matt Pocock Skills Reviewer — O_NOFOLLOW || 0 silently drops the symlink guard on platforms where fs.constants.O_NOFOLLOW is unavailable; make that a hard failure instead of silently disabling protection.
  • Review thread from Matt Pocock Skills Reviewer — broaden lineContainsShellHeredoc so it catches heredoc forms beyond cat << (for example tee << or node <<) or narrow the linter contract/documentation to match the current behavior.
  • Review thread from Matt Pocock Skills Reviewer — replace the raw GH_AW_FILE_CONFIG JSON string literal with a marshaled Go struct so the config schema cannot drift.
  • Review thread from Matt Pocock Skills Reviewer — add coverage for symlinked parent-directory escape/TOCTOU behavior and document how to verify the heredoc linter fires on new production regressions.
  • ADR gate is still blocking merge: review the draft ADR at docs/adr/53183-detect-replace-heredocs-generated-workflow-yaml.md, complete any missing sections, and link it from the PR body.

Please refresh the branch if needed, rerun the relevant validation, and run the pr-finisher skill before handing back to maintainers.

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

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 20.1 AIC · ⌖ 6.89 AIC · ⊞ 6.3K ·
Comment /souschef to run again

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

Copy link
Copy Markdown
Collaborator

@copilot resolve the merge conflicts on this branch.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot this PR is ready for the next finishing pass.

Please refresh the branch if needed, run the pr-finisher skill, and rerun the relevant validation so maintainers can investigate quickly.

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

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 3.59 AIC · ⌖ 6.57 AIC · ⊞ 8.8K ·
Comment /souschef to run again

@github-actions
github-actions Bot deployed to aoai-model August 16, 2026 21:52 Active
@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Smoke OTEL completed successfully!

Generated by Smoke OTEL

@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Smoke Copilot Small completed successfully!

Generated by Smoke Copilot Small

@github-actions

Copy link
Copy Markdown
Contributor

📰 BREAKING: Smoke Copilot is now investigating this pull request. Sources say the story is developing...

@github-actions

Copy link
Copy Markdown
Contributor

📰 BREAKING: Smoke Copilot - AOAI (apikey) is now investigating this pull request. Sources say the story is developing...

@github-actions

Copy link
Copy Markdown
Contributor

Heredocs hid shell risk,
JavaScript now writes files safe,
locks rebuilt anew.

Inspired by this PR's shift from shell heredocs to a safe JavaScript file renderer.

Generated by 🌸 Smoke Copilot Auto for #53183 · auto · 9.65 AIC · ⌖ 7.35 AIC · ⊞ 6.3K ·
Add label smoke to run again

@github-actions

Copy link
Copy Markdown
Contributor

Smoke test summary:

  • Recent commits focus on workflow/sandbox reliability, safe-output handling, and docs/CI hardening.
  • Bash commit fetch test: ✅
  • Overall status: PASS

Generated by ⚡ Smoke Copilot MAI · mai10 · 3.87 AIC · ⌖ 1.23 AIC · ⊞ 19.2K ·
Comment /smoke-copilot-mai to run again
Add label smoke to run again

@github-actions

Copy link
Copy Markdown
Contributor

Agent Container Tool Check

Tool Status Version
bash 5.1.16
sh available
git 2.34.1
jq 1.6
yq not found
curl 7.81.0
gh 2.4.0
node 22.23.2
python3 3.9.19
go 1.24.13
java not found
dotnet not found

Result: 9/12 tools available ❌ FAIL

🔧 Tool validation by Agent Container Smoke Test · auto · 7.65 AIC · ⌖ 1.97 AIC · ⊞ 7.3K ·
Comment /smoke-test-tools to run again

@github-actions

Copy link
Copy Markdown
Contributor
Smoke test FAIL

PR reads: #53207 [caveman] Optimize instruction verbosity — action-container-substitutions, developer.instructions (2026-08-16); #53202 Handle bare Design Decision Gate workflow dispatch
❌ GitHub MCP
❌ Serena
✅ Playwright
❌ Web-fetch
✅ File, Bash, Build, Comment-memory, Cache
SKIP Issue field

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
  • www.google.com
  • www.gstatic.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"
    - "www.google.com"
    - "www.gstatic.com"

See Network Configuration for more information.

🔮 The oracle has spoken through Smoke Codex · gpt54 · 41.7 AIC · ⊞ 12.1K ·
Comment /smoke-codex to run again

@github-actions

Copy link
Copy Markdown
Contributor

Commit pushed: 2640922

Generated by Changeset Generator · gpt54 · 53 AIC · ⌖ 10.5 AIC · ⊞ 12.5K

@github-actions

Copy link
Copy Markdown
Contributor

Copilot Engine Smoke Test — Run 31974721397

PR: Detect and replace heredocs in generated workflow YAML

Test Result
GitHub tool / merged PRs
mcpscripts PR query
Serena CLI ❌ (Go not installed in language server env)
Playwright CLI
web-fetch
File + bash
Discussion comment
Build (make build)
Artifact upload
Discussion create
Workflow dispatch
PR review tools
Comment memory
Sub-agent (file-summarizer)
Check run
LSP TypeScript function count ✅ (12 functions found)

Overall status: FAIL (1 test failed: Serena/Go language server)

PR author: @app/copilot-swe-agent · Assignees: @pelikhan, @Copilot

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
  • www.google.com
  • www.gstatic.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"
    - "www.google.com"
    - "www.gstatic.com"

See Network Configuration for more information.

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

@github-actions

Copy link
Copy Markdown
Contributor

Comment Memory

Code compiles at dawn
Tests run silent through the night
Green checks bring us peace

Note

This comment is managed by comment memory.

It stores persistent context for this thread in the code block at the top of this comment.
Edit only the text inside the backtick fences; workflow metadata and the footer are regenerated automatically.

Learn more about comment memory

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
  • www.google.com
  • www.gstatic.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"
    - "www.google.com"
    - "www.gstatic.com"

See Network Configuration for more information.

📰 BREAKING: Report filed by Smoke Copilot · auto · 38.4 AIC · ⌖ 2.35 AIC · ⊞ 8.9K ·
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 - all good.

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
  • www.google.com
  • www.gstatic.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"
    - "www.google.com"
    - "www.gstatic.com"

See Network Configuration for more information.

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

Comments that could not be inline-anchored

.github/workflows/ab-testing-advisor.lock.yml:17

Smoke test inline comment #1 - lock file regenerated as part of PR.

@github-actions

Copy link
Copy Markdown
Contributor

Comment Memory

noop

Note

This comment is managed by comment memory.

It stores persistent context for this thread in the code block at the top of this comment.
Edit only the text inside the backtick fences; workflow metadata and the footer are regenerated automatically.

Learn more about comment memory

📰 BREAKING: Report filed by Smoke Copilot - AOAI (Entra) · o40mini · 19.4 AIC · ⌖ 4.68 AIC · ⊞ 18.9K ·
Comment /smoke-copilot-aoai-entra to run again
Add label smoke to run again

@github-actions

Copy link
Copy Markdown
Contributor

💥 Smoke Test Results — Run 31974687259

Core Tests #1#12:
✅ GitHub MCP | ✅ GH CLI | ✅ Make Build | ✅ Playwright | ✅ Tavily | ✅ File Write | ✅ Bash | ✅ Discussion | ✅ AW MCP | ✅ Slack | ✅ Code Scan | ✅ Check Run

PR Review Tests #13#19:
✅ Update PR | ✅ Review Comments | ✅ Submit Review | ⚠️ Resolve Thread (GraphQL null) | ✅ Add Reviewer | ❌ Push to Branch (allowed-files restriction) | ⚠️ Close PR (skipped — no test PR)

Overall Status: PARTIAL

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
  • www.google.com
  • www.gstatic.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"
    - "www.google.com"
    - "www.gstatic.com"

See Network Configuration for more information.

💥 [THE END] — Illustrated by Smoke Claude · sonnet46 · 80.4 AIC · ⌖ 17.9 AIC · ⊞ 6.6K ·
Comment /smoke-claude 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.

💥 Automated smoke test review - all systems nominal! — Run 31974687259

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
  • www.google.com
  • www.gstatic.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"
    - "www.google.com"
    - "www.gstatic.com"

See Network Configuration for more information.

💥 [THE END] — Illustrated by Smoke Claude · sonnet46 · 80.4 AIC · ⌖ 17.9 AIC · ⊞ 6.6K
Comment /smoke-claude to run again

* @param {string} value
* @returns {FileRenderConfig}
*/
function parseConfig(value) {

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 parseConfig function cleanly validates the files array presence. Consider also validating that each FileRenderItem has non-empty path and content_env fields at parse time to surface config errors earlier.

@@ -0,0 +1,114 @@
// Package generatedyamlheredoc implements a Go analysis linter that flags

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.

🔍 Good addition of the generatedyamlheredoc analyzer! This will help enforce the security boundary around shell injection in generated YAML heredocs. Adding a brief package-level comment explaining the threat model would help future maintainers.

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test Results:
1✅,2✅,3❌,4✅,5✅,6✅,7✅,8✅,9❌,10❌,11✅,12❌,13✅,14❌,15✅

Overall: FAIL

@app/copilot-swe-agent @pelikhan @Copilot

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
  • www.google.com
  • www.gstatic.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"
    - "www.google.com"
    - "www.gstatic.com"

See Network Configuration for more information.

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

@github-actions

Copy link
Copy Markdown
Contributor

Comment Memory

Smoke test flying high
In azure skies of pipeline
Success on the ground

Note

This comment is managed by comment memory.

It stores persistent context for this thread in the code block at the top of this comment.
Edit only the text inside the backtick fences; workflow metadata and the footer are regenerated automatically.

Learn more about comment memory

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
  • www.google.com
  • www.gstatic.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"
    - "www.google.com"
    - "www.gstatic.com"

See Network Configuration for more information.

📰 BREAKING: Report filed by Smoke Copilot - AOAI (apikey) · o40mini · 50.2 AIC · ⌖ 2.59 AIC · ⊞ 19.2K ·
Comment /smoke-copilot-aoai-apikey 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.

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
  • www.google.com
  • www.gstatic.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"
    - "www.google.com"
    - "www.gstatic.com"

See Network Configuration for more information.

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

@pelikhan
pelikhan merged commit b108703 into main Aug 16, 2026
37 checks passed
@pelikhan
pelikhan deleted the copilot/add-go-linter-for-heredoc branch August 16, 2026 22:22
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.87.1

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