EDR 0005 Status: accepted Implementation: none
The control plane never holds a target credential
Target credentials live only where connections are made. The Harbourmaster stores a reference; the Pilot dereferences it at connect time using its own workload identity and never returns it.
What exists: The Harbourmaster serves and links a PostgreSQL driver for its OWN store, and cmd/pilot links one to reach a target — the two homes EDR-0042 permits, and no others. The Harbourmaster opens no target connection and holds no target credential: the Pilot takes --target-dsn and the control plane never sees it. What is absent is everything the relay half needs — there is no Tender, no operator signature on a read, and no quota.
TL;DR
The Harbourmaster stores, for each role on each target, a reference — a path in a secret store, or an instruction to mint a credential from an identity. It never stores, receives, caches, logs or returns the credential itself.
The Pilot resolves that reference at connect time, using its own workload identity, and the resolved
value never leaves the process. Preferred order: native identity-based auth (IAM database
authentication, Cloud SQL IAM, pg_ident) first; a short-lived minted credential second; a
long-lived stored secret only as a documented carve-out with an owner and a review date.
The consequence that matters: an attacker who owns the entire Harbourmaster obtains no credential and no ability to commit a change. They can read requests and approvals, and — because the control plane legitimately relays rehearsals — they can relay operator-signed reads, so a bounded, quota'd, target-visible read channel remains (EDR-0034). They cannot connect to a target themselves — the Harbourmaster reaches one database, its own (EDR-0042).
Context
EDR-0004 removed the control plane's ability to authorise execution on its own. That is only half the property. If the control plane also holds the database passwords, compromising it still yields full access to every target — the approval workflow becomes an elaborate wrapper around a credential vault, and the vault is the thing worth attacking.
ZFN-35 gives the rule: config holds a reference, the workload dereferences it at runtime through its own identity, and rotation needs no redeploy. ZFN-16 gives the placement: the thing that connects is the thing that holds the connection's secrets.
There is a specific trap worth naming, because it has bitten this exact pattern before. A service given a choice between identity-based auth and a stored secret will silently take the stored-secret path if the secret is configured, even when the operator believed identity auth was in use — and a stored secret pointing at an administrative account then connects with far more privilege than anyone intended, quietly, forever. The configuration must not offer both at once.
Decision
Roles are references. A role definition names a target, a database identity, and how to obtain a credential for it:
{
"role": "settings_writer",
"target": "prod-primary",
"db_user": "app_settings_writer",
"credential": { "kind": "aws-iam" }
// or { "kind": "gcp-iam" }
// or { "kind": "secret-ref", "uri": "…", "carve_out": { "owner": "…", "review_by": "2026-11-01" } }
}
aws-iam/gcp-iam— the Pilot generates a short-lived auth token from its own workload identity. Nothing is stored anywhere.secret-ref— a pointer into a secret store, resolved by the Pilot's identity. Requires acarve_outblock: an owner and a review date, both surfaced in the operator console. The build refuses asecret-refwithout one, so the exceptional path stays visibly exceptional.- The kinds are mutually exclusive by schema. There is no configuration in which one silently wins over the other.
Never point a role at a rotating credential you copied. A reference resolves the managed secret at connect time. A snapshot of a rotating secret is wrong within a rotation period and fails in a way that looks like a Marque bug.
The Pilot is the only holder. Resolved credentials live in process memory for the life of a connection, are excluded from every error path and log line, and are never returned over any API. A Pilot exposes no endpoint that reads a credential, by construction and not by permission.
The Harbourmaster cannot connect to a target, and does not try. Anything requiring a target connection — a rehearsal (EDR-0010), a schema introspection for the analyser, a health check — is a request to a Pilot, and comes back as data. It connects to one database only, its own (EDR-0042). It holds no target credential and no target connection parameters, which is what makes that true.
This originally read: "It has no database driver for target engines linked in.", which was a stronger
and cleaner mechanism and is not available: EDR-0013 fixes
Marque's own state on PostgreSQL, PostgreSQL is also a target engine, and one driver serves both. The
sentence was never achievable after that record was accepted and went unchallenged only while the
Harbourmaster had no storage code and so linked no drivers at all.
EDR-0042 replaces it with import discipline — a
driver confined to the two packages that need one — the Harbourmaster's store and the Pilot's
adapter, which must have it — by a check that asks go list -deps what each binary links, with no
control-plane exception for an engine Marque does not store its own state in; a MySQL driver's only
home would be the Pilot's adapter — and is explicit that this is weaker: it reads imports, not
capability.
Verify positively after any change. A lazily-initialised connection pool hides broken database authentication indefinitely: no connection attempt, no error, quiet logs, and the first symptom arrives during an incident. After any change to a role or a Pilot's identity, exercise it and confirm the session's actual database user on the target — not the configuration's opinion of it.
Consequences
Easier.
- The blast radius of a control-plane compromise is bounded to disclosure of request text and approval history, plus a quota'd, target-visible read channel (EDR-0034) and the group-invoker standing-order residual (EDR-0029). Bad, and much better than access.
- Credential rotation is invisible: the reference is stable, the value is fetched fresh.
- Least privilege is enforceable per role, by the database, using primitives the database already has.
Harder.
- The Pilot is now the crown jewels. Everything this record removes from the Harbourmaster is concentrated in the Pilot, which must therefore be the most boring, smallest, least-featured component in the system. Every feature request aimed at it should be read as an attempt to widen the one component that holds credentials.
- The Harbourmaster cannot do anything requiring a target connection, which makes several convenient features into round trips: schema autocomplete, rehearsal, table metadata for the analyser. Each becomes a Pilot API, and each of those is new surface on the component that must stay small.
- Identity-based database auth has to be configured on the target, which is real work per target and not always available on managed engines.
New obligations.
- Every
secret-refcarve-out is reviewed on its date. An expired review is reported as a finding. - Pilot task roles are scoped to exactly the secrets and identities they use. A Pilot able to dereference every role in the deployment has re-created the vault this record removed.
References
- ZFN-35 — reference secrets, dereference at runtime.
- ZFN-9 — federated identity over static keys.
- ZFN-16 — plane separation.
- EDR-0006 — why every statement must name one of these.
Changelog
- 2026-08-15: Accepted.
- 2026-08-16: Amended after review — the TL;DR's "no database access" is corrected to "no credential and no ability to commit a change", with the bounded read channel named (EDR-0034). This line was missing when the amendment was made, which is the corpus's own rule 1 broken in the one place it is most visible.
- 2026-08-16: Amended after the second panel's synthesis: extended the blast-radius bullet to name the read channel and the fast-path residual.
- 2026-08-20: Amended a third time, same day. The mechanism sentence said "a test that parses every first-party file"; EDR-0042's mechanism is now a
go list -depsgraph check over each binary, with that walk demoted to a cross-check. The line below describes the previous shape and is left standing for the sequence. The decision is untouched. - 2026-08-20: Amended again. The mechanism named here was
lint; EDR-0042's rule is a test that parses every first-party file, with adepguardblock beside it as the edit-time report. The three claims that justified moving off the linter were each false and are retracted in EDR-0042; the reason the test is kept claims nothing about capability. The decision is untouched. - 2026-08-20: Amended. The driver sentence — "no database driver for target engines linked in" — is replaced, because it had been unachievable since EDR-0013 fixed Marque's own state on PostgreSQL: PostgreSQL is also a target engine and one driver serves both. It survived only while the Harbourmaster had no storage code. The decision is unchanged — the control plane holds no target credential and cannot mint authority to reach one, which is not the same as "cannot reach a target": the bounded operator-signed read channel this record already carves out survives — and the mechanism is now import discipline (EDR-0042), which that record states plainly is weaker.
implementation_notecorrected for the same reason. Where a target's connection parameters live is issue #36.