Skip to content

fix(lifecycle): sanitize CI logs and reviewer comments before PTY paste#323

Open
areycruzer wants to merge 1 commit into
aoagents:mainfrom
areycruzer:fix/322-sanitize-nudge-content
Open

fix(lifecycle): sanitize CI logs and reviewer comments before PTY paste#323
areycruzer wants to merge 1 commit into
aoagents:mainfrom
areycruzer:fix/322-sanitize-nudge-content

Conversation

@areycruzer

Copy link
Copy Markdown
Collaborator

Fixes #322

Summary

  • CI-failing nudges appended raw LogTail and review nudges appended raw reviewer comment bodies straight into the message that zellij.SendMessage pastes into the agent's live pane — while POST /sessions/{id}/send already stripped control chars first. External CI output / reviewer comments could therefore inject terminal escape sequences into the agent's TTY.
  • Moved the sanitizer out of the HTTP controller into a shared domain.SanitizeControlChars helper so both the send endpoint and the lifecycle nudge path share one definition and can't drift.
  • Applied it to the two attacker-influenced lifecycle fields: ch.LogTail (reactions.go) and the joined review comment bodies in reviewContent.

Root Cause

stripUnsafeControlChars lived in internal/httpd/controllers/sessions.go and was only called on the user /send path. The lifecycle nudge path (internal/lifecycle/reactions.go) builds its message from external content and delivers it through the same zellij paste mechanism, but with no sanitization. So the protection that exists for one PTY-delivery path was simply missing on the other.

Changes

  • internal/domain/text.go — new SanitizeControlChars (drops control chars except \n, \r, \t).
  • internal/httpd/controllers/sessions.go — use domain.SanitizeControlChars; remove the local helper and the now-unused unicode import.
  • internal/lifecycle/reactions.go — sanitize LogTail and review comment bodies before building the nudge. The dedup signature keeps using the raw bytes (it never reaches the terminal).

Test

  • go build ./...
  • go test ./internal/domain/... ./internal/lifecycle/... ./internal/httpd/controllers/... — pass
  • go vet ./internal/domain/... ./internal/lifecycle/... ./internal/httpd/... — clean
  • New tests: TestSanitizeControlChars (domain), TestPRObservation_CINudgeSanitizesLogTailControlChars and TestPRObservation_ReviewNudgeSanitizesCommentControlChars (lifecycle) — assert escape/control bytes are stripped while newlines/tabs and visible text survive.

Notes

  • Scope is limited to PTY-delivered nudge content; no DTO/API/frontend changes.
  • The static nudge wording (the agent's own instruction text) is trusted and left as-is; only the external fields are sanitized — the minimal, intent-preserving fix.

🤖 Generated with Claude Code

CI nudges appended raw LogTail and review nudges appended raw comment
bodies straight into the message that zellij pastes into the agent's live
pane, while the /sessions/{id}/send endpoint stripped control chars first.
External CI output and reviewer comments could therefore inject terminal
escape sequences into the agent's TTY.

Move the sanitizer out of the HTTP controller into a shared
domain.SanitizeControlChars helper and apply it to both external lifecycle
fields. The dedup signature keeps using the raw bytes (it never reaches the
terminal), and legitimate newlines/tabs are preserved.

Fixes aoagents#322

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@codebanditssss codebanditssss left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — this is the right fix for #322.

Root cause is correctly identified and fully addressed. stripUnsafeControlChars only guarded the /send HTTP path; the lifecycle nudge path built messages from attacker-influenced external content (ch.LogTail, reviewer comment bodies) and delivered them through the same zellij PTY paste with no sanitization. Both PTY-delivery paths now share one domain.SanitizeControlChars definition, so they can't drift — that's the real defense against this recurring.

Scope is appropriately minimal: only the two external fields are sanitized; trusted static nudge wording is left as-is; and the dedup signature deliberately stays on the raw bytes since it never reaches the terminal. No DTO/API/frontend churn.

Verified locally against current main (PR was 14 commits behind):

  • Merges cleanly into main, no conflicts, no semantic clash with main's newer LogTail handling
  • go build ./..., go vet, gofmt all clean
  • Tests pass for domain, lifecycle, and httpd/controllers
  • The three new tests correctly assert escape/NUL/bell bytes are stripped while newlines, tabs, and visible text survive

CI is now green across the board (Go, CLI E2E, gitleaks). Good to merge — please rebase/update on main if the merge button asks for it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants