Forge-controlled text reaches the console unsanitized through the gh and glab adapters #469

Open
opened 2026-08-27 11:22:48 +00:00 by mph · 3 comments
Owner

Found triaging IW-437 (noticed during phase 2 code review, while hardening the Forgejo HTTP path).

WHAT. GitHubClient.createPullRequest (core/adapters/GitHubClient.scala:688-712) and GitLabClient.createMergeRequest (core/adapters/GitLabClient.scala:568-592) pass the raw stderr of gh and glab into ForgeCreateError.Other with no filtering and no length limit. That message travels through PhasePr.createPrAndGate (core/commands/PhasePr.scala:121-125) to System.err.println in LiveConsole.err (core/commands/LiveCommandEnv.scala:61).

THE ASYMMETRY. ForgejoClient.bodySnippet strips control characters and bidirectional formatting characters and caps the length of an HTTP response body. The two CLI adapters apply no equivalent to the subprocess stderr they forward. So a remote whose branch name, title or error text carries an ANSI escape or a right-to-left override can still reorder or repaint what iw phase-pr prints - through the CLI path rather than the HTTP path.

THE GENERAL POINT. Text that a forge controls reaches the console through EVERY transport an adapter uses, so a sanitizer placed on one transport leaves the others open.

PROPOSED FIX. One shared sanitizer applied to every forge-controlled string an adapter forwards to the console: the gh and glab stderr paths as well as the Forgejo response body. Extracting the existing bodySnippet logic is the obvious starting point.

PRE-EXISTING. Nothing in IW-437 introduced the CLI path or changed how it forwards stderr; phase 2 was scoped to the Forgejo create-pull-request fall-through.

Found triaging IW-437 (noticed during phase 2 code review, while hardening the Forgejo HTTP path). WHAT. GitHubClient.createPullRequest (core/adapters/GitHubClient.scala:688-712) and GitLabClient.createMergeRequest (core/adapters/GitLabClient.scala:568-592) pass the raw stderr of gh and glab into ForgeCreateError.Other with no filtering and no length limit. That message travels through PhasePr.createPrAndGate (core/commands/PhasePr.scala:121-125) to System.err.println in LiveConsole.err (core/commands/LiveCommandEnv.scala:61). THE ASYMMETRY. ForgejoClient.bodySnippet strips control characters and bidirectional formatting characters and caps the length of an HTTP response body. The two CLI adapters apply no equivalent to the subprocess stderr they forward. So a remote whose branch name, title or error text carries an ANSI escape or a right-to-left override can still reorder or repaint what iw phase-pr prints - through the CLI path rather than the HTTP path. THE GENERAL POINT. Text that a forge controls reaches the console through EVERY transport an adapter uses, so a sanitizer placed on one transport leaves the others open. PROPOSED FIX. One shared sanitizer applied to every forge-controlled string an adapter forwards to the console: the gh and glab stderr paths as well as the Forgejo response body. Extracting the existing bodySnippet logic is the obvious starting point. PRE-EXISTING. Nothing in IW-437 introduced the CLI path or changed how it forwards stderr; phase 2 was scoped to the Forgejo create-pull-request fall-through.
Member

Intake: agent-ready
Workflow: diagnostic
Reason: all gates passed
Analysis: project-management/issues/IW-469/analysis.md on branch IW-469

Intake: agent-ready Workflow: diagnostic Reason: all gates passed Analysis: project-management/issues/IW-469/analysis.md on branch IW-469
Member

Implement: needs-human
Reason: batch-implement exit 1 — the kanon plugin did not compile against the iw-cli core of this branch, so no phase ran. fetchPullRequestForBranch now returns Either[TrackerError, …] (iw-cli #778), and the installed kanon plugin (lib/batch/ForgeCatchUp.scala) still expects Either[String, …]. The issue itself is not at fault: update the kanon plugin, then release the issue to agent-ready again. batch-implement.log was not written; the lines below are the command output.

[error] /home/mph/.local/share/iw/plugins/kanon/lib/batch/ForgeCatchUp.scala:143:7
[error] Found:    Either[iw.core.model.TrackerError, Option[batch.ForgeState]]
[error] Required: Either[String, Option[batch.ForgeState]]
Compilation failed
Implement: needs-human Reason: batch-implement exit 1 — the kanon plugin did not compile against the iw-cli core of this branch, so no phase ran. `fetchPullRequestForBranch` now returns `Either[TrackerError, …]` (iw-cli #778), and the installed kanon plugin (`lib/batch/ForgeCatchUp.scala`) still expects `Either[String, …]`. The issue itself is not at fault: update the kanon plugin, then release the issue to `agent-ready` again. batch-implement.log was not written; the lines below are the command output. ``` [error] /home/mph/.local/share/iw/plugins/kanon/lib/batch/ForgeCatchUp.scala:143:7 [error] Found: Either[iw.core.model.TrackerError, Option[batch.ForgeState]] [error] Required: Either[String, Option[batch.ForgeState]] Compilation failed ```
Member

Release: the implement loop failed this issue for a reason outside it. The kanon plugin did not compile against iw-cli main, because the tracker port now returns TrackerError (iw-cli #778). The plugin fix is dev-docs #484, and iw-cli 0.22.1 carries the core change. The issue goes back to the implement loop.

Release: the implement loop failed this issue for a reason outside it. The kanon plugin did not compile against iw-cli main, because the tracker port now returns TrackerError (iw-cli #778). The plugin fix is dev-docs #484, and iw-cli 0.22.1 carries the core change. The issue goes back to the implement loop.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
iterative-works/iw-cli#469
No description provided.