guardrail-security 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/attack-surface/analyzer.d.ts +50 -0
  2. package/dist/attack-surface/analyzer.d.ts.map +1 -0
  3. package/dist/attack-surface/analyzer.js +83 -0
  4. package/dist/attack-surface/index.d.ts +5 -0
  5. package/dist/attack-surface/index.d.ts.map +1 -0
  6. package/dist/attack-surface/index.js +20 -0
  7. package/dist/index.d.ts +15 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +33 -0
  10. package/dist/languages/index.d.ts +21 -0
  11. package/dist/languages/index.d.ts.map +1 -0
  12. package/dist/languages/index.js +78 -0
  13. package/dist/languages/java-analyzer.d.ts +72 -0
  14. package/dist/languages/java-analyzer.d.ts.map +1 -0
  15. package/dist/languages/java-analyzer.js +417 -0
  16. package/dist/languages/python-analyzer.d.ts +70 -0
  17. package/dist/languages/python-analyzer.d.ts.map +1 -0
  18. package/dist/languages/python-analyzer.js +425 -0
  19. package/dist/license/compatibility-matrix.d.ts +28 -0
  20. package/dist/license/compatibility-matrix.d.ts.map +1 -0
  21. package/dist/license/compatibility-matrix.js +323 -0
  22. package/dist/license/engine.d.ts +77 -0
  23. package/dist/license/engine.d.ts.map +1 -0
  24. package/dist/license/engine.js +264 -0
  25. package/dist/license/index.d.ts +6 -0
  26. package/dist/license/index.d.ts.map +1 -0
  27. package/dist/license/index.js +21 -0
  28. package/dist/sbom/generator.d.ts +108 -0
  29. package/dist/sbom/generator.d.ts.map +1 -0
  30. package/dist/sbom/generator.js +271 -0
  31. package/dist/sbom/index.d.ts +5 -0
  32. package/dist/sbom/index.d.ts.map +1 -0
  33. package/dist/sbom/index.js +20 -0
  34. package/dist/secrets/guardian.d.ts +113 -0
  35. package/dist/secrets/guardian.d.ts.map +1 -0
  36. package/dist/secrets/guardian.js +334 -0
  37. package/dist/secrets/index.d.ts +10 -0
  38. package/dist/secrets/index.d.ts.map +1 -0
  39. package/dist/secrets/index.js +30 -0
  40. package/dist/secrets/patterns.d.ts +42 -0
  41. package/dist/secrets/patterns.d.ts.map +1 -0
  42. package/dist/secrets/patterns.js +165 -0
  43. package/dist/secrets/pre-commit.d.ts +39 -0
  44. package/dist/secrets/pre-commit.d.ts.map +1 -0
  45. package/dist/secrets/pre-commit.js +127 -0
  46. package/dist/secrets/vault-integration.d.ts +83 -0
  47. package/dist/secrets/vault-integration.d.ts.map +1 -0
  48. package/dist/secrets/vault-integration.js +295 -0
  49. package/dist/secrets/vault-providers.d.ts +110 -0
  50. package/dist/secrets/vault-providers.d.ts.map +1 -0
  51. package/dist/secrets/vault-providers.js +417 -0
  52. package/dist/supply-chain/detector.d.ts +80 -0
  53. package/dist/supply-chain/detector.d.ts.map +1 -0
  54. package/dist/supply-chain/detector.js +168 -0
  55. package/dist/supply-chain/index.d.ts +11 -0
  56. package/dist/supply-chain/index.d.ts.map +1 -0
  57. package/dist/supply-chain/index.js +26 -0
  58. package/dist/supply-chain/malicious-db.d.ts +41 -0
  59. package/dist/supply-chain/malicious-db.d.ts.map +1 -0
  60. package/dist/supply-chain/malicious-db.js +82 -0
  61. package/dist/supply-chain/script-analyzer.d.ts +54 -0
  62. package/dist/supply-chain/script-analyzer.d.ts.map +1 -0
  63. package/dist/supply-chain/script-analyzer.js +160 -0
  64. package/dist/supply-chain/typosquat.d.ts +58 -0
  65. package/dist/supply-chain/typosquat.d.ts.map +1 -0
  66. package/dist/supply-chain/typosquat.js +257 -0
  67. package/dist/supply-chain/vulnerability-db.d.ts +114 -0
  68. package/dist/supply-chain/vulnerability-db.d.ts.map +1 -0
  69. package/dist/supply-chain/vulnerability-db.js +310 -0
  70. package/package.json +34 -0
  71. package/src/__tests__/license/engine.test.ts +250 -0
  72. package/src/__tests__/supply-chain/typosquat.test.ts +191 -0
  73. package/src/attack-surface/analyzer.ts +152 -0
  74. package/src/attack-surface/index.ts +5 -0
  75. package/src/index.ts +21 -0
  76. package/src/languages/index.ts +91 -0
  77. package/src/languages/java-analyzer.ts +490 -0
  78. package/src/languages/python-analyzer.ts +498 -0
  79. package/src/license/compatibility-matrix.ts +366 -0
  80. package/src/license/engine.ts +345 -0
  81. package/src/license/index.ts +6 -0
  82. package/src/sbom/generator.ts +355 -0
  83. package/src/sbom/index.ts +5 -0
  84. package/src/secrets/guardian.ts +448 -0
  85. package/src/secrets/index.ts +10 -0
  86. package/src/secrets/patterns.ts +186 -0
  87. package/src/secrets/pre-commit.ts +158 -0
  88. package/src/secrets/vault-integration.ts +360 -0
  89. package/src/secrets/vault-providers.ts +446 -0
  90. package/src/supply-chain/detector.ts +252 -0
  91. package/src/supply-chain/index.ts +11 -0
  92. package/src/supply-chain/malicious-db.ts +103 -0
  93. package/src/supply-chain/script-analyzer.ts +194 -0
  94. package/src/supply-chain/typosquat.ts +302 -0
  95. package/src/supply-chain/vulnerability-db.ts +386 -0
@@ -0,0 +1,446 @@
1
+ /**
2
+ * Vault Providers
3
+ *
4
+ * Real implementations for secret vault integrations:
5
+ * - AWS Secrets Manager
6
+ * - HashiCorp Vault
7
+ * - Azure Key Vault
8
+ * - GCP Secret Manager
9
+ */
10
+
11
+
12
+ export interface VaultProvider {
13
+ name: string;
14
+ createSecret(name: string, value: string): Promise<string>;
15
+ getSecret(name: string): Promise<string | null>;
16
+ deleteSecret(name: string): Promise<boolean>;
17
+ listSecrets(): Promise<string[]>;
18
+ testConnection(): Promise<boolean>;
19
+ }
20
+
21
+ export interface VaultProviderConfig {
22
+ type: 'aws_secrets_manager' | 'hashicorp_vault' | 'azure_keyvault' | 'gcp_secret_manager';
23
+ region?: string;
24
+ endpoint?: string;
25
+ projectId?: string;
26
+ credentials?: {
27
+ accessKeyId?: string;
28
+ secretAccessKey?: string;
29
+ token?: string;
30
+ clientId?: string;
31
+ clientSecret?: string;
32
+ tenantId?: string;
33
+ };
34
+ }
35
+
36
+ /**
37
+ * AWS Secrets Manager Provider
38
+ */
39
+ export class AWSSecretsManagerProvider implements VaultProvider {
40
+ name = 'AWS Secrets Manager';
41
+ private client: any;
42
+ private region: string;
43
+
44
+ constructor(config: VaultProviderConfig) {
45
+ this.region = config.region || 'us-east-1';
46
+ }
47
+
48
+ private async getClient() {
49
+ if (!this.client) {
50
+ const { SecretsManagerClient } = await import('@aws-sdk/client-secrets-manager');
51
+ this.client = new SecretsManagerClient({ region: this.region });
52
+ }
53
+ return this.client;
54
+ }
55
+
56
+ async createSecret(name: string, value: string): Promise<string> {
57
+ const client = await this.getClient();
58
+ const { CreateSecretCommand } = await import('@aws-sdk/client-secrets-manager');
59
+
60
+ try {
61
+ const result = await client.send(new CreateSecretCommand({
62
+ Name: name,
63
+ SecretString: value,
64
+ Description: 'Migrated by Guardrail AI',
65
+ }));
66
+ return result.ARN || name;
67
+ } catch (error: any) {
68
+ if (error.name === 'ResourceExistsException') {
69
+ const { PutSecretValueCommand } = await import('@aws-sdk/client-secrets-manager');
70
+ await client.send(new PutSecretValueCommand({
71
+ SecretId: name,
72
+ SecretString: value,
73
+ }));
74
+ return name;
75
+ }
76
+ throw error;
77
+ }
78
+ }
79
+
80
+ async getSecret(name: string): Promise<string | null> {
81
+ const client = await this.getClient();
82
+ const { GetSecretValueCommand } = await import('@aws-sdk/client-secrets-manager');
83
+
84
+ try {
85
+ const result = await client.send(new GetSecretValueCommand({ SecretId: name }));
86
+ return result.SecretString || null;
87
+ } catch (error: any) {
88
+ if (error.name === 'ResourceNotFoundException') {
89
+ return null;
90
+ }
91
+ throw error;
92
+ }
93
+ }
94
+
95
+ async deleteSecret(name: string): Promise<boolean> {
96
+ const client = await this.getClient();
97
+ const { DeleteSecretCommand } = await import('@aws-sdk/client-secrets-manager');
98
+
99
+ try {
100
+ await client.send(new DeleteSecretCommand({
101
+ SecretId: name,
102
+ ForceDeleteWithoutRecovery: false,
103
+ }));
104
+ return true;
105
+ } catch (error) {
106
+ return false;
107
+ }
108
+ }
109
+
110
+ async listSecrets(): Promise<string[]> {
111
+ const client = await this.getClient();
112
+ const { ListSecretsCommand } = await import('@aws-sdk/client-secrets-manager');
113
+
114
+ const result = await client.send(new ListSecretsCommand({}));
115
+ return (result.SecretList || []).map((s: any) => s.Name).filter(Boolean);
116
+ }
117
+
118
+ async testConnection(): Promise<boolean> {
119
+ try {
120
+ await this.listSecrets();
121
+ return true;
122
+ } catch (error) {
123
+ return false;
124
+ }
125
+ }
126
+ }
127
+
128
+ /**
129
+ * HashiCorp Vault Provider
130
+ */
131
+ export class HashiCorpVaultProvider implements VaultProvider {
132
+ name = 'HashiCorp Vault';
133
+ private client: any;
134
+ private endpoint: string;
135
+ private token: string;
136
+ private mountPath: string;
137
+
138
+ constructor(config: VaultProviderConfig) {
139
+ this.endpoint = config.endpoint || 'http://127.0.0.1:8200';
140
+ this.token = config.credentials?.token || process.env['VAULT_TOKEN'] || '';
141
+ this.mountPath = 'secret';
142
+ }
143
+
144
+ private async getClient() {
145
+ if (!this.client) {
146
+ const vault = (await import('node-vault')).default;
147
+ this.client = vault({
148
+ endpoint: this.endpoint,
149
+ token: this.token,
150
+ });
151
+ }
152
+ return this.client;
153
+ }
154
+
155
+ async createSecret(name: string, value: string): Promise<string> {
156
+ const client = await this.getClient();
157
+ const path = `${this.mountPath}/data/${name}`;
158
+
159
+ await client.write(path, {
160
+ data: { value },
161
+ metadata: { created_by: 'Guardrail-ai' },
162
+ });
163
+
164
+ return path;
165
+ }
166
+
167
+ async getSecret(name: string): Promise<string | null> {
168
+ const client = await this.getClient();
169
+ const path = `${this.mountPath}/data/${name}`;
170
+
171
+ try {
172
+ const result = await client.read(path);
173
+ return result?.data?.data?.value || null;
174
+ } catch (error: any) {
175
+ if (error.response?.statusCode === 404) {
176
+ return null;
177
+ }
178
+ throw error;
179
+ }
180
+ }
181
+
182
+ async deleteSecret(name: string): Promise<boolean> {
183
+ const client = await this.getClient();
184
+ const path = `${this.mountPath}/metadata/${name}`;
185
+
186
+ try {
187
+ await client.delete(path);
188
+ return true;
189
+ } catch (error) {
190
+ return false;
191
+ }
192
+ }
193
+
194
+ async listSecrets(): Promise<string[]> {
195
+ const client = await this.getClient();
196
+ const path = `${this.mountPath}/metadata`;
197
+
198
+ try {
199
+ const result = await client.list(path);
200
+ return result?.data?.keys || [];
201
+ } catch (error) {
202
+ return [];
203
+ }
204
+ }
205
+
206
+ async testConnection(): Promise<boolean> {
207
+ try {
208
+ const client = await this.getClient();
209
+ await client.health();
210
+ return true;
211
+ } catch (error) {
212
+ return false;
213
+ }
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Azure Key Vault Provider
219
+ */
220
+ export class AzureKeyVaultProvider implements VaultProvider {
221
+ name = 'Azure Key Vault';
222
+ private client: any;
223
+ private vaultUrl: string;
224
+
225
+ constructor(config: VaultProviderConfig) {
226
+ this.vaultUrl = config.endpoint || '';
227
+ if (!this.vaultUrl) {
228
+ throw new Error('Azure Key Vault URL is required');
229
+ }
230
+ }
231
+
232
+ private async getClient() {
233
+ if (!this.client) {
234
+ const { SecretClient } = await import('@azure/keyvault-secrets');
235
+ const { DefaultAzureCredential } = await import('@azure/identity');
236
+
237
+ const credential = new DefaultAzureCredential();
238
+ this.client = new SecretClient(this.vaultUrl, credential);
239
+ }
240
+ return this.client;
241
+ }
242
+
243
+ async createSecret(name: string, value: string): Promise<string> {
244
+ const client = await this.getClient();
245
+ const sanitizedName = name.replace(/[^a-zA-Z0-9-]/g, '-');
246
+
247
+ const result = await client.setSecret(sanitizedName, value, {
248
+ tags: { createdBy: 'Guardrail-ai' },
249
+ });
250
+
251
+ return result.properties.id || sanitizedName;
252
+ }
253
+
254
+ async getSecret(name: string): Promise<string | null> {
255
+ const client = await this.getClient();
256
+ const sanitizedName = name.replace(/[^a-zA-Z0-9-]/g, '-');
257
+
258
+ try {
259
+ const result = await client.getSecret(sanitizedName);
260
+ return result.value || null;
261
+ } catch (error: any) {
262
+ if (error.code === 'SecretNotFound') {
263
+ return null;
264
+ }
265
+ throw error;
266
+ }
267
+ }
268
+
269
+ async deleteSecret(name: string): Promise<boolean> {
270
+ const client = await this.getClient();
271
+ const sanitizedName = name.replace(/[^a-zA-Z0-9-]/g, '-');
272
+
273
+ try {
274
+ await client.beginDeleteSecret(sanitizedName);
275
+ return true;
276
+ } catch (error) {
277
+ return false;
278
+ }
279
+ }
280
+
281
+ async listSecrets(): Promise<string[]> {
282
+ const client = await this.getClient();
283
+ const secrets: string[] = [];
284
+
285
+ for await (const secretProperties of client.listPropertiesOfSecrets()) {
286
+ secrets.push(secretProperties.name);
287
+ }
288
+
289
+ return secrets;
290
+ }
291
+
292
+ async testConnection(): Promise<boolean> {
293
+ try {
294
+ await this.listSecrets();
295
+ return true;
296
+ } catch (error) {
297
+ return false;
298
+ }
299
+ }
300
+ }
301
+
302
+ /**
303
+ * GCP Secret Manager Provider
304
+ */
305
+ export class GCPSecretManagerProvider implements VaultProvider {
306
+ name = 'GCP Secret Manager';
307
+ private client: any;
308
+ private projectId: string;
309
+
310
+ constructor(config: VaultProviderConfig) {
311
+ this.projectId = config.projectId || process.env['GOOGLE_CLOUD_PROJECT'] || '';
312
+ if (!this.projectId) {
313
+ throw new Error('GCP Project ID is required');
314
+ }
315
+ }
316
+
317
+ private async getClient() {
318
+ if (!this.client) {
319
+ const { SecretManagerServiceClient } = await import('@google-cloud/secret-manager');
320
+ this.client = new SecretManagerServiceClient();
321
+ }
322
+ return this.client;
323
+ }
324
+
325
+ async createSecret(name: string, value: string): Promise<string> {
326
+ const client = await this.getClient();
327
+ const parent = `projects/${this.projectId}`;
328
+ const sanitizedName = name.replace(/[^a-zA-Z0-9_-]/g, '_');
329
+
330
+ try {
331
+ await client.createSecret({
332
+ parent,
333
+ secretId: sanitizedName,
334
+ secret: {
335
+ replication: { automatic: {} },
336
+ labels: { created_by: 'Guardrail-ai' },
337
+ },
338
+ });
339
+ } catch (error: any) {
340
+ if (error.code !== 6) {
341
+ throw error;
342
+ }
343
+ }
344
+
345
+ const secretName = `${parent}/secrets/${sanitizedName}`;
346
+ await client.addSecretVersion({
347
+ parent: secretName,
348
+ payload: { data: Buffer.from(value, 'utf8') },
349
+ });
350
+
351
+ return secretName;
352
+ }
353
+
354
+ async getSecret(name: string): Promise<string | null> {
355
+ const client = await this.getClient();
356
+ const sanitizedName = name.replace(/[^a-zA-Z0-9_-]/g, '_');
357
+ const secretName = `projects/${this.projectId}/secrets/${sanitizedName}/versions/latest`;
358
+
359
+ try {
360
+ const [version] = await client.accessSecretVersion({ name: secretName });
361
+ return version.payload?.data?.toString() || null;
362
+ } catch (error: any) {
363
+ if (error.code === 5) {
364
+ return null;
365
+ }
366
+ throw error;
367
+ }
368
+ }
369
+
370
+ async deleteSecret(name: string): Promise<boolean> {
371
+ const client = await this.getClient();
372
+ const sanitizedName = name.replace(/[^a-zA-Z0-9_-]/g, '_');
373
+ const secretName = `projects/${this.projectId}/secrets/${sanitizedName}`;
374
+
375
+ try {
376
+ await client.deleteSecret({ name: secretName });
377
+ return true;
378
+ } catch (error) {
379
+ return false;
380
+ }
381
+ }
382
+
383
+ async listSecrets(): Promise<string[]> {
384
+ const client = await this.getClient();
385
+ const parent = `projects/${this.projectId}`;
386
+
387
+ const [secrets] = await client.listSecrets({ parent });
388
+ return secrets.map((s: any) => s.name?.split('/').pop()).filter(Boolean);
389
+ }
390
+
391
+ async testConnection(): Promise<boolean> {
392
+ try {
393
+ await this.listSecrets();
394
+ return true;
395
+ } catch (error) {
396
+ return false;
397
+ }
398
+ }
399
+ }
400
+
401
+ /**
402
+ * Factory function to create vault provider
403
+ */
404
+ export function createVaultProvider(config: VaultProviderConfig): VaultProvider {
405
+ switch (config.type) {
406
+ case 'aws_secrets_manager':
407
+ return new AWSSecretsManagerProvider(config);
408
+ case 'hashicorp_vault':
409
+ return new HashiCorpVaultProvider(config);
410
+ case 'azure_keyvault':
411
+ return new AzureKeyVaultProvider(config);
412
+ case 'gcp_secret_manager':
413
+ return new GCPSecretManagerProvider(config);
414
+ default:
415
+ throw new Error(`Unsupported vault type: ${config.type}`);
416
+ }
417
+ }
418
+
419
+ /**
420
+ * Local environment provider (for development/testing)
421
+ */
422
+ export class LocalEnvProvider implements VaultProvider {
423
+ name = 'Local Environment';
424
+ private secrets: Map<string, string> = new Map();
425
+
426
+ async createSecret(name: string, value: string): Promise<string> {
427
+ this.secrets.set(name, value);
428
+ return `local://${name}`;
429
+ }
430
+
431
+ async getSecret(name: string): Promise<string | null> {
432
+ return this.secrets.get(name) || process.env[name] || null;
433
+ }
434
+
435
+ async deleteSecret(name: string): Promise<boolean> {
436
+ return this.secrets.delete(name);
437
+ }
438
+
439
+ async listSecrets(): Promise<string[]> {
440
+ return Array.from(this.secrets.keys());
441
+ }
442
+
443
+ async testConnection(): Promise<boolean> {
444
+ return true;
445
+ }
446
+ }
@@ -0,0 +1,252 @@
1
+ import { prisma } from "@guardrail/database";
2
+ import { typosquatDetector, TyposquatResult } from "./typosquat";
3
+ import { maliciousPackageDB } from "./malicious-db";
4
+ import { scriptAnalyzer, ScriptAnalysisResult } from "./script-analyzer";
5
+ import { readFileSync } from "fs";
6
+ import { join } from "path";
7
+
8
+ /**
9
+ * Package analysis result
10
+ */
11
+ export interface PackageAnalysisResult {
12
+ packageName: string;
13
+ version: string;
14
+ registry: string;
15
+ riskScore: number;
16
+ threats: Threat[];
17
+ isMalicious: boolean;
18
+ isTyposquat: boolean;
19
+ isDeprecated: boolean;
20
+ typosquatResult?: TyposquatResult;
21
+ scriptAnalysis?: ScriptAnalysisResult[];
22
+ license?: string;
23
+ maintainerRisk?: MaintainerRisk;
24
+ }
25
+
26
+ export interface Threat {
27
+ type: string;
28
+ severity: "low" | "medium" | "high" | "critical";
29
+ description: string;
30
+ }
31
+
32
+ export interface MaintainerRisk {
33
+ accountAge: number;
34
+ packageCount: number;
35
+ suspiciousActivity: boolean;
36
+ riskLevel: "low" | "medium" | "high";
37
+ }
38
+
39
+ /**
40
+ * SBOM (Software Bill of Materials)
41
+ */
42
+ export interface SBOM {
43
+ id?: string;
44
+ projectId: string;
45
+ version: number;
46
+ format: "CycloneDX" | "SPDX";
47
+ specVersion: string;
48
+ components: SBOMComponent[];
49
+ generatedAt: Date;
50
+ }
51
+
52
+ export interface SBOMComponent {
53
+ type: "library" | "application" | "framework";
54
+ name: string;
55
+ version: string;
56
+ purl: string; // Package URL
57
+ licenses?: string[];
58
+ hashes?: { algorithm: string; value: string }[];
59
+ dependencies?: string[];
60
+ }
61
+
62
+ /**
63
+ * Supply Chain Attack Detector
64
+ */
65
+ export class SupplyChainDetector {
66
+ /**
67
+ * Detect typosquatting
68
+ */
69
+ async detectTyposquatting(packageName: string): Promise<TyposquatResult> {
70
+ return typosquatDetector.detectTyposquatting(packageName);
71
+ }
72
+
73
+ /**
74
+ * Detect dependency confusion
75
+ */
76
+ async detectDependencyConfusion(
77
+ _packageName: string,
78
+ internalRegistry?: string,
79
+ ): Promise<{ isDependencyConfusion: boolean; reason: string }> {
80
+ // Check if package exists in both public and internal registries
81
+ // This is a simplified implementation
82
+
83
+ if (!internalRegistry) {
84
+ return {
85
+ isDependencyConfusion: false,
86
+ reason: "No internal registry configured",
87
+ };
88
+ }
89
+
90
+ // In production, this would check both registries
91
+ // and compare versions, publish dates, etc.
92
+
93
+ return {
94
+ isDependencyConfusion: false,
95
+ reason: "Package only found in public registry",
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Full package analysis
101
+ */
102
+ async analyzePackage(
103
+ packageName: string,
104
+ version: string,
105
+ projectId: string,
106
+ ): Promise<PackageAnalysisResult> {
107
+ const threats: Threat[] = [];
108
+ let riskScore = 0;
109
+
110
+ // Check for typosquatting
111
+ const typosquatResult = await this.detectTyposquatting(packageName);
112
+ const isTyposquat = typosquatResult.isTyposquat;
113
+
114
+ if (isTyposquat) {
115
+ threats.push({
116
+ type: "typosquatting",
117
+ severity: "high",
118
+ description: `Possible typosquatting of ${typosquatResult.targetPackage}`,
119
+ });
120
+ riskScore += 40;
121
+ }
122
+
123
+ // Check against malicious database
124
+ const maliciousCheck = await maliciousPackageDB.checkPackage(
125
+ packageName,
126
+ version,
127
+ );
128
+ const isMalicious = maliciousCheck.isMalicious;
129
+
130
+ if (isMalicious) {
131
+ for (const match of maliciousCheck.matches) {
132
+ threats.push({
133
+ type: "known_malicious",
134
+ severity: match.severity,
135
+ description: match.reason,
136
+ });
137
+ riskScore += 50;
138
+ }
139
+ }
140
+
141
+ // Analyze scripts
142
+ const scriptAnalysis = await scriptAnalyzer.analyzeScripts(
143
+ packageName,
144
+ version,
145
+ );
146
+
147
+ for (const analysis of scriptAnalysis) {
148
+ if (analysis.isSuspicious) {
149
+ threats.push({
150
+ type: "suspicious_script",
151
+ severity: "high",
152
+ description: `Suspicious script: ${analysis.scriptName}`,
153
+ });
154
+ riskScore += analysis.riskScore * 0.5;
155
+ }
156
+ }
157
+
158
+ // Save to database
159
+ await prisma.dependencyAnalysis.create({
160
+ data: {
161
+ projectId,
162
+ packageName,
163
+ version,
164
+ registry: "npm",
165
+ isMalicious,
166
+ isTyposquat,
167
+ isDeprecated: false,
168
+ riskScore: Math.min(100, riskScore),
169
+ threats: JSON.parse(JSON.stringify(threats)),
170
+ },
171
+ });
172
+
173
+ return {
174
+ packageName,
175
+ version,
176
+ registry: "npm",
177
+ riskScore: Math.min(100, riskScore),
178
+ threats,
179
+ isMalicious,
180
+ isTyposquat,
181
+ isDeprecated: false,
182
+ typosquatResult: isTyposquat ? typosquatResult : undefined,
183
+ scriptAnalysis,
184
+ };
185
+ }
186
+
187
+ /**
188
+ * Generate SBOM (CycloneDX format)
189
+ */
190
+ async generateSBOM(projectPath: string, projectId: string): Promise<SBOM> {
191
+ const components: SBOMComponent[] = [];
192
+
193
+ try {
194
+ // Read package.json
195
+ const packageJsonPath = join(projectPath, "package.json");
196
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
197
+
198
+ // Extract dependencies
199
+ const deps = {
200
+ ...packageJson.dependencies,
201
+ ...packageJson.devDependencies,
202
+ };
203
+
204
+ for (const [name, version] of Object.entries(deps)) {
205
+ components.push({
206
+ type: "library",
207
+ name,
208
+ version: version as string,
209
+ purl: `pkg:npm/${name}@${version}`,
210
+ });
211
+ }
212
+ } catch (error) {
213
+ // Handle error
214
+ }
215
+
216
+ const sbom: SBOM = {
217
+ id: `sbom_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
218
+ projectId,
219
+ version: 1,
220
+ format: "CycloneDX",
221
+ specVersion: "1.4",
222
+ components,
223
+ generatedAt: new Date(),
224
+ };
225
+
226
+ // @ts-ignore - SBOM model exists in schema, Prisma client may need regeneration
227
+ const savedSBOM = await (prisma as any).sBOM.create({
228
+ data: {
229
+ id: sbom.id,
230
+ projectId,
231
+ version: sbom.version,
232
+ format: sbom.format,
233
+ specVersion: sbom.specVersion,
234
+ components: JSON.parse(JSON.stringify(components)),
235
+ generatedAt: sbom.generatedAt,
236
+ },
237
+ });
238
+
239
+ return {
240
+ id: savedSBOM.id,
241
+ projectId: savedSBOM.projectId,
242
+ version: savedSBOM.version,
243
+ format: savedSBOM.format as "CycloneDX" | "SPDX",
244
+ specVersion: savedSBOM.specVersion,
245
+ components: savedSBOM.components as unknown as SBOMComponent[],
246
+ generatedAt: savedSBOM.generatedAt,
247
+ };
248
+ }
249
+ }
250
+
251
+ // Export singleton
252
+ export const supplyChainDetector = new SupplyChainDetector();
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Supply Chain Attack Detection
3
+ *
4
+ * Detects typosquatting, malicious packages, and generates SBOMs
5
+ */
6
+
7
+ export * from './detector';
8
+ export * from './typosquat';
9
+ export * from './malicious-db';
10
+ export * from './script-analyzer';
11
+ export * from './vulnerability-db';