Skip to content

Backport 5 fixes already live in the website's vendored copy#1

Open
balloob wants to merge 1 commit into
OpenDisplay:mainfrom
balloob:fix/backport-webtools-fixes
Open

Backport 5 fixes already live in the website's vendored copy#1
balloob wants to merge 1 commit into
OpenDisplay:mainfrom
balloob:fix/backport-webtools-fixes

Conversation

@balloob

@balloob balloob commented Jul 5, 2026

Copy link
Copy Markdown

The website (opendisplay.org) ships a vendored copy of nrf_web_tools.js that received five bug fixes which never flowed back upstream to this standalone repo. This PR backports those five fixes so both copies match. Each change below was already validated and running in the website copy; after applying them the two files are byte-identical.

Fixes

  1. XSS in the DFU error page. renderErrorPage built its HTML with innerHTML, interpolating data.message and data.details directly. Those strings can include a filename taken from a loaded DFU-zip manifest, so a crafted package could inject markup into the DOM. The message/details are now written via textContent into empty placeholder elements.

  2. HCI sequence-number reset on ACK timeout. waitForAck's timeout handler did this.hciSequenceNumber = 0 mid-transfer. sendPacket resends the same packet built with the old sequence number, and the adafruit-nrfutil reference does not reset on timeout, so this desynced the reliable-transport counter and a single lost ACK could wedge the rest of the transfer. The reset is removed.

  3. ACK-listener race in sendPacket. The packet was written before waitForAck armed this.resolveAck, so an ACK that arrived immediately after the write could be dropped by readLoop. The ack promise is now created before the write.

  4. disconnectPort() threw before closing the port. It called .releaseLock() on port.readable / port.writable (ReadableStream/WritableStream have no such method — only their reader/writer do), so it threw before port.close() ran, leaving the port open with a stale this.port and blocking the next reconnect. Rewritten with per-step try/catch and a finally that always nulls reader/writer/port.

  5. Cancelled-picker misdetection. The DFU error path checked e.message for 'NotFoundError', but a cancelled serial-port picker throws a DOMException whose name is NotFoundError (its message is "No port selected by the user."). The check now uses e.name, correctly routing cancellation to the reconnect path.

Verification

  • node --check nrf_web_tools.js passes.
  • After the changes, diff against the website's vendored copy is empty.
  • Small Node assertions confirm the cancelled-picker DOMException is detected via e.name (not e.message) and that textContent never parses injected markup.

Open question (not addressed here)

It's unclear what the live opendisplay.org/nrf_web_tools/*.js URL actually serves versus the vendored httpdocs/js/nrf_web_tools.js used as the reference here. Worth confirming which file is authoritative on the site so future fixes don't diverge again.

🤖 Generated with Claude Code

https://claude.ai/code/session_012g2e8mr132vcizx92WsgiR

These fixes were made in the opendisplay.org website's vendored copy of
nrf_web_tools.js but never flowed back upstream to this standalone repo.
Backporting them here so both copies match.

1. XSS: DFU error page built HTML via innerHTML interpolating
   data.message/data.details (a crafted DFU-zip manifest filename could
   inject markup). Now set via textContent.
2. HCI sequence-number reset on ACK timeout: waitForAck reset
   hciSequenceNumber = 0 mid-transfer, desyncing the reliable-transport
   counter so one lost ACK could wedge the rest of the transfer. Removed.
3. ACK-listener race: sendPacket wrote before arming waitForAck, so an
   ACK landing immediately after the write could be dropped. Arm the ack
   promise before writing.
4. disconnectPort() threw before closing: called releaseLock() on
   ReadableStream/WritableStream (which have no such method), so
   port.close() never ran and this.port went stale, blocking reconnect.
   Rewrote with per-step try/catch and a finally that always nulls fields.
5. Cancelled-picker misdetection: checked e.message for 'NotFoundError',
   but a cancelled port picker throws a DOMException carrying that in
   e.name. Check e.name instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012g2e8mr132vcizx92WsgiR
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