Skip to content

Security

Arnel Robles edited this page Oct 1, 2026 · 1 revision

Security

The full policy is SECURITY.md in the repo. This page is the part an integrator needs.

Reporting

Use GitHub Security Advisories on the repo ("Report a vulnerability"), or email security@baryo.dev. Do not open a public issue for a vulnerability. The 2.x line is supported.

Player state belongs to the player

Progress is stored in the browser. Anyone can open the dev tools and set their XP to a million. The library cannot stop that, and no client-side library can.

What it does: saved state is validated on load, so an edited or corrupt save becomes valid numbers and plain records. It cannot crash the app, produce NaN XP, or write to Object.prototype.

What you do: if XP, a level or an achievement unlocks something of value (a discount, a prize, a leaderboard place), compute it on your server from events your server saw. Treat the client's numbers as display.

achievement.icon is HTML

GameUI.showAchievement inserts icon with innerHTML, so an icon can be an <img> or an inline SVG. Every other field (name, description, xp) is inserted as text.

Never build icon from user input or from anything a user can influence. Define icons in your own code.

// fine
{ icon: '<img src="/icons/star.svg" alt="">' }

// never
{ icon: userProfile.avatarHtml }

Webhook secrets in the browser

WebhookIntegration signs each request with HMAC-SHA256 when given a secret. In browser code, that secret ships to every player and can be read.

So the signature proves the request came from a copy of your app. It does not prove the payload is true. On the receiving end:

  1. Verify X-Dopamine-Signature against the raw request body, before parsing. A verification sample is on Plugin: Ecosystem.
  2. Treat the payload as a claim by that player.
  3. Check anything that matters against your own records.

If you used dopaminejs-plugin-ecosystem before 2.0.0 with a secret, rotate it. The old "signature" was the secret itself, base64 encoded with the payload, sent on every request.

Caller-supplied keys

Game names, achievement ids and sound keys are looked up by own property. recordGame('__proto__', ...) throws a TypeError. unlockAchievement('constructor') returns false.

Content Security Policy

  • GameUI attaches handlers with addEventListener. It uses no inline event attributes, so it works under a script-src without 'unsafe-inline'.
  • Styles are set through element.style, which a style-src policy does not restrict. The one exception is the XP bar, whose markup carries style="width: 0%". A style-src without 'unsafe-inline' blocks that attribute and reports it. The bar still gets its width from updateXP.
  • SoundManager fetches registered audio URLs, which falls under connect-src.

Supply chain

  • The core has zero runtime dependencies, checked in CI.
  • Releases are published from GitHub Actions with npm Trusted Publishing. No npm token exists. Each release carries a provenance attestation.
  • Workflow actions are pinned to commit SHAs.

Clone this wiki locally