OPM.js documentationOnline demos

Generated from Markdown source. Edit the source, not this HTML.

SECURITY.md — OPM.js Security Policy & Review Mechanism #

OPM.js is a client-side, four-operator FM synthesis library. It processes voice definitions, DX7 SysEx and PCM buffers, and runs real-time DSP in an AudioWorklet. It has no application server or authentication system and has zero runtime dependencies. The host application remains responsible for website security, untrusted downloads, resource budgets and safe audio presentation.

For installation, browser deployment, and API examples, start with the README or the English / Traditional Chinese usage guide.

Contents: Supported versions · Reporting a vulnerability · Safe embedding · Threat model · Security review mechanism · Non-goals

Supported versions #

VersionSupported
1.xSupported security line; use the latest available patch
< 1.0Not supported

The current checkout requires Node.js 22 or newer for Node usage and development. Browser applications require ES modules, AudioWorklet and a secure context (HTTPS or localhost). The Node requirement does not provide a browser sandbox or replace browser updates.

Use a complete distribution from a known checkout or package, not individual modules from unrelated builds. Check CHANGELOG for observed verification; an advertised feature or configured CI job is not proof that every runtime or platform has passed.

Reporting a vulnerability #

Report exploitable problems privately to the repository maintainer. Use the GitHub private vulnerability reporting form when it is enabled. If a private reporting route is unavailable, request a private contact method without publishing exploit details, sensitive files or a proof of concept.

Include enough information to reproduce the issue safely:

Do not include credentials, private voice banks, personal recordings or unrelated user data. Use a synthetic input when possible. Minimize denial-of-service examples; do not test against other people's websites or production audio sessions.

The acknowledgement target is 72 hours, on a best-effort basis, not a guaranteed response or remediation deadline. Coordinate public disclosure with the maintainer so affected users can receive an actionable fix or mitigation. Ordinary usage questions may use public issues; exploitable details should remain private.

Safe embedding #

Website deployment and browser permissions #

Voice banks and JavaScript objects #

Use loadVoice(), normalizeVoice(), validateVoice() or parseVoiceBank() at the appropriate documented boundary; do not send unchecked definitions straight into the DSP or raw worklet protocol.

InputBoundary
Single-voice APIRequired fields and exactly four complete operators; finite numeric values and documented ranges; unknown fields and accessors reject
Voice-bank JSON stringAt most 262,144 UTF-8 bytes (256 KiB), then JSON parsing and bank validation
Voice-bank array1–128 complete versioned voices, unique validated names, and frozen normalized copies
Legacy versions 1–6Original shapes only: v1 excludes key scaling, v1/2 exclude velocity sensitivity, v1–3 exclude waveform, v1–4 exclude expressive fields, v1–5 exclude per-operator LFO targets, v1–6 exclude operator waveform/noiseRate; all normalize to v7. noiseRate is 20–20000 Hz
.opm text patchesimportOPM/describeOPM: at most 262,144 bytes, 128 patches and 512 bytes per line; NUL bytes, unknown/duplicate/missing sections, non-decimal or out-of-range fields reject with a line number; results pass the normal v7 validation path
MIDI program/drum mapsprogramVoices/drumVoices: plain data objects or native Maps, at most 128 entries, canonical integer keys 0–127 and valid voice names; accessors and extra Map properties reject. RPN 0 state is per channel and bounded to 0–48 semitones
PreparedVoiceImmutable detached core snapshot, including nested pitch envelopes; only private identity membership grants trust, never a structural brand/copy
Tuning / score / controlsA4 20–20000 Hz, 128 bounded cents offsets; strict dense four-element level/ratio/Hz/ADSR and LFO-target tuples; short scores retain 128-note/256-slot/60-second bounds; long scores have separate event/time/chunk limits and immutable own-data snapshots
Atomic OPM bank replacementValidate all entries before swapping ≤128 lookups; plain empty array clears, while JSON/standalone empty bank parsing rejects; exports use detached canonical names and ≤256 KiB UTF-8
Transport / performance≤1024 tempo points, quarter-note positions ≤86400; bounded scheduling density and scoped ID ownership; 1–16 parts, ≤128 physical keys/tracked gates with detached snapshots

Single-voice APIs reject out-of-range fields. Bank validation clamps finite numeric fields to documented bounds, but still rejects malformed types, non-finite numbers and invalid version/algorithm/feedback values. Clamping is not a substitute for validation.

Keep allowlisted, own-data copying at trust boundaries. Do not merge untrusted objects into prototypes, invoke getters to inspect them, or treat a valid voice name as authorization to construct a URL or filesystem path. Engine bank parsing does not load paths or fetch voice URLs.

Independent note gain and expression are both bounded to 0..1 and multiply before stereo mix saturation. Layer fades do not bypass control validation, note ownership, worklet queue limits or rejection reporting.

Score projects and Standard MIDI files #

Versioned score-project parsing accepts only the documented own-data schema, detached validated voices, bounded beat events and synthesis settings. Loading a project does not fetch assets, execute code or start playback. Compilation converts note gate endpoints through the complete tempo map; offline resource budgets still apply after conversion.

The separate version-1 Arrangement project uses the same own-data beat/voice/settings boundaries and one pure definition validator shared with live arrangements. Bounds include 8 MiB serialized input/output, 128 voices/256 KiB voice JSON, 16 layers, 32 sections and 65,536 total events. Parsing is inert; persisted musical definitions do not include live IDs, permissions, pending commands or DSP state. Host replay must explicitly load validated voices and start from a gesture.

The independent Standard MIDI file adapter validates native bytes, bounded chunks, PPQN timing and event framing. It does not provide a native MIDI driver, transmit SysEx or interpret uploaded bytes as executable content. Hosts must check file size before buffering, choose explicit channel-to-voice mappings, expose import warnings and reject unsupported export semantics instead of silently changing a score.

Opt-in expressive SMF conversion separately bounds generated per-note fanout to 65,536 events. Frozen loss summaries separate omitted content, approximations and recognized source-message counts. Channel expression after a closed gate reports possible patch-dependent release-tail loss; strict unsupported policy rejects it. Export rejects incompatible channel ownership, unsupported controls and after-gate semantics rather than guessing. Explicit pitch-range policy is not RPN support.

External downloads and file uploads #

The engine does not provide a voice-download API. Host loaders must validate the source and enforce an input budget before buffering or parsing. Engine checks cannot recover memory already consumed by an oversized download.

DX7 SysEx conversion #

importDX7() and describeDX7() accept one Uint8Array containing a standard 163-byte single-voice or 4104-byte 32-voice bank message. Length, framing, checksum and seven-bit payload checks reject malformed input. The playground checks the maximum file size before buffering.

This is approximate six-to-four-operator voice conversion, not a DX7 emulator or an arbitrary SysEx interpreter. Conversion does not execute the payload. Review the returned descriptions and warnings before auditioning a patch; keep descriptions outside the strict voice schema.

PCM, offline rendering and WAV export #

encodeWav() encodes PCM; it does not decode/play uploaded audio. Own-data arguments are left, optional right, sampleRate and optional format (pcm16, pcm24, float32). Native Float32 samples must be finite in −1–1; stereo lengths match, with at most 4,000,000 frames per channel for this full-buffer convenience API.

Project render results onto those fields rather than forwarding diagnostics or other metadata. Reject encoding/rendering errors visibly instead of substituting a fake result.

Core rendering authenticates native Float32Array kind/length instead of overridable properties. Validate offset before deriving the default length; numeric coercion, proxies, forged channels and out-of-bounds ranges reject without advancing sound. Idle clearing uses the native fill method. Hosts still own the actual PCM storage and must not mutate it concurrently.

Offline rendering allocates output buffers and executes synchronously. The host should limit duration, sample rate, envelope tails, concurrent requests and export frequency to suit its device and UI. Valid input may still consume significant time and memory; no universal deadline or whole-page memory guarantee is made.

Long-score rendering and incremental createWavEncoder() use bounded reusable PCM/encoded chunks, not unlimited aggregate buffers. Consume borrowed PCM before advancing; declare an exact encoder frame count and finalize only after all frames. Encoder chunks are 1–65536 frames and RIFF32 files remain <4 GiB; convenience rendering/encodeWav retain their 4,000,000-frame bound. Hosts must bound cumulative duration/work, retained bytes, concurrent renders and export frequency.

renderSequenceInWorker() validates before Worker allocation, transfers at most one unacknowledged encoded chunk and awaits sink writes before advancing. Capture intrinsic byte counts before handing ownership to a sink; storage workers may transfer/detach the buffers. Progress/abort callbacks are untrusted host behavior. Native AbortSignal cancellation terminates/rejects immediately without waiting for hung writes/abort/close. The sink owns invalidation and rollback; cancellation cannot retract persisted bytes. Acquire file sinks from trusted gestures, retain lifetime cancellation across picker/writable awaits, and clean up acquisition failures before starting a Worker. Never replace a long-file sink with unbounded Blob accumulation.

Create download URLs only for successfully encoded bytes. Replace/revoke obsolete Blob URLs and release them when the owning page or component is disposed. Treat filenames and rendered audio as potentially private application data.

AudioWorklet, scheduling and context ownership #

Source maps, privacy and redistribution #

Every distribution JS has a matching source map with embedded TypeScript and a compiler-generated declaration. Publishing maps intentionally makes the implementation source readable; minification is not secrecy. Never place credentials or private configuration in source that will be built or deployed.

OPM.js does not provide an account, storage or telemetry service. Hosting providers and applications can still record request metadata or persist voices/audio. Document those host-specific behaviors separately and do not claim this library prevents data collection by the surrounding page.

Keep each deployment's JS, maps and declarations from the same build. Review exact development dependency versions, build plugins, CI actions, package contents and license compatibility before changing the supply chain.

Threat model #

SurfaceRiskRequired controls
External voice JSON or JavaScript objectsHighType/shape/byte/count limits; finite bounds; own-data validation; no untrusted prototype merging
Host download/upload and DOM codeHighPre-buffer limits, permitted sources, text-only presentation and host-page XSS defenses
Raw AudioWorklet messagesHighStrict message validation, bounded IDs/events and observable failure handling
DX7 binary input and WAV PCM argumentsMediumBounded lengths, framing/checksum or sample validation; no native decoder
Score-project JSON and Standard MIDI filesHighByte/event/schema/framing limits, own-data options, detached validated voices and explicit unsupported-semantics handling
DSP and offline renderingMediumFinite output, bounded per-instance work, duration/sample-rate budgets and error diagnostics
Public scheduling APIMediumArgument validation, admission handling, cancellation and bounded scheduling batches
Build and deployment supply chainMediumExact development pins, reviewed CI actions, complete matching assets and zero runtime dependencies
Output audio and downloadable artifactsMediumUser-controlled playback, conservative gain and host-managed privacy

An AudioWorklet separates real-time processing from the main thread, but is not a security boundary against a compromised host page. Malicious host JavaScript can create nodes, spam messages, fetch data or manipulate the UI independently of OPM.js. Input validation and per-instance limits reduce accidental or adversarial input damage; they do not sandbox XSS or guarantee availability under arbitrary page load.

This policy addresses the library and checkout examples. Authentication, authorization, cookies, cross-origin access, backend storage, upload endpoints and rate limiting belong to the application deploying them.

Security review mechanism #

1. Review triggers (mandatory) #

A security review is required before:

2. Review checklist #

A. Untrusted data (voice banks, config)

B. Worklet boundary

C. DSP loop safety

D. Supply chain

3. Verification gates #

Run development commands from a source checkout after npm ci; installed npm packages do not contain the development tooling.

4. Review record #

Each release notes in the CHANGELOG which checklist sections were exercised (A/B/C/D) and by whom. Checklist failures block release; waivers require a written reason in the CHANGELOG.

For v1.2, final A/B/C/D independent review and observed integration verification are recorded in CHANGELOG before release. Configured Node 22/24/26 and Chromium/Firefox/WebKit CI jobs are capabilities, not evidence of an external run. Missing browser binaries/host support or unrun CI must be reported, not represented as passing coverage.

v1.5 feature-completion review: InputBoundaryReview covered A/B; DspSupplyReview covered C/D. The integration owner reproduced and fixed null mixGain/tuning acceptance and overridable render-buffer metadata, then also prevented offset coercion before default-length calculation. Runtime evidence and remaining registry/physical-device prerequisites are recorded separately in CHANGELOG; static review is not a claim of runtime coverage.

FinalInputsReview (A/B) and FinalDspReview (C/D) rechecked the final hardened source and release controls, with no new evidence-backed findings. Their PASS applies to scoped static inspection only; they ran no build, payload, tests, audit or browser commands.

v1.5 release review: Release15Inputs approved scoped static A/B inspection; Release15DspSupply approved C/D for GitHub distribution of package 1.5.0, with no evidence-backed blockers. Neither reviewer executed runtime gates or certified external npm settings. The integration owner's fresh package, native browser, numerical, audit and report-only benchmark evidence is recorded in CHANGELOG.

v1.5 interruption-repair recheck: Release15Inputs initially found that deferred running notifications could leave deduplication state stale. Both initial and existing-node resume now synchronize successful state observation; repeated fully deferred transitions and pending diagnostics are covered by consumer regressions. Release15Inputs then approved A/B and Release15DspSupply approved C/D without remaining scoped static findings. The original Linux WebKit CI failure blocks publication until repaired-candidate verification; static approval is not a waiver or runtime certification.

v1.6 release review: a security review found two command-waiter boundary issues (abort-signal state/listener shadowing and receipt-eviction handling around correlated panic resets). Both were fixed with regressions and rechecked natively against a borrowed context. No fresh independent A/B/C/D approval was obtained for v1.6; the maintainer explicitly authorized the GitHub release on the integration owner's runtime evidence, recorded as a waiver in CHANGELOG. npm publication still requires the independent review record described in doc/publishing.md.

Unreleased seven-capability checkout review: SecurityDataReview approved scoped A, SecurityProtocolReview B, SecurityDSPReview C, and SecuritySupplyReview D static inspection. Initial findings about release-tail controls, sink-transferred buffer accounting and deferred file-picker/writable teardown were fixed and the affected source rechecked without remaining scoped findings. These reviewers executed no tests, builds, audits or browser commands; their PASS does not certify runtime behavior, deployment permissions, physical devices or registry protections. The integration owner's observed tests, package gates, native browser smoke and report-only CPU measurements are recorded separately in CHANGELOG. This is not a release approval or a new waiver.

v1.7 release review: Release17Data approved scoped static A, Release17Protocol B, Release17DSP C, and Release17Supply D, with no evidence-backed blockers in the final seven-capability source and 1.7.0 release metadata. All four reviews were read-only and executed no runtime gates. Fresh isolated package/build/test/type/audit/quality checks and managed native Chromium AudioWorklet observations are recorded in CHANGELOG. Remote CI must pass before the release tag is published; its run URL and outcome belong in the GitHub release record. No waiver or npm authentication/provenance/external-protection certification is asserted.

v1.8 review record: InputProtocolReview approved scoped static A/B and DspAssetsReview scoped static C/D after the bounded arrangement, MIDI own-data, finite tempo-slope and canonical asset-overlap fixes. Their static approval does not certify runtime or external npm settings. The release commit d793d69056c68a971ede1af988338412687227fd has an observed successful Node 22/24/26 and Chromium/Firefox/WebKit CI run; the separate review record and release package record retain the evidence and tarball digest. This is not registry provenance, physical-device/MIDI or listening acceptance.

v1.8.1 documentation/package patch review: Patch181Inputs approved scoped static A/B and Patch181DspSupply scoped static C/D, with no evidence-backed blockers. Reviewers were read-only and ran no build, tests or external actions. The integration owner confirmed the source diff against v1.8 changes only src/version.ts; runtime validation, protocols and DSP are unchanged. Final regenerated assets, package gates and remote CI are separate release prerequisites, recorded in CHANGELOG and the GitHub release notes. No npm provenance, physical-device/MIDI or listening certification is asserted.

Unreleased integration-quality review: FinalImprovementInputs approved scoped static A/B after the pedal-ownership and applied-workload evidence fixes. FinalImprovementDspSupply approved scoped static C/D specifically for an isolated immutable canonical candidate (47 runtime modules with 141 matching distribution assets), after generated-link validation and the DOM-free core/project/MIDI type boundary were hardened. No surviving scoped findings remain. Both reviewers were read-only and executed no runtime gates or publication. The integration owner separately observed 381 passing behavioral tests, type checks including installed DOM-free Node consumers, numerical/dependency/package checks and native Chromium UI/smoke/stress checks. Unexpected numbered generated-file copies were preserved outside the checkout; live-tree packaging remains blocked by their reappearance and is not covered by the isolated-candidate approval. Desktop automation and numerical analysis do not certify physical devices, physical MIDI or human listening. No release approval, npm authentication/provenance/registry protection, physical-device or listening certification is asserted.

v1.9 release review: Release19Inputs approved scoped static A/B and Release19Supply scoped static C/D for an isolated canonical package 1.9.0 candidate, with no evidence-backed security findings. Reviewers ran no runtime gates. Numbered live-tree generated copies are not approved for packaging and must remain outside the candidate. GitHub publication requires exact clean-commit artifacts, passing remote Node/browser CI and an archive digest retained in the release record. Physical-device/MIDI, human listening and npm provenance remain unverified and are not claimed by this GitHub-only release.

v1.9 deadline repair: Release19Supply rechecked and approved scoped static C/D topology-cache/pooling/render changes after the initial release candidate failed the Node 22 prepared-burst p99 gate. Fixed slot-local caches derive only from the existing validated graphs, overwrite all edges, preserve summation order and retain finite checks, bounded fades and reentrant callback behavior. The reviewer executed no checks; this is not a performance waiver. The repaired exact commit must pass the unchanged remote gates before tagging.

Unreleased readiness review: ExpressionSecurity approved scoped static A/B for final expressive SMF boundaries, ownership and loss accounting; ArrangementCaptureSecurity approved scoped A/C/D for portable/shared arrangement validation, lifecycle, inert imports, pure core contracts and bounded text-only local MIDI capture. No evidence-backed findings remained. Reviewers executed no gates. The integration owner separately observed 403 passing tests, installed package/types/security/audit checks, 108 bit-identical baseline renders, scoped capacity measurements and native isolated Edge UI/worklet checks, recorded in CHANGELOG. Physical phones/controllers and genuine eighteen-preset listening originals remain blocked/unverified. This is not release/publication approval.

v1.10 release review: Release110Inputs approved scoped static A/B and Release110DspSupply scoped static C/D for an immutable canonical package 1.10.0 candidate (48 runtime modules and 144 matching distribution assets), with no evidence-backed blockers. Both approved the final documentation/generated-HTML delta. Reviewers ran no runtime gates. The integration owner separately observed 403 behavioral tests, public/DOM-free core types, numerical matrices, zero-vulnerability development/runtime/Vite audits, isolated installed-package/asset/type gates, expressive MIDI-to-Arrangement PCM/WAV smoke and the unchanged isolated p99 deadline gate. Unexpected numbered live-tree generated copies are preserved and excluded. Publish only exact committed canonical assets after remote Node/browser CI passes; retain the commit, CI URL and archive digest in the GitHub release record. This is not npm provenance or physical-device/MIDI/listening certification.

v1.11 review: SecInputsProtocol approved scoped static A/B (voice v7 validation, .opm parser, MIDI program/RPN boundaries, synth and effects worklet protocols, effects URL/lifecycle) with no evidence-backed vulnerability. SecDspSupply returned approved-with-fixes C/D with low/informational findings only: the deployment inventory now requires worklet/fx-processor.js (with a rejection test), the effects headroom documentation is qualified as a steady-state ceiling (a feedback decrease can briefly exceed 0.7 during its 20 ms ramp), the input table above now covers v7 and .opm, and capacity candidates are documented as sine-operator measurements. Reviewers ran no runtime gates. The integration owner separately observed the typecheck, the full behavioral suite, the npm run security gate, and a real Chromium AudioWorklet smoke of the noise and waveform voices through the effects insert. Physical devices, MIDI hardware and human listening remain unverified; this is not a release, npm publication or deployment record.

v1.11.1 review: SecFastSin approved a scoped static review of the fastSin polynomial and all-sine render fast path (NaN/Infinity containment, |value| ≤ 1 bound, determinism, O(1) cost, state parity with the generic path) with no evidence-backed vulnerability. The reviewer ran no runtime gates; the integration owner ran tests, typecheck, the package-smoke security gates and a Chrome AudioWorklet smoke.

Non-goals #