Field notes / The information-flow lattice

The finding you can actually close

The lethal trifecta is a heuristic: capabilities co-occurring in one agent session. A reviewer who knows the information-flow literature discounts that. This page is the layer that answers them: labels, a lattice, and a violation that discharges when you apply the fix.

Finding: ATL-217 Layer: L1 deterministic Module: attestral/ifc.py Lineage: Denning 1976

Attestral's headline finding is the lethal trifecta: an agent whose tools can read private data and reach an outbound channel, so one injected instruction anywhere in its inputs can exfiltrate what it sees. As shipped in ATL-202, that finding is a heuristic: named capability groups co-occurring in one session. It is the right smoke alarm, and it is also exactly the shape of claim a security reviewer is trained to push back on. "These capabilities co-occur" is a correlation, not a property.

The information-flow lattice answers that reviewer. It attaches confidentiality and integrity labels to every tool surface, and states the violation as the classic lattice property, with the named label path in the finding text. Same flow, different epistemic standing: ATL-217 is defensible and citable, not a severity with an opinion.

The heuristic is the smoke alarm. The lattice is the instrument. The difference shows the moment you apply a fix: the instrument respects it, the alarm keeps ringing.

The labels

Two dimensions, straight from Denning

Every tool surface in the system model gets two labels, derived from the same capability classes the fleet rules use. The dimensions are the classic ones from Denning's 1976 lattice model, specialized to an agent's tool fleet:

DimensionSourceSink
Confidentiality High: reads secret data · filesystem, database, saas_data, memory Low: lets data leave the boundary · network, messaging
Integrity Low: ingests attacker-influenceable content · network, saas_data, memory High: performs a trust-critical action · shell

A violation is a flow that crosses the lattice the wrong way with no mitigation on the path: high-confidentiality data reaching a low sink, or low-integrity input reaching a high sink.

CONFIDENTIALITY high source filesystem · database · saas · memory declassifier egress allowlist low egress sink network · messaging INTEGRITY low source network · saas · memory endorser human approval on the sink high sink shell / execution
The two flows ATL-217 checks. Each dashed box is a modeled mitigation that breaks its flow when present. The mitigations are dimension-specific, which is what a lattice is for: an egress allowlist declassifies the confidentiality flow but does nothing for integrity, and a human-approval gate endorses the integrity flow but does nothing for confidentiality.

The violations

A property with a named path

Two lattice properties, each stated with the actual component names on the path. A confidentiality violation: a high source can reach a low egress sink with no declassifier, so confidential data can leave the boundary. That is the lethal trifecta, stated as a property. An integrity violation: a low source can reach a high sink with no endorser, so untrusted input can drive a trust-critical action. That is indirect-injection-to-execution, stated as a property.

On the landing page's demo fixture, both fire alongside the heuristics:

$ attestral scan examples/vulnerable-agent
6 components · 17 findings · 4 critical · 13 high
  ATL-202  Tool fleet forms an exfiltration chain (lethal trifecta)
  ATL-207  Unsafe data flow - untrusted input can reach a sensitive action
  ATL-217  Information-flow lattice violation across the tool fleet  (model:ifc)
    fix: Interpose a declassifier (validation, allowlist, or human approval)
    between the labelled source and sink so the flow is broken...

The finding text names the labels and the components. From that same scan, verbatim: "High-confidentiality source(s) [filesystem, jira] can reach low-confidentiality egress sink(s) [web] with no declassifier on the path, so confidential data can leave the boundary. Low-integrity source(s) [jira, web] can reach high-integrity sink(s) [deploy, shell] with no endorser on the path, so untrusted input can drive a trust-critical action." A reviewer can check every word of that against the config. Nothing in it is an opinion.

The payoff

Apply the fix, and the finding clears

A declassifier (confidentiality) or an endorser (integrity) is a modeled mitigation that breaks the flow. Attestral detects one of each, derived by the ingester from the launch configuration, and both are exactly the fixes the heuristic rules already recommend.

The declassifier: an egress allowlist

Constrain the outbound tool to a destination allowlist (--allowed-hosts and kin, matched conservatively: an egress-scoped token, never a bare --allow). Data can now only leave to known hosts, so the confidentiality flow is broken:

$ attestral scan examples/ifc-declassified   # postgres + fetch --allowed-hosts
2 components · 3 findings · 1 critical · 1 high · 1 medium
  ATL-202  Tool fleet forms an exfiltration chain (lethal trifecta)
  ATL-217 does not fire: the egress sink is allowlist-declassified

The endorser: human approval on the sink

Gate the trust-critical tool behind explicit human approval (--require-approval and kin, the positive inverse of the auto-approve signal). An injected command cannot run uninterrupted, so the integrity flow is broken:

$ attestral scan examples/ifc-endorsed   # fetch + shell runner --require-approval
2 components · 4 findings · 1 critical · 3 high
  ATL-203  Tool fleet combines shell execution with outbound network reach
  ATL-207  Unsafe data flow - untrusted input can reach a sensitive action
  ATL-217 does not fire: the trust-critical sink is approval-endorsed

The alarm keeps ringing

ATL-202/203/207 still fire in both fixtures. They score raw capability co-occurrence and do not model mitigations. That is by design: the smoke alarm should be hard to silence.

The instrument respects the fix

ATL-217 discharges the half whose mitigation is in place, and only that half. Apply the allowlist and confidentiality clears; integrity still fires if the flow exists, because an allowlisted fetch tool still ingests untrusted content.

That asymmetry is the whole argument for having both. A finding you can close changes behavior: the fix is concrete, and the scanner visibly credits it. A finding that fires forever gets muted.

Honest limits

What the lattice does not claim yet

Two limits, stated plainly. First, a declassifier or endorser is currently detected fleet-wide by capability, not proven to sit on the specific source-to-sink path; tightening that to a per-path check is the next refinement. Second, the modeled endorser is a human-approval gate on the sink. An input-validation endorser, a sanitizer the untrusted content passes through, is not modeled yet.

Neither limit weakens the direction of error: both can only make ATL-217 fire when a stricter analysis might clear it, never stay silent when a violation exists.

Where it sits

Pure structure, offline, always on

attestral/ifc.py computes violations(model) over the system model: pure structure, no network, no model download. The engine evaluates it behind the model_ifc_violation matcher as ATL-217, in the always-on deterministic layer alongside the attack-path and fleet reasoning. It composes the delegation hop the same way the capability rules do, so a code-defined agent's flows are covered exactly like a config-defined fleet.

$ pip install attestral
$ attestral scan examples/ifc-declassified
$ attestral explain ATL-217

Denning, "A Lattice Model of Secure Information Flow" (CACM, 1976)
Cited frameworks on the finding: OWASP-ASI02/ASI03:2026 · OWASP LLM01 · NIST AC-4, SC-7
Related systems: FIDES · CaMeL · the deterministic layer field notes