Catalyst / admin/Synapse-Sonar 14.8 GB / 57.8 GB 40.0 GB free
Help Sign in

admin / Synapse-Sonar

public

Attack Surface Simulation

Code Issues Pull requests Pipelines Packages Security Insights Wiki Settings
Synapse-Sonar / synapse-sonar / prisma / schema.prisma 7607 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
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
// Synapse Sonar — multi-tenant graph + identity schema
// PostgreSQL with JSONB for dynamic asset metadata.

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

// ---------------------------------------------------------------------------
// Identity & Tenancy
// ---------------------------------------------------------------------------

/// A distinct client organization. The root of every isolation boundary.
model Tenant {
  id        String   @id @default(cuid())
  name      String
  slug      String   @unique

  /// When true, every member must enroll in MFA before using the app, and MFA
  /// is enforced at login. Toggled by a Tenant Admin (org-wide policy).
  mfaRequired Boolean @default(false)

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  users        User[]
  integrations IntegrationSetting[]
  assets       Asset[]
  connections  Connection[]
  baselines    SegmentationRule[]

  @@map("tenants")
}

enum Role {
  TENANT_ADMIN
  SECURITY_ANALYST
  READ_ONLY_VIEWER
}

enum UserStatus {
  INVITED
  ACTIVE
  DISABLED
}

model User {
  id           String     @id @default(cuid())
  tenantId     String
  username     String     @unique // global login identifier
  email        String? // optional metadata (notifications, etc.)
  name         String?
  passwordHash String? // null while status = INVITED
  role         Role       @default(READ_ONLY_VIEWER)
  status       UserStatus @default(INVITED)

  // --- MFA (optional TOTP) ---
  mfaEnabled   Boolean  @default(false)
  mfaSecret    String? // encrypted TOTP secret; only set once enrollment completes
  inviteToken  String?  @unique
  createdAt    DateTime @default(now())
  updatedAt    DateTime @updatedAt
  lastLoginAt  DateTime?

  tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)

  @@index([tenantId])
  @@map("users")
}

// ---------------------------------------------------------------------------
// Integrations
// ---------------------------------------------------------------------------

enum IntegrationProvider {
  NETSCAN_XI // vulnerability ingestion
  SYNAPSE_IRONNODE // SOAR / alert webhook receiver
}

/// One row per provider per tenant. `config` holds AES-256-GCM encrypted
/// secrets (API keys, signing keys, URLs) — never store them in plaintext.
model IntegrationSetting {
  id        String              @id @default(cuid())
  tenantId  String
  provider  IntegrationProvider
  enabled   Boolean             @default(false)

  /// Encrypted blob: { iv, authTag, ciphertext } of the JSON config.
  /// Decrypted shape (NetscanXi):  { endpointUrl, apiKey }
  /// Decrypted shape (IronNode):   { webhookUrl, signingKey }
  config    Json?

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)

  @@unique([tenantId, provider])
  @@index([tenantId])
  @@map("integration_settings")
}

// ---------------------------------------------------------------------------
// Network Topology (Graph)
// ---------------------------------------------------------------------------

enum AssetType {
  SERVER
  WORKSTATION
  CONTAINER
  DATABASE
  NETWORK_DEVICE
  CLOUD_SERVICE
  UNKNOWN
}

enum CriticalityTier {
  TIER_0_CROWN_JEWEL
  TIER_1_CRITICAL
  TIER_2_STANDARD
  TIER_3_LOW
}

/// A discovered node on the network. Scoped to a tenant.
model Asset {
  id          String          @id @default(cuid())
  tenantId    String
  ipAddress   String
  hostname    String?
  assetType   AssetType       @default(UNKNOWN)
  criticality CriticalityTier @default(TIER_2_STANDARD)

  /// Set true when an IP is seen that is NOT present in CMDB/AD sync data.
  isRogue     Boolean         @default(false)

  /// Manually marked offline by an operator. Overrides live status in the UI
  /// (the node renders dimmed) until cleared.
  manualOffline Boolean       @default(false)

  openPorts   Int[]           @default([])

  /// Additional addresses for the same host (e.g. Docker bridge / secondary NIC
  /// IPs) reported by an agent. The node is keyed by the primary host IPv4
  /// (ipAddress); these are shown as "related" in the UI.
  relatedIps  String[]        @default([])

  /// Raw, schema-less discovery payload (flow-log sensor output, enrichment, etc.)
  metadata    Json?

  firstSeenAt DateTime @default(now())
  lastSeenAt  DateTime @default(now())
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt

  tenant          Tenant          @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  outboundEdges   Connection[]    @relation("SourceAsset")
  inboundEdges    Connection[]    @relation("TargetAsset")
  vulnerabilities Vulnerability[]

  // An IP is unique within a tenant; the same IP may exist for another tenant.
  @@unique([tenantId, ipAddress])
  @@index([tenantId])
  @@index([tenantId, isRogue])
  @@map("assets")
}

/// A directed edge between two assets (observed flow). Scoped to a tenant.
model Connection {
  id            String   @id @default(cuid())
  tenantId      String
  sourceAssetId String
  targetAssetId String
  protocol      String? // tcp | udp | icmp ...
  port          Int?
  bytes         BigInt   @default(0) // cumulative volume — drives edge thickness/speed
  lastSeenAt    DateTime @default(now())
  firstSeenAt   DateTime @default(now())

  tenant      Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  sourceAsset Asset  @relation("SourceAsset", fields: [sourceAssetId], references: [id], onDelete: Cascade)
  targetAsset Asset  @relation("TargetAsset", fields: [targetAssetId], references: [id], onDelete: Cascade)

  // One edge per (source, target, port) per tenant — upserts hit this key.
  @@unique([tenantId, sourceAssetId, targetAssetId, port])
  @@index([tenantId])
  @@index([sourceAssetId])
  @@index([targetAssetId])
  @@map("connections")
}

enum Severity {
  LOW
  MEDIUM
  HIGH
  CRITICAL
}

/// A known vulnerability tied to an asset, sourced from NetscanXi.
model Vulnerability {
  id          String   @id @default(cuid())
  tenantId    String
  assetId     String
  cveId       String
  title       String?
  cvssScore   Float // base CVSS (0.0 - 10.0)
  severity    Severity
  source      String   @default("NETSCAN_XI")
  discoveredAt DateTime @default(now())

  asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)

  @@unique([assetId, cveId])
  @@index([tenantId])
  @@index([assetId])
  @@map("vulnerabilities")
}

// ---------------------------------------------------------------------------
// Zero-Trust Governance
// ---------------------------------------------------------------------------

enum RuleAction {
  ALLOW
  DENY
}

/// Baseline segmentation policy for a tenant. New flows are evaluated against
/// these rules; a flow that matches a DENY (or matches no ALLOW) is a violation.
model SegmentationRule {
  id            String     @id @default(cuid())
  tenantId      String
  description   String?
  // Simple CIDR/port matchers for the MVP rule engine.
  sourceCidr    String     @default("0.0.0.0/0")
  destCidr      String     @default("0.0.0.0/0")
  port          Int? // null = any
  action        RuleAction @default(ALLOW)
  priority      Int        @default(100) // lower = evaluated first
  createdAt     DateTime   @default(now())

  tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)

  @@index([tenantId])
  @@map("segmentation_rules")
}