Repository navigation
Make safeoutputs CLI transport fail loudly instead of fail open #61427
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
Changes from 1 commit
6b5ad0d
cc0d873
e786783
f83f180
4a8b4a9
13bdf8c
6042238
67f059e
a444378
556c34c
f0d8b29
cd61925
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3176,11 +3176,44 @@ const SAFEOUTPUTS_CLI_EXCERPT_MAX_LENGTH = 300; | |
| * @returns {boolean} | ||
| */ | ||
| function isDroppedPipeSafeOutputsCommand(line) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/diagnosing-bugs] The dropped-pipe heuristic can false-positive on prose. 💡 ReproductionisDroppedPipeSafeOutputsCommand(`echo "See docs: safeoutputs add_comment example"`) // => true (false positive)Consider requiring @copilot please address this. |
||
| const match = /(printf|echo)\b([^\n]*?)safeoutputs\b/.exec(line); | ||
| if (!match) return false; | ||
| // A pipe (or command separator) between the writer and `safeoutputs` means the | ||
| // binary does run; only the separator-free form is silently skipped. | ||
| return !/[|;&><]/.test(match[2]); | ||
| let quote = ""; | ||
| let writerSeen = false; | ||
| for (let i = 0; i < line.length;) { | ||
| const char = line[i]; | ||
| if (quote) { | ||
| if (char === "\\" && quote === '"' && i + 1 < line.length) i += 2; | ||
| else if (char === quote) { | ||
| quote = ""; | ||
| i++; | ||
| } else i++; | ||
| continue; | ||
| } | ||
| if (char === "'" || char === '"' || char === "`") { | ||
| quote = char; | ||
| i++; | ||
| continue; | ||
| } | ||
| if ("|;&><".includes(char)) return false; | ||
| if (/\s/.test(char)) { | ||
| i++; | ||
| continue; | ||
| } | ||
| const start = i; | ||
| while (i < line.length && !/\s/.test(line[i]) && !"'\"`|;&><".includes(line[i])) i++; | ||
| const word = line.slice(start, i); | ||
| if (word === "printf" || word === "echo") writerSeen = true; | ||
| else if (word === "safeoutputs") return writerSeen; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Escape command text before inserting it in a fenced Markdown block. | ||
| * @param {string} command - Redacted command excerpt | ||
| * @returns {string} | ||
| */ | ||
| function escapeMarkdownCodeFence(command) { | ||
| return command.replace(/`/g, "\\`"); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -3217,13 +3250,14 @@ function buildSafeOutputsCliInvocationContext(stdioLogPathOverride) { | |
| if (!/(^|[|;&(\s"'`])safeoutputs\s+[a-z_][a-z0-9_]*/i.test(line)) continue; | ||
| const redacted = applyAddMaskRedaction(line, maskedValues); | ||
| const truncated = redacted.length > SAFEOUTPUTS_CLI_EXCERPT_MAX_LENGTH ? `${redacted.slice(0, SAFEOUTPUTS_CLI_EXCERPT_MAX_LENGTH)}…` : redacted; | ||
| const escaped = escapeMarkdownCodeFence(truncated); | ||
| if (isDroppedPipeSafeOutputsCommand(line)) { | ||
| hasDroppedPipe = true; | ||
| } | ||
| if (seen.has(truncated)) continue; | ||
| seen.add(truncated); | ||
| if (seen.has(escaped)) continue; | ||
| seen.add(escaped); | ||
| if (commands.length < SAFEOUTPUTS_CLI_EXCERPT_LIMIT) { | ||
| commands.push(truncated); | ||
| commands.push(escaped); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2598,8 +2598,17 @@ describe("handle_agent_failure", () => { | |
| it("classifies piped and unpiped invocations", () => { | ||
| expect(isDroppedPipeSafeOutputsCommand(`printf '{"message":"x"}' safeoutputs noop .`)).toBe(true); | ||
| expect(isDroppedPipeSafeOutputsCommand(`printf '{"message":"x"}' | safeoutputs noop .`)).toBe(false); | ||
| expect(isDroppedPipeSafeOutputsCommand(`printf '{"message":">"}' safeoutputs noop .`)).toBe(true); | ||
| expect(isDroppedPipeSafeOutputsCommand(`echo "$(safeoutputs noop .)"`)).toBe(false); | ||
| expect(isDroppedPipeSafeOutputsCommand(`safeoutputs noop --message "x"`)).toBe(false); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] No regression test covers the false-positive case above (prose mentioning 💡 Suggested testit("does not misclassify prose containing 'safeoutputs' as a dropped pipe", () => {
expect(isDroppedPipeSafeOutputsCommand(`echo "See docs: safeoutputs add_comment example"`)).toBe(false);
});@copilot please address this. |
||
| }); | ||
|
|
||
| it("does not render Markdown fence delimiters from command excerpts", () => { | ||
| fs.writeFileSync(stdioLogPath, "safeoutputs noop --message '```\\n'"); | ||
| const result = buildSafeOutputsCliInvocationContext(); | ||
| expect(result).toContain("\\`\\`\\`"); | ||
| expect(result).not.toContain("\n```\n`\n"); | ||
| }); | ||
| }); | ||
|
|
||
| describe("buildEngineFailureContext", () => { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[/codebase-design] This
.github/aw/actions-lock.jsonchange downgradesdocker/build-push-actionv7.4.0→v7.3.0 anddocker/setup-buildx-actionv4.4.0→v4.3.0, unrelated to this PR's stated purpose (safeoutputs CLI transport). Every generated.lock.ymlin the repo (e.g.detection-analysis-report.lock.yml,daily-cache-strategy-analyzer.lock.yml) still references the newer v7.4.0/v4.4.0 SHAs, so this diff introduces a lock-file/workflow-output inconsistency rather than fixing one.💡 Suggestion
Revert this hunk unless it was an intentional, separately-justified downgrade (e.g. reverting a bad upstream release). If unintentional, it's likely a stale rebase artifact — regenerate the lock file from the current
mainbefore merging.@copilot please address this.