Skip to content

fix(control): sanitize help content and stop runaway camera moves - #520

Merged
MrYuion merged 1 commit into
developfrom
fix/control-security-camera
Oct 2, 2026
Merged

MrYuion merged 1 commit into
developfrom
fix/control-security-camera

Conversation

@MrYuion

@MrYuion MrYuion commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Help content from system settings was rendered with bypassSecurityTrustHtml, so raw HTML in the help setting (for example <img onerror=…>) ran script on the panel. The "select a camera" overlay covered the whole vidconf tab, so you could not pick a camera or dial. Cameras could keep moving: the tooltip sent stop only when both axes stopped, and a cancelled touch never sent stop. One tap on the splash screen powered on the room twice, and the version link on the splash also powered it on.

Changes

  • Help XSS: remove | safe from the help tab and help modal, so Angular's sanitizer cleans the markdown output.
  • Camera overlay: the overlay sits inside the controls body, so the camera select and dial view stay usable. The overlay is semi-transparent (bg-base-100/75).
  • Camera movement: the tooltip sends stop before each new direction. The joystick and zoom buttons use pointer events with pointer capture; release, cancel, lost capture and destroy all stop the camera. touch-action: none stops drags from scrolling the tooltip.
  • Splash: remove the duplicate (touchend) power-on, and stop the changelog button click from reaching the splash.

Testing

  • Unit tests for each fix (227 pass). Each new test fails on the old code.
  • Tested in the running app (mock mode) with Playwright and touch emulation: injected <img onerror>, <script>, javascript: links and iframes are stripped, normal markdown still renders; overlay hit-tests; stop after drag, drag-out, touch cancel and tab change; one power(true) per tap.
  • E2E (mock): 179 pass, 19 fail. The same 19 specs fail on every branch in this stack and on the commit before fix/control-ux-fixes (bootstrap host visibility, app routing timing, and Escape not closing tooltips, which the last PR in the stack fixes). None of them reach this change. They were not run on develop itself.

Help media

The sanitizer also strips <video> and <iframe> from help content. This is intended.

Merge order

1 of 5 in a stack. Based on develop. Next: fix/control-state-bugs.


Changes made by Claude Opus 5.5 (1M context) in Claude Code, running in T3 Code.

🤖 Generated with Claude Code

- Remove the `safe` bypass from help rendering so Angular strips
  script handlers from markdown help content.
- Move the camera "select a camera" overlay inside the controls body
  so it no longer covers the camera picker and dial view.
- Send `stop` before each camera move in the tooltip, matching
  camera-controls, so a released axis does not keep moving.
- Use pointer events with pointer capture for the joystick and zoom
  buttons. Cancel, capture loss and destroy now stop the camera.
- Remove the duplicate `touchend` power-on from the splash screens and
  stop the changelog button from powering on the room.
@vercel

vercel Bot commented Oct 1, 2026

Copy link
Copy Markdown

Deployment failed for project frontend-templates with the following error:

Resource is limited - try again in 24 hours (more than 100, code: "api-deployments-free-per-day").

Learn More: https://vercel.com/placeos?upgradeToPro=build-rate-limit

@greptile-apps

greptile-apps Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

[Medium risk] Refactors camera and joystick input handling and removes HTML sanitization.

The PR appears safe to merge, with two non-blocking issues worth fixing.

Findings

  1. P2 Camera move throws without module ▶
  2. P2 Help videos disappear ▶

Summary

This PR sanitizes help content, keeps camera controls usable and stops camera movement when pointer gestures end. It also prevents splash-screen taps and changelog clicks from triggering extra power-on actions.

  • Help content goes through Angular’s normal HTML sanitizer in the tab and modal.
  • The no-camera message stays within the controls area, leaving the camera selector outside it.
  • Camera controls use pointer events and send stop commands when movement or zoom ends.
  • Splash screens use one power-on click path, and the changelog button no longer triggers it.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Joystick changes direction] --> B[Wait 50 ms]
  B --> C[Get selected camera module]
  C --> D[Send stop]
  D --> E[Send tilt or pan]
Loading

Reviews (1) · Last reviewed commit: "fix(control): sanitize help content and ..."

const mod = getModule(this.id, camera.mod);
if (!mod) return;
// Stop first so an axis that returned to Stop does not keep moving
await mod.execute('stop', index ? [index] : []);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Camera move throws without module

If the selected camera's module is unavailable, moveCamera() calls mod.execute('stop') without checking mod. The timer throws instead of quietly returning, as it did before this change. Check mod before sending stop.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Not a bug. getModule() never returns null: in ts-client it is Qr(t).module(e, n), which always builds a module binding (typed PlaceModuleBinding, not nullable). A missing module makes execute reject; it does not throw on mod. The old if (!mod) return was dead code.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You’re right — getModule() returns a PlaceModuleBinding created by system.module(...) and does not return null. The previous if (!mod) return was dead code, so removing it does not introduce the reported null dereference. A missing or unavailable engine module is handled through the promise returned by execute(), not by mod being absent. I’m withdrawing this finding.

[innerHTML]="
help().content | markdown | safe
"
[innerHTML]="help().content | markdown"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Help videos disappear

Removing safe also removes video and iframe embeds from help in both this tab and the help modal. The mock help includes a <video> tag. Help authors who use embedded media will lose those instructions. Keep sanitization, but decide which safe media embeds help needs and test that they render.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Known and intended for now: the PR description lists this under "Needs a decision". Help content is admin-editable and the panel holds a long-lived token, so it now goes through Angular's sanitizer. If help pages need embedded <video>, we will add a narrow allowlist in a follow-up rather than bypass sanitizing.

@MrYuion
MrYuion merged commit 98e6337 into develop Oct 2, 2026
4 of 5 checks passed
@MrYuion
MrYuion deleted the fix/control-security-camera branch October 2, 2026 00:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant