> ## Content Index
> Fetch the complete content index at: https://www.ostering.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# src2sink

- URL: https://www.ostering.com/src2sink/
- Published: 2026-08-19T22:00:00.000Z
- Updated: 2026-08-19T22:08:41.380Z
- Description: src2sink, ostering.com, Brett Crawley
- Author: Brett Crawley
- Tags: AI Security, Catasto, Graph Analysis, SAST, src2sink, Supply Chain Security

Back in February, in my deep dive comparing SAST with Claude Code Security, I described something in theoretical terms: a metabase of versioned, validated facts about an organisation's source code, built so that cross-repository taint analysis becomes possible. Sources in one repo, sinks in another, internal libraries acting as transparent pass-throughs to dangerous APIs.

It's not theoretical any more. **src2sink** is open source, on PyPI, and has already completed its first full estate-scale fleet run, that was 34 GB of source code processed in under 15 minutes.

```
pip install src2sink
```

**Key takeaways**

- Per-repository SAST is structurally blind to taint paths that cross repository or service boundaries; no amount of scanner precision fixes a scope problem.
- src2sink builds a deterministic metabase of an estate's sources, sinks, propagators, and stores: per-repo flow graphs plus cross-repo taint catalogues, service-call graphs, and bidirectional endpoint traces.
- The metabase is designed to be loaded as LLM context: the graph decides what the model looks at and what is structurally true; the model judges what the code means. Determinism where determinism is possible, inference only where it's necessary.
- src2sink catalogues the endpoints that accept raw code or SQL in the payload and traces who can reach them, a class of finding per-repository tooling cannot even represent.
- The tool is public; your metabase is not. The output is a concentrated map of an estate's weaknesses and personal data, and it ships with its own data-classification guidance.
- Every release from 3.1.0 carries SLSA Build Level 3 provenance. Verify it; don't trust me.

## Five Things Your Scanner Cannot Follow

In the estate-graph analysis post I made the structural argument; here it is as five concrete failures, every one of them mundane and every one invisible one repository at a time:

1. **Cross-repo SQL injection.** Service A constructs a SQL fragment and forwards it as a payload to service B, which executes it. Repo A has a source and no sink; repo B has a sink and "internal" input.
2. **Internal-library black boxes.** Your shared data-access wrapper hides the JDBC sink. Every consuming repo's analysis terminates at a method call into a library it can't see into.
3. **Cross-repo PII flow.** A phone number arrives at an ingress service, crosses a queue, lands in a store, appears in a log, and leaves for a third party. Five repos, one lifecycle, zero tools that can draw it.
4. **Crypto agility.** The algorithms and keys are decided in configuration and shared libraries, not in the application code any scanner is pointed at.
5. **Dangerous request payloads.** An HTTP endpoint that accepts raw SQL in the body and runs it. Perfectly visible in one file, but the question that matters (who can reach this endpoint, from where, carrying what?) spans the estate.

![Diagram: five repos, five clean scanner reports, one exploitable path; the problem statement](https://www.ostering.com/media/posts/64/src2sink-silos.png =2400x1200)

## Not Another Scanner

Here's the design decision that makes src2sink different, and I want to be precise about it because the precision *is* the tool: src2sink does not try to be a smarter scanner. It builds a **metabase**: a structured, human-readable knowledge base of facts about the estate, extracted deterministically with tree-sitter across Java, Kotlin, Python, Go, TypeScript, and JavaScript.

For every repository it produces a flow graph: nodes classified as source, propagator, sink, or store, each with its family, file, line, framework, PII classification, data class, and a confidence level. Across the estate it aggregates the catalogues those graphs imply: SQL construction sites and execution sinks, file and HTTP sinks, PII sources and sinks, crypto operations, security-sensitive configuration, and the endpoints that accept raw code in their payloads. And then it builds the graphs that connect them: the service-call graph stitching outbound HTTP calls to the inbound handlers that receive them, the queue graph joining topic producers to consumers, and the registry of API client libraries mapped to the services they target.

That last group is the part I'm proudest of, because it's the runtime dimension no static tool models: the taint path that leaves one codebase over HTTP or a queue and re-enters another as fresh, apparently trustworthy input. The service-call edges carry explicit confidence levels (high for strong structural patterns, medium for heuristics, low for weak signals), and the documentation says what I'll repeat here: treat low as "investigate", never as "confirmed".

![Diagram: the same five repos as the silos diagram, now one continuous typed path; the before/after pair](https://www.ostering.com/media/posts/64/src2sink-stitched-path.png =2400x1120)

![Diagram: how it's built: per-repo extraction, entry/exit metadata, exits joined to entries; the 'how' for sceptics](https://www.ostering.com/media/posts/64/src2sink-path-construction-2.png =2400x1520)

On top of the metabase sits the trace: point src2sink at a dangerous endpoint and it walks the graph bidirectionally, producing a single report covering the inbound routes, the payload nodes, the SQL sinks, the config-discovered stores behind them, and the upstream callers that can reach the endpoint. The full path, in one artefact, for the first time.

![Diagram: one raw-payload endpoint walked upstream and downstream, every node labelled with type, family, repo, PII/data class, confidence](https://www.ostering.com/media/posts/64/src2sink-labelled-trace.png =2400x1280)

## Where the LLM Comes In

If you've read the estate-graph post, you know the argument: LLMs reason about code semantics better than any rule engine, but they analyse whatever fits in a context window, and nothing in service A's repository tells a model that a POST to an internal path lands in service C's command handler. That wiring lives in configuration, gateways, and client libraries the model was never shown.

The metabase is built to be that missing context. It's deliberately dual-format (human-readable Markdown alongside machine-readable JSONL) and the repo documents the exact minimum pack to load for a single-repo analysis: the repo's own flow graph, the taint tables for each internal dependency, the relevant catalogue rows, and the graph edges that touch the repo. The model then does the one thing it's genuinely better at than any deterministic tool: judging whether the sanitisation at each hop actually sanitises.

The division of labour is the design principle, and it's the same one I've argued for AI systems generally: the deterministic layer decides what is structurally true and what the model gets to see; the model provides semantic judgement; and the model's output is a set of candidate findings for verification, never the system of record. The graph is the system of records for what was extracted, and I'll be equally plain below about where that record ages.

## Proven at Estate Scale

This isn't a proof of concept looking for its first user. src2sink has been through a full fleet run at genuine estate scale: hundreds of repositories extracted, catalogued, and graphed, with incremental re-runs skipping unchanged repos so the metabase stays cheap to keep current. The raw-code-payload catalogue (HTTP handlers that accept `sql` or similar in the request body and reach execution) is the output I'd point any sceptic at first, because per-repository tooling doesn't merely miss the cross-repo reachability of those endpoints; it has no representation for the question.

Try a three-way evaluation on a constructed corpus — per-repo SAST alone vs LLM alone vs LLM + metabase context, true positives found by each; plus the boundary-crossing percentage: what share of true source-to-sink paths cross a repository or service boundary. Let me know how it goes, I'm truly interested in hearing your feedback.

## The Chapters I Didn't Plan to Write

Two capabilities grew out of the extraction that I didn't set out to build, and both turned out to matter as much as the taint analysis.

The first is the **PII lifecycle**. Once you're classifying nodes with GDPR-aware categories (direct PII, sensitive, special category, quasi-identifiers) and you have the queue and service-call graphs, cross-repo data flows fall out: the metabase traces a phone number from ingress through queues, stores, logs, and third-party egress, across repository boundaries. And once you have that, a draft **Article 30 ROPA projection** is a report, not a project. If you've ever tried to assemble a record of processing activities by interviewing engineers, you'll understand why I think this might quietly be the most valuable output in the repo.

![Diagram: one phone number across five repos: ingress, queue, store, log leak, third-party egress](https://www.ostering.com/media/posts/64/src2sink-pii-lifecycle.png =2400x1120)

The second is **crypto agility**. The metabase catalogues cryptographic operations and the cipher suites and signing algorithms configured across the estate, per repo, as cards. If post-quantum migration is on your horizon (it should be), the first deliverable of any migration plan is exactly this inventory, and nobody has it.

![Diagram: per-repo crypto cards and why the inventory precedes any PQC migration](https://www.ostering.com/media/posts/64/src2sink-crypto-inventory.png =2400x1120)

## Handle With Care

A word to the wise: the tool is publicly available, your metabase shouldn't be. By design, its output is a treasure map for the bad guys of where an estate's exploitable weaknesses and personal data live. Ours is classified RESTRICTED, and the repo ships an operations-security document covering handling, classification, and retention of your own copy. Build it, use it, feed it to your analysis, and handle with care you don't want it falling into the wrong hands as it's more or less a vulnerability report.

## What It Doesn't Do (Yet)

In the established spirit: there are still a few gaps, and they are documented in the repo but I'll repeat them here for good measure.

The one I most want to spell out before anyone else does: **the metabase is built from a snapshot, and it does not yet model versions.** src2sink extracts each repository at the state it finds it, which means the graph and the estate drift apart as code moves: the paths it describes are the paths that existed when the extraction ran, and the version a service actually has deployed may not be the version the metabase read. The incremental rebuild keeps drift cheap to correct (unchanged repos are skipped on their git SHA, changed ones re-extracted), but between builds, and across version skew, the metabase is **a guide to the LLM, not a guarantee**. Verify at the call site is the standing rule, and it's why findings are candidates, never conclusions. Version-awareness is already in the pipeline, if not in full at least in part, and the estate graph's version-keyed dependency data is the obvious foundation to build it on.

The other gaps: internal libraries remain black boxes until their taint tables are curated or their source is cloned into the analysis; auto-curation seeds the tables from public method signatures where source is available, but human review is still required, and the hand-curated per-library tables are where durable knowledge lives. Edge confidence is explicit and heuristic edges are labelled as such. And traces are generated as a separate step, deliberately, rather than exploding the build. 

## Verify This Release: Don't Take My Word for It

The SLSA badge on the README is a claim, like every badge. The attestations it links to are the evidence, and the commands below check the artefact you actually downloaded rather than the repository's reputation:

```
# SLSA provenance, attached to the GitHub release
slsa-verifier verify-artifact src2sink-3.1.0-py3-none-any.whl \
  --provenance-path multiple.intoto.jsonl \
  --source-uri github.com/mimecast/src2sink --source-tag v3.1.0

# GitHub attestation, same artefact
gh attestation verify src2sink-3.1.0-py3-none-any.whl --repo mimecast/src2sink

# Installed from PyPI instead (PEP 740 attestation)
python -m pypi_attestations verify pypi --repo mimecast/src2sink src2sink-3.1.0-*.whl
```

Provenance is generated by an isolated builder the build steps cannot reach (SLSA Build Level 3), publication to PyPI runs over OIDC Trusted Publishing with no API token in the repository, and publishing only proceeds after provenance succeeds, so a failure stops the release before the irreversible step. The version history is honest too: 1.0.0 and 1.0.1 shipped with no provenance, 1.0.2 reached Level 2, and 1.0.3 is where Level 3 begins. That progression is documented rather than hidden, because "we improved" is a better story than a backdated badge. What the claim does and doesn't cover (the Build track says nothing about dependencies or source trustworthiness) is in the repo's SLSA document.

If verification fails, don't run the tool. The control is the verification, not my assurance.

One honest note: SBOM-Graph doesn't have this yet. It's a monorepo with five Python subprojects, three container images, and a Helm chart, so bringing every release target to the same standard is a bigger pipeline job, and it's in progress. I'll write that one up when it's done, scars and all.

## Where src2sink Fits

The analysis is fast, deterministic and lightweight, so you won't need to wait for SAST vendors to finally implement this piece of the puzzle, charging an arm and a leg, you alsowon't blow up your token usage when using AI to perform the SAST analysis because it has a map of where to dig to find the bodies.

src2sink is the third of the four questions from the [estate-graph analysis post](https://www.ostering.com/catasto/): SBOM-Graph answers *what exists*, scarno answers *what is reachable*, src2sink answers *where taint flows*, and the estate-wide clone analysis for *what is duplicated* is in design. The integration between them is designed and under way: estate-level detection routing to repo-level confirmation, and reachability and taint annotations flowing back into one graph. A worked example of the three you can already use working together is coming and it's an eye opener.

**Note to engineering leaders:** ask your AppSec team one question this week: how many HTTP endpoints in our estate accept code or a query in the payload, and who can reach them? If the answer is a shrug, that's not your team's failure; it's a tooling category that didn't exist. It does now, and it's free.

Stay tuned!
