Skip to content

What dqx stores, and where ​

dqx writes to your machine's trust store, mounts your credentials into containers, and generates a file that gets committed. None of it is exotic. It is written down because a tool that touches these things and says nothing is harder to review than one that does.

The short version ​

committed to the projecton your machine only
.dq/config.ymlyes, it is the source
local database credentialsyes, inside that file
the generated stack and confsyes, and regenerable
certificate private keysno, gitignoredyes
composer and registry credentialsno, never written into a projectyes
your ssh keynoyes, mounted read-only
.dq/compose.envno, it is this machine's port pinsyes
the Bitbucket token for dqx reviewnoyes, in the Keychain or libsecret
review files, with snippets of client codenoyes, 0600, removed after 30 days

Database credentials are in the config, on purpose ​

.dq/config.yml carries the database user and, where a project must differ from the fleet's default, its password. That file is committed.

Safe because the value is a local development credential and nothing else: the database it opens exists only in a container on your laptop. One value shared across the fleet is what keeps the compose environment, the init SQL and each site's .env from disagreeing, which is a real class of bug.

Never put a real secret here

This file is in git, in every clone, in every fork and in every backup ever taken of them. A production credential, an API token or a customer's password does not belong in it, and no amount of "it is only local" makes a committed file private.

A real secret belongs in the password manager.

database.password and database.rootPassword are declared only when a project must differ, for example because its data directory was initialised with something else and a healthcheck pinging with the wrong password never passes.

Credentials dqx reads but does not copy ​

Private packages need registry credentials. dqx finds the ones your machine already holds and passes them to composer inside the container, without writing them into the project. Searched in order, first hit wins:

text
~/.composer-docker/auth.json      the canonical location
~/.config/composer/auth.json
~/.composer/auth.json
COMPOSER_AUTH                     the environment variable
bash
dqx auth            # what this machine holds, and where

Its output is redacted: it says which registries you have credentials for, not what they are, and whether a Bitbucket token for dqx review is stored and when it expires. ~/.composer-docker/auth.json is mounted read-only into the app container, and so is your ssh key. Mounted, never copied.

The review token and review files ​

dqx review needs a Bitbucket API token, set with dqx auth --set bitbucket. It is the one secret dqx stores, and it goes to the macOS login Keychain or libsecret's secret service, handed over on stdin so it never appears on a process list. It is never printed, never an argument, never in the dashboard's state or a log line. Without either store dqx refuses, unless --insecure-store is typed in a terminal: then it is a 0600 file in ~/.config/dqx/credentials/, refused if its mode ever changes. Its non-secret half (e-mail, expiry, scopes) is in ~/.local/share/dqx/bitbucket.json.

GitHub has no token of dqx's: dqx review borrows the gh CLI's login.

Every review is a file in ~/.local/share/dqx/reviews/, and those files hold snippets of client code. So they are the exception to "nothing secret in the state directory": 0600 in a 0700 directory, and pruned after 30 days. Consent per repository, the first time its code goes to Claude, is recorded in ~/.local/share/dqx/review-consent.json.

The code under review is untrusted, so Claude Code reviews it in a detached worktree with as little as possible to act on: restricted mode (--restricted, which ignores your own and the project's Claude settings files), only the read-only Read, Grep and Glob tools, confined to the worktree, no MCP servers, no hooks. The worktree gets the target branch's CLAUDE.md, .claude/ and .mcp.json back before Claude starts, so a pull request cannot configure its own reviewer, and git creates the worktree without running your hooks. See reviewing a pull request.

Credentials dqx deliberately does not read ​

When dqx adopt reads a project's existing docker/conf/mysql/init/*.sql, it takes the schema names and the application user. It does not read the password, does not carry it and does not write it back; the generated SQL uses the stack's own.

A file holding a password that differs from the stack's is a fact for a person to reconcile, not for a tool to copy from one file into another.

The local certificate authority ​

For https:// on a local domain to be green rather than a warning, something has to be trusted by your system. That is mkcert, and dqx uses it rather than reimplementing it.

the CA itself~/Library/Application Support/mkcert on macOS
installed intoyour system trust store, and Firefox's if present
the router's certificates~/.local/share/dqx/router/certs/

mkcert -install is what adds the CA, it asks for your password, and dqx never does it silently. dqx doctor distinguishes "mkcert is not installed" from "installed but has no CA" from "the CA exists and is not trusted", because the remedy differs.

A local CA is a real CA

Anything holding its private key can issue a certificate your browser trusts for any domain, not only local ones. That key is on your machine and should stay there. Do not copy rootCA-key.pem to another machine, and do not commit it.

A project's certificate private key is gitignored. Before dqx 1.4.0 it was not, and it reached the history of some wrapper repositories. If you maintain one of those, treat that key as exposed and reissue.

What the router can reach ​

Traefik, mounting no docker socket: its routes come from files dqx writes. A container with the engine's socket can control the engine; this one cannot, and that was the deciding reason for the design rather than a side effect.

Its dashboard is bound to 127.0.0.1:8088, so it answers to your machine and not to the network.

The dashboard's token ​

The dashboard binds 127.0.0.1 only, checks the Origin header against its own address, and requires a per-run token of 24 random bytes. Both checks are needed: the origin check stops a page you happen to have open from posting to it, and the token stops DNS rebinding, where an attacker's name resolves to loopback and the origin is genuinely theirs. The token comparison is length-independent, so a wrong guess leaks no prefix through timing.

The token reaches the page in the URL fragment, which browsers do not send to servers and do not put in Referer, and the page moves it into a header for every request. One route differs: the event stream accepts ?token=, because EventSource cannot set headers. A query string is the part of a URL that tends to end up in logs, and there is no proxy and no access log in front of this server, so today it reaches nothing. Written down rather than fixed, because the alternative is a hand-rolled reconnect.

A read-only dashboard refuses everything that changes the machine: starting, stopping, copying, removing a copy, and opening a folder, a terminal or a client. "Changes nothing" includes "does not open a terminal on your machine".

Where a copy may be written. dqx clone --to in a terminal writes wherever you can write. The same action from a card is confined to the directories dqx already found projects in: anything holding the token can ask it to place files, and a token is not something you type on purpose each time.

Removing a copy is a move to the trash, not a delete. Its database directory and .env files are on disk until the trash is emptied.

Updating verifies what it installs ​

dqx self-update reads a manifest that publishes a sha256 per asset. dqx hashes the download and refuses anything that does not match, before the file is made executable and before it replaces the running binary. It refuses a manifest with no checksum at all, and fetches an asset only from the same host the manifest came from, so a manifest cannot redirect the next binary elsewhere. DQX_RELEASE_MANIFEST still overrides where the manifest is read from, and dqx prints that host when it does.

The update check that dqx up, doctor and the dashboard run only reads the manifest's version. It downloads nothing, and the dashboard's page never talks to the network: the server asks.

The dashboard token is kept on disk ​

So that a restart or an update does not strand an open tab, the dashboard keeps its token in ~/.local/share/dqx/dashboard.json, created owner-only (0600). What can read that file already runs as you and could run dqx itself. What it changes is how long a leaked link works: until the token is 30 days old, or until dqx dashboard --new-token. A file that others can read is not reused, and dqx doctor --all says so. The token is still never served to anything that has not sent it.

The binaries are ad-hoc signed, not Developer ID signed; the install guide says what that means for Gatekeeper.

The MCP server ​

Read-only by default. Anything that changes what is running needs --allow-write, and installing, switching, cleaning or killing an existing project is not exposed at any flag. See AI tooling for the full table.

Reporting something ​

If you find a way dqx exposes a credential, writes one somewhere it should not, or weakens the trust store beyond what is described here, report it privately rather than in a public issue: inside Dolphiq that is the ISMS incident procedure, and from outside it is the Dolphiq team you work with. Either way, not a public tracker.

dqx v1.78.0