Skip to content

Make generated Copilot setup workflows Zizmor-compliant - #55641

Merged
pelikhan merged 12 commits into
mainfrom
copilot/fix-generate-workflows-zizmor-compliance
Aug 25, 2026
Merged

Make generated Copilot setup workflows Zizmor-compliant#55641
pelikhan merged 12 commits into
mainfrom
copilot/fix-generate-workflows-zizmor-compliance

Conversation

Copilot AI commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Generated copilot-setup-steps.yml workflows still emitted actions/checkout@v6, leaving consumers with Zizmor findings after upgrading. The setup workflow also needed checkout hardening for clean Zizmor output.

  • Pin generated checkout usage
    • Resolve actions/checkout through the embedded action pins mapping.
    • Replace generated actions/checkout@v6 with the latest SHA-pinned ref.
- name: Checkout repository
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
  with:
    persist-credentials: false
  • Harden generated setup workflows

    • Add top-level read-only permissions.
    • Add workflow concurrency.
    • Name the copilot-setup-steps job.
    • Disable checkout credential persistence.
  • Upgrade existing setup workflows

    • Update existing checkout steps to the embedded pinned ref.
    • Add persist-credentials: false while preserving surrounding YAML formatting and line endings.
  • Add regression coverage

    • Cover generated release/action setup workflows.
    • Cover upgrade behavior, including existing with: blocks and blank-line edge cases.

Copilot AI and others added 4 commits August 25, 2026 04:57
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Comment thread pkg/cli/copilot_setup.go Outdated
return fmt.Sprintf(copilotSetupActionTemplate, checkoutRef, actionRepo, actionRef, version)
}

// Default (dev/script mode): try to resolve the main branch to a pinned SHA so the

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@copilot use GitHub API to resolve the repository default branch.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in bfeed00 by resolving github/gh-aw's default branch through the GitHub API before resolving the install script ref. Follow-up logging cleanup is in 7a1410b.

Copilot AI and others added 4 commits August 25, 2026 05:09
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix generated workflows for Zizmor compliance Make generated Copilot setup workflows Zizmor-compliant Aug 25, 2026
Copilot AI requested a review from pelikhan August 25, 2026 05:23
@pelikhan
pelikhan marked this pull request as ready for review August 25, 2026 05:27
Copilot AI balanced review requested due to automatic review settings August 25, 2026 05:27
@github-actions

github-actions Bot commented Aug 25, 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 test file diff

🧪 Test quality analysis by Test Quality Sentinel

@github-actions

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

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ failed to deliver outputs during design decision gate check.

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Ponytail Reviewer completed successfully!

Generated by Ponytail Reviewer for #55641

@github-actions

Copy link
Copy Markdown
Contributor

Comment Memory

reviewed_at: 2026-08-25T00:00:00Z
review_event: REQUEST_CHANGES
top_themes:
  - checkout upgrade preserves insecure persist-credentials:true
files_reviewed:
  - pkg/cli/copilot_setup.go
  - pkg/cli/copilot_setup_test.go
comment_count: 1

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

🔎 Code quality review by PR Code Quality Reviewer · pi · gpt54 · 5.53 AIC · ⌖ 6.93 AIC · ⊞ 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.

Request changes

The checkout pinning is headed in the right direction, but the upgrader still leaves an insecure configuration behind when an existing workflow already sets persist-credentials: true.

Blocking theme
  • The upgrade path rewrites actions/checkout to the pinned ref but does not force persist-credentials: false, so previously customized workflows can keep persisting credentials after gh aw upgrade.

I did not find other changed-line issues severe enough to block beyond that.

🔎 Code quality review by PR Code Quality Reviewer · pi · gpt54 · 5.53 AIC · ⌖ 6.93 AIC · ⊞ 7K
Comment /review to run again

Comment thread pkg/cli/copilot_setup.go

if i+1 < len(lines) && strings.HasPrefix(lines[i+1], indent+"with:") {
blockEnd := checkoutWithBlockEnd(lines, i+2, indent)
if !checkoutWithBlockHasPersistCredentials(lines, i+2, blockEnd) {

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 upgrader treats any existing persist-credentials: entry as "good enough", so a workflow that explicitly sets persist-credentials: true will keep persisting the GitHub token after upgrade and still violate the hardening this change is supposed to enforce.

💡 Why this needs to be fixed

checkoutWithBlockHasPersistCredentials only checks for the key name, not the value. In the with: branch that means we skip insertion whenever the file already contains persist-credentials: true, and we also rewrite the checkout action to the pinned ref, which makes the upgrade look successful while silently preserving the insecure setting.

Please normalize the checkout block to persist-credentials: false during upgrade instead of only adding the key when it is missing. For example, replace an existing persist-credentials: line in the checkout with: block, and only insert the setting when the key is absent.

if line is persist-credentials:
    rewrite it to false
else if key absent:
    insert persist-credentials: false

Without that, previously customized setup workflows remain credential-persisting even after gh aw upgrade.

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

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

Pins generated checkout actions and hardens Copilot setup workflows for Zizmor compliance.

Changes:

  • Adds permissions, concurrency, job naming, and credential hardening.
  • Upgrades existing checkout references.
  • Adds generation and upgrade regression tests.
Show a summary per file
File Description
pkg/cli/copilot_setup.go Generates and upgrades hardened setup workflows.
pkg/cli/copilot_setup_test.go Tests checkout pinning and YAML upgrades.

Review details

Suppressed comments (2)

pkg/cli/copilot_setup.go:671

  • Checking only for the key treats persist-credentials: true (or a dynamic expression) as already hardened, so upgrades can leave the checkout credentials persisted and retain the Zizmor finding. Require a literal false; replace other values or report that the workflow could not be hardened.
	}
	return len(lines)
}

func checkoutNextNonBlankLineInBlock(lines []string, start int, usesIndent string) bool {

pkg/cli/copilot_setup.go:639

  • The helper reports changed=true for every matched checkout line, even when the reconstructed result is byte-for-byte identical. Since the caller trusts this flag, every repeated upgrade reports success and rewrites an already-current file instead of taking the no-op path.
			i = blockEnd - 1
			continue
		}

		lines = slices.Insert(lines, i+1,
  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment thread pkg/cli/copilot_setup.go
Comment on lines +614 to +618
if i+1 < len(lines) && strings.HasPrefix(lines[i+1], indent+"with:") {
blockEnd := checkoutWithBlockEnd(lines, i+2, indent)
if !checkoutWithBlockHasPersistCredentials(lines, i+2, blockEnd) {
insert := indent + " persist-credentials: false"
lines = slices.Insert(lines, i+2, insert)
Comment thread pkg/cli/copilot_setup.go
Comment on lines +599 to +600
hadTrailingNewline := strings.HasSuffix(contentStr, "\n")
lines := strings.Split(strings.TrimSuffix(contentStr, "\n"), "\n")

@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 a few correctness and test-coverage gaps.

📋 Key Themes & Highlights

Key Themes

  • Unguarded empty pin: pinCheckoutUsesInContent doesn't guard against latestCheckoutActionRef() returning "", which would silently produce malformed YAML (uses: ).
  • Testability: pinCheckoutUsesInContent bakes in the global pin lookup; accepting checkoutRef as a parameter would make unit tests independent of the embedded pins map.
  • Missing edge-case tests: no coverage for persist-credentials: true surviving upgrade unchanged, nor for CRLF-encoded files.

Positive Highlights

  • ✅ Excellent extraction of the large generateCopilotSetupStepsYAML into named template constants — much easier to read and audit.
  • ✅ The refactor into handleExistingCopilotSetupSteps / upgradeExistingCopilotSetupSteps is a clean decomposition of a previously deeply-nested function.
  • ✅ Good blank-line-in-with-block tests (checkoutWithBlockEnd edge cases) — these are exactly the fiddly cases that break regex-only approaches.
  • copilotSetupStepsYAML now reuses copilotSetupScriptTemplate so the static test fixture stays in sync with the runtime template automatically.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · copilot · sonnet46 · 49.1 AIC · ⌖ 10.4 AIC · ⊞ 7.6K
Comment /matt to run again

Comments that could not be inline-anchored

pkg/cli/copilot_setup.go:390

[/diagnosing-bugs] If latestCheckoutActionRef() returns &quot;&quot; (e.g. the pin is absent from the embedded map), the function silently writes uses: — syntactically valid YAML that breaks at runtime with no warning.

<details>
<summary>💡 Suggested fix</summary>

Add an early return at the top of pinCheckoutUsesInContent:

checkoutRef := latestCheckoutActionRef()
if checkoutRef == &quot;&quot; {
    return content, false
}

And add a test asserting no-op behavior when the resolved pin is …

pkg/cli/copilot_setup.go:376

[/codebase-design] latestCheckoutActionRef() is called twice per pinCheckoutUsesInContent invocation (once at call site in upgradeSetupCliVersionInContent, once inside the loop). Since actionpins.ResolveLatestActionPin is deterministic, this is harmless today but makes the contract ambiguous. Passing checkoutRef as a parameter would make the function easier to test in isolation (inject any ref string without touching global state).

<details>
<summary>💡 Suggested signature</summa…

pkg/cli/copilot_setup.go:421

[/tdd] checkoutWithBlockEnd treats a blank line as a potential block terminator but delegates the look-ahead to checkoutNextNonBlankLineInBlock. The two functions are tightly coupled and neither has a dedicated unit test for the case where the blank line is the last line of the file (i.e. start &gt;= len(lines) in checkoutNextNonBlankLineInBlock). The existing tests use blank lines mid-block but not at EOF — a blank trailing line currently returns false, which correctly terminates …

pkg/cli/copilot_setup.go:338

[/diagnosing-bugs] checkoutUsesLinePattern does not handle \r in the uses: value itself (only \r? at line end). A Windows CRLF file with uses: actions/checkout@v4\r will match, but if the SHA ref returned by latestCheckoutActionRef() contains no \r, the replacement produces a mixed-CRLF line that subtly corrupts YAML on Windows runners. The hadTrailingNewline guard only checks the final newline, not per-line endings.

@copilot please address this.

pkg/cli/copilot_setup_test.go:567

[/tdd] The TestPinCheckoutUsesInContent suite doesn't cover the case where persist-credentials: true is already present — the function should leave it untouched (not inject a duplicate false). checkoutWithBlockHasPersistCredentials guards against the inject, but there's no regression test validating that persist-credentials: true survives the upgrade unchanged.

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

t.Run(&quot;does not overwrite existing persist-credentials&quot;, …

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

Zizmor Compliance Review

This PR correctly adds:

  • Pinned SHA for actions/checkout via actionpins
  • persist-credentials: false in new and upgraded workflows
  • Workflow-level permissions: contents: read
  • concurrency block with cancel-in-progress: true

Two blocking issues were found:

1. Empty checkout ref produces invalid YAML silently

latestCheckoutActionRef() does not guard against actionpins.ResolveLatestActionPin returning "". An empty return produces a uses: line that is syntactically invalid YAML. A panic-on-empty guard (or explicit error return) would surface this at build/test time.

2. pinCheckoutUsesInContent does not fix persist-credentials: true

When upgrading a file that already has persist-credentials: true, the function detects the key, skips insertion, and leaves the true value in place — defeating the Zizmor compliance goal for that case. The function needs to replace the value, not just skip if any key is found.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · copilot · sonnet46 · 45.9 AIC · ⌖ 9.31 AIC · ⊞ 6.2K

Comment thread pkg/cli/copilot_setup.go
func latestCheckoutActionRef() string {
return actionpins.ResolveLatestActionPin("actions/checkout", nil)
}

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.

[harden] Silent empty checkout ref can produce invalid YAML

latestCheckoutActionRef() returns actionpins.ResolveLatestActionPin("actions/checkout", nil) with no guard on an empty result. If pin resolution returns "" (e.g., no embedded data found), the generated template will contain uses: — an invalid YAML value. GitHub Actions does not reject it at generation time.

Consider adding a validation guard before writing:

func latestCheckoutActionRef() string {
    ref := actionpins.ResolveLatestActionPin("actions/checkout", nil)
    if ref == "" {
        panic("no embedded pin for actions/checkout — rebuild required")
    }
    return ref
}

Or have generateCopilotSetupStepsYAML / validateCopilotSetupStepsContent catch an empty ref and return an error before persisting.

@copilot please address this.

Comment thread pkg/cli/copilot_setup.go
fmt.Fprintln(os.Stderr, console.FormatInfoMessageStderr(fmt.Sprintf("Skipping %s (already has gh-aw extension install step)", setupStepsPath)))
}
return nil
upgraded, updatedContent, err := upgradeSetupCliVersionInContent(ctx, content, actionMode, version, resolver)

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.

[harden] pinCheckoutUsesInContent is not idempotent when persist-credentials already exists but is true

checkoutWithBlockHasPersistCredentials returns true whenever any persist-credentials: key is found in the block — including persist-credentials: true. This means if an existing workflow already has persist-credentials: true, the upgrade will:

  1. Replace the actions/checkout ref with the pinned SHA ✓
  2. Skip inserting persist-credentials: false because it sees the existing key ✓
  3. Leave persist-credentials: true in place ✗

The Zizmor compliance goal is defeated: the credential is still persisted. The function should either replace the existing persist-credentials value, or at minimum warn when the existing value is true.

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

pkg/cli/copilot_setup.go:L412-L468: yagni: extra handleExistingCopilotSetupSteps/upgradeExistingCopilotSetupSteps wrappers for one branch. Keep the existing-file logic inline until a second caller exists.
pkg/cli/copilot_setup.go:L597-L675: shrink: line-by-line YAML mutation with four tiny helpers. Replace with one small regex-based replacement for the checkout block.
net: -24 lines possible.

Generated by ✂️ Ponytail Reviewer for #55641 · codex · mai10 · 9.92 AIC · ⌖ 2.11 AIC · ⊞ 16.7K
Comment /ponytail to run again

Comment thread pkg/cli/copilot_setup.go
hasLegacyInstall := strings.Contains(contentStr, "install-gh-aw.sh") ||
(strings.Contains(contentStr, "Install gh-aw extension") && strings.Contains(contentStr, "curl -fsSL"))
hasActionInstall := strings.Contains(contentStr, "actions/setup-cli")
func handleExistingCopilotSetupSteps(ctx context.Context, verbose bool, actionMode workflow.ActionMode, version string, resolver workflow.SHAResolver, setupStepsPath string, upgradeVersion bool) error {

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.

pkg/cli/copilot_setup.go:L412-L468: yagni: extra handleExistingCopilotSetupSteps/upgradeExistingCopilotSetupSteps wrappers for one branch. Keep the existing-file logic inline until a second caller exists.

Comment thread pkg/cli/copilot_setup.go
return true, updated, nil
}

func pinCheckoutUsesInContent(content []byte) ([]byte, 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.

pkg/cli/copilot_setup.go:L597-L675: shrink: line-by-line YAML mutation with four tiny helpers. Replace with one small regex-based replacement for the checkout block.

@github-actions

Copy link
Copy Markdown
Contributor

Warning

The push_to_pull_request_branch operation failed: Cannot push to pull request branch: bundle modifies files outside the allowed-files list (pkg/cli/copilot_setup.go, pkg/cli/copilot_setup_test.go). Add the files to the allowed-files configuration field or remove them from the bundle.. The code changes were not applied.

🏗️ Architecture Decision Record Required

This PR modifies 345+ lines in pkg/cli/copilot_setup.go — a business-logic file — which triggers ADR enforcement for this repository.

No ADR was found in the PR body, the PR branch, or a linked issue.


Draft ADR committed to this branch

A draft ADR has been generated and committed to this PR branch:

📄 docs/adr/55641-sha-pin-generated-checkout-refs-for-zizmor-compliance.md

Inferred decision: Use the existing actionpins embedded pin mapping to resolve actions/checkout to an immutable SHA-pinned ref in all generated and upgraded Copilot setup workflows, and add persist-credentials: false plus top-level permissions: contents: read so that generated output is Zizmor-clean by default.


Next steps for the PR author

  1. Review the draft ADR — check that the Context, Decision, Alternatives Considered, and Consequences sections accurately reflect the intent of this change.
  2. Correct or expand any section that does not match the actual trade-offs (especially the Alternatives — the draft infers these from the diff, which may not capture options that were ruled out earlier).
  3. Change the Status from Draft to Accepted (or Proposed if team review is needed first) once you're satisfied.
  4. Merge — the gate will recognise the ADR on the next run and verify alignment.

If the draft is inaccurate or the ADR should live elsewhere (e.g., in a linked issue), update it and re-run the gate.


Evidence used
  • Files changed: pkg/cli/copilot_setup.go (+208 / -118), pkg/cli/copilot_setup_test.go (+137 / -9)
  • PR body: No ADR link detected; fixes 0.85.4 generated workflows are still not Zizmor compliant #50808
  • docs/adr/ search: No file matching 55641-*.md found on the branch before this run
  • ADR enforcement trigger: default_business_additions = 345 > threshold 100 (no implementation label)

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · claude · sonnet46 · 76.4 AIC · ⌖ 27.7 AIC · ⊞ 8.4K ·
Comment /review to run again

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@pelikhan
pelikhan merged commit 62da221 into main Aug 25, 2026
@pelikhan
pelikhan deleted the copilot/fix-generate-workflows-zizmor-compliance branch August 25, 2026 05:51
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.

0.85.4 generated workflows are still not Zizmor compliant

3 participants