From b9a5dd4f70ca4ae092d5f96dc2f6be7b7d38b2be Mon Sep 17 00:00:00 2001 From: JarLob Date: Fri, 24 Jul 2026 01:43:03 +0300 Subject: [PATCH 1/2] The regex wasn't escaping external input --- .../codeql/actions/security/OutputClobberingQuery.qll | 5 ++++- .../change-notes/2026-07-28-output-clobbering-regex.md | 4 ++++ .../Security/CWE-074/.github/workflows/output2.yml | 10 ++++++++++ .../Security/CWE-074/OutputClobberingHigh.expected | 6 ++++++ 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 actions/ql/src/change-notes/2026-07-28-output-clobbering-regex.md diff --git a/actions/ql/lib/codeql/actions/security/OutputClobberingQuery.qll b/actions/ql/lib/codeql/actions/security/OutputClobberingQuery.qll index 7d560f8b6242..57f0e31a25b4 100644 --- a/actions/ql/lib/codeql/actions/security/OutputClobberingQuery.qll +++ b/actions/ql/lib/codeql/actions/security/OutputClobberingQuery.qll @@ -196,7 +196,10 @@ class WorkflowCommandClobberingFromFileReadSink extends OutputClobberingSink { clobbering_cmd.regexpMatch(["ls", Bash::fileReadCommand()] + "\\s.*") and ( // - run: echo "foo=$(= 0 + ) or // A file content is printed to stdout // - run: cat pr-id.txt diff --git a/actions/ql/src/change-notes/2026-07-28-output-clobbering-regex.md b/actions/ql/src/change-notes/2026-07-28-output-clobbering-regex.md new file mode 100644 index 000000000000..d9e84a447c91 --- /dev/null +++ b/actions/ql/src/change-notes/2026-07-28-output-clobbering-regex.md @@ -0,0 +1,4 @@ +--- +category: fix +--- +* Fixed a performance issue in the `actions/output-clobbering/high` query caused by using unescaped source-code input in a regular expression. diff --git a/actions/ql/test/query-tests/Security/CWE-074/.github/workflows/output2.yml b/actions/ql/test/query-tests/Security/CWE-074/.github/workflows/output2.yml index 896f6c820152..63298fdd57b2 100644 --- a/actions/ql/test/query-tests/Security/CWE-074/.github/workflows/output2.yml +++ b/actions/ql/test/query-tests/Security/CWE-074/.github/workflows/output2.yml @@ -114,3 +114,13 @@ jobs: run: | # VULNERABLE: halt_error emits its input without JSON encoding jq '.value | halt_error(1)' pr-number.json + - id: clob18 + run: | + # VULNERABLE: the file name contains regex metacharacters + echo "VALUE=$(cat 'pr[number](final).txt')" + echo "::set-output name=OUTPUT::SAFE" + - id: clob19 + run: | + # VULNERABLE: echo is invoked through env + env echo "VALUE=$(cat 'pr[number](final).txt')" + echo "::set-output name=OUTPUT::SAFE" diff --git a/actions/ql/test/query-tests/Security/CWE-074/OutputClobberingHigh.expected b/actions/ql/test/query-tests/Security/CWE-074/OutputClobberingHigh.expected index 58b0df462e48..08ff1495a662 100644 --- a/actions/ql/test/query-tests/Security/CWE-074/OutputClobberingHigh.expected +++ b/actions/ql/test/query-tests/Security/CWE-074/OutputClobberingHigh.expected @@ -16,6 +16,8 @@ edges | .github/workflows/output2.yml:36:9:41:6 | Uses Step | .github/workflows/output2.yml:106:14:108:51 | # VULNERABLE: raw-output0 emits strings without JSON encoding\njq '.value' --raw-output0 pr-number.json\n | provenance | Config | | .github/workflows/output2.yml:36:9:41:6 | Uses Step | .github/workflows/output2.yml:110:14:112:46 | # VULNERABLE: stderr emits its input without JSON encoding\njq '.value \| stderr' pr-number.json\n | provenance | Config | | .github/workflows/output2.yml:36:9:41:6 | Uses Step | .github/workflows/output2.yml:114:14:116:53 | # VULNERABLE: halt_error emits its input without JSON encoding\njq '.value \| halt_error(1)' pr-number.json\n | provenance | Config | +| .github/workflows/output2.yml:36:9:41:6 | Uses Step | .github/workflows/output2.yml:118:14:121:48 | # VULNERABLE: the file name contains regex metacharacters\necho "VALUE=$(cat 'pr[number](final).txt')"\necho "::set-output name=OUTPUT::SAFE"\n | provenance | Config | +| .github/workflows/output2.yml:36:9:41:6 | Uses Step | .github/workflows/output2.yml:123:14:126:48 | # VULNERABLE: echo is invoked through env\nenv echo "VALUE=$(cat 'pr[number](final).txt')"\necho "::set-output name=OUTPUT::SAFE"\n | provenance | Config | nodes | .github/workflows/output1.yml:9:18:9:49 | github.event.comment.body | semmle.label | github.event.comment.body | | .github/workflows/output1.yml:10:14:13:50 | # VULNERABLE\necho "OUTPUT_1=HARDCODED" >> $GITHUB_OUTPUT\necho "OUTPUT_2=$BODY" >> $GITHUB_OUTPUT\n | semmle.label | # VULNERABLE\necho "OUTPUT_1=HARDCODED" >> $GITHUB_OUTPUT\necho "OUTPUT_2=$BODY" >> $GITHUB_OUTPUT\n | @@ -39,6 +41,8 @@ nodes | .github/workflows/output2.yml:106:14:108:51 | # VULNERABLE: raw-output0 emits strings without JSON encoding\njq '.value' --raw-output0 pr-number.json\n | semmle.label | # VULNERABLE: raw-output0 emits strings without JSON encoding\njq '.value' --raw-output0 pr-number.json\n | | .github/workflows/output2.yml:110:14:112:46 | # VULNERABLE: stderr emits its input without JSON encoding\njq '.value \| stderr' pr-number.json\n | semmle.label | # VULNERABLE: stderr emits its input without JSON encoding\njq '.value \| stderr' pr-number.json\n | | .github/workflows/output2.yml:114:14:116:53 | # VULNERABLE: halt_error emits its input without JSON encoding\njq '.value \| halt_error(1)' pr-number.json\n | semmle.label | # VULNERABLE: halt_error emits its input without JSON encoding\njq '.value \| halt_error(1)' pr-number.json\n | +| .github/workflows/output2.yml:118:14:121:48 | # VULNERABLE: the file name contains regex metacharacters\necho "VALUE=$(cat 'pr[number](final).txt')"\necho "::set-output name=OUTPUT::SAFE"\n | semmle.label | # VULNERABLE: the file name contains regex metacharacters\necho "VALUE=$(cat 'pr[number](final).txt')"\necho "::set-output name=OUTPUT::SAFE"\n | +| .github/workflows/output2.yml:123:14:126:48 | # VULNERABLE: echo is invoked through env\nenv echo "VALUE=$(cat 'pr[number](final).txt')"\necho "::set-output name=OUTPUT::SAFE"\n | semmle.label | # VULNERABLE: echo is invoked through env\nenv echo "VALUE=$(cat 'pr[number](final).txt')"\necho "::set-output name=OUTPUT::SAFE"\n | subpaths #select | .github/workflows/output1.yml:10:14:13:50 | # VULNERABLE\necho "OUTPUT_1=HARDCODED" >> $GITHUB_OUTPUT\necho "OUTPUT_2=$BODY" >> $GITHUB_OUTPUT\n | .github/workflows/output1.yml:9:18:9:49 | github.event.comment.body | .github/workflows/output1.yml:10:14:13:50 | # VULNERABLE\necho "OUTPUT_1=HARDCODED" >> $GITHUB_OUTPUT\necho "OUTPUT_2=$BODY" >> $GITHUB_OUTPUT\n | Potential clobbering of a step output in $@. | .github/workflows/output1.yml:10:14:13:50 | # VULNERABLE\necho "OUTPUT_1=HARDCODED" >> $GITHUB_OUTPUT\necho "OUTPUT_2=$BODY" >> $GITHUB_OUTPUT\n | # VULNERABLE\necho "OUTPUT_1=HARDCODED" >> $GITHUB_OUTPUT\necho "OUTPUT_2=$BODY" >> $GITHUB_OUTPUT\n | @@ -58,3 +62,5 @@ subpaths | .github/workflows/output2.yml:106:14:108:51 | # VULNERABLE: raw-output0 emits strings without JSON encoding\njq '.value' --raw-output0 pr-number.json\n | .github/workflows/output2.yml:36:9:41:6 | Uses Step | .github/workflows/output2.yml:106:14:108:51 | # VULNERABLE: raw-output0 emits strings without JSON encoding\njq '.value' --raw-output0 pr-number.json\n | Potential clobbering of a step output in $@. | .github/workflows/output2.yml:106:14:108:51 | # VULNERABLE: raw-output0 emits strings without JSON encoding\njq '.value' --raw-output0 pr-number.json\n | # VULNERABLE: raw-output0 emits strings without JSON encoding\njq '.value' --raw-output0 pr-number.json\n | | .github/workflows/output2.yml:110:14:112:46 | # VULNERABLE: stderr emits its input without JSON encoding\njq '.value \| stderr' pr-number.json\n | .github/workflows/output2.yml:36:9:41:6 | Uses Step | .github/workflows/output2.yml:110:14:112:46 | # VULNERABLE: stderr emits its input without JSON encoding\njq '.value \| stderr' pr-number.json\n | Potential clobbering of a step output in $@. | .github/workflows/output2.yml:110:14:112:46 | # VULNERABLE: stderr emits its input without JSON encoding\njq '.value \| stderr' pr-number.json\n | # VULNERABLE: stderr emits its input without JSON encoding\njq '.value \| stderr' pr-number.json\n | | .github/workflows/output2.yml:114:14:116:53 | # VULNERABLE: halt_error emits its input without JSON encoding\njq '.value \| halt_error(1)' pr-number.json\n | .github/workflows/output2.yml:36:9:41:6 | Uses Step | .github/workflows/output2.yml:114:14:116:53 | # VULNERABLE: halt_error emits its input without JSON encoding\njq '.value \| halt_error(1)' pr-number.json\n | Potential clobbering of a step output in $@. | .github/workflows/output2.yml:114:14:116:53 | # VULNERABLE: halt_error emits its input without JSON encoding\njq '.value \| halt_error(1)' pr-number.json\n | # VULNERABLE: halt_error emits its input without JSON encoding\njq '.value \| halt_error(1)' pr-number.json\n | +| .github/workflows/output2.yml:118:14:121:48 | # VULNERABLE: the file name contains regex metacharacters\necho "VALUE=$(cat 'pr[number](final).txt')"\necho "::set-output name=OUTPUT::SAFE"\n | .github/workflows/output2.yml:36:9:41:6 | Uses Step | .github/workflows/output2.yml:118:14:121:48 | # VULNERABLE: the file name contains regex metacharacters\necho "VALUE=$(cat 'pr[number](final).txt')"\necho "::set-output name=OUTPUT::SAFE"\n | Potential clobbering of a step output in $@. | .github/workflows/output2.yml:118:14:121:48 | # VULNERABLE: the file name contains regex metacharacters\necho "VALUE=$(cat 'pr[number](final).txt')"\necho "::set-output name=OUTPUT::SAFE"\n | # VULNERABLE: the file name contains regex metacharacters\necho "VALUE=$(cat 'pr[number](final).txt')"\necho "::set-output name=OUTPUT::SAFE"\n | +| .github/workflows/output2.yml:123:14:126:48 | # VULNERABLE: echo is invoked through env\nenv echo "VALUE=$(cat 'pr[number](final).txt')"\necho "::set-output name=OUTPUT::SAFE"\n | .github/workflows/output2.yml:36:9:41:6 | Uses Step | .github/workflows/output2.yml:123:14:126:48 | # VULNERABLE: echo is invoked through env\nenv echo "VALUE=$(cat 'pr[number](final).txt')"\necho "::set-output name=OUTPUT::SAFE"\n | Potential clobbering of a step output in $@. | .github/workflows/output2.yml:123:14:126:48 | # VULNERABLE: echo is invoked through env\nenv echo "VALUE=$(cat 'pr[number](final).txt')"\necho "::set-output name=OUTPUT::SAFE"\n | # VULNERABLE: echo is invoked through env\nenv echo "VALUE=$(cat 'pr[number](final).txt')"\necho "::set-output name=OUTPUT::SAFE"\n | From 9c83964dd25369c26508d8e85a0a361897dc3ac2 Mon Sep 17 00:00:00 2001 From: JarLob Date: Fri, 31 Jul 2026 13:40:26 +0300 Subject: [PATCH 2/2] Revalidate output clobbering expectations