Conversation
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> Agent-Logs-Url: https://github.com/github/gh-aw/sessions/f8159da5-ba15-44a4-82cb-4637701718e6
…t_handler_manager Security fix for MEDIUM severity finding from weekly red team scan 2026-03-22. - JS: sanitize scriptFilename using path.basename() + path.relative() containment check before calling require() in safe_output_handler_manager.cjs - Go: add isSafeScriptName() validation in safe_scripts.go to block path traversal characters from being written into GH_AW_SAFE_OUTPUT_SCRIPTS at compile time Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> Agent-Logs-Url: https://github.com/github/gh-aw/sessions/f8159da5-ba15-44a4-82cb-4637701718e6
| const customScriptHandlers = loadCustomSafeOutputScriptHandlers(); | ||
| if (customScriptHandlers.size > 0) { | ||
| core.info(`Loading ${customScriptHandlers.size} custom script handler(s): ${[...customScriptHandlers.keys()].join(", ")}`); | ||
| const nodePath = require("path"); |
There was a problem hiding this comment.
Moved require("path") to a module-level constant at the top of the file (commit 870ee71). The inline const nodePath = require("path") inside loadHandlers is gone — the function now uses the module-level nodePath.
…l tests
- Move require("path") to module top-level in safe_output_handler_manager.cjs
- Add JS adversarial tests in safe_output_handler_manager.test.cjs:
- 7 new tests in "loadHandlers - path traversal sanitization" covering: leading
../, embedded /, absolute Unix path, multi-level traversal, ".." alone,
mixed safe+evil entries, and a valid plain filename
- Add Go adversarial tests in safe_scripts_test.go:
- TestIsSafeScriptName: table-driven test for isSafeScriptName covering 11 cases
- TestBuildCustomSafeOutputScriptsJSONPathTraversal: 5 cases verifying traversal
names are excluded from the GH_AW_SAFE_OUTPUT_SCRIPTS JSON
- TestBuildCustomSafeOutputScriptsJSONPathTraversalMixedWithSafe: verifies safe
names are preserved when mixed with traversal names
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/07b67d12-f668-4f48-abc9-85093de4905d
Added adversarial tests in commit 870ee71: JavaScript (
Go (
|
There was a problem hiding this comment.
Pull request overview
Hardens custom safe-output script loading to prevent path traversal via GH_AW_SAFE_OUTPUT_SCRIPTS, adding both runtime JS checks (before require()) and compile-time Go validation plus new adversarial tests.
Changes:
- Add path traversal sanitization when resolving/
require()-ing custom script handlers insafe_output_handler_manager.cjs. - Add
isSafeScriptName()validation in the Go workflow compiler to filter unsafe script names before emittingGH_AW_SAFE_OUTPUT_SCRIPTS. - Add adversarial test suites for both the JS loader and Go compiler validation.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
pkg/workflow/safe_scripts.go |
Adds isSafeScriptName() and filters unsafe script names when building GH_AW_SAFE_OUTPUT_SCRIPTS JSON. |
pkg/workflow/safe_scripts_test.go |
Adds table-driven tests for isSafeScriptName() and JSON emission behavior under adversarial keys. |
actions/setup/js/safe_output_handler_manager.cjs |
Sanitizes custom script filenames before require() and asserts resolved paths remain under the expected base dir. |
actions/setup/js/safe_output_handler_manager.test.cjs |
Adds a dedicated path traversal sanitization test suite for loadHandlers(). |
.github/aw/actions-lock.json |
Adds a pinned lock entry for github/gh-aw-actions/setup@v0. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (safeFilename !== scriptFilename) { | ||
| core.error(`Invalid script filename for ${scriptType}: path traversal detected in "${scriptFilename}" — skipping`); | ||
| continue; | ||
| } |
There was a problem hiding this comment.
The sanitization still allows scriptFilename values like "" or "." which make nodePath.join(scriptBaseDir, safeFilename) resolve to scriptBaseDir and then require(scriptBaseDir) will load a directory (package.json/main or index.js). To fully constrain what can be required, explicitly reject empty/"." (and consider enforcing the expected .cjs extension/prefix for custom scripts) before building scriptPath.
| } | |
| } | |
| // Reject empty or "." basenames, which would resolve to the base directory itself. | |
| if (!safeFilename || safeFilename === ".") { | |
| core.error(`Invalid script filename for ${scriptType}: "${scriptFilename}" is not a valid script file name — skipping`); | |
| continue; | |
| } | |
| // Enforce the expected .cjs extension for custom script handlers. | |
| if (nodePath.extname(safeFilename) !== ".cjs") { | |
| core.error(`Invalid script filename for ${scriptType}: "${scriptFilename}" must have a .cjs extension — skipping`); | |
| continue; | |
| } |
| scriptMapping := make(map[string]string, len(data.SafeOutputs.Scripts)) | ||
| for scriptName := range data.SafeOutputs.Scripts { | ||
| normalizedName := stringutil.NormalizeSafeOutputIdentifier(scriptName) | ||
| // Reject names that could cause path traversal when the filename is passed to require() | ||
| if !isSafeScriptName(normalizedName) { | ||
| safeScriptsLog.Printf("Warning: skipping script %q — name contains unsafe path characters: %q", scriptName, normalizedName) | ||
| continue | ||
| } | ||
| scriptMapping[normalizedName] = safeOutputScriptFilename(normalizedName) |
There was a problem hiding this comment.
isSafeScriptName is only applied when building GH_AW_SAFE_OUTPUT_SCRIPTS, but normalizedName is also used to generate on-disk filenames for these scripts (see pkg/workflow/compiler_safe_outputs_job.go where safeOutputScriptFilename(normalizedName) is written via shell redirection). If an unsafe script name contains path separators or traversal patterns, the workflow could still attempt to write outside the intended directory even though the env-var mapping is skipped. Consider enforcing the same validation at the point scripts are compiled/emitted (or failing compilation) so unsafe names can’t affect any file paths.
…st.go Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> Agent-Logs-Url: https://github.com/github/gh-aw/sessions/20d7e076-1c29-4dca-8396-3d51c9733af7
Fixed in 6384cb3 — replaced the two |
scriptFilenamevalues fromGH_AW_SAFE_OUTPUT_SCRIPTSwere passed unsanitized intopath.join()before being loaded viarequire(), allowing path traversal (e.g.../evil.cjs) to load arbitrary files at runtime.Changes
actions/setup/js/safe_output_handler_manager.cjsrequire("path")to module top-level asnodePathpath.basename()and reject any filename wherebasename !== original(contains/,\, or..)path.relative()to assert the resolved path stays withinscriptBaseDir— portable across platforms unlike astartsWith(dir + sep)checkpkg/workflow/safe_scripts.goisSafeScriptName()to reject names containing/,\, or..buildCustomSafeOutputScriptsJSON()before any script name is written intoGH_AW_SAFE_OUTPUT_SCRIPTS, blocking traversal sequences at the source rather than relying solely on the runtime JS checkTests
actions/setup/js/safe_output_handler_manager.test.cjsAdded 7 adversarial tests in a new
loadHandlers - path traversal sanitizationsuite covering:../in filename/directory separator/etc/passwd)../../etc/shadow)..filenamepkg/workflow/safe_scripts_test.goTestIsSafeScriptName: 11-case table-driven test covering valid names and all traversal patternsTestBuildCustomSafeOutputScriptsJSONPathTraversal: 5 adversarial keys verified absent from output JSONTestBuildCustomSafeOutputScriptsJSONPathTraversalMixedWithSafe: safe names preserved when mixed with traversal names💬 Send tasks to Copilot coding agent from Slack and Teams to turn conversations into code. Copilot posts an update in your thread when it's finished.