fix(web): prevent USB Serial teardown reset (#204) - #206
Conversation
Implementation candidate evidence — Issue #204Exact candidate HEAD: Web Serial mitigation
Preserved security/lifecycle boundaries
Regression evidence
Exact-head CI
Collision / staleness
Remaining gatePhysical verification is still mandatory because CI cannot reproduce the Windows Chrome -> ESP32-S3 USB Serial/JTAG control-line transition. PR remains Draft and is not READY_FOR_REVIEW until the exact candidate Web head passes the #204 Human Gate. |
Human Gate ownership updateThe human owner has directed that the #204 physical Human Gate be executed under the Integration role rather than directly from the Implementation chat. Current exact candidate remains:
The previously recorded physical checks and exact-head setup remain authoritative. Integration should perform/coordinate those checks on this exact head and persist the Human Gate result durably. No additional implementation change has been made after the candidate CI completed. PR #206 remains Draft while the Human Gate is unresolved. |
Integration Human Gate reconstructionExact candidate HEAD:
Remaining gate: exact-head physical validation of Windows Chrome -> ESP32-S3 USB Serial/JTAG control-line teardown behavior. HANDOFF |
Human Gate completedExact validated HEAD:
Human Gate no longer blocks Review. |
miso-develop
left a comment
There was a problem hiding this comment.
Review disposition: READY_FOR_INTEGRATION
Reviewed exact PR head 0c0f476fd40cee0e537e36636775d3680cf4eedd against Issue #204, Decision #195, Requirement #137/51, Decision #49, the durable Device→Web root-cause handoff, and the completed exact-head Human Gate.
No blocking finding identified.
Root-cause / platform boundary:
- The original physical reproduction used exact then-current Web/Firmware build
b3aa8fc0cc06143990dcf32a971853264f73eba8, Chrome 153.0.8010.48 Stable 64-bit, and Automatic LOCK disabled. - Reload and tab close visibly restarted the Device; reconnect then correctly reported LOCKED and required fresh Unlock.
- Explicit Lock & Disconnect remained LOCKED, while physical USB removal with battery preserving the same runtime remained UNLOCKED.
- This distinguishes the defect from stale UI, stale build, automatic-lock expiry, or ordinary transport-disconnect locking: the unwanted LOCKED observation was caused by an actual browser/page-teardown-correlated Device reset.
- Device-side tracing remains non-locking for transport teardown. The exact firmware used in the candidate Human Gate (
46802e2baa9e03cd1331244885b9e9c76f005a85) and current PR base have the same relevant boundary: USB disconnect calls protocol/session teardown, whileCanonicalProtocolV2Handler::disconnect()cancels session/pending state without callingruntime_.lock(). - Independent external check agrees with the durable diagnosis:
- Espressif ESP32-S3 TRM documents USB Serial/JTAG CDC control-line reset behavior including the RTS=1/DTR=0 reset state.
- Web Serial's
setSignals()algorithm applies DTR before RTS when both members are supplied in one call; the specification explicitly notes this ordering may be relied upon by applications. - Therefore a combined clear of both signals is unsafe for this hardware boundary, while a separate RTS-first sequence avoids traversing the reset state by construction.
- References: https://documentation.espressif.com/esp32-s3_technical_reference_manual_en.pdf and https://wicg.github.io/serial/#serialport-methods
Implementation verification:
SerialPortLikeadds only optional Web SerialsetSignals(); browsers lacking it retain the pre-existing transport path.- Every successful serial connection invokes control-line normalization before the initial Protocol-v2 hello.
- Normalization performs two distinct awaited operations:
setSignals({ requestToSend: false });- only after that succeeds,
setSignals({ dataTerminalReady: false }).
- No combined RTS/DTR update is used.
- The same safe sequence is re-established immediately before stream/port teardown.
- If RTS deassertion fails, the function returns without touching DTR.
- If DTR deassertion fails after RTS is known deasserted, teardown continues; this does not create the known RTS=1/DTR=0 transition.
- Existing reader/writer cancellation/release and deterministic
port.close()behavior are otherwise unchanged. - Connection-time normalization means correctness does not depend solely on asynchronous
pagehidecleanup finishing before browser teardown.
Security/lifecycle boundaries preserved:
cleanupBrowserLifecycle()still performs transient-state cleanup then transport-only teardown.CanonicalDeviceManagement.disconnectTransport()sends nodevice.lock.- Explicit
CanonicalDeviceManagement.close()still sends exactly onedevice.lockbefore transport close. - Automatic Lock behavior is unchanged.
- Actual reboot/power loss still destroys the RAM-only VMK and starts LOCKED.
- Protocol, Vault, TOTP, Trusted Browser authorization, credential handling, firmware, eFuse, and Device USB configuration are unchanged.
- No relaxation of a Device security boundary was introduced; this fix prevents an unintended hardware reset so the already-approved same-runtime lifecycle semantics from #195 can actually hold.
Regression coverage:
- New serial tests prove safe RTS→DTR ordering immediately after open and again before close.
- Regression explicitly proves DTR is not changed when RTS deassertion fails.
- Regression proves DTR failure after safe RTS does not prevent deterministic close.
- Existing lifecycle tests independently prove:
- browser lifecycle cleanup uses transport-only disconnect;
- no
device.lockis emitted on page lifecycle teardown; - explicit close still emits one
device.lock.
Exact-head Human Gate:
- Exact Web candidate:
0c0f476fd40cee0e537e36636775d3680cf4eedd. - Chrome: 153.0.8010.48 Stable 64-bit.
- Reload: PASS — no Device restart; reconnect succeeds; state remains UNLOCKED; no fresh physical Unlock.
- Tab close: PASS — same.
- Navigation away: PASS — no restart; reconnect succeeds; state remains UNLOCKED.
- Explicit Lock & Disconnect positive control: PASS.
- USB removal while battery preserves runtime: PASS.
- No stale Web Serial ownership/deadlock was observed.
- The localhost active-writer conflict is expected origin isolation; no Recovery/reconciliation or Trusted Browser replacement was used to bypass it.
Exact-head CI:
- Foundation #931: SUCCESS.
- Web unit suite: 38 files / 304 tests passed.
serial.test.ts: 15 tests passed.browser-lifecycle.test.ts: 4 tests passed.- Linux Chrome production smoke, normal Web build/lockfile verification, Windows Chrome smoke, firmware regressions, isolated ESP-IDF build/package validation, and dependency-lock verification succeeded.
- Security #934: SUCCESS, including existing lifecycle/security/release contracts and repository scan.
Staleness/collision:
- Handoff HEAD matches current PR HEAD.
- PR is open, mergeable, and exact base/current main is
40f7061eb89dcc676599d7f74f1b45037e589ea2. - Compare is 4 commits ahead / 0 behind.
- Changed files are exactly
web/src/serial.tsandweb/src/serial.test.ts. - Open PR #209 is README-only and PR #210 is Web presentation/style-only; neither overlaps Web Serial transport ownership.
Repository state:
- PR remains Draft. This is not a Review blocker under the current Integration contract: Integration's typical input is a PR marked ready after Review.
- Integration must transition PR #206 out of Draft before merge and re-check HEAD/checks/collision at that point.
No additional Security-role review is required: the governing lifecycle/security semantics were already resolved, the patch does not weaken Lock/VMK boundaries, Security CI is green, and the mandatory physical gate has passed on this exact Web head.
READY_TO_MERGEHEAD: Integration reconstruction:
STATE: READY_TO_MERGE |
INTEGRATEDPR: #206 Integration evidence:
STATE: INTEGRATED |
Addresses #204
Root cause boundary
Physical evidence on the previous exact Web/Firmware baseline showed:
Device-domain tracing found no transport-disconnect -> runtime-lock path. The causal surface is the ESP32-S3 USB Serial/JTAG CDC control-line state used by Web Serial teardown.
ESP32-S3 USB Serial/JTAG treats RTS=1,DTR=0 as reset. The mitigation must therefore never clear DTR while RTS may still be asserted.
Candidate Web fix
SerialPortLikenow exposes optional Web SerialsetSignals().Each successful Web Serial connection establishes the safe host-control state using separate ordered operations:
setSignals({ requestToSend: false })setSignals({ dataTerminalReady: false })SerialSession.close()re-establishes the same order immediately before reader/writer release andport.close().Safety behavior:
setSignals()retain the existing close path rather than failing transport cleanup.Keeping the safe state from connection time also avoids relying solely on asynchronous
pagehidework completing before the browser tears the page down.Preserved boundaries
Unchanged:
device.lockon Lock & Disconnect;Regression coverage
serial.test.tsnow verifies:port.close();Existing #196 lifecycle tests continue to prove:
device.lock;device.lockbefore transport close.Base / collision
40f7061eb89dcc676599d7f74f1b45037e589ea2Required Human Gate
This PR remains Draft until the exact candidate head is physically verified with the same Chrome/M5StickS3 scenario.
Required exact-head checks:
If
port.close()still causes a reset despite the safe control-line state, the next #204-approved experiment is page-lifecycle-specific omission of explicitport.close(), not a Device security relaxation.CI evidence pending.