Skip to main content

Runtime Guard Reference

Reference

Lookup material for Runtime Guard — endpoints, the capability-state contract, reconciliation row and destination fields, suppression reasons, and destination identity rungs. For what these mean and how to read them, see Runtime Guard.

Endpoints

Three read-only endpoints. All require an admin or viewer role; a developer (builder) token cannot read cluster-wide runtime findings.

EndpointReturns
GET /api/v1/runtime/statusCapability snapshot: CRD presence, per-node sensor and BPF-LSM state, RBAC gaps, ingest lag
GET /api/v1/runtime/reconciliationOne row per workload for the window, sorted by bypassedDestinations descending
GET /api/v1/runtime/workloads/{key}/observationsRaw observations for one workload, grouped by signal family
curl "$YOUR_WORKSPACE_URL/api/v1/runtime/reconciliation?window=6h" \
-H "Authorization: Bearer $ADMIN_TOKEN"

The window parameter

/reconciliation and /observations accept an optional window duration (for example 15m, 1h, 6h, 24h).

  • Default: 1h
  • Clamped to the range 5m24h
  • An unparseable value falls back to the default rather than erroring

The key path parameter

The workload key is the canonical <namespace>/<kind>/<name> string, percent-encoded so it arrives as a single path segment:

curl "$YOUR_WORKSPACE_URL/api/v1/runtime/workloads/search%2FDeployment%2Frag-indexer/observations" \
-H "Authorization: Bearer $ADMIN_TOKEN"

The disabled shape is a contract, not an error

When the feature is off, the tenant is Nirmata-hosted, or the controller could not start, all three endpoints return HTTP 200 with enabled: false and a human-readable reason. They never return a 4xx or 5xx for this case.

The enabled and reason fields are common to all three. The rest of each payload is that endpoint's own data fields, present but empty:

EndpointEmpty-data fields in the disabled shape
/statusnodes: [], rbacGaps: []
/reconciliationrows: []
/workloads/{key}/observationsgroups: [], plus workloadKey echoing the key you requested

For example, /reconciliation:

{
"enabled": false,
"reason": "unavailable on Nirmata-hosted (SaaS) tenants",
"rows": []
}

This is deliberate: a client — and the admin console page — must be able to state why the surface is dark rather than render an empty view that looks like a clean result. The reasons are:

reasonMeaning
not running in KubernetesThe environment predicate failed; the config flag is irrelevant
unavailable on Nirmata-hosted (SaaS) tenantsEnvironment predicate; fails closed even with runtimeGuard.enabled: true
disabled by configEnvironment is eligible, but runtimeGuard.enabled is false
runtime guard is enabled but inactive on this replica (no Kubernetes API access or no shared store)Configured and eligible, but this replica did not start the controller
runtime guard is not configuredNo configuration is present at all

Capability states

Every capability fact is three-valued, and unknown is never conflated with unavailable.

StateMeaningRead it as
availableThe capability was positively establishedEvidence of this class can be produced
unavailableAbsence was positively establishedEvidence of this class cannot be produced here
unknownThe evidence to decide is not presentCannot tell — not clean, and not absent

unknown arises from, among others: no capability probe has completed yet; a node's status shard is older than the freshness window (two minutes — four missed 30-second flushes); or no positive BPF-LSM evidence source is wired.

The distinction is load-bearing rather than pedantic. A node that has simply been idle since the sensor started looks identical to a node whose hook never loaded, so idleness must yield unknown. Reporting it as unavailable would be a false negative; reporting it as available would be a fail-open — and a fail-open of exactly this kind shipped once and had to be fixed, when a hosted runner with no BPF-LSM token was reported as capable.

status fields

FieldTypeNotes
enabledboolFalse means read reason; all other fields are empty
crd / crdReasonstate / stringWhether the RuntimePolicy CRD is served. Re-probed every 5 minutes, so a later sensor install is picked up without restarting AIControls
bpfLsm / bpfLsmReasonstate / stringThe policy-level BPF-LSM answer across the fleet
nodes[]arrayPer-node node, sensorAlive, bpfLsm, reason
rbacGaps[]arrayPermissions the ServiceAccount lacks, from the startup SelfSubjectAccessReview: group, resource, verb, namespace
ingestLagobjectSee below
probedAtUnixintWhen the capability probe last ran. 0 means never — state is unknown, not clean

ingestLag

Reconciles the sensor's own counters against what AIControls stored.

FieldMeaning
lagKernel observations the sensor recorded that are not reflected in stored findings. A figure > 0 indicates possible loss
perNodeThe same shortfall, per node
scrapedAtUnixWhen the sensor counters were last read. 0 means never scraped — which is unknown, not clean
scrapeErrorSet when either side of the comparison could not be read. When present, any lag figure is untrustworthy and completeness is unknown

A small transient lag is usually just the sensor's roughly-10-second report flush. A figure that persists across refreshes means findings were dropped — kernel map pressure, or the sensor's 500-result-per-report cap, after which later findings are discarded permanently.

On a busy cluster the common cause is network volume: that policy carries no allow list by design, so in-cluster service-to-service traffic is reported alongside external traffic and competes for the same cap, and a genuine provider destination can be crowded out. This is the operator-visible symptom of an incomplete table — loss surfaces as a persistent lag figure, not as a low count with nothing to explain it.

Reconciliation payload fields

Top-level fields on /reconciliation, alongside enabled and reason.

FieldTypeMeaning
windowStartUnix / windowEndUnixintThe window actually used, after clamping
rows[]arrayOne row per workload — see below
networkDetectorDisabledboolTrue when the network-based detector stood down for this window. Two causes produce it — the egress-NAT auto-detect, and unusable cluster facts — so the flag alone does not say which. Network-sourced evidence is suppressed with the matching reason (egress-nat-detector-disabled or cluster-facts-unavailable); DNS-based findings are unaffected in either case
disabledReasonstringWhy, in prose, when networkDetectorDisabled is true. This is what distinguishes the two causes — present so a client can state the reason rather than render a thinner table with no explanation
ingestLagobjectRepeats /status's lag figure so the table can show its banner without a second request
networkDetectorDisabled: true means the window is thinner, not cleaner

Connections are the dominant evidence class. A window where the network detector stood down is backed by DNS evidence alone, so a low bypassedDestinations in that window carries much less weight than the same number in an ordinary one. Surface disabledReason — never present the count on its own, and never assume the egress-NAT cause: read the reason to find out which stand-down fired.

Reconciliation row fields

FieldTypeMeaning
workloadKeystringCanonical <namespace>/<kind>/<name>
namespace, kind, namestringThe same, split out
bypassedDestinationsintThe headline. Count of distinct unsuppressed provider destinations this workload demonstrably reached outside governance — by kernel-observed connection, or by resolving the provider name itself. Both evidence classes feed this one set; there is no separate connection count. A set count: unit-free, unaffected by DNS caching or connection reuse. 0 always means the set was empty
mintRaw evidence. Kernel events (connections + resolutions) to unsuppressed external provider destinations
nintRaw evidence. Gateway-recorded calls joined to this workload — every audited request type (MCP tool calls, LLM requests, and egress), not only provider traffic
dnsOnlybooltrue means the row's entire unsuppressed evidence is DNS resolutions, with no observed connection behind any of it — a weaker finding than a connection-backed one. false means either at least one destination was connection-backed or the row has no unsuppressed evidence at all. It must be read together with m / bypassedDestinations, never alone
joinMethodstringAlways name-based: a heuristic join of gateway identities to workloads, never a cryptographic binding
attributionConfirmedboolFalse means the pod could not be resolved to an owning workload and the row fell back to pod-level attribution. The row keeps its full headline count — the evidence is real; only who is uncertain. Rendered as "attribution unconfirmed"
attributionNotestringpod-level (multi-container) when a known mesh or telemetry sidecar was detected: the sensor attributes to the pod cgroup, so sidecar egress cannot be separated from the application's
destinations[]arrayPer-destination evidence, suppressed entries included
m and n are not comparable

m counts kernel events; n counts gateway requests; the populations do not overlap. HTTP/2 multiplexing means one connection carries many requests, and a governed call produces no workload-side kernel evidence at all. Never compute m - n — it is not a quantity. The headline is bypassedDestinations, and it is derived from kernel evidence alone.

dnsOnly: false is not by itself a connection-backed finding

dnsOnly is true only when the row has unsuppressed evidence and all of it is resolutions. A row with no unsuppressed evidence — m: 0, bypassedDestinations: 0 — is therefore false as well, with no connection behind it. The network observation gives nearly every pod in the cluster a row, so most rows in a healthy cluster are exactly that shape. The pair to test is dnsOnly: false and a non-zero m; the admin console likewise does not show a network evidence badge on a row with no evidence.

Rows are sorted by bypassedDestinations descending, then by m descending, then by workload key.

destinations[] fields

FieldTypeMeaning
destNamestringResolved display name, or ip:<address> (unattributed) when no identity could be established
destIPstringDestination address; empty for DNS-only evidence that never produced a connection
sourcenetwork | dnsThe strongest evidence that fired. network when any connection to this destination was observed, otherwise dns — the destination was resolved but no connection to it was seen. A resolution is not a connection; both are observed, and this field is which one you have
behaviors[]arrayEvery sensor behavior that fired for this destination
countintConnection count when source is network; resolution count when dns
governedCallsintGateway-recorded calls this workload made to this same host. Rendered as "also governed (n)". On an unsuppressed finding this means split traffic and strengthens the finding; it never clears it. Sourced from egress-plane audit events only — see the note below
rung / confidencestring / floatDestination identity ladder result — see below
kindprovider | infra | imds | internalFinding classification
suppressed / suppressReasonbool / stringWhether this destination is excluded from the count, and why. Suppressed destinations remain in the list
governedCalls requires the egress plane

Only audit events with a request type of egress carry a destination host, so governedCalls is derived from network egress filtering alone. Where that plane is not enabled, governedCalls is 0 on every destination and the "also governed" tag never appears — which is not evidence that traffic was unsplit. n is unaffected: it counts every audited request type.

Finding kinds

kindMeaning
providerAn external destination. Counts toward bypassedDestinations when unsuppressed
infraPackage registries and similar. Suppressed from the count, retained in the list
imdsThe cloud instance metadata service (169.254.169.254). Never suppressed — it gets its own finding kind rather than being swept up with link-local traffic — and not counted as a provider connection
internalCluster or node infrastructure suppressed by a deterministic rule

Suppression reasons

suppressReasonWhat it excludes
corednsCluster DNS
kube-apiserverThe Kubernetes API server
in-clusterIn-cluster addresses and *.svc.cluster.local names, derived from the API server rather than guessed from CIDR configuration
node-or-link-localNode and link-local addresses — except 169.254.169.254
aicontrols-gatewayAIControls' own Service, by IP or by resolved name. This is the expected shape for a correctly governed workload
reverse-dnsin-addr.arpa PTR questions — resolver plumbing, not destinations
search-domain-expansionVariants generated by the Kubernetes default ndots:5 resolver configuration, which expands every multi-label question several-fold
sidecar-control-planeService-mesh and telemetry control planes, which are never AI providers
package-registryPackage registries (kind: infra)
egress-nat-detector-disabledNetwork-sourced evidence in a window where the egress-NAT auto-detect disabled the network detector. DNS-based findings are unaffected
cluster-facts-unavailableNetwork-sourced evidence in a window where the API-server-derived cluster facts were missing or stale, so an address could not be told apart from an in-cluster one. DNS-based findings are unaffected — they are classified by name

The last two are stand-down reasons rather than per-destination rules: they apply to network-sourced evidence across the whole window and always come with networkDetectorDisabled: true and a disabledReason. Every other rule in the table matches on a name, which is why unusable cluster facts leave an address-only connection with nothing to classify it and force the stand-down.

Separately from suppression, findings attributed to AIControls' own pods — including a previous AIControls pod — are dropped before any counting. The proxy is an in-cluster hop, so a governed workload's provider calls leave the AIControls pod; counting them would inflate the number in the customer's favour.

Destination identity rungs

An observed connection yields an address, not a name: the kernel supplies IPv4 addresses and TLS SNI is invisible to the sensor. Destination names are therefore resolved through a confidence-graded ladder. Resolution is monotonic-upgrade-only within a window.

rungSourceconfidence
R0The pod's own DNS answer was observed0.95
R2Cluster facts from the API server — Service ClusterIPs, EndpointSlice addresses, Pod IPs0.98
R6No identity could be established. destName is rendered ip:<address> (unattributed) rather than guessed0

Rungs R1, R3, R4, and R5 are reserved for later work and are not produced today; they would carry 0 confidence if they appeared.

R2 is what names the in-cluster half of a connection. The non-cluster half lands on R6 whenever the sensor did not observe the pod's own DNS answer for it — encrypted DNS, a cached answer, or a hardcoded IP. An R6 destination is a real, counted finding whose name is missing, not a discarded one. Closing that gap needs the reserved rungs, which are not implemented.

The sensor is IPv4-only pre-1.0, so an IPv6 destination produces no row at all — not an unattributed one, not a flagged one. No field, rung, or reason records that IPv6 traffic was outside the sensor's reach. Read an empty result as evidence about IPv4 alone: it cannot distinguish "no IPv6 traffic occurred" from "IPv6 traffic occurred and was never observed".

Observation families

/observations groups raw findings by signal family. AIControls authors one monitor-mode observation policy per family below (except other), covering every namespace but kube-system and kube-public. Families available depend on node capability — open and exec require nodes booted with lsm=bpf.

familyAdmin console headingRequires BPF-LSMFeeds bypassedDestinations
dnsNames resolved (DNS)NoYes
networkDestinations reached (network)NoYes
openPaths opened (file)YesNo
execBinaries executed (exec)YesNo
otherOther observationsNo

The network policy carries a deny-all match with no allow list, so every connection a governed pod opens is reported, in-cluster traffic included. Allow-listing provider destinations would hide the traffic the feature exists to surface; uninteresting destinations are removed after the fact by suppression rules, which stay visible with a reason attached. The cost is volume: on a busy cluster in-cluster chatter competes with external traffic for the sensor's 500-result-per-report-per-node cap, and the symptom of losing that race is a persistent ingestLag figure, never a clean-looking result.

Each item carries rule, behavior, subject, destIP, destName, count, node, podName, namespace, result, mode, and observedAt (Unix seconds).

See also