Skip to content

Repository files navigation

This repo is under early active development and breaking changes should be expected.

stride-service

An agentic STRIDE threat-modeling engine: semi-structured text describing a system goes in, a structured JSON threat report comes out. The analysis runs as a Google ADK multi-agent graph over per-tier models from any supported vendor — Vertex, Anthropic or OpenAI, with no privileged default.

flowchart LR
    src(["sources<br/>(text)"]) --> extract["extract<br/>(base)"]
    extract --> analyze["lane agents<br/>in parallel<br/>(strong)"]
    analyze --> critic["critic<br/>(per framework, strong)"]
    critic --> report(["Report<br/>(JSON)"])

    classDef io fill:#f1f5f9,stroke:#64748b,stroke-width:1.5px,color:#0f172a
    classDef llm fill:#ede9fe,stroke:#7c3aed,stroke-width:1.5px,color:#2e1065
    class src,report io
    class extract,analyze,critic llm
Loading

One extraction, many frameworks. A single pass builds the canonical System Model (a DFD), and every framework the job selected analyses that one model. A framework ships as an in-repo package — its lanes, its deterministic rules, its record type and its text — with STRIDE as the first one rather than as the architecture. Its six lane agents draft threats in parallel, each citing the grounds for every threat it raises, and each package's own critic reviews its merged union in one pass — confirming, deduping, calibrating severity, and rejecting what the model does not support. See CONTEXT.md for the domain glossary.

Two ways to call it

  • In process — embed StrideEngine and get a report back from a function call. The path for swapping the engine in behind an existing analysis interface.
  • Over HTTP — the async /v1 job API, authenticated with a bearer token from any OIDC identity provider, for a decoupled front end.

Both surfaces drive the same pipeline and return the same Report: one envelope carrying the shared system model, with one block per framework the job named.

New here? docs/First-Run.md takes you from a clone to a real report to the engine embedded in your own code, in five steps.

No vendor is selected out of the box. config/model_tiers.toml ships with both tiers empty, so choosing one is a required first step rather than an override — "no privileged default" is a property of what ships, not just of the mechanism. Reaching the models then needs credentials for whichever vendor each tier names: Google Cloud application default credentials plus a project and location for Vertex, or an API key for Anthropic or OpenAI. If either the selection or its credentials are missing, startup stops with an error naming what to set rather than running on some fallback nobody chose; see docs/Configuration.md. Offline tests and the in-memory stub runner need none of it.

Repository layout

Path What lives here
src/stride_service/ The shipped engine. Graph, agents, config loaders, report schema, HTTP API.
config/ Versioned config that stops startup rather than falling back: model_tiers.toml, sampling.toml, resilience.toml, blessed-fingerprints.toml.
prompts/ Agent prompts and per-category exemplars.
skills/ The per-category STRIDE skill Markdown baked into the image.
docs/ User-facing documentation (see below).
examples/ Runnable embedding examples and the shared sample source. The source of truth for every code block in the docs.
webapp/ The lite first-run web app. Never ships in the wheel; run from a clone.
evals/ Golden-case corpus, scorer, and the eval harness. Never ships in the image.
tests/ Offline test suite (no credentials required).

The wheel bundles config/, prompts/ and skills/ alongside the engine (under stride_service/_bundled/), so pip install stride-service elsewhere resolves them with no extra step. This checkout's own config/, prompts/ and skills/ stay the source of truth — edit them here, not the bundled copy, which only exists inside a built wheel. The STRIDE_*_DIR variables still redirect any of them, in either layout.

Documentation

  • First-Run — start here; clone to embedded engine in five steps.
  • Integration-Guide — embed the engine in process.
  • Web-App — the lite local front end used on the first run.
  • Report-Schema — the result shape, provenance, and the three outcomes.
  • Configuration — config files, environment variables, and per-tier decoding.
  • HTTP-API — the /v1 async job contract, and its bearer auth.
  • Architecture — the graph, the seams, and how a run is certified.

Measuring the analysis, for contributors changing prompts, sampling or the corpus:

  • evals/README — what the harness measures, and what every number does and does not mean.
  • evals/TUNING — change one lever, measure it against the run-to-run noise, ship what beats it.
  • evals/VOTING — hold a review sitting: what each answer moves, and how a vote reaches the numbers.
  • evals/BLESSING — author a golden case and its reference set, per framework.

Development

The project uses uv. Python ≥ 3.11.

uv sync                                    # install deps into .venv
uv run pytest                              # the offline suite — no credentials needed
uv run ruff check .                        # lint
uv run mypy                                # type check
uv run python evals/verify_corpus.py       # mechanical checks over the golden corpus
uv run python examples/sync_docs.py --check # the docs' code blocks match examples/

The tracked pre-push hook runs the two ruff checks and the type check CI runs. Git does not enable hooks for a clone on its own, so opt in once:

git config core.hooksPath .githooks

It is a convenience guard rather than a gate — CI is authoritative, and git push --no-verify skips it.

Everything under tests/ and evals/verify_corpus.py is credential-free and deterministic. So is most of the eval harness: only run calls a provider, and scoring, re-scoring, calibration and the review queue all read files. Two commands need configured provider credentials — python -m stride_service.smoke runs one small job through the shipped graph to check that the vendor you selected actually serves it, and python -m evals.harness.run run sweeps the golden corpus. See evals/TUNING.md for the loop those numbers feed.

Status

The analysis code is complete and covered by an offline test suite. Three things are worth stating separately, because they are different kinds of claim:

  • The blessed-fingerprint list ships empty, permanently and by design. config/blessed-fingerprints.toml is deployment-local: certification attests that a report came from the exact model builds and sampling parameters your deployment blessed, so the project can never ship a certified pair on your behalf. An empty list is the correct shipped state, not missing work.
  • Per-tier sampling is an open tuning loop. The shipped file states no decoding value it cannot state for every model, so each tier runs at its model's own default. Improving the per-tier values is a measured process against the golden corpus — see evals/TUNING.md.
  • No provider has served a request in CI. Two kinds of live lane exist and neither has run: the per-vendor provider smoke, which asks whether a vendor serves this graph at all, and the golden-corpus eval sweeps, which ask how good its threat models are. Both need credentials this repository does not hold, and each says so in its own job summary rather than passing quietly. This is a not-yet rather than a cannot.

Persistent job and session backends are left as seams — the in-memory defaults are enough to get a report in process.

Licence

The code is Apache-2.0.

The ASVS package's text is not. src/stride_service/frameworks/asvs/catalog.json and the 17 files at frameworks/asvs/lanes/*/skill.md reproduce the 345 requirement sentences of OWASP ASVS 5.0.0, which OWASP publishes under CC BY-SA 4.0. Those files carry that licence and its ShareAlike condition. The Python that reads the catalog collects it rather than adapts it, so the rest of the tree stays Apache-2.0.

Four evaluation cases are prose conversions of OWASP Threat Model Cookbook models, which are CC BY 4.0 or Apache-2.0 upstream. Attribution only, no ShareAlike; each case names its source model in the provenance field of its case.json.

NOTICE is the authoritative list: it names every third-party work, every file the work governs, and what changed. CONTENT_LICENSE in src/stride_service/frameworks/__init__.py keys the same fact by framework, so a new package that quotes a standard cannot ship without an entry and a NOTICE line.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages