@neurcode-ai/cli 0.7.12 → 0.8.1

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 (70) hide show
  1. package/dist/api-client.d.ts +38 -18
  2. package/dist/api-client.d.ts.map +1 -1
  3. package/dist/api-client.js +226 -121
  4. package/dist/api-client.js.map +1 -1
  5. package/dist/commands/apply.d.ts.map +1 -1
  6. package/dist/commands/apply.js +127 -1
  7. package/dist/commands/apply.js.map +1 -1
  8. package/dist/commands/check.d.ts.map +1 -1
  9. package/dist/commands/check.js +56 -13
  10. package/dist/commands/check.js.map +1 -1
  11. package/dist/commands/doctor.d.ts.map +1 -1
  12. package/dist/commands/doctor.js +14 -11
  13. package/dist/commands/doctor.js.map +1 -1
  14. package/dist/commands/init.d.ts.map +1 -1
  15. package/dist/commands/init.js +95 -33
  16. package/dist/commands/init.js.map +1 -1
  17. package/dist/commands/login.d.ts.map +1 -1
  18. package/dist/commands/login.js +47 -57
  19. package/dist/commands/login.js.map +1 -1
  20. package/dist/commands/plan.d.ts +2 -0
  21. package/dist/commands/plan.d.ts.map +1 -1
  22. package/dist/commands/plan.js +245 -11
  23. package/dist/commands/plan.js.map +1 -1
  24. package/dist/commands/revert.d.ts.map +1 -1
  25. package/dist/commands/revert.js +82 -0
  26. package/dist/commands/revert.js.map +1 -1
  27. package/dist/commands/session.d.ts +29 -0
  28. package/dist/commands/session.d.ts.map +1 -0
  29. package/dist/commands/session.js +382 -0
  30. package/dist/commands/session.js.map +1 -0
  31. package/dist/commands/verify.d.ts.map +1 -1
  32. package/dist/commands/verify.js +177 -8
  33. package/dist/commands/verify.js.map +1 -1
  34. package/dist/commands/watch.d.ts.map +1 -1
  35. package/dist/commands/watch.js +20 -15
  36. package/dist/commands/watch.js.map +1 -1
  37. package/dist/index.js +78 -3
  38. package/dist/index.js.map +1 -1
  39. package/dist/services/integrations/TicketService.d.ts +68 -0
  40. package/dist/services/integrations/TicketService.d.ts.map +1 -0
  41. package/dist/services/integrations/TicketService.js +151 -0
  42. package/dist/services/integrations/TicketService.js.map +1 -0
  43. package/dist/services/security/SecurityGuard.d.ts +88 -0
  44. package/dist/services/security/SecurityGuard.d.ts.map +1 -0
  45. package/dist/services/security/SecurityGuard.js +576 -0
  46. package/dist/services/security/SecurityGuard.js.map +1 -0
  47. package/dist/services/watch/Syncer.d.ts.map +1 -1
  48. package/dist/services/watch/Syncer.js +22 -1
  49. package/dist/services/watch/Syncer.js.map +1 -1
  50. package/dist/utils/ROILogger.d.ts +16 -0
  51. package/dist/utils/ROILogger.d.ts.map +1 -0
  52. package/dist/utils/ROILogger.js +45 -0
  53. package/dist/utils/ROILogger.js.map +1 -0
  54. package/dist/utils/box.d.ts +16 -0
  55. package/dist/utils/box.d.ts.map +1 -0
  56. package/dist/utils/box.js +85 -0
  57. package/dist/utils/box.js.map +1 -0
  58. package/dist/utils/messages.d.ts +81 -0
  59. package/dist/utils/messages.d.ts.map +1 -0
  60. package/dist/utils/messages.js +306 -0
  61. package/dist/utils/messages.js.map +1 -0
  62. package/dist/utils/tier.d.ts +21 -0
  63. package/dist/utils/tier.d.ts.map +1 -0
  64. package/dist/utils/tier.js +150 -0
  65. package/dist/utils/tier.js.map +1 -0
  66. package/dist/utils/user-context.d.ts +28 -0
  67. package/dist/utils/user-context.d.ts.map +1 -0
  68. package/dist/utils/user-context.js +68 -0
  69. package/dist/utils/user-context.js.map +1 -0
  70. package/package.json +4 -4
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Security Guard - Shadow AI Shield
3
+ *
4
+ * Local privacy scanner that runs before any API call to detect and mask secrets.
5
+ * Uses regex patterns and AST analysis (via ts-morph) to identify sensitive data.
6
+ * Also includes hallucination detection for phantom packages.
7
+ */
8
+ export interface SecretDetection {
9
+ type: 'aws_key' | 'bearer_token' | 'github_token' | 'generic_secret' | 'ast_literal';
10
+ severity: 'high' | 'medium' | 'low';
11
+ location: string;
12
+ pattern: string;
13
+ masked?: boolean;
14
+ }
15
+ export interface HallucinationDetection {
16
+ packageName: string;
17
+ location: string;
18
+ importStatement: string;
19
+ }
20
+ export interface ScanResult {
21
+ secrets: SecretDetection[];
22
+ hasSecrets: boolean;
23
+ maskedText?: string;
24
+ }
25
+ export interface HallucinationScanResult {
26
+ hallucinations: HallucinationDetection[];
27
+ hasHallucinations: boolean;
28
+ blocked: boolean;
29
+ }
30
+ /**
31
+ * Security Guard for local secret detection and hallucination detection
32
+ */
33
+ export declare class SecurityGuard {
34
+ private readonly REDACTION_PLACEHOLDER;
35
+ private readonly patterns;
36
+ private readonly sensitiveVarNames;
37
+ private readonly safePackageList;
38
+ /**
39
+ * Scan text content for secrets using regex patterns
40
+ */
41
+ scanText(text: string, location?: string): SecretDetection[];
42
+ /**
43
+ * Extract package names from import/require statements (GREEDY - catches all patterns)
44
+ * Returns array of { packageName, importStatement }
45
+ */
46
+ private extractPackageImports;
47
+ /**
48
+ * Load package.json dependencies from project root
49
+ */
50
+ private loadProjectDependencies;
51
+ /**
52
+ * Extract package names mentioned in text (heuristic for plan summaries/reasons)
53
+ * Looks for patterns like: 'package-name', "package-name", library 'package-name', etc.
54
+ */
55
+ private extractPackageMentions;
56
+ /**
57
+ * Scan code for hallucinated packages (phantom packages)
58
+ * Checks against safe list and project's package.json
59
+ * Now includes heuristic detection for package names mentioned in text (not just import statements)
60
+ *
61
+ * PRO feature only - FREE users get a message to upgrade
62
+ */
63
+ scanForHallucinations(code: string, location?: string, rootDir?: string): Promise<HallucinationScanResult>;
64
+ /**
65
+ * Scan TypeScript/JavaScript files using AST analysis
66
+ */
67
+ scanFile(filePath: string, rootDir?: string): Promise<SecretDetection[]>;
68
+ /**
69
+ * Scan multiple files
70
+ */
71
+ scanFiles(filePaths: string[], rootDir?: string): Promise<ScanResult>;
72
+ /**
73
+ * Scan intent string for secrets
74
+ */
75
+ scanIntent(intent: string): ScanResult;
76
+ /**
77
+ * Mask secrets in text
78
+ */
79
+ maskSecrets(text: string, detections: SecretDetection[]): string;
80
+ /**
81
+ * Complete scan with masking
82
+ */
83
+ scanAndMask(intent: string, filePaths: string[], rootDir?: string): Promise<ScanResult & {
84
+ maskedIntent?: string;
85
+ maskedFiles?: Map<string, string>;
86
+ }>;
87
+ }
88
+ //# sourceMappingURL=SecurityGuard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SecurityGuard.d.ts","sourceRoot":"","sources":["../../../src/services/security/SecurityGuard.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,SAAS,GAAG,cAAc,GAAG,cAAc,GAAG,gBAAgB,GAAG,aAAa,CAAC;IACrF,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,uBAAuB;IACtC,cAAc,EAAE,sBAAsB,EAAE,CAAC;IACzC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAA4B;IAGlE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAYvB;IAGF,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAQhC;IAIF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAwB7B;IAEH;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAe,GAAG,eAAe,EAAE;IAyDpE;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAoF7B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAiC/B;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IAuF9B;;;;;;OAMG;IACG,qBAAqB,CACzB,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,MAAe,EACzB,OAAO,GAAE,MAAsB,GAC9B,OAAO,CAAC,uBAAuB,CAAC;IAwFnC;;OAEG;IACG,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,MAAsB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAyE7F;;OAEG;IACG,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,MAAsB,GAAG,OAAO,CAAC,UAAU,CAAC;IAc1F;;OAEG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU;IAQtC;;OAEG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,eAAe,EAAE,GAAG,MAAM;IAgBhE;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,MAAsB,GAAG,OAAO,CAAC,UAAU,GAAG;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;CAkD5K"}
@@ -0,0 +1,576 @@
1
+ "use strict";
2
+ /**
3
+ * Security Guard - Shadow AI Shield
4
+ *
5
+ * Local privacy scanner that runs before any API call to detect and mask secrets.
6
+ * Uses regex patterns and AST analysis (via ts-morph) to identify sensitive data.
7
+ * Also includes hallucination detection for phantom packages.
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.SecurityGuard = void 0;
44
+ const ts_morph_1 = require("ts-morph");
45
+ const fs_1 = require("fs");
46
+ const path_1 = require("path");
47
+ /**
48
+ * Security Guard for local secret detection and hallucination detection
49
+ */
50
+ class SecurityGuard {
51
+ REDACTION_PLACEHOLDER = '[REDACTED_BY_NEURCODE]';
52
+ // High-performance regex patterns for common secrets
53
+ patterns = {
54
+ // AWS Access Keys (AKIA followed by 16 base32 characters)
55
+ aws_key: /\bAKIA[0-9A-Z]{16}\b/g,
56
+ // Bearer tokens / API keys (common patterns)
57
+ bearer_token: /\b(bearer|token|apikey)\s*[:=]\s*['"]?([a-zA-Z0-9_\-]{32,})['"]?/gi,
58
+ // GitHub tokens (ghp_, gho_, ghu_, ghs_, ghr_)
59
+ github_token: /\b(ghp|gho|ghu|ghs|ghr)_[a-zA-Z0-9]{36,}\b/g,
60
+ // Generic high-entropy strings labeled as secrets
61
+ generic_secret: /\b(password|secret|key|token|api[_-]?key|private[_-]?key)\s*[:=]\s*['"]?([a-zA-Z0-9_\-+/=]{20,})['"]?/gi,
62
+ };
63
+ // Variable names that suggest secrets (case-insensitive)
64
+ sensitiveVarNames = [
65
+ /api[_-]?key/i,
66
+ /secret/i,
67
+ /password/i,
68
+ /token/i,
69
+ /private[_-]?key/i,
70
+ /access[_-]?token/i,
71
+ /auth[_-]?token/i,
72
+ ];
73
+ // Safe list of common packages and standard library modules
74
+ // Using Set for O(1) lookup performance
75
+ safePackageList = new Set([
76
+ // Standard Node.js modules
77
+ 'fs', 'path', 'os', 'crypto', 'http', 'https', 'url', 'util', 'events', 'stream',
78
+ 'buffer', 'process', 'child_process', 'cluster', 'dgram', 'dns', 'net', 'readline',
79
+ 'repl', 'tls', 'tty', 'vm', 'zlib', 'assert', 'querystring', 'string_decoder',
80
+ 'timers', 'punycode', 'v8', 'worker_threads', 'perf_hooks', 'async_hooks',
81
+ 'inspector', 'module', 'console', 'domain', 'constants',
82
+ // Common npm packages (top 100 most popular)
83
+ 'react', 'react-dom', 'lodash', 'express', 'axios', 'moment', 'vue', 'angular',
84
+ 'typescript', 'webpack', 'babel', 'jest', 'mocha', 'chai', 'sinon', 'eslint',
85
+ 'prettier', 'next', 'gatsby', 'nuxt', 'svelte', 'rxjs', 'redux', 'mobx',
86
+ 'styled-components', 'emotion', 'tailwindcss', 'bootstrap', 'material-ui',
87
+ '@mui/material', '@mui/icons-material', 'antd', 'semantic-ui', 'chakra-ui',
88
+ 'node-fetch', 'got', 'request', 'superagent', 'cheerio', 'puppeteer', 'playwright',
89
+ 'mongoose', 'sequelize', 'typeorm', 'prisma', 'knex', 'pg', 'mysql2', 'sqlite3',
90
+ 'redis', 'ioredis', 'ws', 'socket.io', 'graphql', 'apollo', 'relay',
91
+ 'dotenv', 'cross-env', 'nodemon', 'pm2', 'forever', 'concurrently',
92
+ 'uuid', 'nanoid', 'crypto-js', 'bcrypt', 'jsonwebtoken', 'passport',
93
+ 'winston', 'morgan', 'pino', 'debug', 'chalk', 'colors', 'commander',
94
+ 'yargs', 'inquirer', 'ora', 'listr', 'glob', 'minimist', 'dot-prop',
95
+ 'fast-glob', 'micromatch', 'rimraf', 'mkdirp', 'fs-extra', 'graceful-fs',
96
+ 'chokidar', 'watchman', 'nodemailer', 'handlebars', 'ejs', 'pug', 'mustache',
97
+ 'marked', 'highlight.js', 'prismjs', 'showdown', 'remark', 'rehype',
98
+ ]);
99
+ /**
100
+ * Scan text content for secrets using regex patterns
101
+ */
102
+ scanText(text, location = 'text') {
103
+ const detections = [];
104
+ // Check AWS keys
105
+ const awsMatches = text.match(this.patterns.aws_key);
106
+ if (awsMatches) {
107
+ awsMatches.forEach(match => {
108
+ detections.push({
109
+ type: 'aws_key',
110
+ severity: 'high',
111
+ location,
112
+ pattern: match,
113
+ });
114
+ });
115
+ }
116
+ // Check GitHub tokens
117
+ const githubMatches = text.match(this.patterns.github_token);
118
+ if (githubMatches) {
119
+ githubMatches.forEach(match => {
120
+ detections.push({
121
+ type: 'github_token',
122
+ severity: 'high',
123
+ location,
124
+ pattern: match,
125
+ });
126
+ });
127
+ }
128
+ // Check bearer tokens and generic secrets
129
+ const bearerMatches = Array.from(text.matchAll(this.patterns.bearer_token));
130
+ bearerMatches.forEach(match => {
131
+ if (match[2] && match[2].length >= 32) {
132
+ detections.push({
133
+ type: 'bearer_token',
134
+ severity: 'high',
135
+ location,
136
+ pattern: match[0],
137
+ });
138
+ }
139
+ });
140
+ const genericMatches = Array.from(text.matchAll(this.patterns.generic_secret));
141
+ genericMatches.forEach(match => {
142
+ if (match[2] && match[2].length >= 20) {
143
+ detections.push({
144
+ type: 'generic_secret',
145
+ severity: match[1]?.toLowerCase().includes('password') ? 'high' : 'medium',
146
+ location,
147
+ pattern: match[0],
148
+ });
149
+ }
150
+ });
151
+ return detections;
152
+ }
153
+ /**
154
+ * Extract package names from import/require statements (GREEDY - catches all patterns)
155
+ * Returns array of { packageName, importStatement }
156
+ */
157
+ extractPackageImports(code) {
158
+ const imports = [];
159
+ const seenStatements = new Set(); // Track to avoid duplicates
160
+ // Pattern 1: ES6 import statements (GREEDY - matches all variations)
161
+ // import x from 'package'
162
+ // import { x, y } from 'package'
163
+ // import * as x from 'package'
164
+ // import x, { y } from 'package'
165
+ // import type { x } from 'package'
166
+ // import 'package' (side-effect imports)
167
+ const es6ImportPattern = /import\s+(?:(?:type\s+)?(?:\*\s+as\s+\w+)|(?:\{[^}]*\})|(?:\w+)|(?:\w+\s*,\s*\{[^}]*\})|(?:type\s+\{[^}]*\}))\s+from\s+['"]([^'"]+)['"]/g;
168
+ // Pattern 2: Side-effect imports (standalone import 'package')
169
+ const sideEffectImportPattern = /^import\s+['"]([^'"]+)['"];?$/gm;
170
+ // Pattern 3: CommonJS require (GREEDY - matches all variations)
171
+ // const x = require('package')
172
+ // const { x } = require('package')
173
+ // require('package')
174
+ // module.exports = require('package')
175
+ const requirePattern = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
176
+ // Pattern 4: Dynamic/lazy imports (GREEDY)
177
+ // import('package')
178
+ // await import('package')
179
+ // const x = import('package')
180
+ const dynamicImportPattern = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
181
+ // Extract ES6 imports with 'from' clause
182
+ let match;
183
+ while ((match = es6ImportPattern.exec(code)) !== null) {
184
+ const packageName = match[1];
185
+ if (packageName && !packageName.startsWith('.') && !packageName.startsWith('/')) {
186
+ const importStatement = match[0].trim();
187
+ if (!seenStatements.has(importStatement)) {
188
+ imports.push({ packageName, importStatement });
189
+ seenStatements.add(importStatement);
190
+ }
191
+ }
192
+ }
193
+ // Extract side-effect imports (reset regex lastIndex)
194
+ sideEffectImportPattern.lastIndex = 0;
195
+ while ((match = sideEffectImportPattern.exec(code)) !== null) {
196
+ const packageName = match[1];
197
+ if (packageName && !packageName.startsWith('.') && !packageName.startsWith('/')) {
198
+ const importStatement = match[0].trim();
199
+ if (!seenStatements.has(importStatement)) {
200
+ imports.push({ packageName, importStatement });
201
+ seenStatements.add(importStatement);
202
+ }
203
+ }
204
+ }
205
+ // Extract require statements
206
+ requirePattern.lastIndex = 0;
207
+ while ((match = requirePattern.exec(code)) !== null) {
208
+ const packageName = match[1];
209
+ if (packageName && !packageName.startsWith('.') && !packageName.startsWith('/')) {
210
+ const importStatement = match[0].trim();
211
+ if (!seenStatements.has(importStatement)) {
212
+ imports.push({ packageName, importStatement });
213
+ seenStatements.add(importStatement);
214
+ }
215
+ }
216
+ }
217
+ // Extract dynamic/lazy imports
218
+ dynamicImportPattern.lastIndex = 0;
219
+ while ((match = dynamicImportPattern.exec(code)) !== null) {
220
+ const packageName = match[1];
221
+ if (packageName && !packageName.startsWith('.') && !packageName.startsWith('/')) {
222
+ const importStatement = match[0].trim();
223
+ if (!seenStatements.has(importStatement)) {
224
+ imports.push({ packageName, importStatement });
225
+ seenStatements.add(importStatement);
226
+ }
227
+ }
228
+ }
229
+ return imports;
230
+ }
231
+ /**
232
+ * Load package.json dependencies from project root
233
+ */
234
+ loadProjectDependencies(rootDir = process.cwd()) {
235
+ const packageJsonPath = (0, path_1.join)(rootDir, 'package.json');
236
+ const dependencies = new Set();
237
+ if (!(0, fs_1.existsSync)(packageJsonPath)) {
238
+ return dependencies;
239
+ }
240
+ try {
241
+ const packageJson = JSON.parse((0, fs_1.readFileSync)(packageJsonPath, 'utf-8'));
242
+ // Add dependencies
243
+ if (packageJson.dependencies && typeof packageJson.dependencies === 'object') {
244
+ Object.keys(packageJson.dependencies).forEach(pkg => dependencies.add(pkg));
245
+ }
246
+ // Add devDependencies
247
+ if (packageJson.devDependencies && typeof packageJson.devDependencies === 'object') {
248
+ Object.keys(packageJson.devDependencies).forEach(pkg => dependencies.add(pkg));
249
+ }
250
+ // Add peerDependencies
251
+ if (packageJson.peerDependencies && typeof packageJson.peerDependencies === 'object') {
252
+ Object.keys(packageJson.peerDependencies).forEach(pkg => dependencies.add(pkg));
253
+ }
254
+ }
255
+ catch (error) {
256
+ // If package.json can't be read or parsed, return empty set
257
+ // This is non-fatal - we'll just rely on safe list
258
+ }
259
+ return dependencies;
260
+ }
261
+ /**
262
+ * Extract package names mentioned in text (heuristic for plan summaries/reasons)
263
+ * Looks for patterns like: 'package-name', "package-name", library 'package-name', etc.
264
+ */
265
+ extractPackageMentions(text) {
266
+ const mentions = [];
267
+ const seen = new Set();
268
+ // Pattern 1: Package names in quotes with keyword BEFORE (e.g., "uses 'package'", "library 'package'")
269
+ // Matches: uses 'react-ultimate-super-charts', library "package", package 'package'
270
+ const quotedPackagePatternBefore = /(?:library|package|uses?|imports?|requires?|from|using|depends?|dependency)\s+['"]([a-zA-Z0-9@][a-zA-Z0-9._-]*[a-zA-Z0-9])['"]/gi;
271
+ // Pattern 2: Package names in quotes with keyword AFTER (e.g., "'package' library", "'package' package")
272
+ // Matches: 'react-ultimate-super-charts' library, "package-name" package
273
+ const quotedPackagePatternAfter = /['"]([a-zA-Z0-9@][a-zA-Z0-9._-]*[a-zA-Z0-9])['"]\s+(?:library|package|npm|module)/gi;
274
+ // Pattern 3: Standalone quoted package names (more aggressive - checks context)
275
+ // Matches: 'package-name' or "package-name" when in package-related context
276
+ const standaloneQuotedPattern = /['"]([a-zA-Z0-9@][a-zA-Z0-9._-]*[a-zA-Z0-9])['"]/g;
277
+ // Extract from library/package mentions (keyword BEFORE)
278
+ let match;
279
+ while ((match = quotedPackagePatternBefore.exec(text)) !== null) {
280
+ const packageName = match[1];
281
+ // Validate it looks like a package name (not a file path, not too short)
282
+ if (packageName && packageName.length >= 2 && !packageName.startsWith('.') && !packageName.startsWith('/')) {
283
+ if (!seen.has(packageName)) {
284
+ mentions.push({ packageName, context: match[0] });
285
+ seen.add(packageName);
286
+ }
287
+ }
288
+ }
289
+ // Extract from library/package mentions (keyword AFTER)
290
+ quotedPackagePatternAfter.lastIndex = 0;
291
+ while ((match = quotedPackagePatternAfter.exec(text)) !== null) {
292
+ const packageName = match[1];
293
+ if (packageName && packageName.length >= 2 && !packageName.startsWith('.') && !packageName.startsWith('/')) {
294
+ if (!seen.has(packageName)) {
295
+ mentions.push({ packageName, context: match[0] });
296
+ seen.add(packageName);
297
+ }
298
+ }
299
+ }
300
+ // Extract standalone quoted strings (but exclude if already found)
301
+ // This is a fallback for cases like "uses the 'package-name' library" or other variations
302
+ standaloneQuotedPattern.lastIndex = 0;
303
+ while ((match = standaloneQuotedPattern.exec(text)) !== null) {
304
+ const packageName = match[1];
305
+ // Only consider if it looks like an npm package name
306
+ // Valid package names: start with letter/number/@, contain letters/numbers/dots/dashes/underscores
307
+ const isValidPackageName = /^[a-zA-Z0-9@][a-zA-Z0-9._-]*[a-zA-Z0-9]$/.test(packageName);
308
+ // Exclude component names (PascalCase single words that are likely React components)
309
+ // Component names are typically single capitalized words like "DataVisualizer", "Button", etc.
310
+ const isLikelyComponentName = /^[A-Z][a-zA-Z0-9]*$/.test(packageName) &&
311
+ !packageName.includes('-') &&
312
+ !packageName.includes('_') &&
313
+ !packageName.includes('.') &&
314
+ packageName.length < 30; // Components are usually shorter
315
+ if (isValidPackageName &&
316
+ packageName.length >= 2 &&
317
+ !packageName.startsWith('.') &&
318
+ !packageName.startsWith('/') &&
319
+ !seen.has(packageName) &&
320
+ !isLikelyComponentName) { // Exclude component names
321
+ // Check if it's mentioned in context that suggests it's a package
322
+ const beforeMatch = text.substring(Math.max(0, match.index - 50), match.index).toLowerCase();
323
+ const afterMatch = text.substring(match.index + match[0].length, Math.min(text.length, match.index + match[0].length + 50)).toLowerCase();
324
+ const context = beforeMatch + match[0] + afterMatch;
325
+ // Only flag if context suggests it's a package/library (not a file path or variable name)
326
+ // Require stronger evidence for standalone matches (must have library/package/npm keywords)
327
+ const isPackageContext = /(library|package|npm|install|import|require|from|using|uses?|depends?|dependency|module)/i.test(context);
328
+ const isNotFilePath = !/\.(js|ts|jsx|tsx|json|md|txt|html|css|scss|less)$/i.test(context);
329
+ const isNotVariable = !/(const|let|var|function|class|interface|type)\s+['"]/.test(beforeMatch);
330
+ const isNotStringLiteral = !/(['"])\s*\+/.test(afterMatch) && !/\+\s*(['"])/.test(beforeMatch);
331
+ const isNotComponentContext = /(component|class|function|interface|type)\s+['"]/.test(beforeMatch);
332
+ if (isPackageContext && isNotFilePath && isNotVariable && isNotStringLiteral && !isNotComponentContext) {
333
+ mentions.push({ packageName, context: match[0] });
334
+ seen.add(packageName);
335
+ }
336
+ }
337
+ }
338
+ return mentions;
339
+ }
340
+ /**
341
+ * Scan code for hallucinated packages (phantom packages)
342
+ * Checks against safe list and project's package.json
343
+ * Now includes heuristic detection for package names mentioned in text (not just import statements)
344
+ *
345
+ * PRO feature only - FREE users get a message to upgrade
346
+ */
347
+ async scanForHallucinations(code, location = 'code', rootDir = process.cwd()) {
348
+ // Check user tier - Hallucination Shield is PRO only
349
+ const { getUserTier } = await Promise.resolve().then(() => __importStar(require('../../utils/tier')));
350
+ const tier = await getUserTier();
351
+ if (tier === 'FREE') {
352
+ // Return empty result for FREE users (feature disabled)
353
+ // The calling code should check tier and show upgrade message
354
+ return {
355
+ hallucinations: [],
356
+ hasHallucinations: false,
357
+ blocked: false,
358
+ };
359
+ }
360
+ const hallucinations = [];
361
+ // Extract all package imports (from actual import/require statements)
362
+ const imports = this.extractPackageImports(code);
363
+ // Also extract package names mentioned in text (heuristic for plan summaries)
364
+ const mentions = this.extractPackageMentions(code);
365
+ // If no imports or mentions found, return early
366
+ if (imports.length === 0 && mentions.length === 0) {
367
+ return {
368
+ hallucinations: [],
369
+ hasHallucinations: false,
370
+ blocked: false,
371
+ };
372
+ }
373
+ // Load project dependencies
374
+ const projectDependencies = this.loadProjectDependencies(rootDir);
375
+ // Check each import statement
376
+ for (const { packageName, importStatement } of imports) {
377
+ // Check if package is in safe list
378
+ if (this.safePackageList.has(packageName)) {
379
+ continue; // Safe package, skip
380
+ }
381
+ // Check if package is in project dependencies
382
+ if (projectDependencies.has(packageName)) {
383
+ continue; // Package exists in project, skip
384
+ }
385
+ // Not in safe list or dependencies - flag as hallucination
386
+ hallucinations.push({
387
+ packageName,
388
+ location,
389
+ importStatement,
390
+ });
391
+ }
392
+ // Check each package mention (from text heuristics)
393
+ for (const { packageName, context } of mentions) {
394
+ // Skip if we already flagged this package from an import statement
395
+ if (hallucinations.some(h => h.packageName === packageName)) {
396
+ continue;
397
+ }
398
+ // Check if package is in safe list
399
+ if (this.safePackageList.has(packageName)) {
400
+ continue; // Safe package, skip
401
+ }
402
+ // Check if package is in project dependencies
403
+ if (projectDependencies.has(packageName)) {
404
+ continue; // Package exists in project, skip
405
+ }
406
+ // Not in safe list or dependencies - flag as hallucination
407
+ // Use context as the "import statement" for mentions
408
+ hallucinations.push({
409
+ packageName,
410
+ location,
411
+ importStatement: context,
412
+ });
413
+ }
414
+ return {
415
+ hallucinations,
416
+ hasHallucinations: hallucinations.length > 0,
417
+ blocked: hallucinations.length > 0, // Block if any hallucinations detected
418
+ };
419
+ }
420
+ /**
421
+ * Scan TypeScript/JavaScript files using AST analysis
422
+ */
423
+ async scanFile(filePath, rootDir = process.cwd()) {
424
+ const detections = [];
425
+ const fullPath = (0, path_1.resolve)(rootDir, filePath);
426
+ // Check if file exists and is a .ts or .js file
427
+ if (!(0, fs_1.existsSync)(fullPath)) {
428
+ return detections;
429
+ }
430
+ const ext = filePath.split('.').pop()?.toLowerCase();
431
+ if (ext !== 'ts' && ext !== 'js' && ext !== 'tsx' && ext !== 'jsx') {
432
+ // For non-TS/JS files, use regex scanning
433
+ try {
434
+ const content = (0, fs_1.readFileSync)(fullPath, 'utf-8');
435
+ return this.scanText(content, filePath);
436
+ }
437
+ catch {
438
+ return detections;
439
+ }
440
+ }
441
+ try {
442
+ // Use ts-morph for AST analysis
443
+ const project = new ts_morph_1.Project({
444
+ skipAddingFilesFromTsConfig: true,
445
+ skipFileDependencyResolution: true,
446
+ });
447
+ const sourceFile = project.addSourceFileAtPath(fullPath);
448
+ // Scan for variable declarations with sensitive names
449
+ sourceFile.getVariableDeclarations().forEach(variable => {
450
+ const name = variable.getName();
451
+ const initializer = variable.getInitializer();
452
+ // Check if variable name suggests a secret
453
+ const isSensitive = this.sensitiveVarNames.some(pattern => pattern.test(name));
454
+ if (isSensitive && initializer) {
455
+ // Check if initializer is a string literal
456
+ const kind = initializer.getKindName();
457
+ if (kind === 'StringLiteral' || kind === 'NoSubstitutionTemplateLiteral') {
458
+ const text = initializer.getText().replace(/['"`]/g, '');
459
+ // Check if the value looks like a secret (high entropy)
460
+ if (text.length >= 20 && /[a-zA-Z0-9_\-+/=]{20,}/.test(text)) {
461
+ detections.push({
462
+ type: 'ast_literal',
463
+ severity: 'high',
464
+ location: filePath,
465
+ pattern: `${name} = ${initializer.getText()}`,
466
+ });
467
+ }
468
+ }
469
+ }
470
+ });
471
+ // Also run regex scanning as fallback
472
+ const content = sourceFile.getFullText();
473
+ const regexDetections = this.scanText(content, filePath);
474
+ detections.push(...regexDetections);
475
+ }
476
+ catch (error) {
477
+ // If AST parsing fails, fall back to regex scanning
478
+ try {
479
+ const content = (0, fs_1.readFileSync)(fullPath, 'utf-8');
480
+ return this.scanText(content, filePath);
481
+ }
482
+ catch {
483
+ // File cannot be read, return empty
484
+ }
485
+ }
486
+ return detections;
487
+ }
488
+ /**
489
+ * Scan multiple files
490
+ */
491
+ async scanFiles(filePaths, rootDir = process.cwd()) {
492
+ const allDetections = [];
493
+ for (const filePath of filePaths) {
494
+ const detections = await this.scanFile(filePath, rootDir);
495
+ allDetections.push(...detections);
496
+ }
497
+ return {
498
+ secrets: allDetections,
499
+ hasSecrets: allDetections.length > 0,
500
+ };
501
+ }
502
+ /**
503
+ * Scan intent string for secrets
504
+ */
505
+ scanIntent(intent) {
506
+ const detections = this.scanText(intent, 'intent');
507
+ return {
508
+ secrets: detections,
509
+ hasSecrets: detections.length > 0,
510
+ };
511
+ }
512
+ /**
513
+ * Mask secrets in text
514
+ */
515
+ maskSecrets(text, detections) {
516
+ let maskedText = text;
517
+ // Sort detections by pattern length (longest first) to avoid partial replacements
518
+ const sortedDetections = [...detections].sort((a, b) => b.pattern.length - a.pattern.length);
519
+ for (const detection of sortedDetections) {
520
+ // Escape special regex characters in the pattern
521
+ const escapedPattern = detection.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
522
+ const regex = new RegExp(escapedPattern, 'g');
523
+ maskedText = maskedText.replace(regex, this.REDACTION_PLACEHOLDER);
524
+ }
525
+ return maskedText;
526
+ }
527
+ /**
528
+ * Complete scan with masking
529
+ */
530
+ async scanAndMask(intent, filePaths, rootDir = process.cwd()) {
531
+ const intentResult = this.scanIntent(intent);
532
+ const filesResult = await this.scanFiles(filePaths, rootDir);
533
+ const allDetections = [...intentResult.secrets, ...filesResult.secrets];
534
+ const hasSecrets = allDetections.length > 0;
535
+ let maskedIntent;
536
+ let maskedFiles;
537
+ if (hasSecrets) {
538
+ // Mask intent
539
+ if (intentResult.hasSecrets) {
540
+ maskedIntent = this.maskSecrets(intent, intentResult.secrets);
541
+ }
542
+ // Mask files
543
+ if (filesResult.hasSecrets) {
544
+ maskedFiles = new Map();
545
+ const fileDetections = new Map();
546
+ // Group detections by file
547
+ for (const detection of filesResult.secrets) {
548
+ if (detection.location !== 'intent') {
549
+ const fileDetectionsList = fileDetections.get(detection.location) || [];
550
+ fileDetectionsList.push(detection);
551
+ fileDetections.set(detection.location, fileDetectionsList);
552
+ }
553
+ }
554
+ // Mask each file
555
+ for (const [filePath, detections] of fileDetections.entries()) {
556
+ try {
557
+ const content = (0, fs_1.readFileSync)((0, path_1.resolve)(rootDir, filePath), 'utf-8');
558
+ const masked = this.maskSecrets(content, detections);
559
+ maskedFiles.set(filePath, masked);
560
+ }
561
+ catch {
562
+ // Skip files that can't be read
563
+ }
564
+ }
565
+ }
566
+ }
567
+ return {
568
+ secrets: allDetections,
569
+ hasSecrets,
570
+ maskedIntent,
571
+ maskedFiles,
572
+ };
573
+ }
574
+ }
575
+ exports.SecurityGuard = SecurityGuard;
576
+ //# sourceMappingURL=SecurityGuard.js.map