Skip to content

Security model

gapcert issues real, publicly-trusted certificates, so its security matters. Each mode went through a staged cadence — audit, fix, then multiple independent adversarial review rounds, declared clean only when a round finds nothing new. This page states the trust model per mode and the residual risks no audit removes. Read it before deploying.

Verification, honestly

What's verified: every vector the review rounds surfaced is closed and pinned by a regression test, and the final round of each mode's cadence came back empty. CI runs lint, go vet, govulncheck, unit tests, the race detector, and hermetic integration tests against pebble (Let's Encrypt's test CA) on every push to main.

What's not claimed: that the code is unexploitable. The residuals below are inherent to the designs; dependencies, the kernel/OS, and unknown unknowns remain.

Trust model by mode

Standalone — the host holds the DNS-provider credentials and its own key. Only for hosts you're comfortable giving DNS API access.

Broker + agents — the DMZ broker holds the ACME account and DNS credentials so internal hosts never do. Agents enroll with a one-time token pinned to the broker's CA fingerprint, then use mTLS; the client-cert identity is set from the token, never the CSR. Per-host keys are generated on the agent and never transit the network. The broker is authoritative — compromise of the broker host compromises issuance for the zones its credentials control.

Wildcard distribution — the broker holds a shared *.zone key encrypted at rest (AES-256-GCM) and serves it over mTLS only to agents an explicit policy agents: list grants. A shared key is a larger blast radius than per-host, bounded by renewing at 1/3 lifetime; grant it narrowly.

Offline courier — keys are generated on the airgapped inside host and never cross the airgap (only CSRs travel). Bundles are Ed25519-signed both directions with out-of-band peer-key pinning; the outside companion holds the credentials. Within the threat model (the media between the two sides is fully attacker-controlled), a media attacker is confined to denial-of-service plus benign duplicate re-issuance of an already-requested certificate — never a forged, wrong-key, or wrong-domain certificate.

Residual risks (inherent — not bugs)

  • The broker / courier-outside host is high value. It holds the ACME account key and live DNS credentials. Scope those credentials — delegate the challenge to a throwaway zone (acme-dns / CNAME), use zone-scoped API tokens — so a host compromise can't rewrite production DNS. The allowlist constrains what this tool orders, not the credentials themselves.
  • Revocation is name-based (broker) or absent (courier). Deleting an agent record cuts its API access immediately, but the issued certificate stays cryptographically valid until it expires; gapcert issues no CRL. The courier path has no revoke flow — revoke via the ACME account out of band.
  • Courier pairing is trust-on-first-use. courier pair pins a peer key with no confirmation beyond the fingerprint you verify out of band. Verify it; protect the data directory.
  • Courier expiry is wall-clock. Bundle freshness (14 days) depends on both hosts' clocks being roughly right. A long-airgapped host with a wrong clock can reject fresh bundles or, set far in the past, honor stale ones. Keep the inside clock sane — this is the residual most worth watching for the airgapped population.
  • Certificate Transparency. Every publicly-issued certificate is logged. Per-host certificates publish internal hostnames; prefer wildcards for internal zones, or an internal ACME CA for names that must never appear publicly.
  • Dependencies, kernel, OS, and unknown unknowns are outside what any audit eliminates. govulncheck in CI catches known dependency vulnerabilities.

Reporting

Found a vulnerability? Please report it privately rather than opening a public issue.