ghostpatch 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 (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +213 -0
  3. package/__tests__/detectors.test.ts +224 -0
  4. package/__tests__/rules.test.ts +117 -0
  5. package/__tests__/scanner.test.ts +222 -0
  6. package/dist/ai/anthropic.d.ts +11 -0
  7. package/dist/ai/anthropic.d.ts.map +1 -0
  8. package/dist/ai/anthropic.js +76 -0
  9. package/dist/ai/anthropic.js.map +1 -0
  10. package/dist/ai/huggingface.d.ts +12 -0
  11. package/dist/ai/huggingface.d.ts.map +1 -0
  12. package/dist/ai/huggingface.js +95 -0
  13. package/dist/ai/huggingface.js.map +1 -0
  14. package/dist/ai/openai.d.ts +11 -0
  15. package/dist/ai/openai.d.ts.map +1 -0
  16. package/dist/ai/openai.js +71 -0
  17. package/dist/ai/openai.js.map +1 -0
  18. package/dist/ai/prompts.d.ts +5 -0
  19. package/dist/ai/prompts.d.ts.map +1 -0
  20. package/dist/ai/prompts.js +101 -0
  21. package/dist/ai/prompts.js.map +1 -0
  22. package/dist/ai/provider.d.ts +9 -0
  23. package/dist/ai/provider.d.ts.map +1 -0
  24. package/dist/ai/provider.js +66 -0
  25. package/dist/ai/provider.js.map +1 -0
  26. package/dist/cli/index.d.ts +3 -0
  27. package/dist/cli/index.d.ts.map +1 -0
  28. package/dist/cli/index.js +318 -0
  29. package/dist/cli/index.js.map +1 -0
  30. package/dist/core/reporter.d.ts +7 -0
  31. package/dist/core/reporter.d.ts.map +1 -0
  32. package/dist/core/reporter.js +366 -0
  33. package/dist/core/reporter.js.map +1 -0
  34. package/dist/core/rules.d.ts +8 -0
  35. package/dist/core/rules.d.ts.map +1 -0
  36. package/dist/core/rules.js +1077 -0
  37. package/dist/core/rules.js.map +1 -0
  38. package/dist/core/scanner.d.ts +6 -0
  39. package/dist/core/scanner.d.ts.map +1 -0
  40. package/dist/core/scanner.js +217 -0
  41. package/dist/core/scanner.js.map +1 -0
  42. package/dist/core/severity.d.ts +100 -0
  43. package/dist/core/severity.d.ts.map +1 -0
  44. package/dist/core/severity.js +52 -0
  45. package/dist/core/severity.js.map +1 -0
  46. package/dist/detectors/auth.d.ts +3 -0
  47. package/dist/detectors/auth.d.ts.map +1 -0
  48. package/dist/detectors/auth.js +138 -0
  49. package/dist/detectors/auth.js.map +1 -0
  50. package/dist/detectors/crypto.d.ts +3 -0
  51. package/dist/detectors/crypto.d.ts.map +1 -0
  52. package/dist/detectors/crypto.js +128 -0
  53. package/dist/detectors/crypto.js.map +1 -0
  54. package/dist/detectors/dependency.d.ts +4 -0
  55. package/dist/detectors/dependency.d.ts.map +1 -0
  56. package/dist/detectors/dependency.js +267 -0
  57. package/dist/detectors/dependency.js.map +1 -0
  58. package/dist/detectors/deserialize.d.ts +3 -0
  59. package/dist/detectors/deserialize.d.ts.map +1 -0
  60. package/dist/detectors/deserialize.js +107 -0
  61. package/dist/detectors/deserialize.js.map +1 -0
  62. package/dist/detectors/injection.d.ts +3 -0
  63. package/dist/detectors/injection.d.ts.map +1 -0
  64. package/dist/detectors/injection.js +158 -0
  65. package/dist/detectors/injection.js.map +1 -0
  66. package/dist/detectors/misconfig.d.ts +3 -0
  67. package/dist/detectors/misconfig.d.ts.map +1 -0
  68. package/dist/detectors/misconfig.js +153 -0
  69. package/dist/detectors/misconfig.js.map +1 -0
  70. package/dist/detectors/pathtraversal.d.ts +3 -0
  71. package/dist/detectors/pathtraversal.d.ts.map +1 -0
  72. package/dist/detectors/pathtraversal.js +90 -0
  73. package/dist/detectors/pathtraversal.js.map +1 -0
  74. package/dist/detectors/prototype.d.ts +3 -0
  75. package/dist/detectors/prototype.d.ts.map +1 -0
  76. package/dist/detectors/prototype.js +79 -0
  77. package/dist/detectors/prototype.js.map +1 -0
  78. package/dist/detectors/secrets.d.ts +4 -0
  79. package/dist/detectors/secrets.d.ts.map +1 -0
  80. package/dist/detectors/secrets.js +137 -0
  81. package/dist/detectors/secrets.js.map +1 -0
  82. package/dist/detectors/ssrf.d.ts +3 -0
  83. package/dist/detectors/ssrf.d.ts.map +1 -0
  84. package/dist/detectors/ssrf.js +78 -0
  85. package/dist/detectors/ssrf.js.map +1 -0
  86. package/dist/detectors/zeroday.d.ts +9 -0
  87. package/dist/detectors/zeroday.d.ts.map +1 -0
  88. package/dist/detectors/zeroday.js +77 -0
  89. package/dist/detectors/zeroday.js.map +1 -0
  90. package/dist/index.d.ts +10 -0
  91. package/dist/index.d.ts.map +1 -0
  92. package/dist/index.js +42 -0
  93. package/dist/index.js.map +1 -0
  94. package/dist/mcp/server.d.ts +2 -0
  95. package/dist/mcp/server.d.ts.map +1 -0
  96. package/dist/mcp/server.js +358 -0
  97. package/dist/mcp/server.js.map +1 -0
  98. package/dist/utils/config.d.ts +4 -0
  99. package/dist/utils/config.d.ts.map +1 -0
  100. package/dist/utils/config.js +97 -0
  101. package/dist/utils/config.js.map +1 -0
  102. package/dist/utils/fingerprint.d.ts +5 -0
  103. package/dist/utils/fingerprint.d.ts.map +1 -0
  104. package/dist/utils/fingerprint.js +55 -0
  105. package/dist/utils/fingerprint.js.map +1 -0
  106. package/dist/utils/languages.d.ts +8 -0
  107. package/dist/utils/languages.d.ts.map +1 -0
  108. package/dist/utils/languages.js +128 -0
  109. package/dist/utils/languages.js.map +1 -0
  110. package/package.json +53 -0
  111. package/src/ai/anthropic.ts +82 -0
  112. package/src/ai/huggingface.ts +111 -0
  113. package/src/ai/openai.ts +75 -0
  114. package/src/ai/prompts.ts +100 -0
  115. package/src/ai/provider.ts +68 -0
  116. package/src/cli/index.ts +314 -0
  117. package/src/core/reporter.ts +356 -0
  118. package/src/core/rules.ts +1089 -0
  119. package/src/core/scanner.ts +201 -0
  120. package/src/core/severity.ts +140 -0
  121. package/src/detectors/auth.ts +152 -0
  122. package/src/detectors/crypto.ts +128 -0
  123. package/src/detectors/dependency.ts +240 -0
  124. package/src/detectors/deserialize.ts +106 -0
  125. package/src/detectors/injection.ts +172 -0
  126. package/src/detectors/misconfig.ts +152 -0
  127. package/src/detectors/pathtraversal.ts +89 -0
  128. package/src/detectors/prototype.ts +77 -0
  129. package/src/detectors/secrets.ts +138 -0
  130. package/src/detectors/ssrf.ts +77 -0
  131. package/src/detectors/zeroday.ts +93 -0
  132. package/src/index.ts +24 -0
  133. package/src/mcp/server.ts +379 -0
  134. package/src/utils/config.ts +64 -0
  135. package/src/utils/fingerprint.ts +21 -0
  136. package/src/utils/languages.ts +95 -0
  137. package/tsconfig.json +20 -0
  138. package/vitest.config.ts +8 -0
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.CONFIG_FILES = exports.DEFAULT_EXCLUDE = exports.SUPPORTED_LANGUAGES = exports.LANGUAGE_MAP = void 0;
37
+ exports.detectLanguage = detectLanguage;
38
+ exports.isSupportedFile = isSupportedFile;
39
+ exports.isConfigFile = isConfigFile;
40
+ const path = __importStar(require("path"));
41
+ exports.LANGUAGE_MAP = {
42
+ '.js': 'javascript',
43
+ '.jsx': 'javascript',
44
+ '.mjs': 'javascript',
45
+ '.cjs': 'javascript',
46
+ '.ts': 'typescript',
47
+ '.tsx': 'typescript',
48
+ '.mts': 'typescript',
49
+ '.cts': 'typescript',
50
+ '.py': 'python',
51
+ '.pyw': 'python',
52
+ '.java': 'java',
53
+ '.go': 'go',
54
+ '.rs': 'rust',
55
+ '.c': 'c',
56
+ '.h': 'c',
57
+ '.cpp': 'cpp',
58
+ '.cc': 'cpp',
59
+ '.cxx': 'cpp',
60
+ '.hpp': 'cpp',
61
+ '.hxx': 'cpp',
62
+ '.cs': 'csharp',
63
+ '.php': 'php',
64
+ '.rb': 'ruby',
65
+ '.swift': 'swift',
66
+ '.kt': 'kotlin',
67
+ '.kts': 'kotlin',
68
+ '.sh': 'shell',
69
+ '.bash': 'shell',
70
+ '.zsh': 'shell',
71
+ '.sql': 'sql',
72
+ '.html': 'html',
73
+ '.htm': 'html',
74
+ '.vue': 'javascript',
75
+ '.svelte': 'javascript',
76
+ };
77
+ exports.SUPPORTED_LANGUAGES = [
78
+ 'typescript', 'javascript', 'python', 'java', 'go', 'rust',
79
+ 'c', 'cpp', 'csharp', 'php', 'ruby', 'swift', 'kotlin',
80
+ 'shell', 'sql',
81
+ ];
82
+ function detectLanguage(filePath) {
83
+ const ext = path.extname(filePath).toLowerCase();
84
+ return exports.LANGUAGE_MAP[ext] || null;
85
+ }
86
+ function isSupportedFile(filePath) {
87
+ return detectLanguage(filePath) !== null;
88
+ }
89
+ exports.DEFAULT_EXCLUDE = [
90
+ 'node_modules/**',
91
+ 'dist/**',
92
+ 'build/**',
93
+ 'out/**',
94
+ '.git/**',
95
+ '.next/**',
96
+ '.nuxt/**',
97
+ 'vendor/**',
98
+ '__pycache__/**',
99
+ '*.min.js',
100
+ '*.min.css',
101
+ '*.map',
102
+ '*.lock',
103
+ 'package-lock.json',
104
+ 'yarn.lock',
105
+ 'pnpm-lock.yaml',
106
+ '.env',
107
+ '.env.*',
108
+ 'coverage/**',
109
+ '.nyc_output/**',
110
+ '*.d.ts',
111
+ '*.bundle.js',
112
+ '*.chunk.js',
113
+ ];
114
+ exports.CONFIG_FILES = [
115
+ 'package.json',
116
+ 'requirements.txt',
117
+ 'Pipfile',
118
+ 'pom.xml',
119
+ 'go.mod',
120
+ 'Gemfile',
121
+ 'Cargo.toml',
122
+ 'composer.json',
123
+ ];
124
+ function isConfigFile(filePath) {
125
+ const basename = path.basename(filePath);
126
+ return exports.CONFIG_FILES.includes(basename);
127
+ }
128
+ //# sourceMappingURL=languages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"languages.js","sourceRoot":"","sources":["../../src/utils/languages.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,wCAGC;AAED,0CAEC;AAuCD,oCAGC;AA9FD,2CAA6B;AAEhB,QAAA,YAAY,GAA2B;IAClD,KAAK,EAAE,YAAY;IACnB,MAAM,EAAE,YAAY;IACpB,MAAM,EAAE,YAAY;IACpB,MAAM,EAAE,YAAY;IACpB,KAAK,EAAE,YAAY;IACnB,MAAM,EAAE,YAAY;IACpB,MAAM,EAAE,YAAY;IACpB,MAAM,EAAE,YAAY;IACpB,KAAK,EAAE,QAAQ;IACf,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,MAAM;IACf,KAAK,EAAE,IAAI;IACX,KAAK,EAAE,MAAM;IACb,IAAI,EAAE,GAAG;IACT,IAAI,EAAE,GAAG;IACT,MAAM,EAAE,KAAK;IACb,KAAK,EAAE,KAAK;IACZ,MAAM,EAAE,KAAK;IACb,MAAM,EAAE,KAAK;IACb,MAAM,EAAE,KAAK;IACb,KAAK,EAAE,QAAQ;IACf,MAAM,EAAE,KAAK;IACb,KAAK,EAAE,MAAM;IACb,QAAQ,EAAE,OAAO;IACjB,KAAK,EAAE,QAAQ;IACf,MAAM,EAAE,QAAQ;IAChB,KAAK,EAAE,OAAO;IACd,OAAO,EAAE,OAAO;IAChB,MAAM,EAAE,OAAO;IACf,MAAM,EAAE,KAAK;IACb,OAAO,EAAE,MAAM;IACf,MAAM,EAAE,MAAM;IACd,MAAM,EAAE,YAAY;IACpB,SAAS,EAAE,YAAY;CACxB,CAAC;AAEW,QAAA,mBAAmB,GAAG;IACjC,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAC1D,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ;IACtD,OAAO,EAAE,KAAK;CACf,CAAC;AAEF,SAAgB,cAAc,CAAC,QAAgB;IAC7C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IACjD,OAAO,oBAAY,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;AACnC,CAAC;AAED,SAAgB,eAAe,CAAC,QAAgB;IAC9C,OAAO,cAAc,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;AAC3C,CAAC;AAEY,QAAA,eAAe,GAAG;IAC7B,iBAAiB;IACjB,SAAS;IACT,UAAU;IACV,QAAQ;IACR,SAAS;IACT,UAAU;IACV,UAAU;IACV,WAAW;IACX,gBAAgB;IAChB,UAAU;IACV,WAAW;IACX,OAAO;IACP,QAAQ;IACR,mBAAmB;IACnB,WAAW;IACX,gBAAgB;IAChB,MAAM;IACN,QAAQ;IACR,aAAa;IACb,gBAAgB;IAChB,QAAQ;IACR,aAAa;IACb,YAAY;CACb,CAAC;AAEW,QAAA,YAAY,GAAG;IAC1B,cAAc;IACd,kBAAkB;IAClB,SAAS;IACT,SAAS;IACT,QAAQ;IACR,SAAS;IACT,YAAY;IACZ,eAAe;CAChB,CAAC;AAEF,SAAgB,YAAY,CAAC,QAAgB;IAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACzC,OAAO,oBAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AACzC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "ghostpatch",
3
+ "version": "1.0.0",
4
+ "description": "AI-powered security vulnerability scanner — like SonarQube but runs locally via npm with zero infrastructure. Uses free HuggingFace models by default.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "ghostpatch": "dist/cli/index.js",
9
+ "gp": "dist/cli/index.js"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "dev": "tsc --watch",
14
+ "test": "vitest run",
15
+ "test:watch": "vitest",
16
+ "start": "node dist/cli/index.js",
17
+ "scan": "node dist/cli/index.js scan",
18
+ "serve": "node dist/cli/index.js serve",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "keywords": [
22
+ "security",
23
+ "scanner",
24
+ "vulnerability",
25
+ "sast",
26
+ "ai",
27
+ "sonarqube",
28
+ "owasp",
29
+ "mcp",
30
+ "static-analysis"
31
+ ],
32
+ "author": "",
33
+ "license": "MIT",
34
+ "engines": {
35
+ "node": ">=18.0.0"
36
+ },
37
+ "dependencies": {
38
+ "commander": "^14.0.3",
39
+ "glob": "^13.0.1",
40
+ "micromatch": "^4.0.8",
41
+ "chokidar": "^4.0.3"
42
+ },
43
+ "optionalDependencies": {
44
+ "@anthropic-ai/sdk": "^0.39.0",
45
+ "openai": "^4.80.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/micromatch": "^4.0.9",
49
+ "@types/node": "^22.12.0",
50
+ "typescript": "^5.7.3",
51
+ "vitest": "^3.2.4"
52
+ }
53
+ }
@@ -0,0 +1,82 @@
1
+ import { AIFinding } from '../core/severity';
2
+ import { AIProvider, parseAIResponse } from './provider';
3
+ import { buildAnalysisPrompt } from './prompts';
4
+
5
+ export class AnthropicProvider implements AIProvider {
6
+ name = 'anthropic';
7
+ private apiKey: string | undefined;
8
+
9
+ constructor() {
10
+ this.apiKey = process.env.ANTHROPIC_API_KEY;
11
+ }
12
+
13
+ isAvailable(): boolean {
14
+ return !!this.apiKey;
15
+ }
16
+
17
+ async analyze(code: string, context: string): Promise<AIFinding[]> {
18
+ if (!this.apiKey) return [];
19
+
20
+ const prompt = buildAnalysisPrompt(code, context, 'security');
21
+
22
+ try {
23
+ // Try to use the SDK if available
24
+ const Anthropic = require('@anthropic-ai/sdk');
25
+ const client = new Anthropic({ apiKey: this.apiKey });
26
+
27
+ const response = await client.messages.create({
28
+ model: 'claude-sonnet-4-5-20250929',
29
+ max_tokens: 4096,
30
+ messages: [{
31
+ role: 'user',
32
+ content: prompt,
33
+ }],
34
+ });
35
+
36
+ const text = response.content
37
+ .filter((c: any) => c.type === 'text')
38
+ .map((c: any) => c.text)
39
+ .join('');
40
+
41
+ return parseAIResponse(text);
42
+ } catch (sdkError) {
43
+ // Fallback to direct API call
44
+ try {
45
+ return await this.callDirectAPI(prompt);
46
+ } catch {
47
+ return [];
48
+ }
49
+ }
50
+ }
51
+
52
+ private async callDirectAPI(prompt: string): Promise<AIFinding[]> {
53
+ const response = await fetch('https://api.anthropic.com/v1/messages', {
54
+ method: 'POST',
55
+ headers: {
56
+ 'Content-Type': 'application/json',
57
+ 'x-api-key': this.apiKey!,
58
+ 'anthropic-version': '2023-06-01',
59
+ },
60
+ body: JSON.stringify({
61
+ model: 'claude-sonnet-4-5-20250929',
62
+ max_tokens: 4096,
63
+ messages: [{
64
+ role: 'user',
65
+ content: prompt,
66
+ }],
67
+ }),
68
+ });
69
+
70
+ if (!response.ok) {
71
+ throw new Error(`Anthropic API error: ${response.status}`);
72
+ }
73
+
74
+ const data: any = await response.json();
75
+ const text = data.content
76
+ ?.filter((c: any) => c.type === 'text')
77
+ ?.map((c: any) => c.text)
78
+ ?.join('') || '';
79
+
80
+ return parseAIResponse(text);
81
+ }
82
+ }
@@ -0,0 +1,111 @@
1
+ import { AIFinding } from '../core/severity';
2
+ import { AIProvider, parseAIResponse } from './provider';
3
+ import { buildAnalysisPrompt } from './prompts';
4
+
5
+ const DEFAULT_MODEL = 'Qwen/Qwen2.5-Coder-32B-Instruct';
6
+ const FALLBACK_MODEL = 'bigcode/starcoder2-15b';
7
+ const HF_API_URL = 'https://api-inference.huggingface.co/models';
8
+
9
+ export class HuggingFaceProvider implements AIProvider {
10
+ name = 'huggingface';
11
+ private token: string | undefined;
12
+ private model: string;
13
+
14
+ constructor(model?: string) {
15
+ this.token = process.env.HF_TOKEN || process.env.HUGGINGFACE_TOKEN;
16
+ this.model = model || DEFAULT_MODEL;
17
+ }
18
+
19
+ isAvailable(): boolean {
20
+ return true; // HuggingFace free tier doesn't require a token for some models
21
+ }
22
+
23
+ async analyze(code: string, context: string): Promise<AIFinding[]> {
24
+ const prompt = buildAnalysisPrompt(code, context, 'security');
25
+
26
+ try {
27
+ const response = await this.callAPI(this.model, prompt);
28
+ return parseAIResponse(response);
29
+ } catch {
30
+ try {
31
+ // Fallback to secondary model
32
+ const response = await this.callAPI(FALLBACK_MODEL, prompt);
33
+ return parseAIResponse(response);
34
+ } catch {
35
+ return [];
36
+ }
37
+ }
38
+ }
39
+
40
+ private async callAPI(model: string, prompt: string): Promise<string> {
41
+ const headers: Record<string, string> = {
42
+ 'Content-Type': 'application/json',
43
+ };
44
+
45
+ if (this.token) {
46
+ headers['Authorization'] = `Bearer ${this.token}`;
47
+ }
48
+
49
+ const body = JSON.stringify({
50
+ inputs: prompt,
51
+ parameters: {
52
+ max_new_tokens: 2048,
53
+ temperature: 0.1,
54
+ return_full_text: false,
55
+ },
56
+ });
57
+
58
+ const response = await fetchWithRetry(`${HF_API_URL}/${model}`, {
59
+ method: 'POST',
60
+ headers,
61
+ body,
62
+ });
63
+
64
+ if (!response.ok) {
65
+ throw new Error(`HuggingFace API error: ${response.status}`);
66
+ }
67
+
68
+ const data = await response.json();
69
+
70
+ if (Array.isArray(data) && data[0]?.generated_text) {
71
+ return data[0].generated_text;
72
+ }
73
+
74
+ return JSON.stringify(data);
75
+ }
76
+ }
77
+
78
+ async function fetchWithRetry(
79
+ url: string,
80
+ options: RequestInit,
81
+ retries: number = 3,
82
+ delay: number = 1000,
83
+ ): Promise<Response> {
84
+ for (let i = 0; i < retries; i++) {
85
+ try {
86
+ const response = await fetch(url, options);
87
+
88
+ // If model is loading, wait and retry
89
+ if (response.status === 503) {
90
+ const body = await response.json().catch(() => ({}));
91
+ const estimatedTime = (body as any)?.estimated_time || 20;
92
+ const waitTime = Math.min(estimatedTime * 1000, 30000);
93
+ await new Promise(resolve => setTimeout(resolve, waitTime));
94
+ continue;
95
+ }
96
+
97
+ // Rate limited
98
+ if (response.status === 429) {
99
+ await new Promise(resolve => setTimeout(resolve, delay * (i + 1)));
100
+ continue;
101
+ }
102
+
103
+ return response;
104
+ } catch (err) {
105
+ if (i === retries - 1) throw err;
106
+ await new Promise(resolve => setTimeout(resolve, delay * (i + 1)));
107
+ }
108
+ }
109
+
110
+ throw new Error('Max retries exceeded');
111
+ }
@@ -0,0 +1,75 @@
1
+ import { AIFinding } from '../core/severity';
2
+ import { AIProvider, parseAIResponse } from './provider';
3
+ import { buildAnalysisPrompt } from './prompts';
4
+
5
+ export class OpenAIProvider implements AIProvider {
6
+ name = 'openai';
7
+ private apiKey: string | undefined;
8
+
9
+ constructor() {
10
+ this.apiKey = process.env.OPENAI_API_KEY;
11
+ }
12
+
13
+ isAvailable(): boolean {
14
+ return !!this.apiKey;
15
+ }
16
+
17
+ async analyze(code: string, context: string): Promise<AIFinding[]> {
18
+ if (!this.apiKey) return [];
19
+
20
+ const prompt = buildAnalysisPrompt(code, context, 'security');
21
+
22
+ try {
23
+ // Try to use the SDK if available
24
+ const OpenAI = require('openai');
25
+ const client = new OpenAI({ apiKey: this.apiKey });
26
+
27
+ const response = await client.chat.completions.create({
28
+ model: 'gpt-4o',
29
+ messages: [{
30
+ role: 'user',
31
+ content: prompt,
32
+ }],
33
+ temperature: 0.1,
34
+ max_tokens: 4096,
35
+ });
36
+
37
+ const text = response.choices[0]?.message?.content || '';
38
+ return parseAIResponse(text);
39
+ } catch (sdkError) {
40
+ // Fallback to direct API call
41
+ try {
42
+ return await this.callDirectAPI(prompt);
43
+ } catch {
44
+ return [];
45
+ }
46
+ }
47
+ }
48
+
49
+ private async callDirectAPI(prompt: string): Promise<AIFinding[]> {
50
+ const response = await fetch('https://api.openai.com/v1/chat/completions', {
51
+ method: 'POST',
52
+ headers: {
53
+ 'Content-Type': 'application/json',
54
+ 'Authorization': `Bearer ${this.apiKey}`,
55
+ },
56
+ body: JSON.stringify({
57
+ model: 'gpt-4o',
58
+ messages: [{
59
+ role: 'user',
60
+ content: prompt,
61
+ }],
62
+ temperature: 0.1,
63
+ max_tokens: 4096,
64
+ }),
65
+ });
66
+
67
+ if (!response.ok) {
68
+ throw new Error(`OpenAI API error: ${response.status}`);
69
+ }
70
+
71
+ const data: any = await response.json();
72
+ const text = data.choices?.[0]?.message?.content || '';
73
+ return parseAIResponse(text);
74
+ }
75
+ }
@@ -0,0 +1,100 @@
1
+ export const SECURITY_ANALYSIS_PROMPT = `You are a senior application security engineer performing a code review. Analyze the following code for security vulnerabilities.
2
+
3
+ Look for:
4
+ 1. **Injection flaws** — SQL injection, command injection, XSS, LDAP injection, template injection
5
+ 2. **Authentication/Authorization bugs** — bypass conditions, privilege escalation, session issues
6
+ 3. **Cryptographic weaknesses** — weak algorithms, hardcoded keys, insecure randomness
7
+ 4. **Business logic vulnerabilities** — race conditions, TOCTOU, integer overflow, logic bypasses
8
+ 5. **Data exposure** — sensitive data in logs, responses, URLs, or error messages
9
+ 6. **Insecure configurations** — debug modes, permissive CORS, missing security headers
10
+ 7. **Novel attack vectors** — unusual patterns that could be exploited
11
+
12
+ For each vulnerability found, respond in this exact JSON format:
13
+ {
14
+ "findings": [
15
+ {
16
+ "title": "Brief vulnerability title",
17
+ "description": "Detailed explanation of the vulnerability and its impact",
18
+ "severity": "critical|high|medium|low|info",
19
+ "confidence": "high|medium|low",
20
+ "line": <line_number_or_null>,
21
+ "cwe": "CWE-XXX",
22
+ "remediation": "How to fix the issue"
23
+ }
24
+ ]
25
+ }
26
+
27
+ If no vulnerabilities are found, return: { "findings": [] }
28
+
29
+ Be precise and avoid false positives. Only report genuine security concerns.`;
30
+
31
+ export const SECRETS_ANALYSIS_PROMPT = `You are a secrets detection specialist. Analyze the following code for hardcoded secrets, credentials, API keys, tokens, and sensitive configuration.
32
+
33
+ Look for:
34
+ - API keys (AWS, GCP, Azure, Stripe, SendGrid, Twilio, etc.)
35
+ - Passwords and credentials
36
+ - Private keys and certificates
37
+ - Database connection strings with credentials
38
+ - OAuth client secrets
39
+ - JWT secrets
40
+ - Encryption keys
41
+ - Webhook URLs with tokens
42
+
43
+ Respond in JSON format:
44
+ {
45
+ "findings": [
46
+ {
47
+ "title": "Type of secret found",
48
+ "description": "What was found and why it's a risk",
49
+ "severity": "critical|high|medium",
50
+ "confidence": "high|medium|low",
51
+ "line": <line_number_or_null>,
52
+ "cwe": "CWE-798",
53
+ "remediation": "How to properly handle this secret"
54
+ }
55
+ ]
56
+ }`;
57
+
58
+ export const ZERO_DAY_PROMPT = `You are an elite security researcher analyzing code for novel, zero-day class vulnerabilities that standard scanners would miss.
59
+
60
+ Focus on:
61
+ 1. **Logic bugs** — Subtle flaws in business logic, authentication flows, or authorization checks
62
+ 2. **Race conditions** — Time-of-check to time-of-use bugs, concurrent access issues
63
+ 3. **Type confusion** — Unexpected type coercion leading to security bypass
64
+ 4. **Integer issues** — Overflow, underflow, truncation leading to security impacts
65
+ 5. **State management** — Inconsistent state that could be exploited
66
+ 6. **Error handling** — Errors that leak info or bypass security checks
67
+ 7. **Deserialization** — Unsafe data handling leading to code execution
68
+ 8. **Side channels** — Timing attacks, cache-based attacks
69
+
70
+ Only report findings you have HIGH confidence in. These should be genuine, exploitable issues.
71
+
72
+ Respond in JSON format:
73
+ {
74
+ "findings": [
75
+ {
76
+ "title": "Vulnerability title",
77
+ "description": "Detailed technical explanation",
78
+ "severity": "critical|high|medium",
79
+ "confidence": "high|medium",
80
+ "line": <line_number_or_null>,
81
+ "cwe": "CWE-XXX",
82
+ "remediation": "Specific fix"
83
+ }
84
+ ]
85
+ }`;
86
+
87
+ export function buildAnalysisPrompt(code: string, context: string, promptType: 'security' | 'secrets' | 'zeroday' = 'security'): string {
88
+ const prompt = promptType === 'secrets' ? SECRETS_ANALYSIS_PROMPT
89
+ : promptType === 'zeroday' ? ZERO_DAY_PROMPT
90
+ : SECURITY_ANALYSIS_PROMPT;
91
+
92
+ return `${prompt}
93
+
94
+ Context: ${context}
95
+
96
+ Code to analyze:
97
+ \`\`\`
98
+ ${code}
99
+ \`\`\``;
100
+ }
@@ -0,0 +1,68 @@
1
+ import { AIFinding, Severity, severityFromString } from '../core/severity';
2
+
3
+ export interface AIProvider {
4
+ name: string;
5
+ analyze(code: string, context: string): Promise<AIFinding[]>;
6
+ isAvailable(): boolean;
7
+ }
8
+
9
+ export function getAvailableProvider(preferred?: string): AIProvider | null {
10
+ // Import providers lazily
11
+ const { HuggingFaceProvider } = require('./huggingface');
12
+ const { AnthropicProvider } = require('./anthropic');
13
+ const { OpenAIProvider } = require('./openai');
14
+
15
+ if (preferred) {
16
+ switch (preferred) {
17
+ case 'anthropic': {
18
+ const p = new AnthropicProvider();
19
+ if (p.isAvailable()) return p;
20
+ break;
21
+ }
22
+ case 'openai': {
23
+ const p = new OpenAIProvider();
24
+ if (p.isAvailable()) return p;
25
+ break;
26
+ }
27
+ case 'huggingface': {
28
+ const p = new HuggingFaceProvider();
29
+ return p;
30
+ }
31
+ }
32
+ }
33
+
34
+ // Auto-detect: prefer Anthropic > OpenAI > HuggingFace
35
+ const anthropic = new AnthropicProvider();
36
+ if (anthropic.isAvailable()) return anthropic;
37
+
38
+ const openai = new OpenAIProvider();
39
+ if (openai.isAvailable()) return openai;
40
+
41
+ // HuggingFace is always available (free tier)
42
+ return new HuggingFaceProvider();
43
+ }
44
+
45
+ export function parseAIResponse(response: string): AIFinding[] {
46
+ try {
47
+ // Try to extract JSON from the response
48
+ const jsonMatch = response.match(/\{[\s\S]*"findings"[\s\S]*\}/);
49
+ if (!jsonMatch) return [];
50
+
51
+ const parsed = JSON.parse(jsonMatch[0]);
52
+ if (!Array.isArray(parsed.findings)) return [];
53
+
54
+ return parsed.findings
55
+ .filter((f: any) => f.title && f.description)
56
+ .map((f: any) => ({
57
+ title: String(f.title),
58
+ description: String(f.description),
59
+ severity: severityFromString(f.severity || 'medium'),
60
+ confidence: (['high', 'medium', 'low'].includes(f.confidence) ? f.confidence : 'medium') as 'high' | 'medium' | 'low',
61
+ line: typeof f.line === 'number' ? f.line : undefined,
62
+ cwe: f.cwe ? String(f.cwe) : undefined,
63
+ remediation: f.remediation ? String(f.remediation) : undefined,
64
+ }));
65
+ } catch {
66
+ return [];
67
+ }
68
+ }