Blog / Notation

Notating Supply-Chain Attack Paths: Where #10 Goes and What the Boundary Operator Carries

A supply-chain step is not "the part where a vendor was involved". It is one specific moment — and the notation is built to pin it down.

BK
Bernhard Kreinz
• • Loading read time...
In short

#10 Supply Chain Attack is placed at exactly one point in a path: the Trust Acceptance Event, the moment a third-party artifact becomes authoritative inside the target domain. Because #10 is a bridge cluster, that step also carries a boundary operator naming the context and the two responsibility spheres it crosses. Everything that happens after the trust is accepted classifies on its own merits — usually #7, sometimes #2 or #3 — and is a separate step.

The question this answers

Ask three analysts to write the attack path for a poisoned dependency and you will get three different answers. One puts #10 where the vendor was breached. One puts it where the malicious code ran. One sprinkles it across the whole chain because "the whole thing was a supply-chain attack". All three paths are unusable for comparison, which defeats the point of having a notation at all.

TLCTC resolves this with a placement rule and a boundary operator. Together they answer two questions that a bare cluster number cannot: at which moment did the trust relationship become causal, and between whose domains did it operate.

#10 is a bridge cluster, so it carries a boundary

The ten clusters split into two topologies. Clusters #1–#7 are internal: they describe an attacker acting against a component inside a domain. Clusters #8, #9 and #10 are bridge clusters: each one describes a crossing between domains — physical, human, and trust respectively.

A bridge step that does not say what it bridged is incomplete. So every bridge cluster step carries a boundary operator:

Boundary operator
||[context][@Source→@Target]||

#10 ||[update][@Vendor→@Org]||     third-party update accepted by the org
#9  ||[human][@External→@Org]||    a person inside the org is manipulated
#8  ||[physical][@External→@Org]|| hardware reached physically

The operator is not an alternative to the cluster number and not a "more operational" view of it. It is part of the same step. Writing #10 alone leaves the reader to guess which boundary was crossed; writing the operator alone leaves out the cause.

R-SUPPLY: the Trust Acceptance Event

The placement rule is normative and short:

#10 Supply Chain Attack MUST be placed at the Trust Acceptance Event (TAE) — the moment the third-party trust link is honored and the trust artifact becomes authoritative inside the target domain.

Two consequences follow, and both are places analysts routinely go wrong.

The vendor's own compromise is not your #10. When an attacker breaches a software vendor, that breach is a path of its own inside the vendor's sphere — perhaps #9 → #4 → #1. It is not a supply-chain step, because nothing has yet been accepted anywhere. Your #10 begins when your systems honour the trust link.

The payload running is not your #10 either. Acceptance and execution are different moments with different generic vulnerabilities, so by Axiom VI they are different steps. Trust acceptance is #10; foreign executable content running is #7, recorded at the execution moment per R-EXEC.

The falsifiability test

A step is a genuine #10 if removing the third-party trust link would have broken the path at that point. If the attack still works with the trust relationship deleted, you are looking at something else — most often #1, #2 or #7 wearing a vendor's logo.

What the operator carries

The operator has three slots. The context names the kind of boundary; the two spheres name who was accountable on each side, written source first.

Contexts and spheres are Layer 2 of the framework — the reference registry — which means they are organisation-specific by design. The published reference registry defines seventeen contexts, and an organisation is expected to extend the list to match how its own responsibilities are actually divided:

Context Boundary it names
humanHuman decision / manipulation boundary (bridged by #9)
physicalPhysical domain boundary (bridged by #8)
updateThird-party update / dependency boundary (bridged by #10)
devBuild / CI-CD responsibility boundary
authIdentity provider / authentication responsibility boundary
apiAPI integration / service-to-service boundary
cloudCloud shared responsibility boundary
messaging, emailMessaging and email transport boundaries
cdn, network, signaling, mediaDelivery and network infrastructure boundaries
browser, exploit, legal, adminRendering, delivery-infrastructure, jurisdictional and management-plane boundaries

Spheres follow the same logic: @Org, @Vendor, @External, @Cloud, @MSP, @Partner and so on. The registry is a starting point, not a closed vocabulary — the useful question is always "who was accountable for this side of the boundary", and that answer differs between organisations.

Transit is not a supply-chain step

A party that merely relays an attack has accepted nothing. Carrying is not trusting, so a relay is marked with the transit operator ⇒, not with #10:

Transit vs trust acceptance
#9 ||[messaging][@Attacker⇒@SMSProvider→@Victim]||
   the SMS provider relays the phishing link — transit, not #10

#10 ||[dev][@External→@Org]||
   the org installs the package — trust accepted, so #10

The related trap is vendor code running on your own device. Under R-TRANSIT-3, a browser or messaging client on the victim's phone is not a transit party — it is the attack surface, and it classifies by the role of the flawed component under R-ROLE, normally #3.

Worked examples

SolarWinds / SUNBURST

The published Layer 3 record for this incident is four steps. Note where #10 starts and stops:

solarwinds-2020.json
#10 ||[update][@Vendor→@Org]|| →[Δt=instant] #7 + [DRE: C] →[Δt=~14d] #4 →[Δt=~2h] #1 + [DRE: C]

The signed Orion update carrying the backdoor is accepted and installed: that is the Trust Acceptance Event, and it is the only #10 in the path. The DLL was digitally signed and passed every trust check, which is precisely why the step is a supply-chain step rather than an exploitation one — nothing was broken, the trust link was honoured exactly as designed.

Fourteen days later the backdoor executes: separate step, #7. Forged SAML tokens follow: #4. Then abuse of legitimate cloud functionality with those tokens: #1. The compromise of SolarWinds' own build system does not appear in this path at all — it belongs to a path inside @Vendor.

The two [DRE: C] tags repay a closer look, because their placement is easy to get wrong. The first sits on #7, not on #4: host profiling went out over the C2 channel and the token-signing certificate was taken during that step, and Axiom X puts the acquisition of credential material on the enabling cluster. The second sits on #1, where mail is actually read through the designed APIs.

#4 carries no DRE at all. Authenticating with a forged token discloses nothing, alters nothing and denies nothing — it opens a door. Tagging it with C would count the same exfiltration twice and would judge the step by what the attacker eventually obtained rather than by what the step did, which is the consequence-side form of the mistake Axiom III exists to prevent.

A poisoned package

A typosquatted dependency is published, a developer installs it, and its post-install script runs:

Dependency compromise
#10 ||[dev][@External→@Org]|| → #7

Publishing the package to the registry is the attacker acting in their own sphere; it creates no step against the target. Installation is the TAE. Execution of the install script is #7 with fec_executed: true, per R-EXEC.

A compromised update channel

Where the vendor is breached first, two paths exist and only one is yours:

Two spheres, two paths
inside @Vendor:  #2 → #4 → #1        (the vendor's own incident)
inside @Org:     #10 ||[update][@Vendor→@Org]|| → #7

If instead the update is tampered with in flight rather than at the vendor, the cause is different and so is the path: an on-path attacker is #5, and there is no #10, because no trust artifact from the vendor was subverted — the channel was.

Four ways this goes wrong

Anti-patterns
  • #10 as an outcome bucket. "It was a supply-chain attack" describes a story, not a step. Only the trust acceptance is #10.
  • #10 spread across the chain. One TAE, one #10. If a second genuinely occurs — a compromised dependency inside a compromised vendor product — it is a second, separately justified step.
  • #10 without a boundary operator. A bridge step that does not name what it bridged cannot be checked, aggregated, or argued with.
  • Vendor breach filed as your #10. It is a path in their sphere. Yours starts at acceptance.

Recording it in Layer 3

The notation has a machine-readable twin. In the Layer 3 attack-path schema, the boundary is a topology_boundary object on the step, and the falsifiability test belongs in the step notes:

Layer 3 step
{
  "step_id": "s1-supply-chain",
  "cluster": "#10",
  "topology_boundary": {
    "context": "update",
    "source_sphere": "@Vendor",
    "target_sphere": "@Org"
  },
  "delta_t_to_next": "instant",
  "notes": "Trust Acceptance Event: signed vendor update accepted and installed.
            Falsifiability test: removing the third-party trust link (not accepting
            the update) would have prevented this step."
}

Records validate against the Layer 3 schema, and the worked example above is published in full as solarwinds-2020.json.

The definitions this rests on

Quoted verbatim from the canonical dictionary, tlctc-framework.v2.5.json, which is the normative source for cluster wording:

Cluster Definition Generic vulnerability
#10 Supply Chain Attack An attacker compromises systems by subverting third-party software, hardware, services, or update mechanisms that the target trusts and integrates, so that the subverted artifact is accepted as authoritative inside the target's domain. Trust in third-party components and update channels can be subverted.
#7 Malware Recorded at the execution moment, per R-EXEC — the step where foreign executable content actually runs.
#5 Man in the Middle The cause when the communication path itself is subverted, rather than a trusted artifact.
Where the rules live

R-SUPPLY, R-EXEC, R-ROLE and R-TRANSIT-3 are part of the nineteen-rule registry in the core paper; the full notation grammar is in §11 of the practitioner handbook. Boundary contexts and responsibility spheres are Layer 2 and are meant to be adapted to your organisation.

Why the placement rule earns its keep

A rule that forces one supply-chain step per accepted artifact makes paths comparable across incidents and across organisations — which is the whole purpose of a taxonomy that claims to be non-overlapping. It also puts the control question in the right place. Controls that would have broken the SolarWinds path at step one are provenance controls: artifact signing you actually verify, SBOM checks, staged rollout, build attestation. Controls aimed at the malware are aimed at step two, and by then the trust decision has already been made.

That is the practical payoff of insisting the notation name a moment rather than a mood.