Skip to content

Security: hacksics/dj-crate

Security

SECURITY.md

Security policy

Reporting a vulnerability

Please do not open a public issue for a security problem.

Report it privately through GitHub Security Advisories:

Report a vulnerability →

That opens a private thread visible only to you and the maintainer, and it is the only channel monitored for this. If GitHub advisories are unavailable to you, open a public issue containing no detail — just "security report, please make contact" — and a private channel will be arranged from there.

What to include

  • What the issue is, and what an attacker gains from it.
  • Steps to reproduce, or a proof of concept.
  • Affected version or commit.
  • macOS version, and whether .venv-ml is installed.

What to expect

This is a solo-maintained hobby project, so honesty about response times is more useful than a promise nobody can keep:

  • Acknowledgement: within about a week.
  • Assessment and a fix, or an explanation of why it isn't one: as soon as is practical after that.
  • Credit: in the advisory and the release notes, unless you'd rather not be named.

There is no bug bounty. Nothing is paid for reports.

Please give a reasonable window before publishing — 90 days is the usual convention, and less is fine by agreement if a fix lands sooner. If a report goes unanswered for 90 days, publish; a silent maintainer is not a reason to sit on a real problem.


Supported versions

Only the latest commit on main is supported. There are no release branches and no backports. Fixes land on main; users update by pulling and running ./djcrate restart.


The threat model

DJ Crate is a single-user desktop application. Understanding that shapes what counts as a vulnerability.

The app binds 127.0.0.1 only

The local HTTP server listens on 127.0.0.1:8765 — the loopback interface, never 0.0.0.0. It is not reachable from your LAN, and not from the internet. The API has no authentication, and that is a deliberate consequence of the bind address rather than an oversight: the trust boundary is the machine, and anything already running as your user on your machine can read ~/.dj_crate/ directly without going through the API at all.

A change that widens the bind address is a security change, not a convenience feature, and would need authentication designed alongside it. If you put the app behind a tunnel, a reverse proxy, or any form of remote access, you have moved it outside its threat model and taken on responsibility for authenticating it. (Note also that AGPL § 13 starts applying the moment other people interact with it over a network — see THIRD-PARTY-NOTICES.md.)

Secrets on disk

~/.dj_crate/config.json holds the mirror server's credentials, your Spotify app credentials, and your Anthropic API key. It is written mode 0600 (owner read/write only) and the values are plaintext — not encrypted, not in the macOS Keychain.

This is a known, accepted limitation and it is stated here so nobody has to discover it. The reasoning: any process running as your user can read the Keychain for an app that can itself read the Keychain, so for a local single-user app the Keychain buys less than it appears to. It does buy something — at-rest protection if the file is copied off the machine, e.g. into a backup or a synced folder — so a Keychain-backed store is a welcome contribution, and the issue tracker is the place to propose it.

Practical advice in the meantime: don't put your crate folder or ~/.dj_crate inside a cloud-synced directory, and use a scoped Anthropic key you can rotate.

What leaves the machine

  • No audio, ever. Analysis is entirely on-device. There is no upload path in the codebase.
  • Popularity and artwork lookups (Spotify / Deezer) send an ISRC, or an artist and title string. Off unless you configure them.
  • The AI planner (Anthropic API) sends track metadata only — tempo, key, energy, cue times, section labels, vocal windows. Never audio, never file paths. Off until you supply a key and it validates.
  • The HTTP mirror sends your Basic-Auth credentials to the server you configured, over whatever transport that URL specifies. Use https://.

In scope

  • Anything reachable from a web page in another tab — the local server is loopback, but a browser on the same machine can still reach it. Cross-origin request forgery against state-changing endpoints, DNS rebinding, and missing origin checks are all in scope and are the most likely real finding here.
  • Path traversal in the static file handler, the artwork cache, or any endpoint that takes a path.
  • Command injection into the ffmpeg subprocess calls, or into the .venv-ml worker invocation.
  • SQL injection in library.py.
  • Anything that causes the app to write to or delete a file it does not own — the origin guards around art embedding and duplicate-trashing are safety properties, and a way around them is a vulnerability, not a bug.
  • Credential leakage into logs, error messages, the rekordbox XML export, or API responses.
  • Anything that lets a crafted audio file achieve code execution during analysis.

Not in scope

  • The API having no authentication on loopback. See above — that's the design, and the trust boundary is the machine.
  • Credentials being plaintext in a 0600 file. Documented above; proposals welcome as a feature, not as an advisory.
  • Anything requiring an attacker to already have code execution as your user, or physical access to an unlocked machine.
  • Vulnerabilities in ffmpeg, essentia, torch or any other dependency — report those upstream. If a dependency's flaw is reachable in a way that is specific to how DJ Crate calls it, that part is in scope, and is worth reporting here as well as upstream.
  • The mirror server you point the downloader at. That's your infrastructure.
  • Denial of service against your own local app.

For contributors

Two habits matter more than any checklist:

  • Never log a credential or an API key, including inside an exception message or a request dump.
  • The origin column is a safety boundary. art_embed_targets carries AND origin IS NULL, and /api/duplicates/trash refuses a borrowed id unconditionally. Those two guards are what stand between the app and deleting files a user only lent it. docs/DESIGN.md explains why each is structural rather than a filter — read that section before touching either.

There aren't any published security advisories