trstctl /docs GitHub ↗

Graph, query & AI — see how everything connects, and ask in plain language

What it is

trstctl doesn't just hold a flat list of credentials — it builds a graph of how they connect (which workload owns which key, which key was issued by which CA, what each can reach), exposes a unified query layer to ask questions across all its data safely, and layers AI on top: a pluggable model adapter, grounded root-cause analysis with natural-language questions, and an MCP server so external AI agents can query trstctl through a safe, read-only interface.

The mental model: the graph is a map of your credential city showing every road between buildings; the query layer is the one inspector's desk every question must pass through (so nobody peeks at another tenant's records); and the AI layer is an analyst who answers your questions only from evidence pulled across that desk, always citing sources.

Why it exists

Security questions are rarely about one credential — they're about relationships: "if this key leaks, what's exposed?", "what does this AI agent actually have access to?", "why did this renewal fail?". Answering those needs a graph and a way to query it. And because those queries touch sensitive data across many subsystems, they need one rigorously scoped path rather than each feature reinventing access control. The AI layer then makes the whole thing approachable — ask in English, get a cited answer — without ever letting a model invent facts or leak across tenants.

How it works

The credential graph (F21)

The graph models your inventory as nodes (workloads, credentials, issuers, resources, crypto assets, attestations) and impact-oriented edges (ISSUED, OWNS, DEPLOYED_TO, GRANTS_ACCESS, CONNECTS_TO, EXHIBITS) where an edge A→B means "compromising A puts B at risk." It's built on demand from the store, every read scoped by tenant (AN-1), so a traversal can never escape the tenant boundary. On top of it: Reachable (breadth-first reach), BlastRadius (everything a compromise touches, grouped by kind), and a deliberately minimal Cypher-style Query.

Code: internal/graph (Build, Reachable, BlastRadius, Query). ServedGET /api/v1/graph, /graph/reachable/{id}, /graph/blast-radius/{id}, POST /api/v1/graph/query, plus the graph CLI group.

The unified semantic query layer (F75)

This is the one security boundary every advanced consumer (AI, MCP, compliance) routes through, so scoping is never reinvented. Callers submit a typed Spec — allow-listed surfaces (log, graph, inventory, owners, CBOM), allow-listed fields and operators, bound values — and never raw SQL or Cypher. The engine enforces, by construction: tenant first (the tenant is always the caller's, non-overridable, RLS underneath — AN-1), then RBAC (you must hold the permission for every selected surface, or the whole query is denied before execution — not post-filtered). It runs on a bulkhead with a wall-clock deadline and row caps (AN-7), pins results to an event-log offset for consistency (AN-2), and returns deliberately coarse errors so a caller can't tell "out of scope" from "not found."

Code: internal/query (Engine, Spec, Surface, Predicate). Library (the designated mount point is the serving-integration sprint).

The pluggable AI model adapter (F76)

trstctl's AI features are model-agnostic: a thin adapter routes reasoning to either a cloud LLM or a local Ollama/vLLM endpoint by config, for air-gapped or data-sovereign deployments. Critically, a redactor runs before any prompt leaves the process — stripping PEM blocks, secret/token assignments, and long base64 runs (AN-8) — so key material can never reach a model or its logs. If no model is configured, the rest of trstctl degrades gracefully. Code: internal/aimodel (Adapter, DefaultRedactor, CloudModel, LocalModel). Library.

Grounded RCA & natural-language query (F77)

You ask a question in plain language ("what's the blast radius of the payments cert?"); trstctl gathers real evidence through the query layer (inheriting its tenant+RBAC scoping), then a synthesizer answers using only that evidence — every claim carries a citation (source#id), and with no evidence it says "insufficient evidence" rather than inventing an answer. The prompt explicitly treats retrieved data as untrusted (so a hostile string in a SAN can't become an instruction), the pipeline is strictly read-only, and every gather is audited. Code: internal/rca (Pipeline, Synthesizer). Library.

The trstctl MCP server (F78)

The Model Context Protocol is how external AI agents call tools. trstctl's MCP server exposes four read-only tools — query_credentials, get_blast_radius, explain_incident, compliance_status — and nothing that writes (it has no remediation tools). Every call is scoped to one tenant (a cross-tenant call is refused before any query), per-caller rate-limited to resist enumeration, and audited; answers flow through the grounded RCA pipeline so they're cited and redacted. Fittingly, the server holds a workload identity issued by trstctl's own broker — it dogfoods the platform. Code: internal/mcpserver (Server, Call, Tools). Library.

Use it

The graph is served — explore relationships and blast radius:

trstctl-cli graph nodes
trstctl-cli graph blast-radius cert:payments-tls
trstctl-cli graph query 'MATCH (w:workload)-[:OWNS]->(c)-[:DEPLOYED_TO]->(r) WHERE w.name = "payments-svc" RETURN c, r.name'

Those map to the served /api/v1/graph* routes. The query engine, RCA pipeline, and MCP server are driven through their Go APIs today — e.g. a grounded question:

ev, _  := pipeline.Gather(ctx, tenantID, "payments cert", "what is its blast radius")
ans, _ := synth.Answer(ctx, ev)   // ans.Citations = ["graph#cert:...", ...]

Pitfalls & limits

Capability Status today
Credential graph (F21) Served/api/v1/graph*, graph CLI
Semantic query layer (F75) Library-complete, tested; serving mount is the integration step (EXC-WIRE-04)
AI model adapter (F76) Library-complete, tested; off unless a model is configured; not served by the binary (EXC-WIRE-04)
Grounded RCA / NL query (F77) Library-complete, tested; not yet served by the binary (EXC-WIRE-04)
MCP server (F78) Library-complete, tested; no HTTP/gRPC wrapper wired yet; not served by the binary (EXC-WIRE-04)

Other notes: the graph and query layer are built per request from the store, so very large tenants pay a build cost (bounded by caps). The AI features are grounded and read-only by design — they won't take actions and won't answer beyond the evidence. With no model configured, RCA returns the raw evidence listing rather than a prose answer. See Current limitations.

Reference

  • Graph (served): GET /api/v1/graph, /graph/reachable/{id}, /graph/blast-radius/{id}, POST /api/v1/graph/query; CLI graph.
  • Node kinds: workload, credential, issuer, resource, crypto-asset, attestation.
  • Query surfaces: log, graph, certificates, owners, CBOM (tenant-then-RBAC, allow-listed fields/operators, no raw SQL/Cypher).
  • AI: model adapter (cloud or local Ollama/vLLM) with boundary redaction; RCA returns cited answers; MCP tools are read-only and rate-limited.

See also

Discovery & inventory (what populates the graph) · Observability & risk (exposure scoring uses the graph) · Incident response & JIT (blast-radius-driven remediation) · Workload identity (the MCP server's own identity) · Semantic query layer design · glossary: event sourcing, RLS

Covers: F21, F75, F76, F77, F78

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