CodeQL alert 132 flagged the reboot status line as a sink that received
user-controlled DOM text (device names from the migration/sync select
options and table rows) without escaping. Six data-flow paths converged
on script.js:1950.
Switch the sink at line 1950 from .innerHTML to .textContent — the
status message has never needed HTML formatting. The pre-existing
display-into-innerHTML pattern still exists elsewhere in this file but
those lines aren't in this PR's scope and are tracked by their own
historical alerts.
Also harden the (newer) `currentP.innerHTML = ... <strong> + data.current
+ </strong> ...` line in loadAccountIDSuggestions: rebuild the paragraph
with replaceChildren + createElement so the account ID never becomes
HTML, even though it's expected to be a 7-digit string.
Coerce known account IDs to String() when populating the existing-account
dropdown so the IDE's type inference stops complaining about
opt.value = id; / opt.textContent = id; on data of unknown[] type.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous copy said "see the panel below" while the Pair Account panel
is intentionally hidden until migration succeeds (loadAccountIDSuggestions
makes it visible). Reword so users know the panel will appear after they
click Migrate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New section §8 records what is currently known about which devices and
firmware our migrateViaTelnet flow handles end-to-end, derived from the
six community sources catalogued in TELNET-COMMAND-REFERENCE.md plus our
issue threads.
* §8.1 — proven to work end-to-end (ST 10, 20, 300, Wave III, Wave IV on
FW 27.0.6 with multi-reporter agreement).
* §8.2 — proven to need the PairAccount telnet fallback (ST Portable,
BST20 Portable: /setMargeAccount missing or wedged on those firmware
builds).
* §8.3 — likely to fail (SA-5 on FW 9.x with the older shell generation;
newer ST Portable builds with shrunk command set). The preflight +
abort-on-first-rejection design ensures these fail cleanly, leaving no
half-configured state.
* §8.4 — unverified targets that are expected to work but lack concrete
captures (ST 30, ST 520, Wave Music System I/II).
* §8.5 — flags the apparent contradiction between S5's enumerated
"valid roots" on ST 10 / FW 27.0.6 (which omits envswitch) and #221's
successful envswitch use on the same firmware. Most plausible reading:
S5 is a non-exhaustive probe, not a negative claim; preflight catches
any real absence.
* §8.6 — maps every failure mode to its observable outcome and the unit
test that exercises it.
* §8.7 — TL;DR.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Synthesises every Bose SoundTouch port-17000 telnet command we have evidence
for, across six community sources: flarn2006's 2014 root-shell post,
Sam Hobbs's 2016 ST 10 setup-mode walkthrough, izndgroup's 2021 reissue,
sijeffrey's 2017 `bose` remote-control script, the 2026 r/bose telnet
probing thread (FW 27.0.6 ST 10), and our own #221 / #236 / soundcork#141
findings.
Groups the commands by family — `key` (front-panel button emulation, the
addition the Reddit thread brought in), `network` (WiFi profile management),
`sys` (verbs + the XML-tag-keyed `sys configuration` setter our migration
uses), `envswitch` (parallel persistence layer), `getpdo` (PDO read), `scm`,
`ws`, `swupdate`, and the historic shell-unlock commands. Each entry notes
firmware-era availability so implementations know whether to expect
"Command not found" on newer builds.
Records the four top-level command roots that S5 confirmed reachable on a
vanilla FW 27.x ST 10 (`key`, `net`, `sys`, `getpdo`), and flags that
`envswitch` works on other ST 20 / Wave models running the same firmware
family — a per-model variation the migration's preflight already handles.
Cross-linked from TELNET-MIGRATION-METHOD.md §2 and indexed in SUMMARY.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Migration dropdown gains a "Telnet (Port 17000) — no SSH required" option
and drops the deprecated /etc/hosts entry from the visible choices. The
hosts code path still exists in the backend for now; it is just no longer
reachable through the UI.
* New `telnet-method-pane` shows a brief explanation, the HTTP-only
limitation, and a hint that pairing may be required after migration.
* New `pair-account-pane` (initially hidden) renders three controls:
- dropdown of accounts already in the local datastore (so a fresh device
can be re-attached to an existing account),
- 7-digit input field with HTML pattern validation,
- a Generate button that picks a random non-colliding 7-digit ID.
When :8090/info already exposes a margeAccountUUID the panel pre-fills
it and offers to keep it; otherwise the device is treated as fresh.
* `pairAccount(deviceId)` POSTs to /setup/pair-account/{deviceId} with the
selected ID and surfaces the breadcrumb (HTTP vs telnet fallback) in the
status line.
* `reboot()` now passes ?method=telnet|ssh, derived from the migration
method dropdown (telnet for telnet, ssh otherwise) so a device that was
migrated without SSH access can also be rebooted without SSH access.
* After a successful telnet migration, `loadAccountIDSuggestions` runs
automatically so the user is led straight into the pairing step.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an SSH-free third migration path that drives the SoundTouch device's
diagnostic shell on TCP port 17000, plus a hardened replacement for the
fragile /setMargeAccount HTTP pairing call.
* `pkg/telnet` — new reusable, dependency-free client (sibling of `pkg/ssh`)
with deadline-driven Dial / Probe / SendCommand / Close. Mock-server tests
cover happy path, command-not-found, mid-stream close, and the wedged-device
read-timeout scenario.
* `setup.MigrationMethodTelnet` — runs `sys configuration` for all four URLs
plus the parallel `envswitch boseurls set` persistence layer that otherwise
wins on reboot, then verifies with `getpdo CurrentSystemConfiguration`.
Aborts on the first non-OK response so configuration is never half-written.
No SSH backup or rw pre-flight (the path is SSH-free by design).
* `setup.PairAccount` — probes :8090/supportedURLs first, time-bounds
POST /setMargeAccount aggressively (5s connect / 12s total) to avoid the
hangs reported in #236, and falls back to `envswitch accountid set <id>`
over telnet when the HTTP endpoint is missing or wedged. Returns a
PairAccountResult breadcrumb so the UI can show which path actually
succeeded.
* `setup.Reboot(deviceIP, method)` — gains a RebootMethod selector;
RebootMethodSSH stays the default (preserving prior behavior),
RebootMethodTelnet sends `sys reboot` over a fresh telnet session and
treats the inevitable socket-close as success.
* New endpoints on `/setup`:
- GET /account-id-suggestions/{deviceId} — returns the device's current
margeAccountUUID (from :8090/info) plus known account IDs from the
datastore, so the UI can offer reuse.
- POST /pair-account/{deviceId}?account_id=NNNNNNN — invokes PairAccount;
the existing reboot endpoint reads ?method=ssh|telnet from the query
string.
* Helpers `IsValidAccountID` (exactly 7 digits) and `GenerateAccountID`
(crypto/rand, retries on collision against a known-IDs list).
Documentation in docs/analysis/TELNET-MIGRATION-METHOD.md is updated to match
the implementation: bare-URL convention for `soundtouch-service`, no automatic
`sys reboot` (user-initiated via the existing button with a method selector),
and the realised package layout. The /etc/hosts method is intentionally not
exposed in the new flow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Documents the SSH-free third migration path on top of the device's diagnostic
shell, synthesised from #221, #236, scheilch/opencloudtouch#167,
deborahgu/soundcork#228, and deborahgu/soundcork#141.
Captures the URL configuration command sequence, the dual persistence layers
(`sys configuration` + `envswitch boseurls set`), the `/setMargeAccount`
failure modes (404, hang, post-migration 502 on power_on) with their bounded
fallbacks, port-17000 preflight requirements, and account-ID sourcing rules
(reuse from `:8090/info`, pick from `DataStore.ListAccounts`, or 7-digit
manual/randomized entry). Cross-links the new doc from
DEVICE-REDIRECT-METHODS.md, marks the `/etc/hosts` method as deprecated, and
fixes the existing margeServerUrl example to use our service's bare-URL
convention with an explicit note for soundcork's `/marge` sub-path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The on-device Sources.xml carries only displayName + sourceKey for AUX,
no id and no type. The previous read path synthesized id="2000001+i" and
type="AUX" (echoed from SourceKey.Type), which the speaker rejects as
INVALID_SOURCE once it pulls config from soundtouch-service after
migration. Look up known providers in getDefaultSources and fill
canonical id/type/sourceproviderid; also drop the AUX carve-out in
marge's ensureSourceType so existing poisoned type="AUX" entries are
normalized to type="Audio" at the served-XML layer.
Relates to https://github.com/gesellix/Bose-SoundTouch/issues/195
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Match release matrix: linux/amd64, linux/arm64, linux/armv7,
darwin/amd64, darwin/arm64, windows/amd64, freebsd/amd64; build cli,
service, web, backup
- Push Docker images on same-repo PRs with preview-pr-N /
preview-sha-<sha> tags so previews are unambiguous and tied to the PR
(forks build but skip push)
- Add a step summary listing each published image as docker pull
commands
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Why: Operators need to control device discovery from the command line
without touching the persisted settings file, and a zero discovery
interval should be unambiguously off rather than running an
immediate-fire scan loop.
- Add --discovery-enabled BoolFlag (default true, env DISCOVERY_ENABLED)
and thread it through serviceConfig, applyPersistedSettings, and
createDefaultSettings so CLI/env can seed initial state and persisted
settings still take precedence on subsequent runs.
- HandleUpdateSettings now forces discoveryEnabled=false whenever the
resulting discoveryInterval is zero.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The speaker rejects /select with source="AUX" and an empty sourceAccount
as INVALID_SOURCE, so the audio path never reaches APAuxSrc. Default the
sourceAccount to "AUX" inside SelectSource and align ItemName to "AUX
IN" to match the device's own button-press payload.
Relates to https://github.com/gesellix/Bose-SoundTouch/issues/195
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The OverrideSdkPrivateCfg.xml override path introduced in #209 does not
work on SoundTouch 10 (and likely other models): the firmware ignores
the override file, leaving the device pointing at the original Bose
cloud URLs. Revert to editing SoundTouchSdkPrivateCfg.xml directly with
a .original backup, which is the approach known to work.
Relates to #214
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
client.Run uses CombinedOutput, so when
`/mnt/nv/OverrideSdkPrivateCfg.xml` is absent (the default for devices
migrated with pre-0.71.0 code) the cat stderr is returned as the
override config and surfaced to the migration page UI as "Current Config
(on Speaker)". Gate the branch on `[ -f ... ]` first, mirroring the
legacy .original check.
Relates to #209
Relates to #214
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Store the user's email address (not Amazon account ID) in
sourceKey.account and set source type to "AMAZON" so the speaker
firmware recognises Amazon Music sources the same way as the original
Bose cloud.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a "Download CA Certificate" button in the Settings tab
(system-level convenience for importing the cert into browsers, curl,
Python clients, etc.) and a "Download CA cert" link next to the existing
"Trust CA Now" button in the Migration tab. Both link to the existing
/setup/ca.crt endpoint.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Use /mnt/nv/OverrideSdkPrivateCfg.xml (the firmware's override path)
rather than editing /opt/Bose/etc/SoundTouchSdkPrivateCfg.xml directly.
A malformed override cannot cause a reboot loop because the device falls
back to the untouched original.
Revert now removes the override file; legacy .original backups are still
restored for devices migrated with older code. checkCurrentConfig reads
the override path first so IsMigrated detection works correctly with the
new approach.
Credit: Ueberbose team, discovered via [soundcork
documentation](https://github.com/deborahgu/soundcork#configuring-the-bose-speaker-to-use-the-soundcork-server).
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a "Docker Compose (recommended for home servers and VMs)" section
to Step 1, pointing users to the existing docker-compose.yml and
.env.example. Clarifies the purpose of docker-compose.ci.yml (CI tests
only) and docker-compose.override.yml (local modifications, not in VCS).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
domains[0] was non-deterministic (Go map iteration) and could resolve to
any domain in the list including Bose-owned domains. Adds CommonName field
to CertificateManager, defaulting to "localhost", set to the device hostname
at startup. All Bose domains remain in the SAN where clients actually look.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On constrained hardware (e.g. ARMv7), RSA key generation can block
startup for minutes. HTTP now starts immediately; HTTPS is brought up
in a background goroutine once cert generation completes. A log message
informs the user that HTTPS will be available shortly after startup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
RSA-4096 CA generation blocks service startup for minutes on slow ARM
hardware. The CA key is only used to sign server certs, never in TLS
handshakes, so 2048 bits provides sufficient security for a local CA
while being ~4-8x faster to generate.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds build-linux-armv7 target (GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=0)
for deployment to old embedded Linux devices (kernel 3.14+). Introduces
BUILDFLAGS=-trimpath -ldflags="-s -w" applied to all build targets for
smaller, reproducible binaries without local path leakage.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- resolveIP now returns (string, error): error when result did not come
from the device's own SSH ping (service-side fallback or total
failure)
- migrateViaResolvConf and parseTargetURLAndResolveIP abort on error,
preventing a bad IP from being written to the device
- GetMigrationSummary captures the error in ResolveIPError and falls
back
to the hostname for the preview display; XML migration is unaffected
- Web UI shows a warning box with the error and a docs link when
resolution
is uncertain; migrate button stays enabled for the XML method
- Add hostname resolution troubleshooting section to TROUBLESHOOTING.md
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rename proxy option values: 'upstream' → 'proxied', 'official' →
'original'
- Add 'original' option to preserve current device URL as-is per field
- Drop proxyURL guard in applyProxyOptions so 'original' works without a
proxy
- Abort migration if on-device backup cannot be created (was
warning-only)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move spotify-mock and amazon-mock services to docker-compose.ci.yml
- Move soundtouch-test-net network definition to docker-compose.ci.yml
- Pin image version via SOUNDTOUCH_VERSION env var (defaults to
'latest')
- Document SOUNDTOUCH_VERSION in .env.example
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy 5 screenshots from _/screenshots/ into docs/images/ and wire them
into the migration guide (Settings, Devices, Sync, Migration tabs) and
the device initial setup guide (speaker AP mode Wi-Fi page). Replace the
images README wishlist with a table of what is actually present.
Also correct the AP mode IP address (192.0.2.1, verified on ST10) and
update the Settings step to match actual UI labels (Target Domain, DNS
Bind Address).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the placeholder MIGRATION-GUIDE.md (which had a "planned to be"
header, a nonexistent install.sh reference, and 9 broken screenshot links)
with a complete, image-free step-by-step walkthrough covering all 6 steps:
install, configure URL, enable SSH via USB stick, discover/sync, migrate
(XML or DNS/DHCP), and verify.
Add the Migration Guide to the README docs section and link to it from
the Survival Guide.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rewrite README.md to be concise and tool-focused (no code snippets),
clearly presenting all five tools and their use cases. Expand the
soundtouch-service section to cover both user scenarios and redirect
method trade-offs.
Rewrite SURVIVAL-GUIDE.md around the same two scenarios with step-by-step
instructions. Remove deprecated hosts-file method from all user-facing
docs; update MIGRATION-SAFETY.md, HTTPS-SETUP.md, and SOUNDTOUCH-SERVICE.md
to reflect only the two supported methods (XML redirect and DNS/DHCP).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Registers HandleAlexaCertificate on POST /alexa/certificate. The handler
logs the device MAC from the request body and returns 501 Not
Implemented with a JSON error explaining that AWS IoT integration is
required to provision Alexa device certificates.
Adds voice.api.bose.io to both /etc/hosts domain lists in setup.go (DNS
intercept was already covered by the bose.io wildcard entry in dns.go).
Relates to https://github.com/gesellix/Bose-SoundTouch/discussions/84
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Endpoints:
- POST /streaming/music/musicprovider/{id}/trial/is_eligible (reuses
is_eligible handler)
- POST /bmx/tunein/v1/favorite/{stationID} with datastore persistence
(SaveTuneInFavorite)
- DELETE /bmx/tunein/v1/favorite/{stationID} (DeleteTuneInFavorite)
- POST /bmx/core02/svc-bmx-adapter-orion/prod/orion/token (anonymous
Orion token)
- GET /bmx-icons/* serving embedded static/media assets (media.bose.io)
- GET /ced/* serving embedded firmware index, release notes, and 10
app-help XMLs (downloads.bose.com)
Add media.bose.io and downloads.bose.com to DNS redirect lists (setup.go
both domain slices, dns.go shouldIntercept list, main.go getDomains
map). Document implemented endpoints in
tests/interactions_20260502_missing_external.md; mark rows 0246–0247 as
self/☑ in the interactions table.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add CAPTURE-MIGRATION-TRAFFIC.md with step-by-step migration runbook
- Include session trace from first interactive ST10 migration run
- Genericize example IP addresses in BOSE-APP-ADB-Emulator.md and CAPTURE-DEVICE-PAIRING.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add scripts/android/ with setup-mitm-avd.sh (one-time) and start-mitm-session.sh (per-session)
- Move frida Dockerfile to scripts/android/; extract frida-server + SSL scripts via Docker
- Use native macOS mitmproxy app for capture (Docker NAT blocks emulator traffic)
- Add native-connect-hook.js to Frida launch — required for Bose app's native networking
- Document verified AP mode Wi-Fi provisioning endpoint (POST :8090/addWirelessProfile)
- Correct factory reset sequences for ST10/ST20 from official Bose guides
- Remove old scripts/setup-mitm-avd.sh and scripts/start-mitm-session.sh (moved to android/)
- Add session trace with lessons learned from first interactive capture run
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces a standalone `soundtouch-backup` CLI with three subcommands:
- `all`: authenticates with the Bose cloud, backs up account data, then
reads device IPs from devices.xml and backs up each reachable speaker
- `cloud`: fetches account profile, devices, sources, presets, and full
endpoint from streaming.bose.com
- `local`: backs up each speaker via HTTP API (12 endpoints) and
optionally via SSH (individual files + /opt/Bose/etc/ and
/mnt/nv/BoseApp-Persistence/1/ directories)
Also centralises pkg/service/ssh → pkg/ssh so both the service and the
backup tool share the same SSH client; adds ReadFile and ReadDir
methods, and handles the firmware quirk where cat exits 1 on empty
files.
Output is a single dated .tar.gz or .zip archive.
Example flow:
```shell
gesellix@Mac Bose-SoundTouch % go run ./cmd/soundtouch-backup all --output _/cloud-backup --email user@example.com
Password:
Authenticating as user@example.com...
✓ Authenticated (account ID: 1234567)
✓ email address (107 bytes)
✓ devices (1492 bytes)
✓ sources (1111 bytes)
✓ presets (2585 bytes)
✓ full account (55037 bytes)
Found 2 device(s) in cloud account, attempting local backup...
✓ ST20: 12 files via HTTP
⚠ ST20: SSH skipped /etc/remote_services (Process exited with status 1)
⚠ ST20: SSH empty file /mnt/nv/remote_services
✓ ST20: 64 files via SSH
✓ ST10: 12 files via HTTP
⚠ ST10: SSH empty file /etc/remote_services
⚠ ST10: SSH skipped /mnt/nv/remote_services (Process exited with status 1)
✓ ST10: 48 files via SSH
Archive written: _/cloud-backup/soundtouch-backup-2026-05-02.tar.gz (141 files)
```
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Attempting to request amazon_music:access with a standard application
client ID (amzn1.application-oa2-client.*) returns HTTP 400
lwa-invalid-parameter-bad-scope from the LWA authorization endpoint.
The scope is gated to Amazon Music partner device client IDs.
Revert scope to "profile" (working state) and document the confirmed
blocker with the exact error. Path forward: Amazon Music partner
registration for a device client ID; one-line change to AmazonScopes
when available.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>