Skip to content

Architecture

OpenTremor is two Python packages: opentremor-platform, a domain-agnostic multi-tenant base, and opentremor-core, the security-analysis engine mounted on top of it. Analyzers are separate plugin packages again, discovered at startup.

This page covers the parts that don’t belong to either half — how a request flows, how content is hashed, how requests are correlated. Everything else has its own page:

PageWhat’s in it
Module mapEvery module in both packages, and what each is for
Platform / product compositionThe two app factories, the six registration points, and the seams
Platform baseWhat opentremor-platform provides on its own
Data modelCollections, relationships, and multi-tenant scoping
Storage layerThe split ABCs, both concrete backends, index management
Analysis engineAnalyzer plugins, auto-routing, server-side analysis, findings lifecycle
Billing & quotaDeployment-mode entitlement and per-org spend caps
GitHub App integrationShared vs. custom App identity, manifest flow, PR round-trip

flowchart TD
    A[HTTP Request] --> B[TelemetryMiddleware\ntimes the request, records a span]
    B --> C[RequestIDMiddleware\nassigns/forwards X-Request-ID]
    C --> D[CORSMiddleware]
    D --> E[FastAPI Router\npath matching + dependency injection]
    E --> F1["require_principal(min_role)\n-> AuthContext(org_id, role, key_id|user_id)"]
    F1 --> F2["get_storage()\n-> app.state.storage"]
    F1 --> F3["get_registry()\n-> app.state.registry"]
    F2 --> G[Route Handler]
    F3 --> G
    G --> H1["storage.*\nasync StorageBackend\n(Mongo or InMemory), every call org_id-scoped"]
    G --> H2["registry.get(name)\n-> BaseAnalyzer.ingest()\nor .get_rules()"]
    H1 --> I[JSONResponse / PlainTextResponse]
    H2 --> I

Every authenticated route resolves an AuthContext first (API key, session cookie, or the anonymous default-org owner when auth is disabled) — org_id from that context is threaded into every downstream storage.* call, so a handler can never accidentally read or write another org’s data.



A resource’s identity is a hash of its own content, but not a hash of the raw text — a few patterns get normalised away first, since they’re artifacts of when the plan was rendered rather than facts about the resource itself:

  • (known after apply) — a value Terraform hasn’t resolved yet
  • (sensitive value) — Terraform’s own redaction of a secret
  • (will be computed) — another apply-time placeholder
  • blank lines and surrounding whitespace

Two otherwise-identical resources that only differ in these ephemeral bits end up with the same hash, so they share one stored resource/analysis instead of piling up near-duplicates.



Every request carries an X-Request-ID — reused if the caller sent one, minted fresh otherwise. A logging.Filter wired into uvicorn’s loggers stamps it onto every log line that request produces, so following one request’s trail through the logs is just a matter of grepping for its ID.