admin / Synapse-Sonar
publicAttack Surface Simulation
Synapse-Sonar / synapse-sonar / README.md
3909 B · main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | # Synapse Sonar **Dynamic Internal Attack Surface & Dependency Mapper** — agentless visibility into internal network topology, with vulnerability overlay, blast-radius simulation, and Zero-Trust drift detection. Multi-tenant, RBAC-secured, self-hosted, fully containerized. Built in the **Synapse IronNode** design language: deep-space dark command center, iron-grey structure, neon cyan/violet/blue synapse accents. --- ## Quick start ```bash cp .env.example .env # generate real secrets: # openssl rand -base64 32 -> NEXTAUTH_SECRET # openssl rand -hex 32 -> INTEGRATION_ENCRYPTION_KEY docker compose up -d --build # builds the app, starts Postgres, runs migrations ``` The initial migration ships in [`prisma/migrations/`](prisma/migrations); the entrypoint runs `prisma migrate deploy` automatically on boot. ### First run — guided setup On a fresh database there are no users, so any visit redirects to **`/setup`** — a two-step wizard that creates your first **organization (tenant)** and **Tenant Admin** account. The bootstrap endpoint (`/api/setup`) is one-shot: it refuses once any user exists. After that, sign in at **`/login`** (organization slug + email + password, plus a TOTP code when MFA is on). ### Optional: demo data instead of the wizard ```bash docker compose exec app npm run db:seed ``` Loads two tenants (isolation demo) and a sample topology. Login: `[email protected]` / `ChangeMe123!` (tenant slug `acme`). App: <http://localhost:3000> --- ## Architecture | Module | Where | | --- | --- | | **1 · Infra & schema** | [`Dockerfile`](Dockerfile), [`docker-compose.yml`](docker-compose.yml), [`prisma/schema.prisma`](prisma/schema.prisma) | | **2 · Identity / RBAC / tenancy** | [`lib/auth.ts`](lib/auth.ts), [`lib/rbac.ts`](lib/rbac.ts), [`lib/authOptions.ts`](lib/authOptions.ts), [`app/api/users/route.ts`](app/api/users/route.ts), [`components/UserAdministration.tsx`](components/UserAdministration.tsx), MFA: [`app/api/mfa/enroll/route.ts`](app/api/mfa/enroll/route.ts) | | **3 · Integration center** | [`components/IntegrationsPage.tsx`](components/IntegrationsPage.tsx), [`app/api/integrations/route.ts`](app/api/integrations/route.ts) | | **4 · Ingestion API** | [`app/api/ingest/flow-logs/route.ts`](app/api/ingest/flow-logs/route.ts) | | **5 · Topology map** | [`components/NetworkGraph.tsx`](components/NetworkGraph.tsx), data: [`app/api/graph/route.ts`](app/api/graph/route.ts) | | **6 · Blast radius** | [`lib/blastRadius.ts`](lib/blastRadius.ts) (recursive CTE), [`app/api/assets/[id]/blast-radius/route.ts`](app/api/assets/[id]/blast-radius/route.ts) | | **7 · Governance / drift** | [`lib/governance.ts`](lib/governance.ts) (Zero-Trust eval + IronNode SOAR hand-off) | ### Multi-tenant isolation Every model carries `tenantId`. Every API route opens with `requireTenantContext()` and scopes all queries by `ctx.tenantId`; cross-tenant writes use `updateMany`/`upsert` guarded by `tenantId`. There is no cross-tenant read path. ### Security posture - Integration secrets are **AES-256-GCM encrypted at rest** ([`lib/crypto.ts`](lib/crypto.ts)); the UI only ever receives masked values. - TOTP MFA secrets are encrypted the same way; MFA is enforced in `authOptions` when enabled. - RBAC: `TENANT_ADMIN` ⊃ `SECURITY_ANALYST` ⊃ `READ_ONLY_VIEWER`, enforced via `assertCan()`. - IronNode webhook payloads are HMAC-SHA256 signed (`X-Sonar-Signature`). --- ## Try the ingestion API ```bash curl -X POST http://localhost:3000/api/ingest/flow-logs \ -H "Authorization: Bearer sensor_acme_demo_key" \ -H "Content-Type: application/json" \ -d '{"flows":[{"sourceIp":"10.0.1.10","destIp":"10.0.3.30","port":5432,"protocol":"tcp","bytes":4096,"knownInventory":["10.0.1.10"]}]}' ``` `10.0.3.30` isn't in `knownInventory` → flagged **Rogue / Shadow IT**, and (with IronNode enabled) a signed violation fires to the webhook. |