Preserve native semantics in plugin links - #2094
Conversation
|
🚨 SLOP COP 🚨 · I am SlopCop. I am reviewing this pull request for security, code quality, performance, architecture, and product behavior. |
There was a problem hiding this comment.
🚨 SLOP COP 🚨 · review
Plain English summary
This PR makes plugin links act more like normal browser links. It encodes file paths and keeps explicit URL targets under browser control.
The direction is good. I found two merge blockers and two important contract gaps.
Findings
- Medium security: A custom named target receives
window.opener. The opened site can replace the BB page. Default each new top-level target tonoopener noreferrer. - Merge blocker: The package CI job fails. The GitHub plugin uses
target="_blank", but its test still expects BB host routing. - Medium: The public SDK test runtime accepts malformed file targets. The app makes the same targets inactive. Plugin tests can now report false success.
- Medium:
encodeURIComponentthrows for an unpaired UTF-16 surrogate. The current validator accepts that string, so a plugin can crash its slot. - Low: Two agent guides still describe the old rules. Update
packages/templates/src/templates/bb-guide-plugins.mdand the built-in plugin authoring skill.
Architecture and performance
I found no second link implementation. Primary-click checks repeat in the app and SDK test runtime.
A small helper for button and modifier checks can reduce drift. Keep each target policy explicit because URL, file, and route links differ.
I found no meaningful performance regression. The change adds one path encoding per render and one attribute read per click.
Verification
- The two app test files passed all 10 tests.
- The SDK harness passed all 41 tests.
- The GitHub plugin passed 23 tests and failed one related test.
- The package CI job failed with the same assertion.
- I started the pull-request app and tested the real components with Doobie.
- Ordinary URL and file clicks reached their hosts. Valid file hrefs used safe encoding. Malformed file links had no href.
- A custom target opened a named window with live access to the BB window.
I left the GitHub review state unchanged.
2223672 to
e2cd712
Compare
e2cd712 to
0524b30
Compare
What was wrong
The new host-rendered plugin link components claimed ordinary activation without fully preserving native anchor semantics at their boundary.
ExperimentalFileLinkexposed the untrusted file path directly ashrefeven though valid relative file names may begin with strings such asvscode:ormailto:, so native or modifier activation could launch an external scheme; a target rejected by runtime validation still kept an active href.ExperimentalUrlLinkdid not exclude anchors with an explicittarget, and itsRouteAnchorwrapper also stripped explicit targets from internal routes, allowing the app navigation host or SPA router to override the requested native browsing context.Review found three related gaps: custom named targets could create a browsing context with live
window.openeraccess, the public SDK test link did not apply production file-target validation, and the validator accepted unpaired UTF-16 surrogates that madeencodeURIComponentthrow during render.What changed
./prefix, and omithrefwhen runtime validation rejects the target. The shared validator now rejects unpaired surrogates before encoding. Ordinary valid activation still goes to the shared file-preview host, while modifier, download, copied-href, keyboard, and accessible-name behavior remains anchor-native and scheme-safe.targetas browser-owned. Targeted links render as direct anchors so internal routes retain their target too; untargeted app routes continue usingRouteAnchor._blankand custom named targets preserve suppliedreltokens while addingnoopener noreferrerunlessopeneris explicit; same-context targets are unchanged._blanklinks remain native instead of expecting the test host to claim them.This is the smallest complete fix because it changes only href construction, validation, and target ownership at the two link boundaries; it shares the existing validator only where production and the SDK harness must be identical, and reuses the existing file, URL, and route hosts without a compatibility path or click-helper abstraction. There is no server/daemon wire change and no
HOST_DAEMON_PROTOCOL_VERSIONbump.How you verified
vscode:foo, the malformed target stayed an active link, all tested explicit external targets were claimed byopenUrl, and an internal_blanktarget was stripped._blanklink; after correction both passed in full.rel, the SDK traversal target remained an active./..%2Fsecretlink, and unpaired surrogates threwURIError: URI malformedin both production and the harness. A separate regression proved an explicitnofollowtoken also needed the safe tokens merged rather than replacing them.pnpm exec turbo run test --filter=@bb/app --force -- src/components/plugin/ExperimentalFileLink.test.tsx src/components/plugin/ExperimentalUrlLink.test.tsx src/lib/live-file-navigation.test.ts— 3 files / 24 tests passed after the final rebase.pnpm exec turbo run test --filter=@get-bb/plugin-sdk --force -- src/testing/__tests__/app-harness.test.tsx— 1 file / 43 tests passed after the final rebase.pnpm exec turbo run test --filter=bb-plugin-github --force— 6 files / 24 tests passed after reproducing the stale Markdown expectation locally.pnpm exec turbo run test --filter='!@bb/server' --filter='!@bb/app' --filter='!@bb/integration-tests' --force— the exact CI Packages shard passed 66/66 Turbo tasks after the final rebase.pnpm exec turbo run test --filter=@bb/app --force— 413 files / 3,182 passed / 3 skipped.pnpm exec turbo run test --filter=@get-bb/plugin-sdk --force— 14 files / 117 tests passed, including package-export and portable bundled-type checks.pnpm exec turbo run build typecheck --filter=@bb/app --filter=@get-bb/plugin-sdk --filter=@bb/server --filter=@bb/templates --force— 11/11 Turbo tasks passed after the final rebase.pnpm exec turbo run lint --filter=@bb/app --filter=@get-bb/plugin-sdk --filter=@bb/server --filter=@bb/templates --force— passed with 0 errors (155 warning-only findings outside the changed files).git diff --check origin/main...HEAD— passed.No linked issue.