ferret-scan 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.
- package/CHANGELOG.md +51 -0
- package/LICENSE +21 -0
- package/README.md +416 -0
- package/bin/ferret.js +822 -0
- package/dist/__tests__/basic.test.d.ts +6 -0
- package/dist/__tests__/basic.test.js +80 -0
- package/dist/analyzers/AstAnalyzer.d.ts +30 -0
- package/dist/analyzers/AstAnalyzer.js +332 -0
- package/dist/analyzers/CorrelationAnalyzer.d.ts +21 -0
- package/dist/analyzers/CorrelationAnalyzer.js +288 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +22 -0
- package/dist/intelligence/IndicatorMatcher.d.ts +50 -0
- package/dist/intelligence/IndicatorMatcher.js +285 -0
- package/dist/intelligence/ThreatFeed.d.ts +99 -0
- package/dist/intelligence/ThreatFeed.js +296 -0
- package/dist/remediation/Fixer.d.ts +71 -0
- package/dist/remediation/Fixer.js +391 -0
- package/dist/remediation/Quarantine.d.ts +102 -0
- package/dist/remediation/Quarantine.js +329 -0
- package/dist/reporters/ConsoleReporter.d.ts +13 -0
- package/dist/reporters/ConsoleReporter.js +185 -0
- package/dist/reporters/HtmlReporter.d.ts +25 -0
- package/dist/reporters/HtmlReporter.js +604 -0
- package/dist/reporters/SarifReporter.d.ts +86 -0
- package/dist/reporters/SarifReporter.js +117 -0
- package/dist/rules/ai-specific.d.ts +8 -0
- package/dist/rules/ai-specific.js +221 -0
- package/dist/rules/backdoors.d.ts +8 -0
- package/dist/rules/backdoors.js +134 -0
- package/dist/rules/correlationRules.d.ts +8 -0
- package/dist/rules/correlationRules.js +227 -0
- package/dist/rules/credentials.d.ts +8 -0
- package/dist/rules/credentials.js +194 -0
- package/dist/rules/exfiltration.d.ts +8 -0
- package/dist/rules/exfiltration.js +139 -0
- package/dist/rules/index.d.ts +51 -0
- package/dist/rules/index.js +97 -0
- package/dist/rules/injection.d.ts +8 -0
- package/dist/rules/injection.js +136 -0
- package/dist/rules/obfuscation.d.ts +8 -0
- package/dist/rules/obfuscation.js +159 -0
- package/dist/rules/permissions.d.ts +8 -0
- package/dist/rules/permissions.js +129 -0
- package/dist/rules/persistence.d.ts +8 -0
- package/dist/rules/persistence.js +117 -0
- package/dist/rules/semanticRules.d.ts +10 -0
- package/dist/rules/semanticRules.js +212 -0
- package/dist/rules/supply-chain.d.ts +8 -0
- package/dist/rules/supply-chain.js +148 -0
- package/dist/scanner/FileDiscovery.d.ts +24 -0
- package/dist/scanner/FileDiscovery.js +282 -0
- package/dist/scanner/PatternMatcher.d.ts +25 -0
- package/dist/scanner/PatternMatcher.js +206 -0
- package/dist/scanner/Scanner.d.ts +14 -0
- package/dist/scanner/Scanner.js +266 -0
- package/dist/scanner/WatchMode.d.ts +29 -0
- package/dist/scanner/WatchMode.js +195 -0
- package/dist/types.d.ts +332 -0
- package/dist/types.js +53 -0
- package/dist/utils/baseline.d.ts +80 -0
- package/dist/utils/baseline.js +276 -0
- package/dist/utils/config.d.ts +21 -0
- package/dist/utils/config.js +247 -0
- package/dist/utils/ignore.d.ts +18 -0
- package/dist/utils/ignore.js +82 -0
- package/dist/utils/logger.d.ts +32 -0
- package/dist/utils/logger.js +75 -0
- package/package.json +119 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WatchMode - Real-time file watching and scanning
|
|
3
|
+
* Monitors files for changes and automatically rescans
|
|
4
|
+
*/
|
|
5
|
+
import chokidar from 'chokidar';
|
|
6
|
+
import { scan } from './Scanner.js';
|
|
7
|
+
import { generateConsoleReport } from '../reporters/ConsoleReporter.js';
|
|
8
|
+
import logger from '../utils/logger.js';
|
|
9
|
+
const DEFAULT_WATCH_OPTIONS = {
|
|
10
|
+
debounceMs: 1000, // Wait 1 second after last change
|
|
11
|
+
batchChanges: true, // Batch multiple changes together
|
|
12
|
+
ignored: [
|
|
13
|
+
'**/node_modules/**',
|
|
14
|
+
'**/.git/**',
|
|
15
|
+
'**/dist/**',
|
|
16
|
+
'**/build/**',
|
|
17
|
+
'**/*.log',
|
|
18
|
+
'**/tmp/**',
|
|
19
|
+
'**/.DS_Store',
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Debounce function calls
|
|
24
|
+
*/
|
|
25
|
+
function debounce(func, wait) {
|
|
26
|
+
let timeout;
|
|
27
|
+
return (...args) => {
|
|
28
|
+
clearTimeout(timeout);
|
|
29
|
+
timeout = setTimeout(() => { func(...args); }, wait);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Start watching files and scanning on changes
|
|
34
|
+
*/
|
|
35
|
+
export async function startWatchMode(config, options = {}) {
|
|
36
|
+
const watchOptions = { ...DEFAULT_WATCH_OPTIONS, ...options };
|
|
37
|
+
const events = [];
|
|
38
|
+
let isScanning = false;
|
|
39
|
+
logger.info(`🔍 Starting watch mode for: ${config.paths.join(', ')}`);
|
|
40
|
+
logger.info(`⏱️ Debounce: ${watchOptions.debounceMs}ms`);
|
|
41
|
+
// Initial scan
|
|
42
|
+
/* eslint-disable no-console */
|
|
43
|
+
console.log('🚀 Running initial scan...\n');
|
|
44
|
+
const initialResult = await scan(config);
|
|
45
|
+
const initialReport = generateConsoleReport(initialResult, {
|
|
46
|
+
verbose: config.verbose,
|
|
47
|
+
ci: config.ci,
|
|
48
|
+
});
|
|
49
|
+
console.log(initialReport);
|
|
50
|
+
console.log('\n👀 Watching for changes...\n');
|
|
51
|
+
/* eslint-enable no-console */
|
|
52
|
+
// Debounced scan function
|
|
53
|
+
const performScan = async () => {
|
|
54
|
+
if (isScanning) {
|
|
55
|
+
logger.debug('Scan already in progress, skipping');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
isScanning = true;
|
|
59
|
+
const changedFiles = events.splice(0); // Clear events
|
|
60
|
+
try {
|
|
61
|
+
logger.info(`📝 File changes detected: ${changedFiles.length} event(s)`);
|
|
62
|
+
if (config.verbose) {
|
|
63
|
+
for (const event of changedFiles.slice(0, 5)) { // Show max 5 files
|
|
64
|
+
const icon = event.type === 'add' ? '➕' : event.type === 'change' ? '📝' : '➖';
|
|
65
|
+
logger.info(` ${icon} ${event.path}`);
|
|
66
|
+
}
|
|
67
|
+
if (changedFiles.length > 5) {
|
|
68
|
+
logger.info(` ... and ${changedFiles.length - 5} more`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/* eslint-disable no-console */
|
|
72
|
+
console.log('🔄 Rescanning...\n');
|
|
73
|
+
const result = await scan(config);
|
|
74
|
+
// Clear previous output and show new results
|
|
75
|
+
if (!config.verbose && !config.ci) {
|
|
76
|
+
process.stdout.write('\x1Bc'); // Clear screen
|
|
77
|
+
}
|
|
78
|
+
const report = generateConsoleReport(result, {
|
|
79
|
+
verbose: config.verbose,
|
|
80
|
+
ci: config.ci,
|
|
81
|
+
});
|
|
82
|
+
console.log(report);
|
|
83
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
84
|
+
console.log(`\n✅ Scan completed at ${timestamp}`);
|
|
85
|
+
console.log('👀 Watching for changes...\n');
|
|
86
|
+
/* eslint-enable no-console */
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
// eslint-disable-next-line no-console
|
|
90
|
+
console.error('❌ Scan failed:', error instanceof Error ? error.message : String(error));
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
isScanning = false;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
const debouncedScan = debounce(() => {
|
|
97
|
+
void performScan();
|
|
98
|
+
}, watchOptions.debounceMs);
|
|
99
|
+
// Set up file watcher
|
|
100
|
+
const watcher = chokidar.watch(config.paths, {
|
|
101
|
+
ignored: [
|
|
102
|
+
...watchOptions.ignored,
|
|
103
|
+
...config.ignore.map(pattern => `**/${pattern}`),
|
|
104
|
+
],
|
|
105
|
+
persistent: true,
|
|
106
|
+
ignoreInitial: true, // Don't trigger on initial scan
|
|
107
|
+
followSymlinks: false,
|
|
108
|
+
depth: 10, // Reasonable recursion depth
|
|
109
|
+
awaitWriteFinish: {
|
|
110
|
+
stabilityThreshold: 100,
|
|
111
|
+
pollInterval: 50,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
// Watch event handlers
|
|
115
|
+
watcher.on('add', (path) => {
|
|
116
|
+
events.push({ type: 'add', path, timestamp: new Date() });
|
|
117
|
+
if (config.verbose) {
|
|
118
|
+
logger.debug(`File added: ${path}`);
|
|
119
|
+
}
|
|
120
|
+
debouncedScan();
|
|
121
|
+
});
|
|
122
|
+
watcher.on('change', (path) => {
|
|
123
|
+
events.push({ type: 'change', path, timestamp: new Date() });
|
|
124
|
+
if (config.verbose) {
|
|
125
|
+
logger.debug(`File changed: ${path}`);
|
|
126
|
+
}
|
|
127
|
+
debouncedScan();
|
|
128
|
+
});
|
|
129
|
+
watcher.on('unlink', (path) => {
|
|
130
|
+
events.push({ type: 'unlink', path, timestamp: new Date() });
|
|
131
|
+
if (config.verbose) {
|
|
132
|
+
logger.debug(`File removed: ${path}`);
|
|
133
|
+
}
|
|
134
|
+
debouncedScan();
|
|
135
|
+
});
|
|
136
|
+
watcher.on('error', (error) => {
|
|
137
|
+
// eslint-disable-next-line no-console
|
|
138
|
+
console.error('❌ Watch error:', error);
|
|
139
|
+
});
|
|
140
|
+
watcher.on('ready', () => {
|
|
141
|
+
const watched = watcher.getWatched();
|
|
142
|
+
const watchedCount = Object.keys(watched).length;
|
|
143
|
+
logger.info(`👁️ Watching ${watchedCount || 'multiple'} directories`);
|
|
144
|
+
});
|
|
145
|
+
// Handle graceful shutdown
|
|
146
|
+
const cleanup = () => {
|
|
147
|
+
logger.info('🛑 Stopping watch mode...');
|
|
148
|
+
void watcher.close();
|
|
149
|
+
};
|
|
150
|
+
process.on('SIGINT', cleanup);
|
|
151
|
+
process.on('SIGTERM', cleanup);
|
|
152
|
+
// Return cleanup function
|
|
153
|
+
return cleanup;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Watch mode with enhanced console output
|
|
157
|
+
*/
|
|
158
|
+
export async function startEnhancedWatchMode(config, options = {}) {
|
|
159
|
+
// Enhanced version with better UX
|
|
160
|
+
const watchOptions = { ...DEFAULT_WATCH_OPTIONS, ...options };
|
|
161
|
+
/* eslint-disable no-console */
|
|
162
|
+
console.log('🚀 Ferret Watch Mode Starting...\n');
|
|
163
|
+
console.log(`📂 Watching: ${config.paths.join(', ')}`);
|
|
164
|
+
console.log(`⚙️ Debounce: ${watchOptions.debounceMs}ms`);
|
|
165
|
+
console.log(`🔍 Severities: ${config.severities.join(', ')}`);
|
|
166
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
|
|
167
|
+
/* eslint-enable no-console */
|
|
168
|
+
return startWatchMode(config, options);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Create a simple file change notifier
|
|
172
|
+
*/
|
|
173
|
+
export function createChangeNotifier(paths, callback, options = {}) {
|
|
174
|
+
const watchOptions = { ...DEFAULT_WATCH_OPTIONS, ...options };
|
|
175
|
+
const changedFiles = [];
|
|
176
|
+
const debouncedCallback = debounce(() => {
|
|
177
|
+
const files = [...changedFiles];
|
|
178
|
+
changedFiles.length = 0; // Clear array
|
|
179
|
+
callback(files);
|
|
180
|
+
}, watchOptions.debounceMs);
|
|
181
|
+
const watcher = chokidar.watch(paths, {
|
|
182
|
+
ignored: watchOptions.ignored,
|
|
183
|
+
persistent: true,
|
|
184
|
+
ignoreInitial: true,
|
|
185
|
+
});
|
|
186
|
+
watcher.on('all', (event, path) => {
|
|
187
|
+
if (['add', 'change', 'unlink'].includes(event)) {
|
|
188
|
+
changedFiles.push(path);
|
|
189
|
+
debouncedCallback();
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
return () => { void watcher.close(); };
|
|
193
|
+
}
|
|
194
|
+
export default { startWatchMode, startEnhancedWatchMode, createChangeNotifier };
|
|
195
|
+
//# sourceMappingURL=WatchMode.js.map
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ferret-Scan Type Definitions
|
|
3
|
+
* Security scanner for AI CLI configurations
|
|
4
|
+
*/
|
|
5
|
+
/** Severity levels for security findings */
|
|
6
|
+
export type Severity = 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW' | 'INFO';
|
|
7
|
+
/** Threat categories detected by the scanner */
|
|
8
|
+
export type ThreatCategory = 'exfiltration' | 'credentials' | 'injection' | 'backdoors' | 'supply-chain' | 'permissions' | 'persistence' | 'obfuscation' | 'ai-specific' | 'advanced-hiding' | 'behavioral';
|
|
9
|
+
/** Component types that can be analyzed */
|
|
10
|
+
export type ComponentType = 'skill' | 'agent' | 'hook' | 'plugin' | 'mcp' | 'settings' | 'ai-config-md' | 'rules-file';
|
|
11
|
+
/** File types supported for analysis */
|
|
12
|
+
export type FileType = 'md' | 'sh' | 'bash' | 'zsh' | 'json' | 'yaml' | 'yml' | 'ts' | 'js' | 'tsx' | 'jsx';
|
|
13
|
+
/** Semantic pattern for AST-based analysis */
|
|
14
|
+
export interface SemanticPattern {
|
|
15
|
+
/** Pattern type */
|
|
16
|
+
type: 'function-call' | 'property-access' | 'dynamic-import' | 'eval-chain' | 'object-structure';
|
|
17
|
+
/** Pattern identifier */
|
|
18
|
+
pattern: string;
|
|
19
|
+
/** Required context */
|
|
20
|
+
context?: string[];
|
|
21
|
+
/** Minimum confidence level (0-1) */
|
|
22
|
+
confidence?: number;
|
|
23
|
+
}
|
|
24
|
+
/** Correlation rule for multi-file analysis */
|
|
25
|
+
export interface CorrelationRule {
|
|
26
|
+
/** Rule identifier */
|
|
27
|
+
id: string;
|
|
28
|
+
/** Description of the correlation pattern */
|
|
29
|
+
description: string;
|
|
30
|
+
/** File patterns that must be present */
|
|
31
|
+
filePatterns: string[];
|
|
32
|
+
/** Content patterns that must exist across files */
|
|
33
|
+
contentPatterns: string[];
|
|
34
|
+
/** Maximum distance between related files (directory levels) */
|
|
35
|
+
maxDistance?: number;
|
|
36
|
+
}
|
|
37
|
+
/** Remediation fix definition */
|
|
38
|
+
export interface RemediationFix {
|
|
39
|
+
/** Fix type */
|
|
40
|
+
type: 'replace' | 'remove' | 'quarantine' | 'permission-change';
|
|
41
|
+
/** Fix description */
|
|
42
|
+
description: string;
|
|
43
|
+
/** Pattern to match for fix */
|
|
44
|
+
pattern: string;
|
|
45
|
+
/** Replacement content (for replace type) */
|
|
46
|
+
replacement?: string;
|
|
47
|
+
/** Safety level (0=dangerous, 1=safe) */
|
|
48
|
+
safety: number;
|
|
49
|
+
/** Whether fix can be applied automatically */
|
|
50
|
+
automatic: boolean;
|
|
51
|
+
}
|
|
52
|
+
/** A single security rule definition */
|
|
53
|
+
export interface Rule {
|
|
54
|
+
/** Unique rule identifier (e.g., "EXFIL-001") */
|
|
55
|
+
id: string;
|
|
56
|
+
/** Human-readable rule name */
|
|
57
|
+
name: string;
|
|
58
|
+
/** Category of threat this rule detects */
|
|
59
|
+
category: ThreatCategory;
|
|
60
|
+
/** Severity level of findings from this rule */
|
|
61
|
+
severity: Severity;
|
|
62
|
+
/** Detailed description of what this rule detects */
|
|
63
|
+
description: string;
|
|
64
|
+
/** Regex patterns to match against content */
|
|
65
|
+
patterns: RegExp[];
|
|
66
|
+
/** File types this rule applies to */
|
|
67
|
+
fileTypes: FileType[];
|
|
68
|
+
/** Component types this rule applies to */
|
|
69
|
+
components: ComponentType[];
|
|
70
|
+
/** Recommended remediation steps */
|
|
71
|
+
remediation: string;
|
|
72
|
+
/** Reference URLs for more information */
|
|
73
|
+
references: string[];
|
|
74
|
+
/** Whether this rule is enabled by default */
|
|
75
|
+
enabled: boolean;
|
|
76
|
+
/** Patterns that exclude a match (false positive filters) */
|
|
77
|
+
excludePatterns?: RegExp[];
|
|
78
|
+
/** Context patterns that must also be present for a match */
|
|
79
|
+
requireContext?: RegExp[];
|
|
80
|
+
/** Context patterns that invalidate a match (documentation indicators) */
|
|
81
|
+
excludeContext?: RegExp[];
|
|
82
|
+
/** Minimum match length to trigger (filters short matches) */
|
|
83
|
+
minMatchLength?: number;
|
|
84
|
+
/** Semantic patterns for AST-based detection */
|
|
85
|
+
semanticPatterns?: SemanticPattern[];
|
|
86
|
+
/** Correlation rules for multi-file analysis */
|
|
87
|
+
correlationRules?: CorrelationRule[];
|
|
88
|
+
/** Available fixes for this rule */
|
|
89
|
+
remediationFixes?: RemediationFix[];
|
|
90
|
+
}
|
|
91
|
+
/** AST node information for semantic findings */
|
|
92
|
+
export interface ASTNodeInfo {
|
|
93
|
+
/** Node type (function, property, etc.) */
|
|
94
|
+
nodeType: string;
|
|
95
|
+
/** Node name/identifier */
|
|
96
|
+
name?: string;
|
|
97
|
+
/** Parent context */
|
|
98
|
+
parent?: string;
|
|
99
|
+
/** Child nodes */
|
|
100
|
+
children?: string[];
|
|
101
|
+
}
|
|
102
|
+
/** Semantic context for advanced analysis */
|
|
103
|
+
export interface SemanticContext {
|
|
104
|
+
/** Function/method name */
|
|
105
|
+
functionName?: string;
|
|
106
|
+
/** Variable names in scope */
|
|
107
|
+
variables?: string[];
|
|
108
|
+
/** Import statements */
|
|
109
|
+
imports?: string[];
|
|
110
|
+
/** Call chain */
|
|
111
|
+
callChain?: string[];
|
|
112
|
+
}
|
|
113
|
+
/** A security finding from the scanner */
|
|
114
|
+
export interface Finding {
|
|
115
|
+
/** Rule ID that triggered this finding */
|
|
116
|
+
ruleId: string;
|
|
117
|
+
/** Rule name */
|
|
118
|
+
ruleName: string;
|
|
119
|
+
/** Severity level */
|
|
120
|
+
severity: Severity;
|
|
121
|
+
/** Category of threat */
|
|
122
|
+
category: ThreatCategory;
|
|
123
|
+
/** Full path to the file */
|
|
124
|
+
file: string;
|
|
125
|
+
/** Relative path for display */
|
|
126
|
+
relativePath: string;
|
|
127
|
+
/** Line number where the finding occurred */
|
|
128
|
+
line: number;
|
|
129
|
+
/** Column number (if available) */
|
|
130
|
+
column?: number;
|
|
131
|
+
/** The matched text */
|
|
132
|
+
match: string;
|
|
133
|
+
/** Context lines around the finding */
|
|
134
|
+
context: ContextLine[];
|
|
135
|
+
/** Remediation recommendation */
|
|
136
|
+
remediation: string;
|
|
137
|
+
/** Additional metadata */
|
|
138
|
+
metadata?: Record<string, unknown>;
|
|
139
|
+
/** Timestamp when finding was detected */
|
|
140
|
+
timestamp: Date;
|
|
141
|
+
/** Risk score (0-100) */
|
|
142
|
+
riskScore: number;
|
|
143
|
+
}
|
|
144
|
+
/** Semantic finding with AST information */
|
|
145
|
+
export interface SemanticFinding extends Finding {
|
|
146
|
+
/** AST node information */
|
|
147
|
+
astNode?: ASTNodeInfo;
|
|
148
|
+
/** Semantic context */
|
|
149
|
+
semanticContext?: SemanticContext;
|
|
150
|
+
/** Confidence level (0-1) */
|
|
151
|
+
confidence: number;
|
|
152
|
+
}
|
|
153
|
+
/** Correlation finding across multiple files */
|
|
154
|
+
export interface CorrelationFinding extends Finding {
|
|
155
|
+
/** Related files in the attack pattern */
|
|
156
|
+
relatedFiles: string[];
|
|
157
|
+
/** Attack pattern name */
|
|
158
|
+
attackPattern: string;
|
|
159
|
+
/** Risk vectors identified */
|
|
160
|
+
riskVectors: string[];
|
|
161
|
+
/** Correlation strength (0-1) */
|
|
162
|
+
correlationStrength: number;
|
|
163
|
+
}
|
|
164
|
+
/** A line of context around a finding */
|
|
165
|
+
export interface ContextLine {
|
|
166
|
+
/** Line number */
|
|
167
|
+
lineNumber: number;
|
|
168
|
+
/** Line content */
|
|
169
|
+
content: string;
|
|
170
|
+
/** Whether this is the matched line */
|
|
171
|
+
isMatch: boolean;
|
|
172
|
+
}
|
|
173
|
+
/** A discovered file to be analyzed */
|
|
174
|
+
export interface DiscoveredFile {
|
|
175
|
+
/** Full absolute path */
|
|
176
|
+
path: string;
|
|
177
|
+
/** Relative path from scan root */
|
|
178
|
+
relativePath: string;
|
|
179
|
+
/** File extension/type */
|
|
180
|
+
type: FileType;
|
|
181
|
+
/** Detected component type */
|
|
182
|
+
component: ComponentType;
|
|
183
|
+
/** File size in bytes */
|
|
184
|
+
size: number;
|
|
185
|
+
/** Last modified timestamp */
|
|
186
|
+
modified: Date;
|
|
187
|
+
}
|
|
188
|
+
/** Results from a complete scan */
|
|
189
|
+
export interface ScanResult {
|
|
190
|
+
/** Whether the scan completed successfully */
|
|
191
|
+
success: boolean;
|
|
192
|
+
/** Timestamp when scan started */
|
|
193
|
+
startTime: Date;
|
|
194
|
+
/** Timestamp when scan completed */
|
|
195
|
+
endTime: Date;
|
|
196
|
+
/** Duration in milliseconds */
|
|
197
|
+
duration: number;
|
|
198
|
+
/** Paths that were scanned */
|
|
199
|
+
scannedPaths: string[];
|
|
200
|
+
/** Total files discovered */
|
|
201
|
+
totalFiles: number;
|
|
202
|
+
/** Files that were actually analyzed */
|
|
203
|
+
analyzedFiles: number;
|
|
204
|
+
/** Files that were skipped (ignored) */
|
|
205
|
+
skippedFiles: number;
|
|
206
|
+
/** All findings from the scan */
|
|
207
|
+
findings: Finding[];
|
|
208
|
+
/** Findings grouped by severity */
|
|
209
|
+
findingsBySeverity: Record<Severity, Finding[]>;
|
|
210
|
+
/** Findings grouped by category */
|
|
211
|
+
findingsByCategory: Record<ThreatCategory, Finding[]>;
|
|
212
|
+
/** Overall risk score (0-100) */
|
|
213
|
+
overallRiskScore: number;
|
|
214
|
+
/** Summary statistics */
|
|
215
|
+
summary: ScanSummary;
|
|
216
|
+
/** Any errors encountered during scanning */
|
|
217
|
+
errors: ScanError[];
|
|
218
|
+
}
|
|
219
|
+
/** Summary statistics for a scan */
|
|
220
|
+
export interface ScanSummary {
|
|
221
|
+
critical: number;
|
|
222
|
+
high: number;
|
|
223
|
+
medium: number;
|
|
224
|
+
low: number;
|
|
225
|
+
info: number;
|
|
226
|
+
total: number;
|
|
227
|
+
}
|
|
228
|
+
/** An error encountered during scanning */
|
|
229
|
+
export interface ScanError {
|
|
230
|
+
/** File path where error occurred */
|
|
231
|
+
file?: string;
|
|
232
|
+
/** Error message */
|
|
233
|
+
message: string;
|
|
234
|
+
/** Error code */
|
|
235
|
+
code?: string;
|
|
236
|
+
/** Whether the error was fatal */
|
|
237
|
+
fatal: boolean;
|
|
238
|
+
}
|
|
239
|
+
/** Scanner configuration options */
|
|
240
|
+
export interface ScannerConfig {
|
|
241
|
+
/** Paths to scan */
|
|
242
|
+
paths: string[];
|
|
243
|
+
/** Severity levels to report */
|
|
244
|
+
severities: Severity[];
|
|
245
|
+
/** Categories to scan for */
|
|
246
|
+
categories: ThreatCategory[];
|
|
247
|
+
/** Patterns to ignore (glob) */
|
|
248
|
+
ignore: string[];
|
|
249
|
+
/** Path to custom rules directory */
|
|
250
|
+
customRules?: string;
|
|
251
|
+
/** Minimum severity to fail on */
|
|
252
|
+
failOn: Severity;
|
|
253
|
+
/** Enable watch mode */
|
|
254
|
+
watch: boolean;
|
|
255
|
+
/** Enable AI-powered detection */
|
|
256
|
+
aiDetection: boolean;
|
|
257
|
+
/** Enable threat intelligence */
|
|
258
|
+
threatIntel: boolean;
|
|
259
|
+
/** Enable behavioral analysis */
|
|
260
|
+
behaviorAnalysis: boolean;
|
|
261
|
+
/** Enable semantic analysis */
|
|
262
|
+
semanticAnalysis: boolean;
|
|
263
|
+
/** Enable cross-file correlation */
|
|
264
|
+
correlationAnalysis: boolean;
|
|
265
|
+
/** Enable auto-remediation */
|
|
266
|
+
autoRemediation: boolean;
|
|
267
|
+
/** Context lines to show around findings */
|
|
268
|
+
contextLines: number;
|
|
269
|
+
/** Maximum file size to scan (bytes) */
|
|
270
|
+
maxFileSize: number;
|
|
271
|
+
/** Output format */
|
|
272
|
+
format: OutputFormat;
|
|
273
|
+
/** Output file path */
|
|
274
|
+
outputFile?: string;
|
|
275
|
+
/** Verbose output */
|
|
276
|
+
verbose: boolean;
|
|
277
|
+
/** CI mode (simplified output) */
|
|
278
|
+
ci: boolean;
|
|
279
|
+
}
|
|
280
|
+
/** Supported output formats */
|
|
281
|
+
export type OutputFormat = 'console' | 'json' | 'sarif' | 'html' | 'csv';
|
|
282
|
+
/** CLI options passed from command line */
|
|
283
|
+
export interface CliOptions {
|
|
284
|
+
path?: string;
|
|
285
|
+
format?: OutputFormat;
|
|
286
|
+
severity?: string;
|
|
287
|
+
categories?: string;
|
|
288
|
+
failOn?: string;
|
|
289
|
+
output?: string;
|
|
290
|
+
watch?: boolean;
|
|
291
|
+
ci?: boolean;
|
|
292
|
+
verbose?: boolean;
|
|
293
|
+
aiDetection?: boolean;
|
|
294
|
+
threatIntel?: boolean;
|
|
295
|
+
semanticAnalysis?: boolean;
|
|
296
|
+
correlationAnalysis?: boolean;
|
|
297
|
+
autoRemediation?: boolean;
|
|
298
|
+
config?: string;
|
|
299
|
+
}
|
|
300
|
+
/** Configuration file structure (.ferretrc.json) */
|
|
301
|
+
export interface ConfigFile {
|
|
302
|
+
severity?: Severity[];
|
|
303
|
+
categories?: ThreatCategory[];
|
|
304
|
+
ignore?: string[];
|
|
305
|
+
customRules?: string;
|
|
306
|
+
failOn?: Severity;
|
|
307
|
+
aiDetection?: {
|
|
308
|
+
enabled: boolean;
|
|
309
|
+
confidence?: number;
|
|
310
|
+
};
|
|
311
|
+
threatIntelligence?: {
|
|
312
|
+
enabled: boolean;
|
|
313
|
+
feeds?: string[];
|
|
314
|
+
updateInterval?: string;
|
|
315
|
+
};
|
|
316
|
+
behaviorAnalysis?: {
|
|
317
|
+
enabled: boolean;
|
|
318
|
+
patterns?: string[];
|
|
319
|
+
};
|
|
320
|
+
remediation?: {
|
|
321
|
+
autoFix?: boolean;
|
|
322
|
+
quarantineDir?: string;
|
|
323
|
+
backupOriginals?: boolean;
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
/** Default scanner configuration */
|
|
327
|
+
export declare const DEFAULT_CONFIG: ScannerConfig;
|
|
328
|
+
/** Severity weights for risk scoring */
|
|
329
|
+
export declare const SEVERITY_WEIGHTS: Record<Severity, number>;
|
|
330
|
+
/** Severity order for sorting */
|
|
331
|
+
export declare const SEVERITY_ORDER: Severity[];
|
|
332
|
+
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ferret-Scan Type Definitions
|
|
3
|
+
* Security scanner for AI CLI configurations
|
|
4
|
+
*/
|
|
5
|
+
/** Default scanner configuration */
|
|
6
|
+
export const DEFAULT_CONFIG = {
|
|
7
|
+
paths: [],
|
|
8
|
+
severities: ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'INFO'],
|
|
9
|
+
categories: [
|
|
10
|
+
'exfiltration',
|
|
11
|
+
'credentials',
|
|
12
|
+
'injection',
|
|
13
|
+
'backdoors',
|
|
14
|
+
'supply-chain',
|
|
15
|
+
'permissions',
|
|
16
|
+
'persistence',
|
|
17
|
+
'obfuscation',
|
|
18
|
+
'ai-specific',
|
|
19
|
+
'advanced-hiding',
|
|
20
|
+
'behavioral',
|
|
21
|
+
],
|
|
22
|
+
ignore: ['**/node_modules/**', '**/.git/**'],
|
|
23
|
+
failOn: 'HIGH',
|
|
24
|
+
watch: false,
|
|
25
|
+
aiDetection: false,
|
|
26
|
+
threatIntel: false,
|
|
27
|
+
behaviorAnalysis: false,
|
|
28
|
+
semanticAnalysis: false,
|
|
29
|
+
correlationAnalysis: false,
|
|
30
|
+
autoRemediation: false,
|
|
31
|
+
contextLines: 3,
|
|
32
|
+
maxFileSize: 10 * 1024 * 1024, // 10MB
|
|
33
|
+
format: 'console',
|
|
34
|
+
verbose: false,
|
|
35
|
+
ci: false,
|
|
36
|
+
};
|
|
37
|
+
/** Severity weights for risk scoring */
|
|
38
|
+
export const SEVERITY_WEIGHTS = {
|
|
39
|
+
CRITICAL: 100,
|
|
40
|
+
HIGH: 75,
|
|
41
|
+
MEDIUM: 50,
|
|
42
|
+
LOW: 25,
|
|
43
|
+
INFO: 10,
|
|
44
|
+
};
|
|
45
|
+
/** Severity order for sorting */
|
|
46
|
+
export const SEVERITY_ORDER = [
|
|
47
|
+
'CRITICAL',
|
|
48
|
+
'HIGH',
|
|
49
|
+
'MEDIUM',
|
|
50
|
+
'LOW',
|
|
51
|
+
'INFO',
|
|
52
|
+
];
|
|
53
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Baseline Management - Track and ignore accepted findings
|
|
3
|
+
* Allows users to create baselines of known/accepted security findings
|
|
4
|
+
*/
|
|
5
|
+
import type { Finding, ScanResult } from '../types.js';
|
|
6
|
+
export interface BaselineFinding {
|
|
7
|
+
ruleId: string;
|
|
8
|
+
file: string;
|
|
9
|
+
line: number;
|
|
10
|
+
match: string;
|
|
11
|
+
hash: string;
|
|
12
|
+
acceptedDate: string;
|
|
13
|
+
reason?: string;
|
|
14
|
+
expiresDate?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface Baseline {
|
|
17
|
+
version: string;
|
|
18
|
+
createdDate: string;
|
|
19
|
+
lastUpdated: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
findings: BaselineFinding[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Load baseline from file
|
|
25
|
+
*/
|
|
26
|
+
export declare function loadBaseline(baselinePath: string): Baseline | null;
|
|
27
|
+
/**
|
|
28
|
+
* Save baseline to file
|
|
29
|
+
*/
|
|
30
|
+
export declare function saveBaseline(baseline: Baseline, baselinePath: string): void;
|
|
31
|
+
/**
|
|
32
|
+
* Create a new baseline from scan results
|
|
33
|
+
*/
|
|
34
|
+
export declare function createBaseline(result: ScanResult, description?: string): Baseline;
|
|
35
|
+
/**
|
|
36
|
+
* Add findings to an existing baseline
|
|
37
|
+
*/
|
|
38
|
+
export declare function addToBaseline(baseline: Baseline, findings: Finding[], reason?: string): Baseline;
|
|
39
|
+
/**
|
|
40
|
+
* Remove findings from baseline
|
|
41
|
+
*/
|
|
42
|
+
export declare function removeFromBaseline(baseline: Baseline, findingHashes: string[]): Baseline;
|
|
43
|
+
/**
|
|
44
|
+
* Filter scan results against baseline
|
|
45
|
+
*/
|
|
46
|
+
export declare function filterAgainstBaseline(result: ScanResult, baseline: Baseline | null): ScanResult;
|
|
47
|
+
/**
|
|
48
|
+
* Check if baseline findings are still valid
|
|
49
|
+
*/
|
|
50
|
+
export declare function validateBaseline(baseline: Baseline, currentResult: ScanResult): {
|
|
51
|
+
valid: BaselineFinding[];
|
|
52
|
+
invalid: BaselineFinding[];
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Get default baseline path for a project
|
|
56
|
+
*/
|
|
57
|
+
export declare function getDefaultBaselinePath(scanPaths: string[]): string;
|
|
58
|
+
/**
|
|
59
|
+
* Baseline statistics
|
|
60
|
+
*/
|
|
61
|
+
export declare function getBaselineStats(baseline: Baseline): {
|
|
62
|
+
totalFindings: number;
|
|
63
|
+
byRule: Record<string, number>;
|
|
64
|
+
bySeverity: Record<string, number>;
|
|
65
|
+
oldestFinding: string;
|
|
66
|
+
newestFinding: string;
|
|
67
|
+
};
|
|
68
|
+
declare const _default: {
|
|
69
|
+
loadBaseline: typeof loadBaseline;
|
|
70
|
+
saveBaseline: typeof saveBaseline;
|
|
71
|
+
createBaseline: typeof createBaseline;
|
|
72
|
+
addToBaseline: typeof addToBaseline;
|
|
73
|
+
removeFromBaseline: typeof removeFromBaseline;
|
|
74
|
+
filterAgainstBaseline: typeof filterAgainstBaseline;
|
|
75
|
+
validateBaseline: typeof validateBaseline;
|
|
76
|
+
getDefaultBaselinePath: typeof getDefaultBaselinePath;
|
|
77
|
+
getBaselineStats: typeof getBaselineStats;
|
|
78
|
+
};
|
|
79
|
+
export default _default;
|
|
80
|
+
//# sourceMappingURL=baseline.d.ts.map
|