-
Notifications
You must be signed in to change notification settings - Fork 499
Fix gvisor+privileged sandbox codemod gap and add missing tools.bash codemod for min-integrity: none #53964
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Fix gvisor+privileged sandbox codemod gap and add missing tools.bash codemod for min-integrity: none #53964
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
93ecd95
Initial plan
Copilot 71bb2ed
Fix sandbox gvisor+privileged codemod and add min-integrity bash codemod
Copilot 1d520ec
Simplify insertBashFalseIntoTopLevelTools per code review
Copilot 9017c9d
Keep runtime: gvisor and drop sudo/legacy-security instead of migrati…
Copilot 3d456ae
docs(adr): add draft ADR-53964 for preferring restrictive safe defaul…
github-actions[bot] d414ca0
Merge branch 'main' into copilot/aw-compat-fix-codemod-gaps
github-actions[bot] 39149f0
Run min-integrity bash codemod before cli-proxy codemod and support i…
Copilot 9c47fb0
Add t.Parallel to single-pass codemod registry test
Copilot b6addb5
Merge branch 'main' into copilot/aw-compat-fix-codemod-gaps
github-actions[bot] 437dc51
Add regression tests for gvisor+sudo+legacy-security and comment-skip…
Copilot 84cd22c
Merge branch 'main' into copilot/aw-compat-fix-codemod-gaps
github-actions[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
8 changes: 8 additions & 0 deletions
8
.changeset/fix-sandbox-gvisor-privileged-and-min-integrity-bash-codemods.md
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
72 changes: 72 additions & 0 deletions
72
docs/adr/53964-prefer-restrictive-safe-default-in-ambiguous-codemod-cases.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| # ADR-53964: Prefer the Restrictive Safe Default When Codemods Encounter Ambiguous Security Configurations | ||
|
|
||
| **Date**: 2026-08-19 | ||
| **Status**: Draft | ||
| **Deciders**: pelikhan, copilot-swe-agent | ||
|
|
||
| --- | ||
|
|
||
| ### Context | ||
|
|
||
| `gh aw fix --write` applies a sequence of registered codemods to repair workflow files that fail strict-mode compilation. Two gaps caused the fix pass to leave files unrepaired: | ||
|
|
||
| 1. The `sandbox-runtime-profiles` codemod hard-errored (and aborted the entire file's fix pass) when it encountered `sandbox.agent.runtime: gvisor` combined with `sudo: true` or `legacy-security: enable`. This combination is no longer supported, but gVisor and privileged options have conflicting intent — gVisor enforces strict network isolation while `sudo`/`legacy-security` request elevated host access. | ||
|
|
||
| 2. No codemod existed for the strict-mode requirement that `tools.bash` must be explicitly specified when `tools.github.min-integrity: none`. Files with this configuration reported "No fixes needed" from `gh aw fix --write` yet still failed `--strict` compilation, silently blocking cross-repo audits. | ||
|
|
||
| Both gaps were reproduced across multiple independently-verified external repositories (e.g. `github/gh-aw-firewall`, `github/gh-aw-mcpg`, `chrizbo/agentics-beyond-code`) during the daily compilation audit. | ||
|
|
||
| ### Decision | ||
|
|
||
| We will resolve ambiguous security configurations by **choosing the more restrictive safe default and auto-applying the fix** rather than aborting or requiring manual intervention: | ||
|
|
||
| - For `runtime: gvisor` combined with `sudo`/`legacy-security`: keep `runtime: gvisor` (the stricter isolation) and drop the incompatible privileged fields. This lets `gh aw fix --write` complete the file instead of aborting. | ||
| - For `min-integrity: none` without explicit `tools.bash`: insert `tools.bash: false`. This preserves the pre-existing behavior (bash was never configured) while satisfying the strict-mode requirement. | ||
|
|
||
| The guiding principle is that when a configuration is ambiguous, the codemod should not block the fix pass — it should apply the change that is safest and most likely correct, and log what it did so the author can review. | ||
|
|
||
| ### Alternatives Considered | ||
|
|
||
| #### Alternative 1: Migrate gVisor + privileged to `docker-sudo-iptables` | ||
|
|
||
| Rewrite `runtime: gvisor` to `runtime: docker-sudo-iptables` when privileged options are present, on the grounds that the author's intent was privileged access and gVisor was incidental. | ||
|
|
||
| Not chosen because gVisor is an explicit runtime choice that signals a deliberate preference for strict network isolation. Silently downgrading isolation to satisfy a `sudo` flag would be a security regression and harder to review. Dropping the privileged fields is the smaller, more auditable change. | ||
|
|
||
| #### Alternative 2: Keep aborting with an actionable error (previous behavior for gVisor) | ||
|
|
||
| Continue returning an error that names the two choices and requires the author to resolve manually. | ||
|
|
||
| Not chosen because this leaves the file completely untouched by `gh aw fix --write` — every other codemod that would have applied to the same file is also skipped. The actionable error approach scales poorly when the same pattern appears across many external repos during automated audits. | ||
|
|
||
| #### Alternative 3: No codemod for `min-integrity: none` + missing `tools.bash`; require manual fix | ||
|
|
||
| Keep the existing behavior where `gh aw fix --write` reports "No fixes needed" and let authors add `tools.bash` themselves. | ||
|
|
||
| Not chosen because `tools.bash: false` is a safe, behavior-preserving default (bash was not configured before) and the strict-mode requirement is mechanical. Requiring manual action for a deterministic, zero-ambiguity fix creates unnecessary friction at scale. | ||
|
|
||
| #### Alternative 4: Insert `tools.bash: true` instead of `false` for the `min-integrity: none` codemod | ||
|
|
||
| Explicitly allow bash when min-integrity is none, arguing that the workflow might need shell access. | ||
|
|
||
| Not chosen because this changes behavior (enabling a tool that was previously absent) and could introduce unintended capabilities. `false` is the conservative, behavior-preserving choice. | ||
|
|
||
| ### Consequences | ||
|
|
||
| #### Positive | ||
| - `gh aw fix --write` can now fully auto-repair all files affected by these two patterns without any manual intervention. | ||
| - gVisor's strict network isolation is preserved wherever it was already explicitly configured, avoiding unintended security downgrades. | ||
| - `tools.bash: false` satisfies the strict-mode compile requirement without changing runtime behavior for workflows that never relied on bash access. | ||
| - The fix pass no longer aborts an entire file when one codemod encounters an ambiguous case, allowing other codemods in the same file to run. | ||
|
|
||
| #### Negative | ||
| - Authors who had both `runtime: gvisor` and `sudo: true` with a genuine intent for privileged host access will have `sudo` silently dropped. The fix log records this, but the author must actively check it to notice. | ||
| - Auto-insertion of `tools.bash: false` is invisible to the author unless they diff the fixed file. Workflows that intended to add bash access later will need to update the field explicitly. | ||
|
|
||
| #### Neutral | ||
| - The `migrateSandboxAgentSecurityLines` function signature changed (added `oldRuntime` parameter, changed `hasRuntime bool` to a derived local variable) to support in-place rewriting of existing `runtime:` values. This is an internal refactor with no external API surface. | ||
| - Both codemods are registered in the standard codemod registry and covered by unit tests, following the existing extension pattern. | ||
|
|
||
| --- | ||
|
|
||
| *ADR created by [adr-writer agent]. Review and finalize before changing status from Draft to Accepted.* |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| package cli | ||
|
|
||
| import ( | ||
| "strings" | ||
|
|
||
| "github.com/github/gh-aw/pkg/logger" | ||
| ) | ||
|
|
||
| var minIntegrityNoneBashCodemodLog = logger.New("cli:codemod_min_integrity_none_bash") | ||
|
|
||
| // getMinIntegrityNoneRequiresBashCodemod creates a codemod that adds an explicit | ||
| // 'tools.bash: false' when 'tools.github.min-integrity' is set to 'none' and | ||
| // 'tools.bash' is not already specified. | ||
| // | ||
| // Strict mode requires bash access to be explicit whenever min-integrity is none, since | ||
| // any external user can trigger the workflow. No bash tool was configured before, so | ||
| // inserting 'bash: false' preserves the existing behavior while satisfying the new | ||
| // strict-mode requirement. | ||
| func getMinIntegrityNoneRequiresBashCodemod() Codemod { | ||
| return Codemod{ | ||
| ID: "min-integrity-none-requires-bash", | ||
| Name: "Add explicit 'tools.bash: false' when 'tools.github.min-integrity' is 'none'", | ||
| Description: "Inserts 'tools.bash: false' when 'tools.github.min-integrity' is set to 'none' and 'tools.bash' is not already specified, preserving current behavior while satisfying strict mode", | ||
| IntroducedIn: "1.5.0", | ||
| Apply: func(content string, frontmatter map[string]any) (string, bool, error) { | ||
| toolsMap, ok := frontmatter["tools"].(map[string]any) | ||
| if !ok { | ||
| return content, false, nil | ||
| } | ||
|
|
||
| if _, hasBash := toolsMap["bash"]; hasBash { | ||
| return content, false, nil | ||
| } | ||
|
|
||
| githubMap, ok := toolsMap["github"].(map[string]any) | ||
| if !ok { | ||
| return content, false, nil | ||
| } | ||
|
|
||
| minIntegrity, ok := githubMap["min-integrity"].(string) | ||
| if !ok || minIntegrity != "none" { | ||
| return content, false, nil | ||
| } | ||
|
|
||
| newContent, applied, err := applyFrontmatterLineTransform(content, insertBashFalseIntoTopLevelTools) | ||
| if applied { | ||
| minIntegrityNoneBashCodemodLog.Print("Inserted 'tools.bash: false' because tools.github.min-integrity is 'none'") | ||
| } | ||
| return newContent, applied, err | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| // insertBashFalseIntoTopLevelTools inserts 'bash: false' as the first child of the | ||
| // top-level 'tools:' block, supporting both block mappings and inline flow mappings. | ||
| // It assumes the caller has already verified that 'tools' exists as a mapping and that | ||
| // 'tools.bash' is not already present. | ||
| func insertBashFalseIntoTopLevelTools(lines []string) ([]string, bool) { | ||
|
github-actions[bot] marked this conversation as resolved.
|
||
| toolsLine := -1 | ||
| for i, line := range lines { | ||
| if isTopLevelBlockKey(line, "tools") { | ||
| toolsLine = i | ||
| break | ||
| } | ||
| } | ||
| if toolsLine == -1 { | ||
| return insertEntryIntoInlineMapping(lines, "tools", "bash: false") | ||
| } | ||
|
|
||
| fieldIndent := " " | ||
| insertAt := toolsLine + 1 | ||
|
|
||
| for i := toolsLine + 1; i < len(lines); i++ { | ||
| line := lines[i] | ||
| trimmed := strings.TrimSpace(line) | ||
| if trimmed == "" || strings.HasPrefix(trimmed, "#") { | ||
| continue | ||
| } | ||
| if hasExitedBlock(line, "") { | ||
| break | ||
| } | ||
| fieldIndent = getIndentation(line) | ||
| insertAt = i | ||
| break | ||
| } | ||
|
|
||
| result := insertLine(lines, insertAt, fieldIndent+"bash: false") | ||
| return result, true | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.