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:
| Page | What’s in it |
|---|---|
| Module map | Every module in both packages, and what each is for |
| Platform / product composition | The two app factories, the six registration points, and the seams |
| Platform base | What opentremor-platform provides on its own |
| Data model | Collections, relationships, and multi-tenant scoping |
| Storage layer | The split ABCs, both concrete backends, index management |
| Analysis engine | Analyzer plugins, auto-routing, server-side analysis, findings lifecycle |
| Billing & quota | Deployment-mode entitlement and per-org spend caps |
| GitHub App integration | Shared vs. custom App identity, manifest flow, PR round-trip |
Request lifecycle
Section titled “Request lifecycle”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.
Hashing
Section titled “Hashing”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.
Correlation IDs
Section titled “Correlation IDs”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.