@eddacraft/anvil-runtime 0.1.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/LICENSE +14 -0
- package/dist/cache/cache-key.d.ts +45 -0
- package/dist/cache/cache-key.d.ts.map +1 -0
- package/dist/cache/cache-key.js +135 -0
- package/dist/cache/index.d.ts +27 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +38 -0
- package/dist/cache/providers/file-cache.d.ts +63 -0
- package/dist/cache/providers/file-cache.d.ts.map +1 -0
- package/dist/cache/providers/file-cache.js +369 -0
- package/dist/cache/providers/memory-cache.d.ts +52 -0
- package/dist/cache/providers/memory-cache.d.ts.map +1 -0
- package/dist/cache/providers/memory-cache.js +197 -0
- package/dist/cache/providers/null-cache.d.ts +26 -0
- package/dist/cache/providers/null-cache.d.ts.map +1 -0
- package/dist/cache/providers/null-cache.js +50 -0
- package/dist/cache/types.d.ts +114 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/cache/types.js +4 -0
- package/dist/concurrency/agent.d.ts +137 -0
- package/dist/concurrency/agent.d.ts.map +1 -0
- package/dist/concurrency/agent.js +440 -0
- package/dist/concurrency/atomic.d.ts +93 -0
- package/dist/concurrency/atomic.d.ts.map +1 -0
- package/dist/concurrency/atomic.js +281 -0
- package/dist/concurrency/git-agent.d.ts +114 -0
- package/dist/concurrency/git-agent.d.ts.map +1 -0
- package/dist/concurrency/git-agent.js +313 -0
- package/dist/concurrency/index.d.ts +95 -0
- package/dist/concurrency/index.d.ts.map +1 -0
- package/dist/concurrency/index.js +127 -0
- package/dist/concurrency/lock-manager.d.ts +170 -0
- package/dist/concurrency/lock-manager.d.ts.map +1 -0
- package/dist/concurrency/lock-manager.js +525 -0
- package/dist/concurrency/queue-manager.d.ts +166 -0
- package/dist/concurrency/queue-manager.d.ts.map +1 -0
- package/dist/concurrency/queue-manager.js +442 -0
- package/dist/concurrency/types.d.ts +382 -0
- package/dist/concurrency/types.d.ts.map +1 -0
- package/dist/concurrency/types.js +204 -0
- package/dist/export/constraint-collector.d.ts +175 -0
- package/dist/export/constraint-collector.d.ts.map +1 -0
- package/dist/export/constraint-collector.js +203 -0
- package/dist/export/formatters/llms-txt-formatter.d.ts +89 -0
- package/dist/export/formatters/llms-txt-formatter.d.ts.map +1 -0
- package/dist/export/formatters/llms-txt-formatter.js +249 -0
- package/dist/export/formatters/mcp-resource-formatter.d.ts +186 -0
- package/dist/export/formatters/mcp-resource-formatter.d.ts.map +1 -0
- package/dist/export/formatters/mcp-resource-formatter.js +139 -0
- package/dist/export/formatters/prompt-formatter.d.ts +83 -0
- package/dist/export/formatters/prompt-formatter.d.ts.map +1 -0
- package/dist/export/formatters/prompt-formatter.js +256 -0
- package/dist/export/index.d.ts +10 -0
- package/dist/export/index.d.ts.map +1 -0
- package/dist/export/index.js +9 -0
- package/dist/gate/check.interface.d.ts +15 -0
- package/dist/gate/check.interface.d.ts.map +1 -0
- package/dist/gate/check.interface.js +18 -0
- package/dist/gate/checks/antipattern.check.d.ts +27 -0
- package/dist/gate/checks/antipattern.check.d.ts.map +1 -0
- package/dist/gate/checks/antipattern.check.js +140 -0
- package/dist/gate/checks/architecture/circular-detector.d.ts +33 -0
- package/dist/gate/checks/architecture/circular-detector.d.ts.map +1 -0
- package/dist/gate/checks/architecture/circular-detector.js +71 -0
- package/dist/gate/checks/architecture/dependency-analyzer.d.ts +81 -0
- package/dist/gate/checks/architecture/dependency-analyzer.d.ts.map +1 -0
- package/dist/gate/checks/architecture/dependency-analyzer.js +136 -0
- package/dist/gate/checks/architecture/layer-validator.d.ts +75 -0
- package/dist/gate/checks/architecture/layer-validator.d.ts.map +1 -0
- package/dist/gate/checks/architecture/layer-validator.js +193 -0
- package/dist/gate/checks/architecture.check.d.ts +56 -0
- package/dist/gate/checks/architecture.check.d.ts.map +1 -0
- package/dist/gate/checks/architecture.check.js +394 -0
- package/dist/gate/checks/command-safety.check.d.ts +12 -0
- package/dist/gate/checks/command-safety.check.d.ts.map +1 -0
- package/dist/gate/checks/command-safety.check.js +230 -0
- package/dist/gate/checks/coverage.check.d.ts +9 -0
- package/dist/gate/checks/coverage.check.d.ts.map +1 -0
- package/dist/gate/checks/coverage.check.js +81 -0
- package/dist/gate/checks/dependency.check.d.ts +17 -0
- package/dist/gate/checks/dependency.check.d.ts.map +1 -0
- package/dist/gate/checks/dependency.check.js +342 -0
- package/dist/gate/checks/eslint.check.d.ts +14 -0
- package/dist/gate/checks/eslint.check.d.ts.map +1 -0
- package/dist/gate/checks/eslint.check.js +79 -0
- package/dist/gate/checks/policy.check.d.ts +78 -0
- package/dist/gate/checks/policy.check.d.ts.map +1 -0
- package/dist/gate/checks/policy.check.js +457 -0
- package/dist/gate/checks/secret/entropy-detector.d.ts +44 -0
- package/dist/gate/checks/secret/entropy-detector.d.ts.map +1 -0
- package/dist/gate/checks/secret/entropy-detector.js +76 -0
- package/dist/gate/checks/secret/git-scanner.d.ts +36 -0
- package/dist/gate/checks/secret/git-scanner.d.ts.map +1 -0
- package/dist/gate/checks/secret/git-scanner.js +90 -0
- package/dist/gate/checks/secret/secret-patterns.d.ts +42 -0
- package/dist/gate/checks/secret/secret-patterns.d.ts.map +1 -0
- package/dist/gate/checks/secret/secret-patterns.js +137 -0
- package/dist/gate/checks/secret.check.d.ts +56 -0
- package/dist/gate/checks/secret.check.d.ts.map +1 -0
- package/dist/gate/checks/secret.check.js +245 -0
- package/dist/gate/config/command-safety-config.d.ts +5 -0
- package/dist/gate/config/command-safety-config.d.ts.map +1 -0
- package/dist/gate/config/command-safety-config.js +69 -0
- package/dist/gate/config/index.d.ts +2 -0
- package/dist/gate/config/index.d.ts.map +1 -0
- package/dist/gate/config/index.js +1 -0
- package/dist/gate/formatters/command-safety-formatter.d.ts +10 -0
- package/dist/gate/formatters/command-safety-formatter.d.ts.map +1 -0
- package/dist/gate/formatters/command-safety-formatter.js +64 -0
- package/dist/gate/formatters/index.d.ts +2 -0
- package/dist/gate/formatters/index.d.ts.map +1 -0
- package/dist/gate/formatters/index.js +1 -0
- package/dist/gate/gate-config.d.ts +44 -0
- package/dist/gate/gate-config.d.ts.map +1 -0
- package/dist/gate/gate-config.js +334 -0
- package/dist/gate/gate-runner.d.ts +160 -0
- package/dist/gate/gate-runner.d.ts.map +1 -0
- package/dist/gate/gate-runner.js +531 -0
- package/dist/gate/index.d.ts +20 -0
- package/dist/gate/index.d.ts.map +1 -0
- package/dist/gate/index.js +14 -0
- package/dist/gate/parsers/command-parser.d.ts +18 -0
- package/dist/gate/parsers/command-parser.d.ts.map +1 -0
- package/dist/gate/parsers/command-parser.js +363 -0
- package/dist/gate/parsers/index.d.ts +2 -0
- package/dist/gate/parsers/index.d.ts.map +1 -0
- package/dist/gate/parsers/index.js +1 -0
- package/dist/gate/policy/index.d.ts +12 -0
- package/dist/gate/policy/index.d.ts.map +1 -0
- package/dist/gate/policy/index.js +10 -0
- package/dist/gate/rules/default-filesystem-rules.d.ts +3 -0
- package/dist/gate/rules/default-filesystem-rules.d.ts.map +1 -0
- package/dist/gate/rules/default-filesystem-rules.js +201 -0
- package/dist/gate/rules/default-git-rules.d.ts +3 -0
- package/dist/gate/rules/default-git-rules.d.ts.map +1 -0
- package/dist/gate/rules/default-git-rules.js +192 -0
- package/dist/gate/rules/index.d.ts +5 -0
- package/dist/gate/rules/index.d.ts.map +1 -0
- package/dist/gate/rules/index.js +3 -0
- package/dist/gate/rules/rule-matcher.d.ts +27 -0
- package/dist/gate/rules/rule-matcher.d.ts.map +1 -0
- package/dist/gate/rules/rule-matcher.js +228 -0
- package/dist/gate/rules/types.d.ts +250 -0
- package/dist/gate/rules/types.d.ts.map +1 -0
- package/dist/gate/rules/types.js +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/types/gate.types.d.ts +42 -0
- package/dist/types/gate.types.d.ts.map +1 -0
- package/dist/types/gate.types.js +94 -0
- package/dist/watch/debouncer.d.ts +90 -0
- package/dist/watch/debouncer.d.ts.map +1 -0
- package/dist/watch/debouncer.js +135 -0
- package/dist/watch/file-watcher.d.ts +73 -0
- package/dist/watch/file-watcher.d.ts.map +1 -0
- package/dist/watch/file-watcher.js +121 -0
- package/dist/watch/git-status.d.ts +98 -0
- package/dist/watch/git-status.d.ts.map +1 -0
- package/dist/watch/git-status.js +266 -0
- package/dist/watch/index.d.ts +16 -0
- package/dist/watch/index.d.ts.map +1 -0
- package/dist/watch/index.js +15 -0
- package/dist/watch/orchestrator.d.ts +113 -0
- package/dist/watch/orchestrator.d.ts.map +1 -0
- package/dist/watch/orchestrator.js +409 -0
- package/dist/watch/types.d.ts +190 -0
- package/dist/watch/types.d.ts.map +1 -0
- package/dist/watch/types.js +76 -0
- package/package.json +60 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Scanner - Scan git history for secrets in recent commits
|
|
3
|
+
*
|
|
4
|
+
* Scans git commit diffs to find secrets that may have been committed in the past.
|
|
5
|
+
*/
|
|
6
|
+
import { execFile } from 'node:child_process';
|
|
7
|
+
import { promisify } from 'node:util';
|
|
8
|
+
import { createDebugger } from '@eddacraft/anvil-core';
|
|
9
|
+
import { SECRET_PATTERNS, PatternMatcher } from './secret-patterns.js';
|
|
10
|
+
const log = createDebugger('check');
|
|
11
|
+
const execFileAsync = promisify(execFile);
|
|
12
|
+
/**
|
|
13
|
+
* Git scanner for finding secrets in repository history
|
|
14
|
+
*/
|
|
15
|
+
export class GitScanner {
|
|
16
|
+
matcher = new PatternMatcher();
|
|
17
|
+
/**
|
|
18
|
+
* Scan git history for secrets in recent commits
|
|
19
|
+
*/
|
|
20
|
+
async scanGitHistory(workspaceRoot, config) {
|
|
21
|
+
log(`git-scanner: starting git history scan, workspace=${workspaceRoot}, depth=${config.git_history_depth}`);
|
|
22
|
+
const findings = [];
|
|
23
|
+
// Clamp depth to a sane range (1–1000)
|
|
24
|
+
const depth = Math.max(1, Math.min(1000, Math.floor(config.git_history_depth)));
|
|
25
|
+
try {
|
|
26
|
+
// Check if we're in a git repository (handles worktrees where .git is a file)
|
|
27
|
+
try {
|
|
28
|
+
await execFileAsync('git', ['rev-parse', '--git-dir'], { cwd: workspaceRoot });
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
log('git-scanner: not a git repository, skipping');
|
|
32
|
+
return findings;
|
|
33
|
+
}
|
|
34
|
+
// Get recent commit diffs
|
|
35
|
+
const { stdout } = await execFileAsync('git', [
|
|
36
|
+
'log',
|
|
37
|
+
'-p',
|
|
38
|
+
`-${depth}`,
|
|
39
|
+
'--all',
|
|
40
|
+
'--diff-filter=A',
|
|
41
|
+
'--',
|
|
42
|
+
'*.ts',
|
|
43
|
+
'*.js',
|
|
44
|
+
'*.json',
|
|
45
|
+
'*.env*',
|
|
46
|
+
'*.yaml',
|
|
47
|
+
'*.yml',
|
|
48
|
+
], {
|
|
49
|
+
cwd: workspaceRoot,
|
|
50
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
51
|
+
timeout: 60_000,
|
|
52
|
+
});
|
|
53
|
+
// Parse git diff output
|
|
54
|
+
const commitBlocks = stdout.split(/^commit /m).slice(1);
|
|
55
|
+
log(`git-scanner: scanning ${commitBlocks.length} commit blocks`);
|
|
56
|
+
for (const block of commitBlocks) {
|
|
57
|
+
const commitMatch = block.match(/^([a-f0-9]+)/);
|
|
58
|
+
const commitHash = commitMatch ? commitMatch[1].substring(0, 8) : 'unknown';
|
|
59
|
+
// Find added lines (starting with +)
|
|
60
|
+
const addedLines = block
|
|
61
|
+
.split('\n')
|
|
62
|
+
.filter((line) => line.startsWith('+') && !line.startsWith('+++'));
|
|
63
|
+
for (const addedLine of addedLines) {
|
|
64
|
+
const lineContent = addedLine.substring(1); // Remove the + prefix
|
|
65
|
+
// Check for pattern matches
|
|
66
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
67
|
+
const matches = lineContent.match(pattern.pattern);
|
|
68
|
+
if (matches && !this.matcher.isAllowlisted(matches[0], config.allowlist)) {
|
|
69
|
+
findings.push({
|
|
70
|
+
file: `git-history:${commitHash}`,
|
|
71
|
+
line: 0,
|
|
72
|
+
type: `${pattern.name} (in git history)`,
|
|
73
|
+
match: this.matcher.redactSecret(matches[0]),
|
|
74
|
+
context: this.matcher.redactLine(lineContent.trim()),
|
|
75
|
+
source: 'git-history',
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
log('git-scanner: git command failed, skipping history scan');
|
|
84
|
+
// Git command failed, likely not a git repo or git not available
|
|
85
|
+
// Silently skip git history scanning
|
|
86
|
+
}
|
|
87
|
+
log(`git-scanner: scan complete, found ${findings.length} findings`);
|
|
88
|
+
return findings;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret Patterns - Pattern definitions and matching for secret detection
|
|
3
|
+
*
|
|
4
|
+
* Contains regex patterns for detecting various types of secrets and sensitive data.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Secret pattern definition
|
|
8
|
+
*/
|
|
9
|
+
export interface SecretPattern {
|
|
10
|
+
name: string;
|
|
11
|
+
pattern: RegExp;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Secret patterns for common API keys, tokens, and sensitive data
|
|
15
|
+
*/
|
|
16
|
+
export declare const SECRET_PATTERNS: SecretPattern[];
|
|
17
|
+
/**
|
|
18
|
+
* Default patterns to allowlist (common false positives)
|
|
19
|
+
*/
|
|
20
|
+
export declare const DEFAULT_ALLOWLIST: RegExp[];
|
|
21
|
+
/**
|
|
22
|
+
* Pattern matcher for secret detection
|
|
23
|
+
*/
|
|
24
|
+
export declare class PatternMatcher {
|
|
25
|
+
/**
|
|
26
|
+
* Check if a string is allowlisted
|
|
27
|
+
*/
|
|
28
|
+
isAllowlisted(str: string, customAllowlist: string[]): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Check if a string looks like code (not a secret)
|
|
31
|
+
*/
|
|
32
|
+
looksLikeCode(str: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Redact secret value for safe display
|
|
35
|
+
*/
|
|
36
|
+
redactSecret(secret: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* Redact potential secrets in a line for safe display
|
|
39
|
+
*/
|
|
40
|
+
redactLine(line: string): string;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=secret-patterns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-patterns.d.ts","sourceRoot":"","sources":["../../../../src/gate/checks/secret/secret-patterns.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,aAAa,EA4C1C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,EAYrC,CAAC;AAEF;;GAEG;AACH,qBAAa,cAAc;IACzB;;OAEG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,GAAG,OAAO;IAwB9D;;OAEG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAiBnC;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAOpC;;OAEG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAQjC"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret Patterns - Pattern definitions and matching for secret detection
|
|
3
|
+
*
|
|
4
|
+
* Contains regex patterns for detecting various types of secrets and sensitive data.
|
|
5
|
+
*/
|
|
6
|
+
import { createDebugger } from '@eddacraft/anvil-core';
|
|
7
|
+
const log = createDebugger('check');
|
|
8
|
+
/**
|
|
9
|
+
* Secret patterns for common API keys, tokens, and sensitive data
|
|
10
|
+
*/
|
|
11
|
+
export const SECRET_PATTERNS = [
|
|
12
|
+
// API Keys
|
|
13
|
+
{ name: 'API Key', pattern: /(?:api[_-]?key|apikey)\s*[:=]\s*['"]?[a-zA-Z0-9_-]{16,}['"]?/i },
|
|
14
|
+
// JWT Tokens
|
|
15
|
+
{ name: 'JWT Token', pattern: /eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*/ },
|
|
16
|
+
// AWS Keys
|
|
17
|
+
{ name: 'AWS Key', pattern: /AKIA[0-9A-Z]{16}/ },
|
|
18
|
+
{
|
|
19
|
+
name: 'AWS Secret Key',
|
|
20
|
+
pattern: /(?:aws_secret|aws_secret_access_key)\s*[:=]\s*['"]?[A-Za-z0-9/+=]{40}['"]?/i,
|
|
21
|
+
},
|
|
22
|
+
// Private Keys
|
|
23
|
+
{ name: 'Private Key', pattern: /-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----/ },
|
|
24
|
+
{ name: 'PGP Private Key', pattern: /-----BEGIN PGP PRIVATE KEY BLOCK-----/ },
|
|
25
|
+
// Database URLs
|
|
26
|
+
{ name: 'Database URL', pattern: /(?:postgres|mysql|mongodb|redis):\/\/[^:\s]+:[^@\s]+@/ },
|
|
27
|
+
// Generic secrets
|
|
28
|
+
{
|
|
29
|
+
name: 'Generic Secret',
|
|
30
|
+
pattern: /(?:secret|password|passwd|pwd)\s*[:=]\s*['"]?[^\s'"]{8,}['"]?/i,
|
|
31
|
+
},
|
|
32
|
+
// Credit Cards (basic pattern)
|
|
33
|
+
{ name: 'Credit Card', pattern: /\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/ },
|
|
34
|
+
// GitHub tokens
|
|
35
|
+
{ name: 'GitHub Token', pattern: /gh[pousr]_[A-Za-z0-9_]{36,}/ },
|
|
36
|
+
// Slack tokens
|
|
37
|
+
{ name: 'Slack Token', pattern: /xox[baprs]-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-9]{24}/ },
|
|
38
|
+
// Stripe keys
|
|
39
|
+
{ name: 'Stripe Key', pattern: /sk_live_[0-9a-zA-Z]{24}/ },
|
|
40
|
+
{ name: 'Stripe Test Key', pattern: /sk_test_[0-9a-zA-Z]{24}/ },
|
|
41
|
+
// Google API keys
|
|
42
|
+
{ name: 'Google API Key', pattern: /AIza[0-9A-Za-z_-]{35}/ },
|
|
43
|
+
// Heroku API key
|
|
44
|
+
{
|
|
45
|
+
name: 'Heroku API Key',
|
|
46
|
+
pattern: /[h|H]eroku[a-zA-Z0-9_-]*[:=]\s*['"]?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}['"]?/,
|
|
47
|
+
},
|
|
48
|
+
// SendGrid API key
|
|
49
|
+
{ name: 'SendGrid API Key', pattern: /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/ },
|
|
50
|
+
// Twilio API key
|
|
51
|
+
{ name: 'Twilio API Key', pattern: /SK[a-f0-9]{32}/ },
|
|
52
|
+
// npm token
|
|
53
|
+
{ name: 'NPM Token', pattern: /npm_[A-Za-z0-9]{36}/ },
|
|
54
|
+
];
|
|
55
|
+
/**
|
|
56
|
+
* Default patterns to allowlist (common false positives)
|
|
57
|
+
*/
|
|
58
|
+
export const DEFAULT_ALLOWLIST = [
|
|
59
|
+
/^[a-f0-9]{32}$/, // MD5 hashes
|
|
60
|
+
/^[a-f0-9]{40}$/, // SHA1 hashes
|
|
61
|
+
/^[a-f0-9]{64}$/, // SHA256 hashes
|
|
62
|
+
/^0x[a-f0-9]+$/i, // Hex literals
|
|
63
|
+
/^data:image\/[a-z]+;base64,/, // Base64 images
|
|
64
|
+
/placeholder/i,
|
|
65
|
+
/example/i,
|
|
66
|
+
/test/i,
|
|
67
|
+
/dummy/i,
|
|
68
|
+
/sample/i,
|
|
69
|
+
/lorem ipsum/i,
|
|
70
|
+
];
|
|
71
|
+
/**
|
|
72
|
+
* Pattern matcher for secret detection
|
|
73
|
+
*/
|
|
74
|
+
export class PatternMatcher {
|
|
75
|
+
/**
|
|
76
|
+
* Check if a string is allowlisted
|
|
77
|
+
*/
|
|
78
|
+
isAllowlisted(str, customAllowlist) {
|
|
79
|
+
// Check default allowlist
|
|
80
|
+
for (const pattern of DEFAULT_ALLOWLIST) {
|
|
81
|
+
if (pattern.test(str)) {
|
|
82
|
+
log('secret-patterns: string allowlisted by default pattern');
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Check custom allowlist
|
|
87
|
+
for (const pattern of customAllowlist) {
|
|
88
|
+
try {
|
|
89
|
+
if (new RegExp(pattern, 'i').test(str)) {
|
|
90
|
+
log(`secret-patterns: string allowlisted by custom pattern: ${pattern}`);
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Check if a string looks like code (not a secret)
|
|
102
|
+
*/
|
|
103
|
+
looksLikeCode(str) {
|
|
104
|
+
// Common code patterns that aren't secrets
|
|
105
|
+
const codePatterns = [
|
|
106
|
+
/^[a-z][a-zA-Z0-9]*\(/, // Function call
|
|
107
|
+
/^[a-z][a-zA-Z0-9]*\.[a-z]/, // Method chain
|
|
108
|
+
/^https?:\/\//, // URLs
|
|
109
|
+
/^[a-z]+:\/\//, // Protocol URLs
|
|
110
|
+
/\.(js|ts|css|html|json|md|txt)$/, // File extensions
|
|
111
|
+
/^[A-Z][A-Z0-9_]+$/, // Constants (all caps)
|
|
112
|
+
/\s+/, // Contains whitespace
|
|
113
|
+
/^[a-z][a-z0-9]*[A-Z]/, // camelCase
|
|
114
|
+
/^[A-Z][a-z]+[A-Z]/, // PascalCase
|
|
115
|
+
];
|
|
116
|
+
return codePatterns.some((pattern) => pattern.test(str));
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Redact secret value for safe display
|
|
120
|
+
*/
|
|
121
|
+
redactSecret(secret) {
|
|
122
|
+
if (secret.length <= 8)
|
|
123
|
+
return '***';
|
|
124
|
+
const prefix = secret.substring(0, 4);
|
|
125
|
+
const suffix = secret.substring(secret.length - 4);
|
|
126
|
+
return `${prefix}...${suffix}`;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Redact potential secrets in a line for safe display
|
|
130
|
+
*/
|
|
131
|
+
redactLine(line) {
|
|
132
|
+
let redacted = line;
|
|
133
|
+
// Redact quoted strings that look like secrets
|
|
134
|
+
redacted = redacted.replace(/(['"])[a-zA-Z0-9_/+=-]{16,}\1/g, '$1[REDACTED]$1');
|
|
135
|
+
return redacted;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { BaseCheck } from '../check.interface.js';
|
|
2
|
+
import { CheckContext, GateResult } from '../../types/gate.types.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
export interface SecretFinding {
|
|
5
|
+
file: string;
|
|
6
|
+
line: number;
|
|
7
|
+
type: string;
|
|
8
|
+
match: string;
|
|
9
|
+
context: string;
|
|
10
|
+
entropy?: number;
|
|
11
|
+
source?: 'pattern' | 'entropy' | 'git-history';
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Zod schema for SecretCheckConfig with runtime validation
|
|
15
|
+
*/
|
|
16
|
+
export declare const SecretCheckConfigSchema: z.ZodObject<{
|
|
17
|
+
enable_entropy: z.ZodOptional<z.ZodBoolean>;
|
|
18
|
+
entropy_threshold: z.ZodOptional<z.ZodNumber>;
|
|
19
|
+
min_entropy_length: z.ZodOptional<z.ZodNumber>;
|
|
20
|
+
scan_git_history: z.ZodOptional<z.ZodBoolean>;
|
|
21
|
+
git_history_depth: z.ZodOptional<z.ZodNumber>;
|
|
22
|
+
allowlist: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
23
|
+
skip_extensions: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
24
|
+
}, z.core.$strip>;
|
|
25
|
+
export type SecretCheckConfig = z.infer<typeof SecretCheckConfigSchema>;
|
|
26
|
+
export declare class SecretCheck extends BaseCheck {
|
|
27
|
+
name: string;
|
|
28
|
+
description: string;
|
|
29
|
+
private matcher;
|
|
30
|
+
private entropyDetector;
|
|
31
|
+
private gitScanner;
|
|
32
|
+
run(context: CheckContext): Promise<GateResult>;
|
|
33
|
+
/**
|
|
34
|
+
* Get configuration with defaults
|
|
35
|
+
*/
|
|
36
|
+
private getConfig;
|
|
37
|
+
/**
|
|
38
|
+
* Check if a file should be skipped based on extension
|
|
39
|
+
*/
|
|
40
|
+
private shouldSkipFile;
|
|
41
|
+
/**
|
|
42
|
+
* Scan file content for secrets using both patterns and entropy
|
|
43
|
+
*/
|
|
44
|
+
private scanFileContent;
|
|
45
|
+
/**
|
|
46
|
+
|
|
47
|
+
* Remove duplicate findings
|
|
48
|
+
*/
|
|
49
|
+
private deduplicateFindings;
|
|
50
|
+
private getFilesFromPlan;
|
|
51
|
+
/**
|
|
52
|
+
* Get all files for full codebase scan
|
|
53
|
+
*/
|
|
54
|
+
private getFilesForFullScan;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=secret.check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret.check.d.ts","sourceRoot":"","sources":["../../../src/gate/checks/secret.check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAuB,MAAM,2BAA2B,CAAC;AAG1F,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,aAAa,CAAC;CAChD;AAED;;GAEG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;iBAelC,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAoBxE,qBAAa,WAAY,SAAQ,SAAS;IACxC,IAAI,SAAY;IAChB,WAAW,SAAuF;IAElG,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,eAAe,CAAyB;IAChD,OAAO,CAAC,UAAU,CAAoB;IAEhC,GAAG,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC;IAoFrD;;OAEG;IACH,OAAO,CAAC,SAAS;IAkBjB;;OAEG;IACH,OAAO,CAAC,cAAc;IAKtB;;OAEG;IACH,OAAO,CAAC,eAAe;IAsDvB;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAU3B,OAAO,CAAC,gBAAgB;IAMxB;;OAEG;YACW,mBAAmB;CA6ClC"}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { BaseCheck } from '../check.interface.js';
|
|
2
|
+
import { getFilesFromContext } from '../../types/gate.types.js';
|
|
3
|
+
import { createDebugger } from '@eddacraft/anvil-core';
|
|
4
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { SECRET_PATTERNS, PatternMatcher } from './secret/secret-patterns.js';
|
|
7
|
+
import { EntropyDetector } from './secret/entropy-detector.js';
|
|
8
|
+
import { GitScanner } from './secret/git-scanner.js';
|
|
9
|
+
const log = createDebugger('check');
|
|
10
|
+
/**
|
|
11
|
+
* Zod schema for SecretCheckConfig with runtime validation
|
|
12
|
+
*/
|
|
13
|
+
export const SecretCheckConfigSchema = z.object({
|
|
14
|
+
/** Enable entropy-based detection (default: true) */
|
|
15
|
+
enable_entropy: z.boolean().optional(),
|
|
16
|
+
/** Minimum entropy threshold for detection (default: 4.5) */
|
|
17
|
+
entropy_threshold: z.number().optional(),
|
|
18
|
+
/** Minimum string length for entropy analysis (default: 16) */
|
|
19
|
+
min_entropy_length: z.number().optional(),
|
|
20
|
+
/** Enable git history scanning (default: false) */
|
|
21
|
+
scan_git_history: z.boolean().optional(),
|
|
22
|
+
/** Number of commits to scan in git history (default: 10) */
|
|
23
|
+
git_history_depth: z.number().optional(),
|
|
24
|
+
/** Patterns to allowlist (reduce false positives) */
|
|
25
|
+
allowlist: z.array(z.string()).optional(),
|
|
26
|
+
/** File extensions to skip */
|
|
27
|
+
skip_extensions: z.array(z.string()).optional(),
|
|
28
|
+
});
|
|
29
|
+
/** File extensions to skip by default */
|
|
30
|
+
const DEFAULT_SKIP_EXTENSIONS = [
|
|
31
|
+
'.lock',
|
|
32
|
+
'.min.js',
|
|
33
|
+
'.min.css',
|
|
34
|
+
'.map',
|
|
35
|
+
'.svg',
|
|
36
|
+
'.png',
|
|
37
|
+
'.jpg',
|
|
38
|
+
'.jpeg',
|
|
39
|
+
'.gif',
|
|
40
|
+
'.ico',
|
|
41
|
+
'.woff',
|
|
42
|
+
'.woff2',
|
|
43
|
+
'.ttf',
|
|
44
|
+
'.eot',
|
|
45
|
+
];
|
|
46
|
+
export class SecretCheck extends BaseCheck {
|
|
47
|
+
name = 'secret';
|
|
48
|
+
description = 'Scan for potential secrets and sensitive data using patterns and entropy analysis';
|
|
49
|
+
matcher = new PatternMatcher();
|
|
50
|
+
entropyDetector = new EntropyDetector();
|
|
51
|
+
gitScanner = new GitScanner();
|
|
52
|
+
async run(context) {
|
|
53
|
+
log(`secret check starting, workspace=${context.workspace_root}, fullScan=${context.fullScan}`);
|
|
54
|
+
try {
|
|
55
|
+
const config = this.getConfig(context);
|
|
56
|
+
log(`secret check config: entropy=${config.enable_entropy}, threshold=${config.entropy_threshold}, gitHistory=${config.scan_git_history}, depth=${config.git_history_depth}`);
|
|
57
|
+
const files = context.fullScan
|
|
58
|
+
? await this.getFilesForFullScan(context.workspace_root, config)
|
|
59
|
+
: this.getFilesFromPlan(context);
|
|
60
|
+
log(`secret check: scanning ${files.length} files`);
|
|
61
|
+
const findings = [];
|
|
62
|
+
// Scan files for pattern-based secrets
|
|
63
|
+
for (const file of files) {
|
|
64
|
+
if (this.shouldSkipFile(file, config)) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (existsSync(file)) {
|
|
68
|
+
const content = readFileSync(file, 'utf-8');
|
|
69
|
+
const fileFindings = this.scanFileContent(file, content, context.workspace_root, config);
|
|
70
|
+
findings.push(...fileFindings);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Scan git history if enabled
|
|
74
|
+
if (config.scan_git_history) {
|
|
75
|
+
log(`secret check: scanning git history (depth=${config.git_history_depth ?? 10})`);
|
|
76
|
+
const historyFindings = await this.gitScanner.scanGitHistory(context.workspace_root, {
|
|
77
|
+
git_history_depth: config.git_history_depth ?? 10,
|
|
78
|
+
allowlist: config.allowlist ?? [],
|
|
79
|
+
});
|
|
80
|
+
findings.push(...historyFindings);
|
|
81
|
+
}
|
|
82
|
+
// Deduplicate findings
|
|
83
|
+
const uniqueFindings = this.deduplicateFindings(findings);
|
|
84
|
+
const passed = uniqueFindings.length === 0;
|
|
85
|
+
const patternCount = uniqueFindings.filter((f) => f.source === 'pattern').length;
|
|
86
|
+
const entropyCount = uniqueFindings.filter((f) => f.source === 'entropy').length;
|
|
87
|
+
const gitHistoryCount = uniqueFindings.filter((f) => f.source === 'git-history').length;
|
|
88
|
+
log(`secret check result: passed=${passed}, total=${uniqueFindings.length} (pattern=${patternCount}, entropy=${entropyCount}, git=${gitHistoryCount})`);
|
|
89
|
+
let message;
|
|
90
|
+
if (passed) {
|
|
91
|
+
message = 'No secrets detected';
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
const parts = [];
|
|
95
|
+
if (patternCount > 0)
|
|
96
|
+
parts.push(`${patternCount} pattern match(es)`);
|
|
97
|
+
if (entropyCount > 0)
|
|
98
|
+
parts.push(`${entropyCount} high-entropy string(s)`);
|
|
99
|
+
if (gitHistoryCount > 0)
|
|
100
|
+
parts.push(`${gitHistoryCount} in git history`);
|
|
101
|
+
message = `Found ${uniqueFindings.length} potential secret(s): ${parts.join(', ')}`;
|
|
102
|
+
}
|
|
103
|
+
return this.createResult(passed, message, passed ? 100 : Math.max(0, 100 - uniqueFindings.length * 10), {
|
|
104
|
+
findings: uniqueFindings,
|
|
105
|
+
summary: {
|
|
106
|
+
total: uniqueFindings.length,
|
|
107
|
+
by_pattern: patternCount,
|
|
108
|
+
by_entropy: entropyCount,
|
|
109
|
+
by_git_history: gitHistoryCount,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
log(`secret check error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
115
|
+
return this.createFailure('Secret scan failed', error instanceof Error ? error.message : 'Unknown error');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get configuration with defaults
|
|
120
|
+
*/
|
|
121
|
+
getConfig(context) {
|
|
122
|
+
// Parse and validate check_config using Zod schema
|
|
123
|
+
const parseResult = SecretCheckConfigSchema.safeParse(context.check_config);
|
|
124
|
+
// If parsing fails, use empty config (all defaults will be applied)
|
|
125
|
+
const checkConfig = parseResult.success ? parseResult.data : {};
|
|
126
|
+
return {
|
|
127
|
+
enable_entropy: checkConfig.enable_entropy ?? true,
|
|
128
|
+
entropy_threshold: checkConfig.entropy_threshold ?? 4.5,
|
|
129
|
+
min_entropy_length: checkConfig.min_entropy_length ?? 16,
|
|
130
|
+
scan_git_history: checkConfig.scan_git_history ?? false,
|
|
131
|
+
git_history_depth: checkConfig.git_history_depth ?? 10,
|
|
132
|
+
allowlist: checkConfig.allowlist ?? [],
|
|
133
|
+
skip_extensions: checkConfig.skip_extensions ?? [],
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Check if a file should be skipped based on extension
|
|
138
|
+
*/
|
|
139
|
+
shouldSkipFile(file, config) {
|
|
140
|
+
const skipExtensions = [...DEFAULT_SKIP_EXTENSIONS, ...(config.skip_extensions || [])];
|
|
141
|
+
return skipExtensions.some((ext) => file.endsWith(ext));
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Scan file content for secrets using both patterns and entropy
|
|
145
|
+
*/
|
|
146
|
+
scanFileContent(file, content, workspaceRoot, config) {
|
|
147
|
+
const findings = [];
|
|
148
|
+
const lines = content.split('\n');
|
|
149
|
+
for (let i = 0; i < lines.length; i++) {
|
|
150
|
+
const line = lines[i];
|
|
151
|
+
const lineNumber = i + 1;
|
|
152
|
+
// Pattern-based detection
|
|
153
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
154
|
+
const matches = line.match(pattern.pattern);
|
|
155
|
+
if (matches && !this.matcher.isAllowlisted(matches[0], config.allowlist ?? [])) {
|
|
156
|
+
findings.push({
|
|
157
|
+
file: file.replace(workspaceRoot, ''),
|
|
158
|
+
line: lineNumber,
|
|
159
|
+
type: pattern.name,
|
|
160
|
+
match: this.matcher.redactSecret(matches[0]),
|
|
161
|
+
context: this.matcher.redactLine(line.trim()),
|
|
162
|
+
source: 'pattern',
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Entropy-based detection (if enabled)
|
|
167
|
+
if (config.enable_entropy) {
|
|
168
|
+
const entropyFindings = this.entropyDetector.detectHighEntropyStrings(line, lineNumber, file.replace(workspaceRoot, ''), {
|
|
169
|
+
entropy_threshold: config.entropy_threshold ?? 4.5,
|
|
170
|
+
min_entropy_length: config.min_entropy_length ?? 16,
|
|
171
|
+
allowlist: config.allowlist ?? [],
|
|
172
|
+
});
|
|
173
|
+
// Filter out findings that were already detected by pattern matching
|
|
174
|
+
const newEntropyFindings = entropyFindings.filter(() => {
|
|
175
|
+
const alreadyDetected = SECRET_PATTERNS.some((p) => p.pattern.test(line));
|
|
176
|
+
return !alreadyDetected;
|
|
177
|
+
});
|
|
178
|
+
findings.push(...newEntropyFindings);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return findings;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
|
|
185
|
+
* Remove duplicate findings
|
|
186
|
+
*/
|
|
187
|
+
deduplicateFindings(findings) {
|
|
188
|
+
const seen = new Set();
|
|
189
|
+
return findings.filter((finding) => {
|
|
190
|
+
const key = `${finding.file}:${finding.line}:${finding.type}:${finding.match}`;
|
|
191
|
+
if (seen.has(key))
|
|
192
|
+
return false;
|
|
193
|
+
seen.add(key);
|
|
194
|
+
return true;
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
getFilesFromPlan(context) {
|
|
198
|
+
// Use unified helper for both planless and plan-based modes
|
|
199
|
+
// This ensures consistent path normalisation and existence checking
|
|
200
|
+
return getFilesFromContext(context);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Get all files for full codebase scan
|
|
204
|
+
*/
|
|
205
|
+
async getFilesForFullScan(workspaceRoot, config) {
|
|
206
|
+
const { glob } = await import('glob');
|
|
207
|
+
const files = [];
|
|
208
|
+
// Scan common file types that might contain secrets
|
|
209
|
+
const patterns = [
|
|
210
|
+
'**/*.ts',
|
|
211
|
+
'**/*.tsx',
|
|
212
|
+
'**/*.js',
|
|
213
|
+
'**/*.jsx',
|
|
214
|
+
'**/*.json',
|
|
215
|
+
'**/*.yaml',
|
|
216
|
+
'**/*.yml',
|
|
217
|
+
'**/*.env*',
|
|
218
|
+
'**/*.config.*',
|
|
219
|
+
];
|
|
220
|
+
for (const pattern of patterns) {
|
|
221
|
+
try {
|
|
222
|
+
const matches = await glob(pattern, {
|
|
223
|
+
cwd: workspaceRoot,
|
|
224
|
+
absolute: true,
|
|
225
|
+
ignore: [
|
|
226
|
+
'**/node_modules/**',
|
|
227
|
+
'**/dist/**',
|
|
228
|
+
'**/build/**',
|
|
229
|
+
'**/.git/**',
|
|
230
|
+
'**/coverage/**',
|
|
231
|
+
'**/*.lock',
|
|
232
|
+
'**/package-lock.json',
|
|
233
|
+
],
|
|
234
|
+
});
|
|
235
|
+
files.push(...matches);
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
// Log glob errors to stderr for debugging but continue scanning
|
|
239
|
+
console.error(`[SecretCheck] Glob error for pattern ${pattern}:`, error);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Filter out skipped extensions
|
|
243
|
+
return files.filter((file) => !this.shouldSkipFile(file, config));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CommandRule, CommandSafetyConfig, ResolvedCommandSafetyConfig } from '../rules/types.js';
|
|
2
|
+
export declare function loadCommandSafetyRules(config: CommandSafetyConfig): CommandRule[];
|
|
3
|
+
export declare function resolveCommandSafetyConfig(userConfig?: CommandSafetyConfig): ResolvedCommandSafetyConfig;
|
|
4
|
+
export declare const DEFAULT_COMMAND_SAFETY_CONFIG: ResolvedCommandSafetyConfig;
|
|
5
|
+
//# sourceMappingURL=command-safety-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-safety-config.d.ts","sourceRoot":"","sources":["../../../src/gate/config/command-safety-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,mBAAmB,EACnB,2BAA2B,EAG5B,MAAM,mBAAmB,CAAC;AAiB3B,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,mBAAmB,GAAG,WAAW,EAAE,CAmCjF;AAED,wBAAgB,0BAA0B,CACxC,UAAU,CAAC,EAAE,mBAAmB,GAC/B,2BAA2B,CAmB7B;AAED,eAAO,MAAM,6BAA6B,EAAE,2BAM3C,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { DEFAULT_GIT_RULES, DEFAULT_FILESYSTEM_RULES } from '../rules/index.js';
|
|
2
|
+
import { createDebugger } from '@eddacraft/anvil-core';
|
|
3
|
+
const debug = createDebugger('gate');
|
|
4
|
+
const DEFAULT_WORKING_DIRECTORY = {
|
|
5
|
+
allowDeleteInCwd: false,
|
|
6
|
+
tempDirPatterns: ['/tmp', '/var/tmp'],
|
|
7
|
+
};
|
|
8
|
+
const DEFAULT_OUTPUT = {
|
|
9
|
+
verbose: true,
|
|
10
|
+
showSuggestions: true,
|
|
11
|
+
showReferences: true,
|
|
12
|
+
};
|
|
13
|
+
export function loadCommandSafetyRules(config) {
|
|
14
|
+
debug('loadCommandSafetyRules: loading default rules');
|
|
15
|
+
let rules = [...DEFAULT_GIT_RULES, ...DEFAULT_FILESYSTEM_RULES];
|
|
16
|
+
if (config.rules?.disabled && config.rules.disabled.length > 0) {
|
|
17
|
+
debug(`loadCommandSafetyRules: disabling ${config.rules.disabled.length} rules`);
|
|
18
|
+
const disabledSet = new Set(config.rules.disabled);
|
|
19
|
+
rules = rules.filter((r) => !disabledSet.has(r.id));
|
|
20
|
+
}
|
|
21
|
+
if (config.rules?.overrides && config.rules.overrides.length > 0) {
|
|
22
|
+
debug(`loadCommandSafetyRules: applying ${config.rules.overrides.length} overrides`);
|
|
23
|
+
for (const override of config.rules.overrides) {
|
|
24
|
+
const ruleIndex = rules.findIndex((r) => r.id === override.id);
|
|
25
|
+
if (ruleIndex !== -1) {
|
|
26
|
+
if (override.action === 'disable') {
|
|
27
|
+
rules.splice(ruleIndex, 1);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
rules[ruleIndex] = {
|
|
31
|
+
...rules[ruleIndex],
|
|
32
|
+
...(override.action && { action: override.action }),
|
|
33
|
+
...(override.severity && { severity: override.severity }),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (config.rules?.custom && config.rules.custom.length > 0) {
|
|
40
|
+
debug(`loadCommandSafetyRules: adding ${config.rules.custom.length} custom rules`);
|
|
41
|
+
rules.push(...config.rules.custom);
|
|
42
|
+
}
|
|
43
|
+
debug(`loadCommandSafetyRules: total rules=${rules.length}`);
|
|
44
|
+
return rules;
|
|
45
|
+
}
|
|
46
|
+
export function resolveCommandSafetyConfig(userConfig) {
|
|
47
|
+
const config = userConfig ?? {};
|
|
48
|
+
debug(`resolveCommandSafetyConfig: enabled=${config.enabled ?? true} strict=${config.strict ?? false}`);
|
|
49
|
+
return {
|
|
50
|
+
enabled: config.enabled ?? true,
|
|
51
|
+
strict: config.strict ?? false,
|
|
52
|
+
rules: loadCommandSafetyRules(config),
|
|
53
|
+
workingDirectory: {
|
|
54
|
+
...DEFAULT_WORKING_DIRECTORY,
|
|
55
|
+
...config.workingDirectory,
|
|
56
|
+
},
|
|
57
|
+
output: {
|
|
58
|
+
...DEFAULT_OUTPUT,
|
|
59
|
+
...config.output,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export const DEFAULT_COMMAND_SAFETY_CONFIG = {
|
|
64
|
+
enabled: true,
|
|
65
|
+
strict: false,
|
|
66
|
+
rules: [...DEFAULT_GIT_RULES, ...DEFAULT_FILESYSTEM_RULES],
|
|
67
|
+
workingDirectory: DEFAULT_WORKING_DIRECTORY,
|
|
68
|
+
output: DEFAULT_OUTPUT,
|
|
69
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/gate/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,0BAA0B,EAC1B,6BAA6B,GAC9B,MAAM,4BAA4B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { loadCommandSafetyRules, resolveCommandSafetyConfig, DEFAULT_COMMAND_SAFETY_CONFIG, } from './command-safety-config.js';
|