Catasto

Your Tools See Repositories. Attackers See an Estate.

Back in February I was at the DevSecOps Leadership Forum in London to talk about proactive risk reduction using graph analysis. Before my slot, a question from the floor, aimed at one of the other presenters, set the tone for the whole day:

“All of our tools generate SBOMs. But what can we actually do with them? What purpose do they serve? We’ve not seen anyone using them yet.”

Heads nodded around the room, and more questions pulled on the same thread before I’d said a word: can’t reachability analysis help sort the wheat from the chaff? Can any of it drive automated patching? My answer to the gentleman was tongue in cheek, but sincere: just wait for my talk, then, and all will become clear.

So I told the room what I genuinely believe, caveats first. Reachability analysis is not infallible: the source-code-based tools don’t consider configuration or declarative programming techniques and can rarely cope with dependency injection, while the runtime ones need complete coverage or production observation, and even then the edge case that fires once when the moon and stars align is exactly the path they’ll never see. Reachability should set your priorities, not your truth. And automated patching is only as good as the absence of breaking changes between the fix and the version you’re currently using, all the way through every level of transitivity along the path from your library to the vulnerable one. Then I showed them what becomes possible when you stop treating SBOMs as compliance artefacts and load them into a graph.

The exhaustion behind those floor questions stayed with me. People are exhausted by vulnerability noise. Exhausted by point-in-time SBOM scans that produce a report nobody acts on. Exhausted by being told to “just patch it” when the blast radius of a single zero day spans 16 services and three levels of transitive inheritance. And now, apparently, exhausted by generating compliance artefacts that serve no purpose anyone can name.

I closed that talk by making the intent public: SBOM-Graph would be open-sourced. Within a month it was live, and the articles and tools since have been an expansion of that answer, category first, code close behind. All the while I’ve been thinking about why the exhaustion exists at all. Some organisations spend serious money on tooling and feel it anyway. The rest of us can’t get budget for the tools signed off in the first place, and feel it more. And I’ve come to the conclusion that every tool in that market, bought or unbought, shares one assumption so deep that nobody bothers to state it any more:

The unit of analysis is the repository.

Your SAST tool analyses a repository. Your SCA tool scans a repository. Your secrets scanner, your linter, your dependency bot: one repository at a time. Even the shiny new AI-powered scanners analyse whatever fits in a context window, which in practice means a repository or less.

Your attackers make no such assumption. They don’t care where your repository boundaries are, because exploits don’t respect git remotes.

In this post I want to name the alternative, define it properly so that it can be discussed (and criticised) on its own terms, and introduce the tooling I’ve been building to implement it. Some of that tooling is open source today, some is coming shortly, and I’ll be honest throughout about which is which.

Key takeaways

  • Every mainstream security tool (SAST, SCA, secrets scanning, and LLM-based analysis alike) uses the repository as its unit of analysis; attackers do not.
  • Several vulnerability classes only exist at the scope of an organisation’s whole software estate: cross-boundary taint through inner-source libraries and service-to-service calls, version-skew control flow from diamond dependencies, vulnerable clone propagation, and blast radius blindness.
  • Estate-graph analysis is the construction and querying of a deterministic graph of the entire software estate, answering four questions: what exists, what is reachable, where taint flows, and what is duplicated.
  • Catasto is the open-source platform implementing this: SBOM-Graph (what exists), scarno (what is reachable), and src2sink (where taint flows) are all open source today; estate-wide clone analysis (what is duplicated) is in design.
  • A deterministic estate graph is also the ground truth an LLM cannot infer from a context window, and the audit layer for the code LLMs generate: it belongs on both sides of the model.

What is a software estate?

Let’s start with a plain-English definition before anything technical.

Your organisation’s software is not a collection of repositories. It is an estate: hundreds of repositories, second-party inner-source libraries shared across teams, services calling services at runtime, configuration deciding which URL resolves to which endpoint, and patterns copied (or, increasingly, regenerated by coding assistants) across codebases with no shared provenance.

I’ve chosen the name Catasto for the platform that implements the ideas in this post. A catasto is the Italian land registry: the single official record of every property, boundary, and right of way in an estate. It is exactly the artefact your software estate is missing. Nobody would survey a city one building at a time and claim to understand its infrastructure, its water mains, or its fire risk. That is precisely what per-repository tooling does, and we’ve normalised it to the point where the blindness is invisible.

What does analysing one repository at a time actually miss?

This is the heart of the argument, so let’s take it one vulnerability class at a time. My claim is not that per-repo tools are imprecise. Many of them are excellent at what they do. My claim is that there are classes of vulnerability that only exist at estate scope, and no amount of precision at repository scope will ever surface them. The limitation is structural, not incidental.

Cross-boundary taint

Consider a command injection. Attacker-controlled data enters service A through a REST endpoint: a payload that happens to contain a command, or a SQL fragment. It passes through your shared inner-source client library (the one every team uses, because that’s the whole point of inner source), crosses a runtime service-to-service call, and lands in a sink two services away in service C.

Now look at what each repository contains. Repository A has a source and no sink. Repository C has a sink and no source, and its input arrives from “internal” traffic that half your tooling treats as trusted. Every SAST tool on the market reports nothing, because no single repository contains the whole path. The taint chain exists; the tooling has simply been pointed at fragments of it.

Catasto Cross Boundary Taint

“But IAST sees cross-service flows,” I hear you say. It does, and credit where due. However, it introduces a new problem: interactive tools only observe the paths your tests or your traffic actually exercise, and only with agents deployed on every service. The path nobody has ever tested (the attacker’s favourite kind) is exactly the path runtime observation never sees. Static estate-level analysis sees it before deployment, with no agents at all.

Version-skew control flow

You probably know the diamond dependency problem: a dependency gets pinned somewhere upstream (often to fix a vulnerability, a perfectly reasonable short-term move), never gets unpinned, and eventually two versions of the same library coexist in your runtime.

The usual framing is that this causes crashes and maddening runtime errors. True, but it undersells the security consequence. Think about what actually happens when the wrong version is on the classpath: a NoSuchMethodError, a ClassNotFoundException, a field that isn’t found. And think about what production code does with those errors. It catches them. Somewhere, a well-meaning try/catch takes a fallback path, degrades gracefully, uses the older code route.

At first glance that looks like resilience. But now the code executing in production is a path that no per-version analysis ever examined, because every analysis resolved a single version and reasoned about that one. And here’s where my concern really arises: what if the security control (the validation, the encoding, the authorisation check) was added in the newer version? The fallback path doesn’t just behave differently. It executes without a control the rest of your analysis assumed was present.

This is exception-driven control flow as attack surface, and I’ve not seen a mainstream scanner that models it, because modelling it requires knowing that two versions coexist and which call sites resolve to which. That knowledge lives in the estate graph, not in any repository. It’s also not hypothetical: SBOM-Graph’s diamond dependency analysis already shows you the coexisting versions and traces exactly which upstream dependency pinned the old one, and scarno’s cross-version ABI diff flags when a symbol your code actually calls differs between the declared and resolved versions, before production finds out for you.

Vulnerable clone propagation

A vulnerability is found and fixed. Job done? Only if the vulnerable code existed in exactly one place. Research going back over a decade (the VUDDY and ReDeBug line of work) shows that vulnerable code clones propagate through codebases and routinely outlive the fix applied to the original.

And the economics of cloning have just changed. Coding assistants reproduce near-identical implementations across repositories with no copy-paste event and no shared provenance, because they’re sampling from the same distribution. A team that would never have hand-copied a crypto routine can now have four independently “written” variants of the same one, sharing the same subtle flaw. I want to be precise here: duplication is the signal, and AI is the accelerant, not the accusation. Clone detection cannot tell you who or what wrote the code. What it can tell you, at estate scale, is that your patch fixed one of five siblings and the other four are still live.

Blast radius blindness

Finally, the one that started me down this road. When the next zero day lands, the question is never really “are we affected?” It’s “what does the remediation work actually look like?” In February I put a shape on that for the room: what arrives as 2,500 vulnerability findings in your reports is, seen through the graph, more like 25 work items: a handful of places to actually fix, and the breaking changes standing between you and each fix. Which projects must update first to avoid cascading breakage? How many of the affected components are even reachable? A flat SBOM cannot answer these questions. A graph can, and I’ve written about this at length in my previous post on SBOM-Graph, so I won’t repeat it here.

Estate-graph analysis, defined

So here’s the definition, stated once so it can be quoted, argued with, and improved:

Estate-graph analysis is the construction and querying of a deterministic graph of an organisation’s entire software estate, answering four questions: what exists, what is reachable, where taint flows, and what is duplicated.

Image description

One tool per question, and all of them open source. That’s not a philosophical position so much as a practical one: I know exactly how the budget conversation for a new security tool tends to end, because I’ve sat through enough of them. It’s why I build.

  • What exists. SBOM-Graph ingests your CycloneDX and SPDX SBOMs into a graph database and continuously enriches them: vulnerabilities from OSV and Sonatype OSS Index, licences from ClearlyDefined, trust scores from OpenSSF Scorecard and deps.dev with inherited-risk propagation through the graph, plus VEX triage, policy gates for CI/CD, patch planning, and blast radius analysis. Open source (MIT) today.
  • What is reachable. scarno performs polyglot reverse-reachability analysis across Python, Java/Kotlin, JavaScript/TypeScript, Go, and C#/.NET, classifying every dependency as safe to remove, uncertain, in use, or undeclared, with per-symbol usage tallies and a JVM cross-version ABI diff. It’s built around the caveat I gave the room in February: reachability sets priorities, not truth, which is why the classification admits uncertainty instead of guessing. Open source (Apache-2.0) today, with SLSA Build Level 3 provenance on every release: pip install scarno.
  • Where taint flows. src2sink builds a deterministic metabase of the estate’s sources, sinks, and pass-throughs, stitches the runtime service-to-service call edges, and traces dangerous endpoints bidirectionally, so that an LLM (or a human) can follow taint across repository and service boundaries with the full path in front of them. Open source (MIT) today, with SLSA Build Level 3 provenance: pip install src2sink.
  • What is duplicated. Tooling for the fourth question, estate-wide clone detection so that a fix in one place surfaces its unfixed siblings everywhere else, is still on the drawing board. I’d rather ship the first three well than name something that doesn’t exist yet.
Image description

To be precise about release state: SBOM-Graph, scarno, and src2sink are all public today; the clone analysis is in design. src2sink’s introduction post is still to come, because it deserves to arrive with evaluation data rather than a day ahead of it. On provenance: scarno and src2sink both ship with SLSA Build Level 3 attestation on every release, and SBOM-Graph is being brought to the same standard, which is a materially bigger pipeline job for a monorepo with five Python subprojects, three container images, and a Helm chart. I’d rather tell you all of that plainly than pretend otherwise.

Why now? LLMs make this urgent, not optional

You might reasonably ask why I’m naming this category now, when graphs and reachability analysis are hardly new ideas. The answer is that large language models (LLMs) have changed the stakes in both directions.

In one direction, LLMs are genuinely better at reasoning about code semantics than any rule engine I’ve benchmarked. But they analyse whatever fits in a context window, with no estate visibility and no guarantee they’ve seen the code that actually executes. Nothing in service A’s repository tells a model that POST /internal/exec lands in service C’s command handler. That wiring lives in Helm charts, service meshes, and API gateway configuration the model was never shown. The estate graph is precisely the ground truth a model cannot infer, however large the context window gets. Determinism where determinism is possible; inference only where it’s needed.

In the other direction, those same models are now generating a meaningful share of the code in your estate, including the convergent duplicates that estate-wide clone detection exists to find. So the graph sits on both sides of the model: it supplies the context going in, and it audits the output coming back.

If you’ve read my threat modelling playbook for AI systems, you’ll recognise the shape of this argument. There I argued: do not rely on the model’s boundary; build enforcement in a deterministic layer outside the model. The constructive mirror of that principle is: do not rely on the model’s context selection; build ground truth in a deterministic layer outside the model. One argument, defence and offence.

Important note: LLM findings are non-deterministic and must be treated as candidate findings requiring verification, never as the system of record. The graph is the system of record. If a vendor tells you otherwise, ask them to run the same analysis twice and diff the results.

Note to vendors: bolting an LLM onto single-repository context does not fix single-repository scope. The vulnerability classes described in this post are invisible at that scope with or without AI. If your roadmap says “AI-powered” and your unit of analysis is still the repository, you have automated the blindness, not cured it.

What’s coming

The release sequence, as a public commitment:

  1. scarno’s introduction post, following this one within the week. The repo and the PyPI package are already live for the impatient.
  2. src2sink, launched with a three-way comparison on the same estate: per-repo SAST alone, LLM alone, and LLM with graph-derived context, counting true positives each approach finds, plus the number I most want to know myself: what percentage of true source-to-sink paths cross a repository or service boundary. Like scarno, it ships with SLSA Build Level 3 provenance on every release.
  3. MCP servers for all of the above, so that Claude Code or any other agent can query reachability, coexisting versions, and cross-boundary paths mid-analysis, turning “context for LLMs” from a metaphor into something you can install.
  4. The joined-up demo, closing the loop with what’s shipped: a multi-version conflict found by the graph, confirmed at the call site by scarno’s ABI diff, and (once released) the taint path traced across services by src2sink. Clone detection joins the sequence when its design is done, not before.

Final thoughts

Some honest limitations, because there are always limitations. Statically resolving a runtime call edge (which URL resolves to which service, through which gateway rewrite) is hard, and src2sink’s edges carry per-edge confidence levels rather than false certainty, the same design choice I made for SBOM-Graph’s trust scores. The “internal traffic is trusted” assumption I’ve criticised above is also a false positive risk for this approach, since many cross-service flows genuinely are safe by policy, and the findings will need to distinguish “taint reaches a sink across a boundary” from “and no sanitisation was observed at any hop”. And src2sink’s metabase is currently built from repository snapshots without version modelling, so there is drift between the graph and the estate as code moves: its output is a guide to analysis, not a guarantee, and version-awareness is on the roadmap. These are engineering problems to be addressed with data, not hand-waving, and the launch posts will show the numbers either way.

The threat modelling community taught me that practices get better when people publish. If you think estate-graph analysis is the wrong name, or the wrong idea, or you’ve solved cross-service taint some other way: publish it, and tell me. And if you’d like to try this on your own estate, SBOM-Graph, scarno, and src2sink are all ready for you today.

And to the gentleman who asked that question from the floor in February: I told you to wait for my talk and all would become clear. This post, and the tools in it, are the full version of that answer. The SBOM was never the deliverable. It’s the seed of the estate graph, and the graph is where the reachability answers, the patch planning, and the blast radius live.

Chi va piano, va sano e va lontano: slow and steady wins the race. The estate wasn’t built in a day, and neither is the map of it.

Stay tuned!