The wrapper prints and posts review service text as is, and follows its redirects to other origins #52

Open
opened 2026-10-06 05:35:52 +00:00 by jercik · 0 comments
Owner

The wrapper treats everything the pinned service origin returns as safe. It prints and posts service text without removing the capability token, and it follows a redirect from the service to any other origin. Both matter only if the service, or something in front of it, repeats the token or sends a redirect, and the wrapper has no defense if that happens.

What happens

Service text is printed.

Service text is posted to the pull request.

Redirects are followed. The bundled @j4k/review client calls fetch with no redirect option (dist/index.mjs:5749), and createCapabilityClient takes only a URL and a token (src/review/session.ts:197-199), so the wrapper cannot change that. The origin pin (src/credentials.ts:61-66) and the https check (src/credentials.ts:40) cover the configured URL and nothing after a redirect. The runtime drops the Authorization header on a cross-origin redirect, but it still sends the request, and the wrapper parses and reconciles the answer.

Reproduced at 9f272a52 with the committed bundle on Node 24.21.0 against a stand-in service on loopback:

  • a 401 body that repeats the header: the token appeared in stderr (s1-reflect-401);
  • a report, a finding body, or a Review whose subject_commit repeats the token: it appeared in the summary comment, the inline review, or the log line (s1-echo-report, s1-claim-body-echo, s1-success-field);
  • a 307 to another origin over plain http: the wrapper followed it and reconciled the Review that came back (s1-redirect-xorigin). When the Location URL carried the token, the token reached the other origin in the URL (s1-redirect-token-in-url).

What it should do instead

  1. Remove the exact token from every service-derived string before it is printed or posted. src/credentials.ts is the only module that reads the token, and src/main.ts is the only module that may import it (src/credentials.test.ts:209-214). So the function should live in credentials.ts and reach the print and post sites through WrapperDeps, as openSession does.
  2. Refuse redirects from the service. A 3xx should fail the request with an error that names the origin it tried to leave. The change belongs in the @j4k/review transport; the wrapper then picks it up by bumping the dependency (package.json:30) and rebuilding the bundle.

Why it matters

A token in a pull request comment is visible to everyone who can read the pull request, which on a public repository is everyone. No runner masks comments. A token that reaches another origin in a URL ends up in that origin's logs, over plain http if the redirect says so. The capability token has no expiry.

The trigger is a service bug, a misconfigured proxy, or a hijacked origin that echoes the Authorization header or redirects. The reproductions used a stand-in service that does so. Nobody has checked whether the deployed service, or a proxy in front of it, ever repeats the credential or redirects.

Related: #23 covers how a claim body renders inside an inline comment (footnotes, deep nesting). This issue is about what service text may contain, such as the token, and what happens to it.

🤖 Generated with Claude Code

The wrapper treats everything the pinned service origin returns as safe. It prints and posts service text without removing the capability token, and it follows a redirect from the service to any other origin. Both matter only if the service, or something in front of it, repeats the token or sends a redirect, and the wrapper has no defense if that happens. ## What happens **Service text is printed.** - `toFailure` builds each mapped failure from the service's own words ([`src/review/session.ts:43-86`](https://code.j4k.dev/j4k-oss/review-wrapper/src/commit/9f272a522e8ed6f748533761de45ee3e94fd7481/src/review/session.ts#L43-L86)), including the rejection body for HTTP 401 and 403 ([`src/review/session.ts:48-53`](https://code.j4k.dev/j4k-oss/review-wrapper/src/commit/9f272a522e8ed6f748533761de45ee3e94fd7481/src/review/session.ts#L48-L53)). - A failure no mapping covers is printed through `describeError` ([`src/wrapper/orchestrator.ts:153-162`](https://code.j4k.dev/j4k-oss/review-wrapper/src/commit/9f272a522e8ed6f748533761de45ee3e94fd7481/src/wrapper/orchestrator.ts#L153-L162)). When the body is not a problem document, that is the whole raw body. - The attach log line prints the commit the returned Review says it was made for ([`src/wrapper/orchestrator.ts:93-98`](https://code.j4k.dev/j4k-oss/review-wrapper/src/commit/9f272a522e8ed6f748533761de45ee3e94fd7481/src/wrapper/orchestrator.ts#L93-L98)). **Service text is posted to the pull request.** - The report markdown goes into the summary comment verbatim ([`src/wrapper/orchestrator.ts:47-55`](https://code.j4k.dev/j4k-oss/review-wrapper/src/commit/9f272a522e8ed6f748533761de45ee3e94fd7481/src/wrapper/orchestrator.ts#L47-L55), [`src/reconcile/summary.ts:16-18`](https://code.j4k.dev/j4k-oss/review-wrapper/src/commit/9f272a522e8ed6f748533761de45ee3e94fd7481/src/reconcile/summary.ts#L16-L18)). - Each finding's title, body, lens and arm go into the inline review ([`src/reconcile/inline.ts:14-27`](https://code.j4k.dev/j4k-oss/review-wrapper/src/commit/9f272a522e8ed6f748533761de45ee3e94fd7481/src/reconcile/inline.ts#L14-L27), [`src/reconcile/inline.ts:83`](https://code.j4k.dev/j4k-oss/review-wrapper/src/commit/9f272a522e8ed6f748533761de45ee3e94fd7481/src/reconcile/inline.ts#L83)). **Redirects are followed.** The bundled `@j4k/review` client calls `fetch` with no `redirect` option ([`dist/index.mjs:5749`](https://code.j4k.dev/j4k-oss/review-wrapper/src/commit/9f272a522e8ed6f748533761de45ee3e94fd7481/dist/index.mjs#L5749)), and `createCapabilityClient` takes only a URL and a token ([`src/review/session.ts:197-199`](https://code.j4k.dev/j4k-oss/review-wrapper/src/commit/9f272a522e8ed6f748533761de45ee3e94fd7481/src/review/session.ts#L197-L199)), so the wrapper cannot change that. The origin pin ([`src/credentials.ts:61-66`](https://code.j4k.dev/j4k-oss/review-wrapper/src/commit/9f272a522e8ed6f748533761de45ee3e94fd7481/src/credentials.ts#L61-L66)) and the `https` check ([`src/credentials.ts:40`](https://code.j4k.dev/j4k-oss/review-wrapper/src/commit/9f272a522e8ed6f748533761de45ee3e94fd7481/src/credentials.ts#L40)) cover the configured URL and nothing after a redirect. The runtime drops the `Authorization` header on a cross-origin redirect, but it still sends the request, and the wrapper parses and reconciles the answer. Reproduced at `9f272a52` with the committed bundle on Node 24.21.0 against a stand-in service on loopback: - a 401 body that repeats the header: the token appeared in stderr (`s1-reflect-401`); - a report, a finding body, or a Review whose `subject_commit` repeats the token: it appeared in the summary comment, the inline review, or the log line (`s1-echo-report`, `s1-claim-body-echo`, `s1-success-field`); - a 307 to another origin over plain http: the wrapper followed it and reconciled the Review that came back (`s1-redirect-xorigin`). When the `Location` URL carried the token, the token reached the other origin in the URL (`s1-redirect-token-in-url`). ## What it should do instead 1. Remove the exact token from every service-derived string before it is printed or posted. `src/credentials.ts` is the only module that reads the token, and `src/main.ts` is the only module that may import it ([`src/credentials.test.ts:209-214`](https://code.j4k.dev/j4k-oss/review-wrapper/src/commit/9f272a522e8ed6f748533761de45ee3e94fd7481/src/credentials.test.ts#L209-L214)). So the function should live in `credentials.ts` and reach the print and post sites through `WrapperDeps`, as `openSession` does. 2. Refuse redirects from the service. A 3xx should fail the request with an error that names the origin it tried to leave. The change belongs in the `@j4k/review` transport; the wrapper then picks it up by bumping the dependency ([`package.json:30`](https://code.j4k.dev/j4k-oss/review-wrapper/src/commit/9f272a522e8ed6f748533761de45ee3e94fd7481/package.json#L30)) and rebuilding the bundle. ## Why it matters A token in a pull request comment is visible to everyone who can read the pull request, which on a public repository is everyone. No runner masks comments. A token that reaches another origin in a URL ends up in that origin's logs, over plain http if the redirect says so. The capability token has no expiry. The trigger is a service bug, a misconfigured proxy, or a hijacked origin that echoes the `Authorization` header or redirects. The reproductions used a stand-in service that does so. Nobody has checked whether the deployed service, or a proxy in front of it, ever repeats the credential or redirects. Related: #23 covers how a claim body renders inside an inline comment (footnotes, deep nesting). This issue is about what service text may contain, such as the token, and what happens to it. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
No labels
No milestone
No assignees
1 participant
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
j4k-oss/review-wrapper#52
No description provided.