Every posture check maqpna doctor runs, what it protects against and the value that fixes it, plus the hardening steps outside the checks.
maqpna doctor and the gateway's GET /v1/posture report every item below as pass, warn, fail, info, skip
or accepted. An installation made from the prod profile (values-production.yaml), with every CHANGE-ME
replaced, has no failed check. Work through this page before you let agents act on production systems.
flowchart LR
A[maqpna doctor --offline<br/>--config gateway.json] --> B[Fix values]
B --> C[maqpna values validate<br/>--profile production]
C --> D[maqpna upgrade]
D --> E[maqpna doctor --strict]
E -->|warn or fail| B
E -->|pass| F[Record accepted risks]
maqpna doctor --offline evaluates a gateway configuration file without a gateway or a cluster. Real output for a
minimal configuration with static admin token, file state and no DLP:
$ maqpna doctor --offline --config gateway.json
STATUS CHECK COMPONENT DETAIL
FAIL admin-auth-mode gateway adminAuth.mode=token
fix: set adminAuth.mode: oidc (Helm adminAuth.mode) so approvers come from verified tokens; the static token is for dev/bootstrap only
WARN audit-failure-policy audit auditFailurePolicy=open
fix: set auditFailurePolicy: closed (Helm gateway.auditFailurePolicy: closed)
WARN state-backend state stateBackend.type=file
fix: set state.backend: postgres and gateway.replicas >= 2 for HA (docs/state-backend.md)
WARN audit-signed-checkpoints audit no auditCheckpointKey (MAQPNA_AUDIT_HMAC_KEY unknown offline)
fix: set audit.checkpointSigning.enabled: true with a Secret holding audit-signing.pem (Ed25519)
WARN dlp-default-profile gateway dlp.defaultProfile=(unset)
fix: define a profile under dlp.profiles and set dlp.defaultProfile
WARN admin-listen gateway adminListen=(unset: admin API on the agent-facing listener)
fix: set gateway.config.adminListen (e.g. ":9090") and expose it only to operators
WARN tls-svid gateway tls off, svidMode=off
fix: enable spire and gateway.tls with svidMode: required so a stolen token is useless outside its sandbox
WARN static-upstreams gateway static upstreams: echo
fix: register MCP servers as MCPServer objects (per namespace, hot-reloaded) and remove gateway.config.upstreams
INFO guards-fail-mode gateway no injection classifier guards configured
fix: configure gateway.guards (F-29) for tool results from untrusted sources
INFO sovereignty-policy gateway no SovereigntyPolicy: upstream egress is not residency-checked
fix: enable sovereignty (Helm sovereignty.enabled) when data residency matters
PASS otel-residency gateway span export off (propagation only)
PASS budget-currency gateway currency USD
SKIP admin-break-glass-token gateway covered by admin-auth-mode
SKIP audit-worm-sink audit MAQPNA_AUDIT_SINK unknown (offline)
SKIP cedar-engine gateway unknown offline
SKIP capture-key-custody gateway captureArgs disabled
SKIP token-vault-issuers identity tokenVault disabled
SKIP license license licence state unknown (offline config)
score 26/100: 1 fail, 7 warn, 2 pass, 2 info, 6 skip, 0 accepted
It exits 3 because a check failed. maqpna config validate gateway.json --strict checks the same file's syntax,
references and gateway rules.
--strict exits 3 on warnings too. Accept a known risk with --accept ID,... for one run, or permanently for gateway
checks with gateway.config.posture.accept: [<id>].
Pin images by digest (image.digest) after you verify them (Verify releases).
Keep the hardened pod defaults. Every pod runs as UID 65532, non-root, read-only root filesystem, no
capabilities, RuntimeDefault seccomp. Override per component only if you know why.
Bind session principals.principalBinding.mode: requester makes spec.user equal to the Kubernetes user who
created the session, unless that user is a trusted creator (for example a portal ServiceAccount).
Map roles to stable identifiers. Role mapping uses claims such as username and email; prefer user:<sub> entries
and a separate identity provider per tenant on shared installations.
Limit approval previews.approvals.preview.mode: redacted (the default) masks obvious secrets; hash or
none shows approvers less.
Narrow egress allow-lists. The egress proxy sees only the CONNECT host; avoid shared-CDN host names in
sovereignty.allowedEgressHosts and browser allow-lists.
Rate-limit public routes at your ingress, in particular /v1/connect/* (consent flows for connected accounts).
Raise maxScanBytes deliberately. DLP denies content over maxScanBytes by default when the direction redacts
or denies; set onOversize: audit only when you accept unscanned content.