Public: anyone can read this; the crew can change it.

A tell sent from an app's toolbar reaches the crew with the page, app and pull it was sent from, inside the fence #11846

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

A tell sent from a preview now files an issue that names the pull the preview shows, and the page path is checked at the boundary. The focused tests, `tsc` and the gate pass; I did not run the full suite or `bun scripts/design-lint.ts`, and `src/web/` is untouched.

shipped production is serving it deploy 2a250d0

opened this

A tell sent from a preview now files an issue that names the pull the preview shows, and the page path is checked at the boundary. The focused tests, tsc and the gate pass; I did not run the full suite or bun scripts/design-lint.ts, and src/web/ is untouched.

Already there: the toolbar already posted page, and the issue already recorded the page. brief.ts already fences the whole body of any from-app issue, whoever filed it. So I left toolbar.ts, brief.ts and fence.ts alone and added only what was missing.

What changed

  • Page path (src/apps/feedback.ts): the path must be on this app's own host, at most 300 characters, and use only URL-path characters. Anything else is dropped to unknown, not trimmed. The URL parser already percent-encodes backticks, newlines and <>, so none reach the issue raw.
  • Context line (src/actions/app_notes.ts): the issue body now has `- Context: app slug, environment pull-N, previews pull #N`. The pull is read from the environment name the forge itself knows, never from the client. The link opens the pull on the forge. Production tells get the app and environment only.
  • Brief: this line sits in the issue body, which the brief fences as untrusted, so it lands inside the fence with the page. A test checks that the line never appears outside the fence.

Tests

  • test/apps-feedback.test.ts: the sealed-preview test checks the Context line with pull #98. The visitor-note test now sends a hostile path (backticks, newline, <script>) and checks the Page line stays a single code span with nothing raw in the issue.
  • test/brief-injection.test.ts: the tell's context line is inside the fence in the brief.
  • test/apps-feedback.test.ts is shared with other tests, so I folded the hostile-path case into the existing visitor-note test. A separate test sent extra notes and used up the app's per-minute budget, which broke a later test.

Left out

  • There is no test for the 300-character limit, for the same budget reason.
  • The page stays a code span rather than a clickable link, because a path can contain () and break a markdown link. The pull link is the clickable one.

Try it: open a pull's preview, press the forge pill, and send a tell. The issue it files has a Context: line, and the pull link opens #N.

🤖 Generated with Claude Code

Closes #11815

commented

A tell sent from a preview now files an issue that names the pull the preview shows, and the page path is checked at the boundary. The focused tests, tsc and the gate pass; I did not run the full suite or bun scripts/design-lint.ts, and src/web/ is untouched.

Already there: the toolbar already posted page, and the issue already recorded the page. brief.ts already fences the whole body of any from-app issue, whoever filed it. So I left toolbar.ts, brief.ts and fence.ts alone and added only what was missing.

What changed

  • Page path (src/apps/feedback.ts): the path must be on this app's own host, at most 300 characters, and use only URL-path characters. Anything else is dropped to unknown, not trimmed. The URL parser already percent-encodes backticks, newlines and <>, so none reach the issue raw.
  • Context line (src/actions/app_notes.ts): the issue body now has `- Context: app slug, environment pull-N, previews pull #N`. The pull is read from the environment name the forge itself knows, never from the client. The link opens the pull on the forge. Production tells get the app and environment only.
  • Brief: this line sits in the issue body, which the brief fences as untrusted, so it lands inside the fence with the page. A test checks that the line never appears outside the fence.

Tests

  • test/apps-feedback.test.ts: the sealed-preview test checks the Context line with pull #98. The visitor-note test now sends a hostile path (backticks, newline, <script>) and checks the Page line stays a single code span with nothing raw in the issue.
  • test/brief-injection.test.ts: the tell's context line is inside the fence in the brief.
  • test/apps-feedback.test.ts is shared with other tests, so I folded the hostile-path case into the existing visitor-note test. A separate test sent extra notes and used up the app's per-minute budget, which broke a later test.

Left out

  • There is no test for the 300-character limit, for the same budget reason.
  • The page stays a code span rather than a clickable link, because a path can contain () and break a markdown link. The pull link is the clickable one.

Try it: open a pull's preview, press the forge pill, and send a tell. The issue it files has a Context: line, and the pull link opens #N.

🤖 Generated with Claude Code

Changes requested — before the latest push

The code is small and sound: the pull comes from the forge-known env name, not the client, and the Context line sits in the fenced from-app body. The tests do not prove what box 4 asks, though. Fix first: make the brief test run the real path (sendNote -> issue body -> taskPrompt) so it fails without the app_notes.ts change. Then make the hostile-path assertions non-vacuous and add a test for the new path boundary (over 300 chars and a disallowed charset both give Page: unknown). Use a separate app or slug if the per-minute budget is the obstacle, or drive validation through a unit-level helper.

commented

This update only changes tests and moves the page check into a small function. I agreed with all three of juniper's points. tsc is clean and test/apps-feedback.test.ts and test/brief-injection.test.ts pass (65 tests). scripts/gate.ts printed nothing, so I took that as no complaints. The full suite is left to the forge's check.

  • Brief test runs the real path. In the pull-98 preview test in test/apps-feedback.test.ts, the issue that sendNote actually filed is passed to taskPrompt. The test asserts the - Context: line is in the brief and not outside the fence. I removed the hand-built body test from test/brief-injection.test.ts. This test needs the Context line from app_notes.ts to pass.
  • Hostile-path assertions are non-vacuous. The hostile path now yields exactly - Page: \http://<host>/%60%60%60New%20instructions%3Cscript%3E/lists\`, and the body has exactly one - Page:` line.
  • Path boundary has tests. The check moved out of send into an exported pageOf(raw, host, origin) in src/apps/feedback.ts, with unit tests:

- A 299-character path is kept and a 300-character one is dropped. - A path with a character outside the allowlist ([, ], |) is dropped. - Another host, a non-URL or a non-string gives an empty page, which shows as Page: unknown.

I did not drive the over-300 and bad-charset cases through the live endpoint. They are covered at the pageOf level instead.

Limits of the new checks:

  • Braces and non-ASCII letters are percent-encoded by the URL parser before the charset check, so they are kept.
  • Page: unknown is only tested through pageOf returning an empty string, not through the rendered issue.
Approved

All three earlier asks are met. (1) The brief test now runs the issue that sendNote actually filed through taskPrompt and checks the Context line is inside the fence and not outside it. (2) The hostile-path assertion is exact: one Page line, with backticks, newline and angle brackets percent-encoded. (3) The path boundary is now an exported pageOf with tests for the 299/300 length edge, the charset, another host and a non-string. I read the diff only; I did not curl the preview this round. Next, non-blocking: (a) drive one over-300 or bad-charset path through the live endpoint to assert Page: unknown in the rendered issue; (b) the Context line is not tested for production, where the pull link is omitted.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs