Source-Code Metabase
A structured, human-readable knowledge base of the analysed source-code ecosystem. Designed to be loaded as context for LLM SAST so that cross-repository taint analysis becomes possible — sources in one repo, sinks in another, internal libraries that act as transparent pass- throughs to dangerous APIs.
Canonical field definitions: SCHEMA.md
Why this exists
Conventional SAST tools work one repository at a time and cannot follow:
- Cross-repo SQL injection — service A constructs a SQL fragment and forwards it as a payload to service B which executes it.
- Internal-library black boxes — shared wrappers hide JDBC/JPA sinks.
- Cross-repo PII flow — ingress → queue → store → log → third party.
- Crypto agility — algorithms and keys decided in config or shared libs.
- Dangerous request payloads — e.g. raw SQL accepted on an HTTP
endpoint (
acme/sql-runner-api/query).
The metabase captures cross-repo facts so an LLM (or human) can reason about the full path, not a single repo in isolation.
Contents at a glance
Per-repo (gitignored after generation)
| Path | Content |
|---|---|
repos/<group>/<repo>.md |
Flow-node summary |
repos/<group>/<repo>.json |
Flow graph (schema_version: 2, nodes[], edges[]) |
Cross-repo catalogues (mostly gitignored)
| Folder / file | Purpose |
|---|---|
taint/sql-sources.md + .jsonl |
SQL string construction (concat / template) |
taint/sql-execution-sinks.md + .jsonl |
JDBC/JPA/native execution sinks |
taint/file-sinks.md + .jsonl |
File-write / archive extract |
taint/http-sinks.md + .jsonl |
Outbound HTTP client calls |
taint/pii-sources.md + .jsonl |
PII field declarations (bounded MD + full jsonl) |
taint/pii-sinks.md + .jsonl |
PII in logs / storage (field_name null = generic .save etc. without nearby PII token — see SCHEMA.md) |
taint/crypto-operations.md + .jsonl |
Crypto algorithm use |
taint/raw-code-payload-endpoints.md + .jsonl |
HTTP handlers that accept sql/… and reach SQL execution in-file |
taint/config-data-stores.md + .jsonl |
JDBC/Mongo/Redis/S3 from config |
taint/config-security.md + .jsonl |
Security-sensitive config keys |
taint/config-crypto.md + .jsonl |
Cipher suites / signing algorithms from config |
graphs/service-call-graph.md + service-call-edges.jsonl |
Cross-repo HTTP edges (http-in ↔ enriched http-out, plus api-client-binding hops) |
graphs/service-call-unmatched.jsonl |
Outbound calls that resolved to nothing — the negative-coverage signal |
graphs/queue-graph.md + .jsonl |
Topic producers / consumers |
graphs/data-store-graph.md + .jsonl |
Config-discovered stores ↔ repos |
graphs/payload-endpoint-producers.md + .jsonl |
Registered API clients → target services |
graphs/pii-lifecycle.md + .jsonl |
PII lifecycle stages (Phase 3) |
graphs/pii-phone-cross-repo.md + .jsonl |
Cross-repo phone hops (queue + HTTP) |
ropa/categories-of-personal-data.md |
ROPA Article 30 projection (Phase 3) |
conventions/auth-models.md + .jsonl |
Per-repo auth cards (Phase 3) |
conventions/crypto-agility.md + .jsonl |
Per-repo crypto cards (Phase 3) |
graphs/traces/<target>.md |
Endpoint traces (trace.py / trace_batch.py) |
Hand-authored (committed)
| Path | Purpose |
|---|---|
SCHEMA.md |
Field and node-family definitions |
README.md |
This file |
internal-libraries/<coord>.md |
Per-library taint tables (hand-curated) |
conventions/*.md |
Auth, crypto agility, PII naming (mixed) |
Scripts (committed)
| Script | Role |
|---|---|
src2sink/build_metabase_v2.py |
Extractor + taint + graph aggregation |
src2sink/trace.py |
Bidirectional trace for a target repo/endpoint |
src2sink/trace_batch.py |
Batch traces from raw-code-payload-endpoints.jsonl |
src2sink/known_api_clients.py |
Registry: client library → target service |
src2sink/schema.py |
v2 FlowNode / FlowEdge dataclasses |
src2sink/extractors/ |
unified, config, http_out, tree-sitter helpers |
src2sink/aggregators/ |
taint catalogues, graphs, payload producers |
Building the metabase
Prerequisites: source repos cloned to your --repos-root path, uv sync, corp network.
# Incremental update — skips repos whose git SHA hasn't changed (~fast after first run)
uv run src2sink-build \
--repos-root repos --metabase-root metabase \
--internal-groups-file internal-groups.json --api-clients api-clients.json
# Force full re-extract of all repos (use after downloading new repo snapshots
# that are not git checkouts, or after changing internal-groups/api-clients config)
uv run src2sink-build \
--repos-root repos --metabase-root metabase --force \
--internal-groups-file internal-groups.json --api-clients api-clients.json
# Single repo — re-extracts only that repo, but re-aggregates taint/graphs/phase3
# from all existing JSONs so cross-repo outputs stay consistent
uv run src2sink-build \
--repos-root repos --metabase-root metabase \
--repo acme/sql-runner-api \
--internal-groups-file internal-groups.json --api-clients api-clients.json
# Re-aggregate taint + graphs from existing v2 JSONs only (fast, no re-extraction)
uv run src2sink-build \
--repos-root repos --metabase-root metabase --graphs-only \
--internal-groups-file internal-groups.json --api-clients api-clients.json
# Phase 3 only (PII lifecycle, ROPA, auth/crypto, cross-repo phone flows)
uv run src2sink-build \
--repos-root repos --metabase-root metabase --phase3-only \
--internal-groups-file internal-groups.json --api-clients api-clients.json
# Resolve flagged library-source-map entries from on-disk pom.xml only
# (fast, no re-extraction). Walks every pom.xml under --repos-root, reads each
# groupId/artifactId, and for any mapping still flagged (status pending/ambiguous
# or with an empty clone_path) whose coordinate matches a pom, fills in clone_path
# and sets status: cloned. excluded and already-cloned entries are left alone.
uv run src2sink-build \
--repos-root repos --metabase-root metabase --fix-source-map
# Tests (Phase 4 regression suite)
uv run pytest tests/ -q
uv run pytest tests/ -q -m fleet # needs metabase/repos v2 JSONs
Build gates
.github/workflows/ci.yml runs six gates on every push,
pull request, and weekly (advisories move even when the code does not):
| Gate | What it enforces | Locally |
|---|---|---|
test |
pytest + coverage floors (80% overall, 90% on the security modules) | make test |
srtm |
every requirement in the SRTM still has a test or a documented audit | make srtm |
mypy (strict) |
mypy --strict over src2sink/ and scripts/ |
make typecheck |
bandit |
Python SAST on first-party code | make bandit |
pip-audit |
dependency vulnerability audit (TA-011 / SC-1) |
make audit |
opengrep |
pattern SAST, pinned ruleset, gated at ERROR severity | make opengrep |
make ci runs everything except opengrep, which needs the external ruleset
checkout. Known false positives carry inline # nosec / # nosemgrep
annotations with a stated reason rather than blanket rule exclusions.
Release gates
A separate workflow, .github/workflows/release.yml,
runs when a v* tag is pushed — and on the 1st of each month as an unattended
rehearsal that builds and attests without publishing, so upstream breakage
surfaces before a release depends on it.
| Stage | What it does |
|---|---|
build |
builds sdist + wheel from the tagged tree, fails if the tag and the packaged version disagree, twine checks the metadata |
provenance |
generates SLSA provenance in an isolated builder the build steps cannot reach (Build L3) |
publish |
uploads to PyPI over OIDC Trusted Publishing — no API token exists in this repository — after a human approves the deployment |
release |
creates the GitHub release with the version's changelog section as the body, and attaches the artefacts plus the provenance |
Publishing runs only after provenance succeeds, so a failure there stops a
release before the irreversible step. Permissions are split per job: only
publish can mint an OIDC token, only release can write to the repository.
Publishing a version to PyPI: docs/releasing.md.
Known small gaps: docs/todo.md.
Verifying a release
The SLSA badge above is a claim, like every badge — it links to this repository's attestations, which is the evidence. Do not take either on trust; the commands below check the artefact you actually downloaded.
Releases carry signed build provenance tying each artefact to the source commit, tag, and workflow run that produced it — so a file claiming to be src2sink can be checked rather than trusted. From 1.0.3 the provenance is generated by an isolated builder (SLSA Build L3); 1.0.2 is L2; 1.0.0 and 1.0.1 have none.
# SLSA provenance, attached to the GitHub release (L3)
slsa-verifier verify-artifact src2sink-1.0.3-py3-none-any.whl \
--provenance-path multiple.intoto.jsonl \
--source-uri github.com/mimecast/src2sink --source-tag v1.0.3
# GitHub attestation, for the same artefact
gh attestation verify src2sink-1.0.3-py3-none-any.whl --repo mimecast/src2sink
# installed from PyPI instead (PEP 740 attestation)
python -m pypi_attestations verify pypi --repo mimecast/src2sink src2sink-1.0.3-*.whl
What the claim does and does not cover is in
docs/slsa.md —
the Build track says nothing about dependencies or source trustworthiness.
Incremental behaviour: each re-run compares the repo's current
git HEADSHA against the SHA stored in the existing per-repo JSON. Repos that haven't changed are skipped (skip group/name (unchanged)); only changed repos are re-extracted. Repos without a.git/directory (bare downloads) are always re-extracted. Cross-repo graphs and taint catalogues are always re-aggregated at the end regardless of how many repos were skipped, so the metabase stays consistent. Use--forceto bypass the SHA check and re-extract everything.
Traces are not run automatically.
src2sink-buildproduces agraphs/traces/INDEX.mdthat lists existing trace files and shows which endpoints still lack one, but it does not generate traces itself. Runsrc2sink-trace-batch(orsrc2sink-trace) as a separate step after the build completes.
Generating traces (separate step)
Traces give a full bidirectional view of a dangerous-payload endpoint:
inbound routes, raw-code-payload nodes, SQL sinks, config stores, and
upstream callers. They are written to graphs/traces/<name>.md.
Batch — trace all raw-code-payload endpoints at once (recommended):
# First run: generates a report for every endpoint in
# taint/raw-code-payload-endpoints.jsonl
uv run src2sink-trace-batch --metabase-root metabase \
--internal-groups-file internal-groups.json --api-clients api-clients.json
# Subsequent runs: skip endpoints that already have a report
uv run src2sink-trace-batch --metabase-root metabase \
--internal-groups-file internal-groups.json --api-clients api-clients.json --skip-existing
Single endpoint:
uv run src2sink-trace \
--metabase-root metabase \
--target acme/sql-runner-api \
--path /query \
--scan-repos repos \
--internal-groups-file internal-groups.json \
--api-clients api-clients.json \
--output metabase/graphs/traces/sql-runner-api-query.md
--scan-repos is optional; it adds a literal import/URL scan of the
cloned source trees to find callers that are not yet in the graph.
After adding new traces, refresh the index:
uv run src2sink-build --repos-root repos --metabase-root metabase --graphs-only \
--internal-groups-file internal-groups.json --api-clients api-clients.json
Fixing flagged internal-library mappings
After the initial scan, library-source-map.json may contain entries the
build couldn't resolve — coordinates with status: pending/ambiguous or an
empty clone_path (an internal dependency whose source repo it couldn't
locate). Every normal build already tries to repair these automatically at the
end, but if you clone the missing repos after a scan you can re-run just the
fix without re-extracting anything:
uv run src2sink-build --repos-root repos --metabase-root metabase --fix-source-map
This walks every pom.xml under --repos-root, reads each groupId /
artifactId, and for any still-flagged mapping whose coordinate matches a pom
it sets clone_path to that repo's path (relative to the repos root) and flips
status to cloned. Matching is by exact groupId:artifactId, falling back to
a unique artifactId-only match. Entries marked excluded, and those already
cloned with a clone_path, are left untouched. It prints how many entries it
resolved; anything still flagged afterwards has no matching pom.xml on disk
(clone the repo, or set clone_path/status by hand). Once resolved,
curate_internal_libraries.py can seed taint tables from that source.
Register another API client library
Add a binding to api-clients.json (see
docs/api-clients-json.md
for the field reference and the --discover-api-clients drafting flow), then
re-run src2sink-build --api-clients api-clients.json (or --graphs-only).
This is how the tool sees hops that regular SAST cannot: a repo that calls a
service through its published client library has no URL, host, or service name
anywhere in its source, so the binding is the only thing that knows the call
crosses a service boundary. If a supplied api-clients.json loads zero bindings
the build fails rather than quietly producing a client-blind metabase; check
api_clients_binding_count in run-manifest.json and the "API-client binding
coverage" table in graphs/service-call-graph.md to confirm detection is live.
How to use this metabase as LLM context
Minimum pack for SAST on one repo
repos/<group>/<repo>.mdand.json(checkschema_version).- For each internal dependency,
internal-libraries/<coord>.md. - Relevant taint rows (filter jsonl by
repocolumn). - graphs edges involving that repo (
service-call-edges.jsonl,payload-endpoint-producers.jsonl,queue-graph.jsonl).
Dangerous-payload / raw SQL in HTTP body
taint/raw-code-payload-endpoints.md(sample) +.jsonl(full).graphs/payload-endpoint-producers.mdfor known client libraries.graphs/traces/<service>-<path>.mdif generated (seegraphs/traces/INDEX.md).- Target repo v2 JSON:
family=raw-code-payload,http-in,sqlsinks.
Use phone numbers (not email) as the canonical PII lifecycle example
when illustrating field flow — email is ambient in an email-security
company (conventions/pii-classification.md).
PII and crypto
- PII lifecycle:
graphs/pii-lifecycle.md,graphs/pii-phone-cross-repo.md. - ROPA draft:
ropa/categories-of-personal-data.md. - Field catalogues:
taint/pii-sources.md,taint/pii-sinks.md. - Crypto:
taint/crypto-operations.md,conventions/crypto-agility.md.
Updating and hand-curation
Per-repo files are regenerated wholesale by build_metabase_v2.py.
Put durable notes in internal-libraries/ or conventions files, not
inside generated output.
Confidence levels
| Level | Meaning |
|---|---|
| high | Strong pattern (e.g. tree-sitter JDBC sink, explicit import of registered client) |
| medium | Heuristic (field name, regex HTTP client) |
| low | Weak signal (host-only match, path suffix overlap) |
Service-call and producer-index edges label confidence explicitly. Treat low as “investigate”, not “confirmed”.
Dependencies
v2 requires tree-sitter and per-language grammars (via
uv sync).
Do not add public PyPI fallbacks.
See also
SCHEMA.md— complete v2 node/edge vocabulary.docs/architecture.md— components, data flow, trust boundaries.docs/threat-model.md— STRIDE risk register.docs/security-privacy-gap-analysis.md— requirements, abuse cases, SRTM.docs/operations-security.md— running it safely; data classification and retention.docs/sast-report.md— self-review of this tool's own code.docs/api-clients-json.md— the internal-service binding file.metabase-usage.md— using the output as LLM context.docs/releasing.md— publishing a version to PyPI.docs/slsa.md— build provenance: what is claimed, how to verify it, and what it does not cover.CHANGELOG.md— what shipped in each version.docs/todo.md— known small gaps./ai-sast-scannerskill — SAST prompt this metabase feeds.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file src2sink-3.0.0.tar.gz.
File metadata
- Download URL: src2sink-3.0.0.tar.gz
- Upload date:
- Size: 336.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cd33e85c163340ee4249fcc4e2505c96d5d5209efaaa47761396c48994b8da00
|
|
| MD5 |
c13d982529820daaeffb7a567fe67a5e
|
|
| BLAKE2b-256 |
f64dd3568ec1a99bb7eb151df46c2037a9ce9714a28378159ca3d8da30516726
|
Provenance
The following attestation bundles were made for src2sink-3.0.0.tar.gz:
Publisher:
release.yml on mimecast/src2sink
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
src2sink-3.0.0.tar.gz -
Subject digest:
cd33e85c163340ee4249fcc4e2505c96d5d5209efaaa47761396c48994b8da00 - Sigstore transparency entry: 2370741085
- Sigstore integration time:
-
Permalink:
mimecast/src2sink@b11eec7a6e33224a7b22a054bf52dd9de4c5757b -
Branch / Tag:
refs/tags/v3.0.0 - Owner: https://github.com/mimecast
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b11eec7a6e33224a7b22a054bf52dd9de4c5757b -
Trigger Event:
push
-
Statement type:
File details
Details for the file src2sink-3.0.0-py3-none-any.whl.
File metadata
- Download URL: src2sink-3.0.0-py3-none-any.whl
- Upload date:
- Size: 216.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ea5d610b6cdce834b03e526f66dc7c3127bdedea3289430b31299221d8e999dc
|
|
| MD5 |
48add9abbc8688e050afea89afc57fa2
|
|
| BLAKE2b-256 |
03a5206ad58ed1e79be487d5ed9c3b28b228f8b3723e7d3be98b0c7e6819be6a
|
Provenance
The following attestation bundles were made for src2sink-3.0.0-py3-none-any.whl:
Publisher:
release.yml on mimecast/src2sink
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
src2sink-3.0.0-py3-none-any.whl -
Subject digest:
ea5d610b6cdce834b03e526f66dc7c3127bdedea3289430b31299221d8e999dc - Sigstore transparency entry: 2370741204
- Sigstore integration time:
-
Permalink:
mimecast/src2sink@b11eec7a6e33224a7b22a054bf52dd9de4c5757b -
Branch / Tag:
refs/tags/v3.0.0 - Owner: https://github.com/mimecast
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b11eec7a6e33224a7b22a054bf52dd9de4c5757b -
Trigger Event:
push
-
Statement type: