trstctl /docs Demo ↗ GitHub ↗

Observability & risk — see your crypto, score it, catch what changes

What it is

Discovery tells you what credentials exist; this page — the smoke detectors and risk register to discovery's census — tells you which ones to worry about and what's changing: Certificate Transparency monitoring (certificates issued for your domains you didn't request), drift detection (a deployed credential moved, replaced, or exposed), credential risk scoring (rank everything by "fix this first"), and the CBOM — a bill of materials flagging weak and quantum-vulnerable crypto.

trstctl also exports the control-plane signal you need to operate those workflows: served HTTP traces and event-sourced audit records can stream to your OpenTelemetry Collector over OTLP/HTTP protobuf. That lets Splunk, Datadog, or any SIEM pipeline consume the same immutable audit stream auditors inspect, with event sequence and tenant attributes for dedupe and gap detection. In air-gapped installs, that collector must be operator-owned on a private host or explicitly allowlisted; the no-phone-home guard blocks public collector endpoints.

Why it exists

A list of credentials only helps if you can act on it. Security teams drown in findings, so the question is always "what first?" — that's risk scoring. Mis-issuance and shadow IT show up as certificates in public logs you didn't request — that's CT monitoring. Quietly broken or loosened deployments cause outages — that's drift. And the looming quantum transition makes "where is our weak/old crypto?" urgent — that's the CBOM. Together they turn inventory into action.

How it works

Credential risk scoring (F19)

Risk scoring assigns every credential a 0–100 score from six weighted factors: age, exposure (blast radius from the graph), privilege, rotation staleness (never-rotated scores highest), owner activity (orphaned scores higher), and sensitivity (wildcards/large SAN sets) — weighted toward exposure and privilege, what hurts most in a breach. Scoring pages the whole inventory, tenant-isolated at the database layer; filter by minimum score, privilege class, or owner.

Status: servedGET /api/v1/risk/credentials, GET /api/v1/risk/contextual-priorities, and the four NHI-posture routes below, each with a matching risk/nhi posture CLI command.

Contextual risk prioritization ranks certificates, NHI, SSH keys, and discovery findings by blast-radius impact, CBOM crypto context, owner state, staleness, and expiry urgency, returning priority reasons, evidence refs, severity, and a recommended action — useful when two credentials score similarly but differ sharply in blast radius.

NHI posture reads the same unified inventory as the dashboard — managed identities, access tokens, and discovery findings — across four conditions:

Endpoint What it answers Key response fields
GET /api/v1/nhi/posture/overprivilege Which grants exceed observed usage? Unused grants, least-privilege recommendation, source row, severity, evidence refs
GET /api/v1/nhi/posture/stale Which credentials are stale, dormant, unused, or orphaned? Thresholds, activity/creation age, owner status, severity, evidence refs, remediation
GET /api/v1/nhi/posture/static-credentials Which credentials are long-lived, static, or overdue for rotation? Credential age, TTL, rotation age, lifetime thresholds, owner status, severity, evidence refs, remediation
GET /api/v1/nhi/posture/exposure Which credentials are internet-exposed or insecurely deployed? Sanitized endpoint/callback URLs, severity, evidence refs, remediation

Grants with no usage evidence stay unclassified rather than counting as a finding; the exposure route is read-only and never echoes credential values. For example, trstctl-cli nhi posture stale reports each stale credential's activity age, owner status, severity, and remediation.

Certificate Transparency monitoring (F17)

Every certificate a public CA issues is recorded in public, append-only CT logs (RFC 6962). If one appears for your domain that you didn't request, that's an early warning of mis-issuance, shadow IT, or attack. trstctl's monitor polls CT logs incrementally from a saved checkpoint, matches entries against watched domains (resistant to the example.com.evil.net suffix trick), and alerts on anything not already in your inventory. Alerts use reliable, journaled, at-least-once delivery with an idempotency key (ct:<log>:<index>) so a retry never double-alerts. Polling runs in its own bounded lane, RFC 6962 binary parsing stays inside the single crypto path, and checkpoints persist so monitoring resumes across restarts, tenant-isolated at the database layer.

Status: served. Use GET/PUT /api/v1/discovery/ct-monitoring, discovery ct-monitoring get|update, or the Posture page to configure watched domains/logs, inspect checkpoints, queue a poll, and review ct_unexpected_issuance findings. The worker polls, records tenant-scoped findings, and queues notifications via the outbox.

Drift detection (F18)

After the agent installs a credential, drift detection notices if reality diverges from intent, comparing each watched file's content fingerprint and permissions and classifying the divergence as Deleted, Replaced (different content), Relocated (same content elsewhere, so a move isn't misreported as deletion), or PermissionChanged (mode/ACL loosened). Permission checks are platform-aware (POSIX mode bits; Windows DACL for broad-access ACEs); the agent reports at startup whether the platform can detect permission loosening. Content hashing goes through the single crypto path; secret material stays in wipeable memory, zeroed after use.

Status: served. Create a Discovery source of kind drift with watched paths, fingerprints, and modes, then start a run; the worker records credential_drift findings and queues notifications. Review findings, see the recommended action, and record a decision via GET /api/v1/discovery/drift-remediation, POST /api/v1/discovery/drift-remediation/{id}/decision, the discovery drift-remediation CLI, or the Posture page. Decisions are event-sourced as discovery.finding.triage_changed audit evidence; the API never stores or returns credential bytes.

The CBOM — cryptographic bill of materials (F52)

You can't plan a crypto migration without knowing what crypto you run. The CBOM scanner inventories cryptographic usage across TLS endpoints and host config files, then classifies each observation: algorithm family and strength, whether it's quantum-vulnerable, and whether it meets policy (default floor: RSA-2048, EC-256, TLS 1.2; bans 3DES/DES/RC4/NULL/EXPORT/MD5/anon). Findings become KindCryptoAsset nodes in the credential graph, feeding blast-radius and compliance reporting and the PQC migration. Scanning runs in its own bounded lane, is non-fatal per source, tenant-isolated at the database layer, and keeps TLS/cert parsing behind the single crypto path.

Status: served. POST /api/v1/cbom/scans (discovery:write, accepts an Idempotency-Key) runs the scanner in the serving binary and records each observation as an immutable cbom.asset.observed event before the read model projects it. GET /api/v1/cbom/assets (risk:read) returns the tenant-scoped inventory plus migration progress. CBOM work has its own bulkhead, so a wide TLS/config sweep rejects fast instead of starving the regular API or enrollment lanes.

Each returned asset includes the discovered algorithm, source, policy result, PQC posture, and a migration target:

Observation Migration target
RSA, ECDSA, Ed25519/EdDSA certificate signatures ML-DSA-65 (FIPS 204)
DSA certificate signatures SLH-DSA-SHA2-128s (FIPS 205)
TLS protocol/cipher findings such as TLS 1.0 or 3DES ML-KEM-768 (FIPS 203)
Already quantum-safe ML-DSA, ML-KEM, or SLH-DSA observations marked post-quantum-ready

migration_progress is computed from the stored inventory: total assets, how many are post-quantum-ready or quantum-vulnerable, and the ready percentage.

Core PQC campaigns turn those observations into owned work without requiring a licence. From /posture, /api/v1/pqc/campaigns, or trstctl-cli pqc campaigns, an operator can assign owner/deadline/wave/readiness, record a manual or third-party remediation for each finding, and close only after every finding has evidence. Closure produces an offline-verifiable signed artifact. Automated fleet execution remains an optional Enterprise executor and is stated as unavailable by edition; campaign tracking itself does not degrade into an upsell-only shell.

In the console

The overview dashboard surfaces a severity-ranked alert center from served risk and certificate-expiry events (no dedicated alerts endpoint — a projection of events the backend already serves); /risk renders a risk-posture summary (counts by band, orphaned credentials, average score) above the scored-credential grid. /notifications serves routing-policy authoring and channel-test delivery; scheduled digest delivery stays outside the served workflow. See The web console.

Use it

Find your riskiest credentials:

# rotate-this-first: high-privilege, score >= 50
trstctl-cli risk credentials --min_score 50 --privilege high --sort score

# blast-radius priority list with CBOM context + recommended action
trstctl-cli risk contextual-priorities

# NHI posture: overprivilege, stale, static-credential, and exposure findings
trstctl-cli nhi posture overprivilege
trstctl-cli nhi posture stale
trstctl-cli nhi posture static-credentials
trstctl-cli nhi posture exposure

Those map to GET /api/v1/risk/credentials?sort=score&min_score=50&privilege=high, GET /api/v1/risk/contextual-priorities, and /api/v1/nhi/posture/*. CT monitoring has a dedicated watchlist/checkpoint endpoint:

# CT-log monitoring: configure watched domains/logs and queue a poll.
trstctl-cli discovery ct-monitoring update --body ct-monitoring.json
trstctl-cli discovery ct-monitoring get
trstctl-cli discovery findings list --run_id "$RUN_ID"

ct-monitoring.json carries the watchlist:

{
  "name": "public-ct-watch",
  "logs": ["https://ct.example.test/log"],
  "watched_domains": ["example.com"],
  "max_batch": 25,
  "run_now": true
}

Drift reuses the source/run/finding path, plus a remediation decision view:

{
  "name": "edge-cert-drift",
  "kind": "drift",
  "config": {
    "watched": [
      {
        "path": "/etc/nginx/tls/edge.crt",
        "class": "certificate",
        "fingerprint": "sha256:...",
        "mode": "0644"
      }
    ]
  }
}
# list drift remediation rows and record an operator decision
trstctl-cli discovery drift-remediation
trstctl-cli discovery drift-remediation decide "$FINDING_ID" --body drift-decision.json
{
  "decision": "investigate",
  "reason": "rotate certificate on the affected host before accepting the new fingerprint",
  "owner": "platform",
  "tags": ["rotation-ticket"]
}

CBOM scan:

curl -sS \
  -H "Authorization: Bearer $TRSTCTL_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: cbom-demo-001" \
  -X POST https://trstctl.example.com/api/v1/cbom/scans \
  -d '{
    "tls_endpoints": ["payments.internal.example:443"],
    "host_configs": ["/etc/nginx/sites-enabled/payments.conf"]
  }'

Then read the migration inventory:

curl -sS \
  -H "Authorization: Bearer $TRSTCTL_TOKEN" \
  https://trstctl.example.com/api/v1/cbom/assets

The response contains items and migration_progress; a non-empty quantum_vulnerable count means crypto needs a migration target.

Pitfalls & limits

Capability Status today
Credential risk scoring (F19) Served/api/v1/risk/credentials, /api/v1/risk/contextual-priorities, and the four /api/v1/nhi/posture/* routes above, plus risk/nhi posture CLI
CT monitoring (F17) Served — CT watchlist/checkpoint API, CLI, Posture UI, plus Discovery ct_log execution and outbox-backed alerts
Drift detection (F18) Served — Discovery drift execution, outbox-backed alerts, remediation API/CLI/Posture dashboard, and event-sourced decisions
CBOM (F52) Served/api/v1/cbom/scans, /api/v1/cbom/assets, core /api/v1/pqc/campaigns, event-backed inventory + signed campaign closure

Other notes: CT monitoring depends on the logs/domains you list. Drift permission detection is best-effort where the ACL model can't be fully read — the agent says so rather than giving false assurance. The CBOM is only as complete as the sources you point it at (TLS endpoints + config files). See Current limitations for the served-vs-library picture.

Reference

  • Served: GET /api/v1/risk/credentials (params sort, min_score, privilege, owner); GET /api/v1/risk/contextual-priorities; CLI risk credentials, risk contextual-priorities.
  • Risk factors: age, exposure, privilege, rotation staleness, owner activity, sensitivity (weighted; defaults favor exposure + privilege).
  • CT: Discovery source kind ct_log; finding kind ct_unexpected_issuance; idempotency key ct:<log>:<index>; RFC 6962.
  • Drift: Discovery source kind drift; finding kind credential_drift; drift types Deleted, Replaced, Relocated, PermissionChanged; remediation routes GET /api/v1/discovery/drift-remediation and POST /api/v1/discovery/drift-remediation/{id}/decision.
  • CBOM API: POST /api/v1/cbom/scans (discovery:write, Idempotency-Key required); GET /api/v1/cbom/assets (risk:read).
  • CBOM policy floor: RSA-2048, EC-256, TLS 1.2; bans 3DES/DES/RC4/NULL/EXPORT/MD5.
  • CBOM event/read model: cbom.asset.observed projects into crypto_assets; rebuilds/snapshots replay the same inventory.

See also

Discovery & inventory (what feeds these) · Graph, query & AI (exposure / blast radius) · Lifecycle & PQC (migrating off weak crypto the CBOM finds) · Policy & governance (compliance reporting) · glossary: Certificate Transparency, drift, CBOM, PQC

Covers: F17, F18, F19, F52

Rendered live from github.com/ctlplne/trstctl — found a mistake? edit this page.