Skip to content

Claude/system audit complete 9czpyd - #1887

Merged
glloq merged 7 commits into
mainfrom
claude/system-audit-complete-9czpyd
Aug 3, 2026
Merged

Claude/system audit complete 9czpyd#1887
glloq merged 7 commits into
mainfrom
claude/system-audit-complete-9czpyd

Conversation

@glloq

@glloq glloq commented Aug 3, 2026

Copy link
Copy Markdown
Owner

No description provided.

claude added 7 commits August 3, 2026 01:45
…een)

The `Security Audit` CI job (`npm audit --omit=dev --audit-level=critical`)
has been red since May: the prod dependency tree pulls a vulnerable `tar`
(node-ble → dbus-next → usocket → node-gyp → tar) flagged by GHSA-vmf3-w455-68vh
(and related) for versions <= 7.5.20.

Pin `tar` to >= 7.5.21 via `overrides` (resolves to 7.5.22). This clears the
only CRITICAL prod advisory, so the audit job passes again. No runtime code
depends on tar directly — it is reached only through node-gyp's build path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjWnM91xMywEtcw7Eu3YUE
…absent

CLAUDE.md documents that `jest.config.cjs` "automatically skips
SQLite-dependent suites" so `npm test` still runs the pure-JS suites after
`npm install --ignore-scripts` (the documented container/CI setup). In
practice the config only skipped a single hard-coded file
(`midi-filter.test.js`), so six other suites that construct a real
better-sqlite3 database failed with a bindings error — `npm test` was red in
the documented dev workflow (27 test failures).

Detect the SQLite-backed suites from their source (they import
`better-sqlite3`, construct the top-level Database/DatabaseManager, or run the
migration runner) and add them to `testPathIgnorePatterns` only when the
native binding probe fails. This is self-maintaining: new SQLite suites are
picked up automatically instead of silently failing once a hand-maintained
list drifts. With bindings present all 96 suites still run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjWnM91xMywEtcw7Eu3YUE
Fixes verified bugs found while auditing every subsystem. Each fix is minimal
and covered by existing or new regression tests. No behavioural features added.

Persistence
- Database façade was missing three instrument methods
  (findInstrumentByNormalizedName / reconcileDeviceId / deduplicateByUsbSerial)
  that InstrumentRepository delegates to, so device reconciliation threw and
  was silently swallowed — saved settings appeared lost after a USB re-enumerate.
  Add the delegations; guard the whole façade surface with a prototype test.
- runMigrations sorted files lexicographically; sort by numeric version prefix
  so a future non-zero-padded/4-digit migration can't run out of order.

Playback
- Seek state reconstruction called this.deviceManager (undefined) — threw on
  every seek past a SysEx, skipping bank/program/CC/pitch-bend restore. Use
  this._deps.deviceManager.
- Seeking while paused stopped the MIDI clock permanently (resumePlayback became
  a no-op); only stop the clock on active-play seeks.
- pause() now resets the scheduler's per-note tracking after All Notes Off so a
  polyphony-limited instrument doesn't drop notes after resume.
- All Notes Off now resolves omni-fallback channels to the omni device.
- Live transposition changes now release sounding notes to avoid stuck notes.

Routing / devices / transports
- MidiRouter relative-compensation cache was keyed `source|channel` but
  invalidated by bare `source`, so route add/remove/enable never cleared it.
- RTP-MIDI parser decoded delta-time as a proper VLQ (respecting the Z bit)
  instead of skipping every high-bit-clear byte, which had swallowed
  running-status commands from real peers.
- File routing status counted split-segment rows as routed channels, reporting
  a partly-unrouted file as fully playable; count distinct channels.
- A device disabled via enableDevice(false) was re-enabled by any device-map
  rebuild; preserve the disabled flag across the rebuild.
- Inbound network SysEx / System Real-Time were dropped; forward raw bytes.
- NobleBleAdapter's unexpected-disconnect handler leaked itself across reconnects.

Adaptation
- compressNoteToRange folded to the wrong pitch class (reflection instead of
  octave folding): note 49 mapped to 71 (B) instead of 61 (C#).
- Capability validator flagged note_range_min === 0 as missing and accepted an
  inverted range (min > max) that later marked every melodic channel incompatible.
- DrumNoteMapper never reserved the side-stick (37) or tom→latin fallback pads,
  allowing double-assignment collisions.

API / core
- WebSocket connection-limit reject path attached no error listener → an
  errored over-limit socket raised an unhandled 'error' that shut the server
  down (remote DoS). Attach a listener before closing.
- CommandRegistry/WebSocketServer dereferenced a null frame in their error/log
  paths (client can send literal JSON `null`); use optional chaining.

Lighting
- sACN packet length was 126+slotCount (off by one), inflating all three PDU
  length fields and the trailing byte; correct to 125+slotCount.
- _applyBrightness now clamps to [0,255] so the schema-less lighting API can't
  overflow a DMX/packed-RGB byte; hsvToRgb normalises negative hue and clamps.
- Fade tracking keys collided within a millisecond, orphaning intervals through
  a blackout; append a monotonic counter.

Frontend
- WebSocket auto-reconnect died after the first failed retry (_reconnecting was
  never cleared on close); reset it so the retry loop survives outages.
- Command responses with falsy data leaked the raw envelope; use `'data' in`.
- EventBus once+debounce listeners were never removed and off() left a pending
  debounce timer that fired after teardown.
- MidiEditorPlayableNotes guarded a null routing value; FilterManager subfolder
  filter now requires a path-separator boundary (/Rock no longer matches /Rockabilly).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjWnM91xMywEtcw7Eu3YUE
The `Lint & Format` CI job's `npm run format:check` step has been red since
May: 352 source/test files did not match the pinned Prettier (3.8.x) output
(mostly assignment/ternary line-break changes), so the whole job failed on
every push.

Run `prettier --write` across src/, public/js/ and tests/. This commit is
formatting-only EXCEPT for one test fix: gm-coverage.test.js matched
KeyboardModal.js source with a regex that assumed a single-line array literal;
Prettier legitimately wrapped that array, so the regex is now whitespace/
newline tolerant (it validates the dispatched view-kind list, not the layout).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjWnM91xMywEtcw7Eu3YUE
…to CI

The 26 non-en/fr locales were each missing ~250 keys added with the lighting,
instrument-settings and playlist UI (the runtime silently fell back to French
for those panels). The `tests/audit-i18n.test.js` guard that should have caught
this ran under NEITHER runner — Jest ignores it and Vitest didn't include it —
so the drift went unnoticed.

- Deep-merge the missing keys from en.json into every locale (English
  placeholders; existing translations untouched, order preserved) so the key
  structure is complete and non-fr users get a real string in these panels.
  Human translation of the placeholders can follow.
- Fill loopCreator.statusIdle in en/fr (it was an empty string while every
  sibling status and every other locale had text).
- Add tests/audit-i18n.test.js to the Vitest include so the guard runs in the
  frontend test job and future drift fails CI. All 28 locales now match en.json
  (2685 keys, no empty strings, 128 GM instrument names each).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjWnM91xMywEtcw7Eu3YUE
…risk

Bound the worst-case memory footprint for the 1 GB Raspberry Pi target.

- MAX_SF2_FILE_SIZE 500 MB -> 160 MB. The upload path buffers the whole file
  and parsing allocates a full-file copy, so the old cap could OOM on a single
  upload; 160 MB still fits common GM soundfonts (FluidR3_GM ~140 MB).
- SF2InstanceCache default capacity 3 -> 2 (holds a melodic + drum instance
  without re-parse thrash, but caps resident parsed soundfonts).
- UploadQueue: default depth 100 -> 8 AND a new 64 MB in-flight BYTE budget
  (100 x 10 MB MIDI could pin ~1 GB). A lone task is always admitted so a
  legal large file can't deadlock the queue; the MIDI upload route now charges
  req.body.length against the budget.

Also document (per maintainer decision to keep current behaviour) that the
trusted-lan same-origin/Sec-Fetch header bypass is forgeable by a non-browser
WAN client, and that security.mode="secure" is the fix when exposing the box.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjWnM91xMywEtcw7Eu3YUE
… persistence)

Files
- FileManager.handleUpload wrapped the post-parse analysis (validate + metadata
  /tempo/text extraction) in try/catch so a parseable-but-pathological AST that
  throws there cleans up its orphan blob instead of leaking it.
- Enforce MAX_MIDI_FILE_SIZE on every user-supplied write path (saveFile,
  replaceFileBytes, createDerivedFile) via a shared _assertMidiSizeLimit; only
  the upload route checked it before. bakeAndSave is exempt (internal transform
  of already-bounded content).
- BlobStore.write now fsyncs the staged file and the directory before/after the
  rename, so a power loss on the Pi SD card can't leave a truncated blob whose
  content no longer matches its content_hash.

Playback
- stop() during a no-gap queue advance no longer restarts playback: a monotonic
  play token lets the in-flight playQueueItem detect it was superseded while
  awaiting loadFile and bail before start(); stop() finalizes cleanly during the
  advance window (when `playing` is briefly false).
- PlaybackStateMachine docs corrected — it is advisory state-tracking, not an
  enforcing guard (the real guards are the playing/paused flag checks). Gating
  callers on tryTransition would regress seek-while-stopped.

Lighting
- note_led note-off now decrements the active-note counter (was leaked to the
  stale-note safety sweep).

Transport
- Inbound RTP-MIDI responder sessions get a 10 s establishment watchdog; a peer
  that never completes the data-port invitation no longer lingers in rtpSessions
  forever.

Persistence
- Database.restoreFromBackup reopens the connection and rebuilds the per-domain
  sub-modules (extracted into _initSubModules), so the manager is usable
  immediately instead of leaving every sub-module on a closed handle.

Deferred (documented in code): the lighting wildcard-rule double-fire needs a
shared per-event id plumbed through the router (risky without rule-engine
tests); the WS same-origin port-match check is left as-is per the maintainer's
decision to keep current auth behaviour.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjWnM91xMywEtcw7Eu3YUE
@glloq
glloq merged commit ebabdbe into main Aug 3, 2026
5 checks passed
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.

2 participants