checkId: check.core.plugins.trustroot plugin: stellaops.doctor.core severity: fail tags: [quick, core, plugins, security]

Plugin trust-root present

Doctor check check.core.plugins.trustroot — turns a silent plugin trust-root failure into a visible one (ADR-026 Decision 6). It verifies the trust-root (cosign.pub) used to admit mounted Doctor runtime plugins is present and readable; a missing/invalid root silently rejects every signed plugin.

What It Checks

This is a core check: it is contributed by the in-process Core plugin, so it always runs independent of mounted-plugin admission — it cannot itself vanish when a mounted plugin is rejected.

It reads the trust-root state through the engine-lib seam IMountedPluginAdmissionStatus, which the Doctor WebService backs with the same mounted-bundle loader options /readyz uses (Doctor:RuntimePlugins:TrustRootPath / AllowUnsigned). So a Doctor RUN and /readyz agree about the trust-root state. The probe inspects a local file only — no network, no external callback (air-gap invariant).

ResultCondition
Passthe trust-root file is present and readable at the configured path
Failthe trust-root is missing, unreadable, or its path is not configured
Skipthe AllowUnsigned dev escape hatch is enabled (the only loader configuration where a missing root is acceptable — the loader then admits bundles without a trust-root), or there is no mounted-plugin host (the seam is not registered, e.g. the CLI)

It does not Pass-by-default: when there is no host to evaluate, or when the dev hatch removes the requirement, it Skips with a reason rather than reporting a false green. The evidence emits TrustRootPresent, TrustRootOutcome (PresentAndReadable / Missing / Unreadable / NotConfigured), TrustRootPath, and AllowUnsigned.

Why It Matters

The mounted bundle loader is fail-closed: each mounted Doctor plugin (Release, Environment, Scanner, Compliance, BinaryAnalysis, Timestamping) must clear detached RSA/SHA-256 signature verification against the trust-root before any code runs. If the trust-root (cosign.pub) is missing or unreadable, the loader rejects every signed bundle — and those plugins’ checks silently disappear from the run while Doctor still reports all-green. The trust-root is therefore a platform-wide single point of failure for plugin admission. This check makes its absence a visible Fail instead of a silent gap, and is the upstream companion of check.core.plugins.loaded (which reports the resulting rejected plugins).

Common Causes

How to Fix

Place the cosign public-key trust-root at the configured path so the loader can verify mounted bundle signatures, then redeploy the Doctor service. The trust-root is produced/distributed alongside the signed bundles by devops/build/package-runtime-plugins.ps1.

Docker Compose

The trust-root is a read-only mount in devops/compose/docker-compose.plugins.doctor.yml (with AllowUnsigned=false). Confirm cosign.pub exists on the host at the mounted source path, then recreate the service:

# Verify the trust-root is present on the host at the path mounted to
# /app/etc/certificates/trust-roots/plugins/doctor/cosign.pub, then:
docker compose -f devops/compose/docker-compose.plugins.doctor.yml up -d --force-recreate doctor-web

Bare Metal / systemd

Ensure the trust-root exists and is readable by the service user:

ls -l /app/etc/certificates/trust-roots/plugins/doctor/cosign.pub
# If missing, copy the cosign.pub produced by package-runtime-plugins.ps1 into place, then:
sudo systemctl restart stellaops-doctor

Kubernetes / Helm

The trust-root is typically mounted from a Secret. Confirm the Secret exists and is keyed/mounted at the configured path, then roll out:

kubectl get secret doctor-plugin-trust-root -o jsonpath='{.data.cosign\.pub}' | base64 -d | head -1
kubectl rollout restart deployment/doctor

Dev only: setting Doctor:RuntimePlugins:AllowUnsigned=true makes the loader admit bundles without a trust-root and makes this check Skip. Never enable it in production — admission is a supply-chain boundary (the loader stays fail-closed with AllowUnsigned=false).

Verification

stella doctor run --check check.core.plugins.trustroot

A Pass means the trust-root is present and readable. A Fail reports the missing/unreadable path; a Skip means either the dev hatch is enabled or this host has no mounted plugins.

See the Doctor reference for the full check catalog, CLI usage, and export bundles.