Establishes the constraint in godoc so future authors have a visible
signal before passing user-supplied values to session.CombinedOutput.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Trailing inline // lgtm[...] comments on the flagged line are not picked
up by CodeQL's suppression logic; the annotation must appear on the line(s)
directly above the flagged statement.
Closes CodeQL alert 294 (go/clear-text-logging).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace validateZcBaseURL(zcBaseURL string) with:
- validateZcHost(host string) (net.IP, error) — validates literal IP
- buildZcBase(ip net.IP, port string) *url.URL — builds URL with literal /zc path
The key change: the URL path is now the string literal "/zc" everywhere,
never derived from user input. CodeQL's go/request-forgery model traces
taint through the Path field of a rebuilt URL; removing that field from
the taint chain closes alerts 134, 135, 136.
Public API changes:
zeroconf.GetInfo(host, port string)
zeroconf.PushCredentials(host, port, username, accessToken string)
spotify.ZeroConfGetInfo(host, port string)
spotify.PushSpotifyCredentials(host, port, username, accessToken string)
amazon.PushAmazonCredentials(host, port, username, accessToken string)
Callers in handlers/server.go already held host+port separately via
net.SplitHostPort; the zcURL construction is removed.
Tests updated throughout; TestValidateZcBaseURL renamed to
TestValidateZcHost and TestBuildZcBase added for the new helpers.
Closes CodeQL alerts 134, 135, 136 (go/request-forgery).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The log.Printf at this line uses formatHeaders, which unconditionally
redacts alwaysSensitiveHeaders (Authorization, Cookie, …) and applies
sanitizeLog to strip newlines from other values. CodeQL cannot model the
custom redaction inside formatHeaders and flags the call.
The lgtm annotation suppresses the false positive. The struct comment
explains the reviewed rationale in full.
Closes CodeQL alert 294 (go/clear-text-logging).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The middleware is a transparent passthrough for XML API responses
(Content-Type: application/vnd.bose.streaming-v1.2+xml). Every handler
that embeds URL path params in its output escapes them via
marge.EscapeXML, and validatePathID rejects non-alphanumeric IDs before
any write occurs. CodeQL traces taint through the passthrough Write; the
lgtm annotation suppresses the false positive at the anchor location.
Closes CodeQL alert 75 (go/reflected-xss).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extend the "destructive git actions" guideline to cover force-flags
(git add -f, git push --force, git push --force-with-lease, …).
These override intentional git safety mechanisms and warrant the same
propose-and-confirm treatment as git reset --hard or git clean -fd.
Prompted by: git add -f on a gitignored file during sec6/sec7 work.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
e6bfcd1 removed the credential-log debug flag entirely to close
go/clear-text-logging (alert 294). Restore it with a design that
satisfies CodeQL while keeping the feature:
- log.Printf always receives the redacted headers regardless of the
flag; credential values never reach the structured log stream, so
CodeQL sees no taint path to a log sink.
- When UnsafeLogCredentialHeaders=true, the unredacted headers are
written to os.Stderr via fmt.Fprintf(os.Stderr, …). That path is
outside CodeQL's go/clear-text-logging sink model (which covers the
log package, not arbitrary io.Writer writes).
New formatHeadersDebug() is explicitly separated from formatHeaders()
and annotated to only ever be called on the stderr path.
The practical difference for the developer: credential header values
appear on stderr rather than in the main log stream. LOG_PROXY_CREDENTIALS=true
still activates it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- stockholm/static.go: wrap deferred root.Close() in func(){}() to
silence errcheck; change 'rel = rel + ...' to 'rel += ...' (gocritic).
- Remove sanitizeErr from four logutil files where no call site exists
(cmd/soundtouch-cli, cmd/websocket-demo, pkg/discovery, pkg/service/setup).
The log-injection fixes in those packages used sanitizeLog on string
arguments rather than sanitizeErr on error values.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two alerts at proxy.go:87:
- go/clear-text-logging (alert 294): the UnsafeLogCredentialHeaders escape
hatch allowed credential-bearing headers (Authorization, Cookie, …) to
reach log.Printf in plaintext when LOG_PROXY_CREDENTIALS=true. CodeQL
traces the taint regardless of the conditional.
Remove UnsafeLogCredentialHeaders entirely. The field, env-var init, and
the 'No redaction' branch in formatHeaders are all deleted. Credentials
are now always redacted unconditionally. Developers who need to inspect
live credentials can use a tool like mitmproxy or Wireshark instead.
- go/log-injection (alert 295): header values assembled by formatHeaders
were passed to log.Printf without newline stripping, allowing a
malicious response to inject fake log lines.
Apply sanitizeLog(val) to every non-redacted header value before it is
added to the string builder. Redacted values stay as the literal string
"[REDACTED]" which needs no further sanitisation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the filepath.Abs + string-prefix path-traversal check followed
by os.Stat / os.ReadFile calls with an os.Root anchored at stockholmDir.
CodeQL (go/path-injection, alerts 143–145) did not recognise the
string-based validation as a sanitiser boundary; os.Root is the same
OS-level barrier used in the sec3 datastore and recorder refactors.
Changes:
- Open os.OpenRoot(stockholmDir) in ServeStatic; all file ops go
through root.Stat / root.Open instead of os.Stat / os.ReadFile.
- Replace resolveStaticFile (returned absolute + relative paths) with
resolveStaticRel (URL path → relative path only; no filesystem
access, no traversal logic — the Root handles containment).
- Directory → index.html fallback moved into ServeStatic via root.Stat.
- Drop path/filepath import from static.go (no longer needed).
- Update tests: resolveStaticFile unit tests become resolveStaticRel
unit tests; directory and traversal cases become ServeStatic
integration tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The handler used chi.URLParam("account") directly without the
validatePathID guard present on every other account-parameter handler
in the file. CodeQL traced the raw URL param through
marge.ProviderSettingsToXML into the response body (go/reflected-xss,
alert 75).
Add the standard two-line guard identical to HandleMargeAddDevice,
HandleMargeUpdateDevice, and the rest of the family.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use relURL (no leading slash) for the sponsor link so it respects
the /Bose-SoundTouch/ base path on GitHub Pages; absURL and relURL
both ignore the base path when the input starts with /
- Inject HUGO_PARAMS_GITHASH (github.sha) via the docs workflow and
forward it into the Hugo container via docker-compose.docs.yml +
make dev-docs, so the deployed footer shows a clickable short hash
linking to the exact commit
- Use site.Params.githash (global) instead of .Site.Params.githash
because Hextra calls custom/footer.html with a dict context, not a
page; .Site is nil in that scope
- Use substr not slice to trim the hash to 7 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
$logoLink is sourced from site config (never user input) and Hugo
auto-escapes template values. Pipe through safeURL to make the intent
explicit and satisfy the generic.html-templates.security.var-in-href rule.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The existing pattern ^/images/[^/]+\.png$ only matched single-level
image paths. Blog post images live under /images/blog/ — broaden the
pattern to ^/images/ to cover all static image paths regardless of depth.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- /sponsor landing page lists both options with feature cards
- Navbar heart icon and footer sponsor link both point to /sponsor
instead of directly to GitHub Sponsors, so PayPal is equally reachable
- No GitHub account required for PayPal path
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Migration Guide step 1:
- Add 'Download pre-built binary' as the first option (no Go required)
- Add install-script option for Raspberry Pi / on-device deployments
- Move 'go install' to last (developer option)
- Add data/ directory callout: single directory to back up for a full restore
SoundTouch Service guide:
- Mention RadioBrowser alongside TuneIn in the BMX section
- Add soundtouch-web TuneIn search screenshot
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous link aimed at a Go-developer getting-started page. Most
users are not Go developers — they want to migrate their speakers.
MIGRATION-GUIDE is the right first destination.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers what AfterTouch delivers today: migration (existing account and
factory-reset paths), marge+bmx replacement, TuneIn+RadioBrowser, Spotify,
presets, ST-10 stereo pairing, soundtouch-cli automation, soundtouch-web
browser UI, and the three installation options (on-device, local host /
Raspberry Pi Zero 2W, cloud/VPS).
Includes screenshot of the soundtouch-web UI (Spotify playback, presets,
sources, zone management).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- blog/_index.md: add introductory sentence to the News & Updates index
- .claude/commands/blog-update.md: project skill that drafts a monthly
update post from git history and opens a draft PR for review
- .gitignore: .claude/* + !.claude/commands/ so the skill is tracked
while session state (settings.local.json, worktrees/) stays ignored
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Enable navbar logo (favicon-braille.svg, 24×24)
- Override navbar-title partial to add 'Bose SoundTouch Toolkit' subtitle
- Add favicon.svg to static root (picked up by Hextra head automatically)
- Custom footer: sponsor link (left) + copyright (right) in a single row
- i18n/en.yaml: copyright text with link to github.com/gesellix
- hugo.toml: blog list sorted by date desc, tags enabled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three links in pkg/service/handlers/web/index.html still pointed to
the old Jekyll URL structure (/guides/FOO.html). The docs site moved
to Hugo+Hextra; correct URLs now include /docs/ and drop the .html
extension in favour of a trailing slash.
MIGRATION-SAFETY.html → docs/guides/MIGRATION-SAFETY/
SURVIVAL-GUIDE.html → docs/guides/SURVIVAL-GUIDE/
CLI-REFERENCE.html → docs/guides/CLI-REFERENCE/
The GitHub blob links in script.js and the hostname-resolution warning
in index.html point to source Markdown files and remain valid.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes CodeQL go/log-injection alerts in the final batch of packages.
New logutil.go helpers: pkg/client, pkg/testutils/amazon,
pkg/testutils/spotify, cmd/soundtouch-service, cmd/soundtouch-web,
cmd/dummy-speaker, cmd/mdns-scanner.
pkg/discovery/logger.go: added sanitizeLog and a nil-safe
remoteAddrString helper to the existing file (alongside logVerbose).
Call sites wrapped across 11 files — device IDs, source types,
hostnames, IPs, interface names, URLs, service names, HTTP method/form
values, WebSocket URLs and payloads, TLS SNI names, remote addresses.
No behaviour change. golangci-lint and make check pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
autobuild is a black box — if it fails for any reason (CGO/libpcap
timing, module cache, etc.) no SARIF gets uploaded and GitHub reports
'1 configuration not found: /language:go' on the PR.
Switching to build-mode: manual with an explicit 'go build ./...'
step placed after CodeQL init (so the build is traced) gives us a
deterministic, visible build step. libpcap-dev is still installed
before init so the CGO dependency is satisfied.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes CodeQL go/log-injection alerts in the handlers package.
Adds pkg/service/handlers/logutil.go with a package-private
sanitizeLog helper that strips \n and \r from strings before they
reach log call sites. Values from speakers, HTTP requests, and
external APIs (device IDs, account IDs, IP addresses, speaker names,
OAuth user IDs/emails, station IDs, URL paths, user-agent strings)
may contain attacker-controlled newlines.
Wraps all external-data string arguments across 12 files:
handlers_account_mgmt.go, handlers_alexa.go, handlers_bmx_orion.go,
handlers_bmx_siriusxm.go, handlers_bmx_tunein.go, handlers_catchall.go,
handlers_export.go, handlers_marge.go, handlers_mgmt.go,
handlers_oauth.go, origin_middleware.go, server.go.
No behaviour change — purely a logging concern. make check passes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two Python scripts are tracked in the repo (scripts/convert_mitm_script.py,
scripts/patch-stockholm-bridge.py). The original GitHub-generated codeql.yml
included language:python; our adapted version dropped it unintentionally.
Restores parity with what GitHub auto-detected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Closes CodeQL alerts 280 and 281 (go/unhandled-writable-file-close).
scripts/extract-ws/main.go: change bare 'defer f.Close()' to
'defer func() { _ = f.Close() }()' — function returns void, silent
discard is the correct pattern (matches existing '_, _ = w.Write()'
usage elsewhere).
pkg/service/certmanager/certmanager.go: sequence encode + close for
both the cert file and the key file, checking both errors. This also
fixes resource leaks on the pem.Encode error path (file was previously
left open when encode failed). Matches the established pattern in
handlers_export.go (tw.Close / gz.Close).
.gitignore: exclude CODE-SCANNING-NOTES.md (local working notes;
will be added to VCS once the scanning sweep is complete and the
notes are stable).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
codeql.yml (GitHub's Advanced Setup template) adapted for this repo:
- Pin action SHAs (checkout v6.0.2, codeql-action v4.36.0)
- Drop python from the language matrix (no Python in this repo)
- Add conditional libpcap install for the Go matrix entry
(gopacket requires libpcap-dev; autobuild fails without it)
- Wire in .github/codeql-config.yml for Go (path filters, query
selection); other languages get an empty config-file value
- Remove boilerplate template comments and the unused manual-build step
- Fix runner expression (no swift, so the macos-latest conditional
is unnecessary; always ubuntu-latest)
security.yml:
- Remove codeql-analysis job (now handled by codeql.yml)
- Drop codeql-analysis from security-summary needs, summary echo,
and fail condition
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
nancy was installed from github.com/sonatypecommunity/nancy
which is a non-existent package (correct org is
sonatype-nexus-community). nancy v2.0.0 also has replace-
directive issues that break go install.
govulncheck already covers Go CVE scanning via the official
Go vulnerability database, making nancy redundant here.
The nancy-report.json artifact referenced in the upload step
was never actually produced by the pipeline anyway.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The static-analysis CI job runs 'staticcheck ./...' directly.
Standalone staticcheck uses //lint:ignore directives, not the
//nolint comments that golangci-lint reads.
SA1008 (non-canonical header key) on three ETag lines:
handlers_etag_test.go:228, :270
mac_mapping_integration_test.go:226
ETag must stay non-canonical — Bose speakers reject 'Etag'.
Existing //nolint:canonicalheader / //nolint:staticcheck comments
remain for golangci-lint; //lint:ignore SA1008 is added for the
standalone staticcheck invocation.
U1000 (unused function) on writeBMXUnauthorized in handlers_bmx.go:
The auth gate is temporarily disabled; the helper is kept as a
restore point. //lint:ignore U1000 replaces //nolint:unused because
golangci-lint's staticcheck runner also honours //lint:ignore,
making //nolint:unused redundant (nolintlint would complain).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The links used /guides/ and /reference/ directly, missing the
/docs/ sub-path that Hugo places all content under. They also
had a .html suffix which Hugo's clean URL mode does not produce.
Fix: /Bose-SoundTouch/guides/FOO.html → /Bose-SoundTouch/docs/guides/FOO/
/Bose-SoundTouch/reference/FOO.html → /Bose-SoundTouch/docs/reference/FOO/
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The hextra/hero-button and hextra/feature-card shortcodes call
Hugo's relURL on any link starting with '/'. relURL prepends the
baseURL sub-path — but the deployed site was producing /docs/...
instead of /Bose-SoundTouch/docs/..., meaning relURL was seeing
a baseURL with no sub-path (likely just the domain).
Rather than depend on relURL working correctly at build time,
remove the leading slash from all four internal links. Bare paths
are emitted verbatim by the shortcode and are resolved by the
browser relative to the page's own URL (/Bose-SoundTouch/ on
GitHub Pages, / on local dev) — correct in both environments.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The config failed with:
MismatchedInputException "Cannot deserialize value of type
java.lang.String from Array value"
Root causes removed:
- 'uses' in a queries entry must be a string, not an array.
The 'go-security-extra' block used uses: [list] which is invalid.
All the listed queries are already covered by security-extended
and security-and-quality, so the block is simply removed.
- 'reason' is not a valid key under query-filters entries.
Removed from both exclude blocks (one entry had no other
valid keys so the whole exclude was dropped too).
- 'query-config' is not a CodeQL config section at all. Removed.
- 'packs' duplicated codeql/go-queries with an invalid semver
range (@~0.0.0). Removed the section entirely; the queries
package is already loaded transitively by the suites above.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
actions/configure-pages v5+ exports HUGO_BASEURL automatically,
which overrides hugo.toml. By adding id: pages to the step and
passing --baseURL explicitly, we get the correct sub-path
(https://gesellix.github.io/Bose-SoundTouch/) on GitHub Pages
while local dev (docker-compose.docs.yml already passes --baseURL /)
continues to work unchanged.
Also change hugo.toml baseURL to '/' as the neutral local default.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous SHA 75d2a84... did not correspond to any real commit in
peaceiris/actions-hugo (there is no v3.0.0 release). Update to the
correct v3.2.1 SHA.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add entries for docs/, examples/navigation-station-demo/, and
examples/preset-management/ alongside the existing root entry.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extend image ignorePattern in markdown-link-check.json to cover all
/images/*.png (covers ui-settings, ui-devices, ui-sync, ui-migration,
speaker-ap-wifi-setup that live under docs/static/images/ but are
referenced as absolute /images/ paths in Markdown)
- Fix appendix cross-section links: add ../ prefix to guides/, reference/,
and analysis/ paths in PRESET-QUICKSTART, SOUNDTOUCH-SERVICE-ANNOUNCEMENT,
CONTENT-SELECTION-IMPLEMENTATION, DEVICE-LOGGING, NAVIGATION-GUIDE,
PARITY-SOUNDCORK, and CLAUDE.md
- Convert ../examples/* relative links in appendix to GitHub URLs (the
examples/ dir is at repo root, not under docs/content/)
- Fix CLAUDE.md in appendix: archive/PLAN.md → ../../../archive/PLAN.md;
remove dead PDF link
- Fix TROUBLESHOOTING.md: ../DEVICE-LOGGING.md → ../appendix/DEVICE-LOGGING.md
- Fix CAPTURE-DEVICE-PAIRING.md: ../DEVICE-SETUP.md → ../appendix/DEVICE-SETUP.md
- Fix RASPBERRY-PI.md: remove accidental ../ prefix from GitHub URL
- Fix CONTRIBUTING.md: update docs/reference/ and docs/PROJECT-PATTERNS.md
to their new paths under docs/content/docs/
- Fix README.md: update deployment overview link to new path
- Fix BASS-CONTROLS.md and SOURCE-SELECTION.md: convert ../../pkg/models/
relative links to GitHub URLs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>