Repository navigation
fix(#6966): keep model output in the generated post-script from issuing workflow commands - #7914
Conversation
…st-script logs it The generated post-script echoed a rejected status raw. The value is model output and jq -r turns JSON \n/\r escapes into real line breaks, so a crafted status could split the runner's log line and start a new one with a :: workflow-command prefix. Flatten CR/LF to spaces, cap the value at 40 characters and write it with printf '%s\n', which never interprets backslash escapes (echo does under xpg_echo). summary already had this check; status did not. Tests feed LF, CR-only and CRLF statuses followed by ::error::injected and assert a single log line, and run the script under bash -O xpg_echo with a literal backslash-n. Goldens regenerated. Assisted-by: Claude (fix), Claude (review) Signed-off-by: Wayne Sun <gsun@redhat.com>
PR Summary by QodoPrevent log-line injection from rejected agent statuses
AI Description
Diagram
High-Level Assessment
Files changed (4)
|
Code Review by Qodo🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)
Great, no issues found!Qodo reviewed your code and found no material issues that require reviewTip of the day💡 Did you know, you can keep summaries lean with Findings visible per group, which tucks the rest behind a View link |
|
🤖 Review · ❌ Terminated · Started 2:51 PM UTC · Ended 3:37 PM UTC Commit: |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
🤖 Finished Review · ❌ Failure (agent timed out after 45m0s without completing (timeout: 45m0s)) · Started 2:51 PM UTC · Completed 3:37 PM UTC Commit: Runtime: pi · Model: sonnet → claude-sonnet-5 · Effort: high · Cost: $2.21 |
|
🤖 Finished Review · ✅ Success · Started 3:43 PM UTC · Completed 4:24 PM UTC Commit: Runtime: pi · Model: sonnet → claude-sonnet-5 · Effort: high · Cost: $3.35 |
…tory on ok Regenerated from fullsend main with fullsend-ai/fullsend#7914 (e64be8f20), #7915 (6fb93c397) and #7917 (c9231c731, which includes #7916 be3093ce5) merged in. Only post-link-check.sh changed, and only on the ok path: - a fullsend that is missing or whose `post-comment --help` fails now fails the run instead of being treated as an older CLI that lacks --only-if-exists; only a working CLI without the flag still posts nothing; - the all-clear passes --keep-history=false, so it replaces the earlier findings comment in place without a "Previous run" block. The ok-path argument test now expects --keep-history=false, a new test runs a stub whose --help exits 1 and 127 and requires a failed run with no post, and the prompt's output contract no longer mentions a history block. Assisted-by: Claude (fix), Claude (review) Signed-off-by: Wayne Sun <gsun@redhat.com>
|
Risk Assessment: moderate (2/5) DetailsSmall, well-tested security hardening fix touching no protected paths or workflows, with moderate recent churn in generator templates and an easily revertible change surface; consistent with the prior assessment, as only a doc wording fix changed since. Previous runRisk Assessment: moderate (2/5) DetailsModerate risk (score 2) reflecting a scoped hardening fix to generated script templates and docs with strong test coverage, no CI or dependency changes, and moderate git churn driven primarily by active documentation files. Previous run (2)Risk Assessment: moderate (2/5) DetailsSmall, well-tested security fix touching no protected paths or workflows, with moderate recent churn in agent generator templates and an easily revertible change surface. |
|
Looks good to me Previous runReviewFindingsLow
Next steps:
Previous run (2)ReviewFindingsHigh
Medium
Low
NoteThe PR's core claim was independently verified: the new Next steps:
|
The dry-run branch printed the model's comment raw. Under GitHub Actions a line starting with "::" (the runner trims leading spaces first) is a workflow command, so a crafted comment could still reach the runner the way the rejected status did. Wrap the preview in a ::stop-commands:: block whose 128-bit token comes from /dev/urandom and is closed after the preview: nothing inside is interpreted, whatever its line breaks. This is the documented Actions mechanism and does not depend on how the runner splits lines, unlike prefixing each line. The building-custom-agents guide's example post-script echoed a raw STATUS the same way; it now flattens, caps and prints it with printf. docs/cli/agent.md shows the new dry-run output, captured from the generated script. Assisted-by: Claude (fix), Claude (review) Signed-off-by: Wayne Sun <gsun@redhat.com>
|
🤖 Review · ❌ Terminated · Started 4:48 PM UTC · Ended 5:06 PM UTC Commit: |
Site previewPreview: https://2b6dcc6d-site.fullsend-ai.workers.dev Commit: |
|
🤖 Finished Review · ✅ Success · Started 4:48 PM UTC · Completed 5:05 PM UTC Commit: Runtime: pi · Model: sonnet → claude-sonnet-5 · Effort: high · Cost: $4.67 |
Outdated: its findings (dry-run workflow-command injection, building-custom-agents.md raw echo) are fixed in c057eb8.
|
@ggallen when you have a moment, could you confirm that the reply on your Since then, c057eb8 also wraps the dry-run preview in a random-token |
|
@waynesun09 I re-tested the exact |
ggallen
left a comment
There was a problem hiding this comment.
The CR/LF concern is resolved in the latest head. I left one minor documentation comment about the token-masking claim.
The note now states only what the guard relies on: the token is random on every run, so the model cannot predict the closing line. Assisted-by: Claude (fix), Claude (review) Signed-off-by: Wayne Sun <gsun@redhat.com>
|
🤖 Finished Review · ✅ Success · Started 7:30 PM UTC · Completed 7:43 PM UTC Commit: Runtime: pi · Model: sonnet → claude-sonnet-5 · Effort: high · Cost: $2.57 |
…tory on ok Regenerated from fullsend main with fullsend-ai/fullsend#7914 (e64be8f20), #7915 (6fb93c397) and #7917 (c9231c731, which includes #7916 be3093ce5) merged in. Only post-link-check.sh changed, and only on the ok path: - a fullsend that is missing or whose `post-comment --help` fails now fails the run instead of being treated as an older CLI that lacks --only-if-exists; only a working CLI without the flag still posts nothing; - the all-clear passes --keep-history=false, so it replaces the earlier findings comment in place without a "Previous run" block. The ok-path argument test now expects --keep-history=false, a new test runs a stub whose --help exits 1 and 127 and requires a failed run with no post, and the prompt's output contract no longer mentions a history block. Assisted-by: Claude (fix), Claude (review) Signed-off-by: Wayne Sun <gsun@redhat.com>
What
The post-script that
fullsend agent newgenerates no longer echoes a rejectedstatusraw. It flattens CR/LF to spaces, caps the value at 40 characters, and writes it withprintf '%s\n'.Dry-run preview: the dry-run branch previews the model's
commenton stdout, and under GitHub Actions a line starting with::is a workflow command. The runner trims leading spaces before checking. The preview is now wrapped in::stop-commands::<token>…::<token>::, where the token is 16 bytes from/dev/urandomin hex. It's guarded to exactly 32 characters, and the script exits 1 before printing anything without one. This is the documented Actions mechanism. It works whatever the line breaks are, unlike prefixing each line.Guide example: the example post-script in
docs/guides/user/building-custom-agents.mdechoed a rawSTATUSthe same way. It now flattens, caps and prints it withprintf.docs/cli/agent.mdshows the new dry-run output, captured from the generated script.Why
statusis model output.jq -rturns JSON\n/\rescapes into real line breaks, so a crafted value could split the runner's log line and start a new one with a::workflow-command prefix.echoalso expands a literal\nunderxpg_echo, whichprintf '%s'never does.summaryalready had this check;statusdid not.Tests
internal/agentnew/postscript_status_test.go:A
statusofbogus+ LF, CR or CRLF +::error::injectedis rejected on a single log line.With
bash -O xpg_echo, a literal backslash-n stays on one line.A dry-run findings comment containing
::error::injected,::add-mask::x(after a CR) and an indented::warning::stays inside a stop-commands block. The block opens on the first line and closes on the last with the same 32-hex token, and the token differs between runs.With an
odthat yields no token, the dry run exits 1 and prints nothing.These two came from the review summary on e64be8f.
Each of these mutations fails a test: removing the flattening, flattening LF only, switching
printfback toecho, removing the 40-character cap, dropping the stop-commands wrap, fixing the token, and dropping the token-length guard.The goldens are regenerated; the diff is this hunk only.
Split out of #7243. Part of #6966.
🤖 Generated with Claude Code