security: harden CI workflows, auth, setup surface, and deps#305
Merged
Conversation
Address findings from a security review of the repo. Critical — GitHub Actions script injection (RCE): - Delete redundant enhance_feature_requests.yml, whose unguarded check-label job interpolated github.event.issue.body into a run: shell on every opened issue (any user -> arbitrary code on the runner). The remaining feature-request-enhance.yml is a functional superset. - feature-request-enhance.yml: pass all untrusted issue content via env vars (never spliced into run:), use random heredoc delimiters for $GITHUB_OUTPUT, and add least-privilege per-job permissions. - confluence-changelog.yml: move release body/fields to env and write with printf instead of a quoted heredoc. - generate-implementation.mjs: validate ISSUE_NUMBER (^[0-9]+$) before it is embedded in execSync/patch file paths. High — dependencies: - Bump undici override to ^7.28.0 and add form-data ^4.0.6 (both High advisories). High — setup surface: - isLocalRequest: treat any forwarded request as non-local so a same-host reverse proxy no longer makes every remote client look local and bypass the setup bootstrap token. - saveConfig: allowlist known keys; drop adminPasswordHash, __proto__, and any other injected/unknown fields. Medium: - auth-handler getClientIp: only trust X-Forwarded-For / X-Real-IP when the new trustProxy config is enabled (default off), so a forged header can no longer bypass login rate limiting. Falls back to the socket peer address. - webauthn-handler: the unauthenticated /status endpoint no longer returns credential-file contents (only exists/size/credentialCount); a configured webauthnOrigin is no longer overwritten by request headers. - setup validate-sonos: reject loopback, link-local (incl. 169.254.169.254 metadata), and multicast/reserved target IPs. Config/tests: - Add trustProxy to config.json.example and the admin allowlist (boolean). - Update auth-handler tests that asserted the old (insecure) X-Forwarded-For default to assert the secure-by-default behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Hardening from a security review of the repo. No product features removed; behavior changes are security-by-default and called out below.
🔴 Critical — GitHub Actions script injection (RCE)
enhance_feature_requests.yml. Itscheck-labeljob ranISSUE_BODY="${{ github.event.issue.body }}"inside arun:shell with noifguard, so any user opening an issue could execute arbitrary code on the runner. The remainingfeature-request-enhance.ymlis a functional superset (addsworkflow_dispatch, concurrency, and the AI codegen job), so nothing is lost.feature-request-enhance.yml: all untrusted issue content is passed viaenv:and referenced as"$VAR"(never interpolated intorun:);$GITHUB_OUTPUTheredocs use random delimiters (an issue body lineEOFcan no longer inject step outputs); each job now has least-privilegepermissions:(onlygenerate-implementationkeepscontents/pull-requests: write).confluence-changelog.yml: release body/fields moved toenv:, body written withprintf.generate-implementation.mjs:ISSUE_NUMBERvalidated^[0-9]+$before it is embedded inexecSync/patch file paths.🟠 High
undicioverride →^7.28.0, addedform-data ^4.0.6(both resolve High advisories; verified installed). Remaining audit highs are the previously-acceptedip/sonosSSRF documented indocs/SECURITY.md.isLocalRequestnow treats any forwarded request as non-local, closing the "behind a same-host reverse proxy every client looks local → setup bootstrap token bypassed" hole.saveConfignow allowlists known keys and dropsadminPasswordHash,__proto__, and arbitrary injected fields.🟡 Medium
getClientIponly trustsX-Forwarded-For/X-Real-IPwhen the newtrustProxyconfig istrue(default off), so a forged header can no longer bypass rate limiting. Falls back to the socket peer address./api/auth/webauthn/statusendpoint no longer returns credential-file contents (onlyexists/size/credentialCount); a configuredwebauthnOriginis no longer overwritten by request headers.validate-sonosrejects loopback, link-local (incl.169.254.169.254metadata), and multicast/reserved target IPs.Config / tests
trustProxytoconfig.json.exampleand the admin config allowlist (boolean).auth-handlertests that asserted the old, insecureX-Forwarded-Fordefault to assert the new secure-by-default behavior.If you run SlackONOS behind a reverse proxy, set
"trustProxy": trueso rate limiting sees real client IPs (otherwise all requests share the proxy IP).Deliberately NOT changed (would break functionality)
featurerequestleft open to normal users — gating it to admin removes the feature's purpose (risk is only issue-spam, Low). Toggle inlib/command-registry.jsif desired.unsafe-inline/unsafe-eval— admin/setup UI relies on inline scripts; removing needs a UI refactor.NET_ADMIN— required for Sonos SSDP discovery.Testing
auth-handler,setup-bootstrap,http-utils,setup-wizard,command-registry,admin-api.node --checkclean on all changed JS/MJS; both edited workflows parse as valid YAML;package.jsonandconfig.json.exampleparse as valid JSON.Git hygiene (verified, no change needed)
No real secrets are or ever were committed —
config/config.json,keys/*.pem, and.envare gitignored and absent from history. The only.pem/.keyin history are dummy "snakeoil" test fixtures from a historicalnode_modulescommit.🤖 Generated with Claude Code