Sentinel / Architecture
← Site preview
◆ ARCHITECTURE.md · tier: small · comprehensive

Unified AppSec platform — system architecture

Sentinel orchestrates best-of-breed OSS security scanners in sandboxed containers, normalizes their output into one canonical Finding model (SARIF-2.1.0-inspired), deduplicates across scanners, and serves a developer-first dashboard. Four runtime processes plus an isolated, untrusted scanner-execution plane.

System layers & data flow

Each layer hands off to the next; the scanner plane is deliberately isolated from the host.

Client

Browser — Next.js 16 dashboard

App Router · Tailwind v4 · TanStack Query v5 · full dark mode. HTTPS, cookie session.
Dashboard
posture rollups
Findings
deduped table + triage
Targets & Scans
connect · verify · run
Auth & Settings
org / members
API

Express 5 · REST /v1 · Prisma 7

Auth + RBAC · CRUD · triage · ownership verification · signed webhook ingress · rate limiting · audit log · RFC 9457 problem+json errors.
withOrgScope
tenant isolation (INV-1)
assertResourceAccess
object authz (INV-2)
SSRF + ownership
live-scan gates (INV-11/12)
rate limit + audit
SEC-5 / SEC-11
State

Persistence & queue

Org-scoped relational data (RLS + app scoping) and the scan job bus.
PostgreSQL 18
Prisma 7 · RLS · JSONB raw output · Finding(orgId,fingerprint) unique (INV-4)
Redis 8 · BullMQ 5
scan queue + parent/child flows
Worker

BullMQ worker — orchestrator

Fans a Scan into per-scanner child jobs, runs each via the hardened SandboxRunner, then aggregates.
Fan-out
one child job / scanner
SandboxRunner
launch hardened container (INV-6)
Normalize → Enrich
adapter → Finding + CVSS/CVE/OSV
Dedup → Persist
fingerprint collapse (INV-4)
Untrusted

Scanner execution plane

--network none|restricted · --read-only · --cap-drop ALL · --no-new-privileges · --pids-limit · --memory · --cpus · --rm · gVisor runsc when available. nmap (NPSL) & masscan (AGPL) run as isolated external-process containers only.
SCA
trivy · osv-scanner
SAST
semgrep
Secrets · IaC
gitleaks · checkov
DAST · Ports
zap · nuclei · nmap · masscan
tenant / authz boundary abuse / SSRF gate queue / copyleft isolation ops control

The spine — one scan, end to end

The same pipeline runs inline (dev) or via BullMQ (production). Adding a scanner = adding one adapter.

01
Connect
repo / domain / IP target
02
Verify
ownership for live targets
03
Enqueue
Scan → BullMQ flow
04
Fan-out
per-scanner child jobs
05
Sandbox
hardened container exec
06
Normalize
adapter → Finding
07
Dedup
fingerprint collapse
08
Triage
fixed / ignored / track

Scanner tools — the orchestrated OSS engines

Each invoked as an isolated container with the verified machine-readable output flag. The demo SCA engine ships default-on; the rest run behind SCANNERS_DOCKER_ENABLED.

EngineTypeTargetOutputNetworkLicense
Trivy 0.71.2SCArepo / image--format sarifnoneApache-2.0
OSV-Scanner 2.4.0SCAlockfiles--format jsonnoneApache-2.0
Semgrep 1.167SASTsource repo--sarifnoneLGPL-2.1
gitleaks 8.30.1SECRETgit history--report-format sarifnoneMIT
checkov 3.3IaCIaC files-o sarifnoneApache-2.0
OWASP ZAP 2.17DASTlive URL-J report.jsonrestrictedApache-2.0
Nuclei 3.9.0DASTlive URL-jsonlrestrictedMIT
nmap 7.99PORThost / IP-oX (→ JSON)restrictedNPSL · isolated
masscan 1.3.2PORTIP ranges-oJrestrictedAGPL-3.0 · isolated
none = no egress (code mounted read-only) restricted = egress only after ownership verification copyleft = external-process container only, never linked

Monorepo packages

Three apps + four shared packages. ESM JavaScript on Node 22 (backend) · TypeScript (web).

apps/web
Next.js dashboard — 11 screens, full design-token system, dark + light.
apps/api
Express REST API — auth, projects, targets, scans, findings, webhooks, health.
apps/worker
BullMQ worker — scan orchestration + sandboxed execution.
packages/core
Finding model + dedup, RFC 9457 errors, authz chokepoint, SSRF, argon2 hashing, validation.
packages/db
Prisma schema + in-memory repo + the withOrgScope wrapper.
packages/scanners
Engine adapters, SandboxRunner, the normalize→dedup→persist orchestrator.
packages/config
Env loading/validation + secrets resolution (env → AWS Secrets Manager).
scripts/invariant-lint.mjs
Machine-checks ARCHITECTURE §9 / invariants.json — wired into CI & the drift gate.

Data model

org-scoped tables carry orgId and are reached only via withOrgScope. Spine: User → Org → Project → Target → Scan → Finding.

Useremail · passwordHash (argon2id) · name
Orgname · slug · (tenant root)
MembershipuserId · orgId · role(owner|admin|member) · unique(userId,orgId)
SessionuserId · tokenHash · expiresAt
● ProjectorgId · name · slug · unique(orgId,slug)
● TargetorgId · kind(repo|domain|ip) · value · ownershipVerified · ownershipToken
● ScanorgId · status(queued→running→completed|partial|failed) · scanners[]
● FindingorgId · type · severity · cvss · cve · location · fingerprint · status · unique(orgId,fingerprint) ◀ INV-4
● AuditLogorgId · actorUserId · action · entity · meta (immutable, 1-yr)
WebhookDeliveryprovider · deliveryId · unique(provider,deliveryId) ◀ idempotency

Architectural invariants (§9 · machine-checked)

Each rule has a invariants.json entry the lint runner enforces in CI and at the drift gate. 11 machine-checkable · 2 manual.

INV-1 · forbidden-pattern
Tenant data only through withOrgScope; no direct ORM on scoped tables.
packages/db/src/index.js
INV-2 · required-file
Object-level authz centralized in assertResourceAccess.
packages/core/src/authz.js
INV-3 · forbidden-pattern
argon2id only — no bcrypt/plaintext substitution.
apps/api/src/auth
INV-4 · unique-constraint
Dedup enforced by DB unique(orgId, fingerprint), not app logic.
packages/db/prisma/schema.prisma
INV-5 · forbidden-pattern
No hardcoded secrets / provider key patterns in source.
repo-wide
INV-6 · forbidden-pattern
Scanners run only through the SandboxRunner.
packages/scanners/src/runner.js
INV-7 · boundary-order
Webhooks verify signature before any DB access.
apps/api/src/routes/webhooks.js
INV-8 · required-file
API errors use the RFC 9457 problem+json helper.
packages/core/src/errors.js
INV-9 · forbidden-pattern
Design tokens are the single source of color — no hardcoded hex in components.
apps/web
INV-11 · required-file
SSRF denylist applied before any live scan.
packages/core/src/ssrf.js
INV-12 · manual
Live (domain/IP) scan rejected while ownershipVerified=false.
apps/api scan-create path
INV-13 · manual
Every UI screen routes & every Key Workflow completes through the UI.
apps/web ↔ SPEC §UI Surface

Stack & cross-cutting concerns

Tier: small (<1k concurrent). HTTPS only · multi-tenant · structured JSON logs · Sentry · daily Postgres snapshots.

Next.js 16React 19Express 5 Prisma 7PostgreSQL 18Redis 8 BullMQ 5TanStack Query 5Tailwind 4 argon2idzodResend SentryAWS Secrets ManagerDocker + gVisor Caddy TLS · HSTS
Sentinel · ARCHITECTURE.md + invariants.json · Built with the MDLC pipeline · static self-contained diagram.
\← Gallery