@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.
Files changed (170) hide show
  1. package/LICENSE +14 -0
  2. package/dist/cache/cache-key.d.ts +45 -0
  3. package/dist/cache/cache-key.d.ts.map +1 -0
  4. package/dist/cache/cache-key.js +135 -0
  5. package/dist/cache/index.d.ts +27 -0
  6. package/dist/cache/index.d.ts.map +1 -0
  7. package/dist/cache/index.js +38 -0
  8. package/dist/cache/providers/file-cache.d.ts +63 -0
  9. package/dist/cache/providers/file-cache.d.ts.map +1 -0
  10. package/dist/cache/providers/file-cache.js +369 -0
  11. package/dist/cache/providers/memory-cache.d.ts +52 -0
  12. package/dist/cache/providers/memory-cache.d.ts.map +1 -0
  13. package/dist/cache/providers/memory-cache.js +197 -0
  14. package/dist/cache/providers/null-cache.d.ts +26 -0
  15. package/dist/cache/providers/null-cache.d.ts.map +1 -0
  16. package/dist/cache/providers/null-cache.js +50 -0
  17. package/dist/cache/types.d.ts +114 -0
  18. package/dist/cache/types.d.ts.map +1 -0
  19. package/dist/cache/types.js +4 -0
  20. package/dist/concurrency/agent.d.ts +137 -0
  21. package/dist/concurrency/agent.d.ts.map +1 -0
  22. package/dist/concurrency/agent.js +440 -0
  23. package/dist/concurrency/atomic.d.ts +93 -0
  24. package/dist/concurrency/atomic.d.ts.map +1 -0
  25. package/dist/concurrency/atomic.js +281 -0
  26. package/dist/concurrency/git-agent.d.ts +114 -0
  27. package/dist/concurrency/git-agent.d.ts.map +1 -0
  28. package/dist/concurrency/git-agent.js +313 -0
  29. package/dist/concurrency/index.d.ts +95 -0
  30. package/dist/concurrency/index.d.ts.map +1 -0
  31. package/dist/concurrency/index.js +127 -0
  32. package/dist/concurrency/lock-manager.d.ts +170 -0
  33. package/dist/concurrency/lock-manager.d.ts.map +1 -0
  34. package/dist/concurrency/lock-manager.js +525 -0
  35. package/dist/concurrency/queue-manager.d.ts +166 -0
  36. package/dist/concurrency/queue-manager.d.ts.map +1 -0
  37. package/dist/concurrency/queue-manager.js +442 -0
  38. package/dist/concurrency/types.d.ts +382 -0
  39. package/dist/concurrency/types.d.ts.map +1 -0
  40. package/dist/concurrency/types.js +204 -0
  41. package/dist/export/constraint-collector.d.ts +175 -0
  42. package/dist/export/constraint-collector.d.ts.map +1 -0
  43. package/dist/export/constraint-collector.js +203 -0
  44. package/dist/export/formatters/llms-txt-formatter.d.ts +89 -0
  45. package/dist/export/formatters/llms-txt-formatter.d.ts.map +1 -0
  46. package/dist/export/formatters/llms-txt-formatter.js +249 -0
  47. package/dist/export/formatters/mcp-resource-formatter.d.ts +186 -0
  48. package/dist/export/formatters/mcp-resource-formatter.d.ts.map +1 -0
  49. package/dist/export/formatters/mcp-resource-formatter.js +139 -0
  50. package/dist/export/formatters/prompt-formatter.d.ts +83 -0
  51. package/dist/export/formatters/prompt-formatter.d.ts.map +1 -0
  52. package/dist/export/formatters/prompt-formatter.js +256 -0
  53. package/dist/export/index.d.ts +10 -0
  54. package/dist/export/index.d.ts.map +1 -0
  55. package/dist/export/index.js +9 -0
  56. package/dist/gate/check.interface.d.ts +15 -0
  57. package/dist/gate/check.interface.d.ts.map +1 -0
  58. package/dist/gate/check.interface.js +18 -0
  59. package/dist/gate/checks/antipattern.check.d.ts +27 -0
  60. package/dist/gate/checks/antipattern.check.d.ts.map +1 -0
  61. package/dist/gate/checks/antipattern.check.js +140 -0
  62. package/dist/gate/checks/architecture/circular-detector.d.ts +33 -0
  63. package/dist/gate/checks/architecture/circular-detector.d.ts.map +1 -0
  64. package/dist/gate/checks/architecture/circular-detector.js +71 -0
  65. package/dist/gate/checks/architecture/dependency-analyzer.d.ts +81 -0
  66. package/dist/gate/checks/architecture/dependency-analyzer.d.ts.map +1 -0
  67. package/dist/gate/checks/architecture/dependency-analyzer.js +136 -0
  68. package/dist/gate/checks/architecture/layer-validator.d.ts +75 -0
  69. package/dist/gate/checks/architecture/layer-validator.d.ts.map +1 -0
  70. package/dist/gate/checks/architecture/layer-validator.js +193 -0
  71. package/dist/gate/checks/architecture.check.d.ts +56 -0
  72. package/dist/gate/checks/architecture.check.d.ts.map +1 -0
  73. package/dist/gate/checks/architecture.check.js +394 -0
  74. package/dist/gate/checks/command-safety.check.d.ts +12 -0
  75. package/dist/gate/checks/command-safety.check.d.ts.map +1 -0
  76. package/dist/gate/checks/command-safety.check.js +230 -0
  77. package/dist/gate/checks/coverage.check.d.ts +9 -0
  78. package/dist/gate/checks/coverage.check.d.ts.map +1 -0
  79. package/dist/gate/checks/coverage.check.js +81 -0
  80. package/dist/gate/checks/dependency.check.d.ts +17 -0
  81. package/dist/gate/checks/dependency.check.d.ts.map +1 -0
  82. package/dist/gate/checks/dependency.check.js +342 -0
  83. package/dist/gate/checks/eslint.check.d.ts +14 -0
  84. package/dist/gate/checks/eslint.check.d.ts.map +1 -0
  85. package/dist/gate/checks/eslint.check.js +79 -0
  86. package/dist/gate/checks/policy.check.d.ts +78 -0
  87. package/dist/gate/checks/policy.check.d.ts.map +1 -0
  88. package/dist/gate/checks/policy.check.js +457 -0
  89. package/dist/gate/checks/secret/entropy-detector.d.ts +44 -0
  90. package/dist/gate/checks/secret/entropy-detector.d.ts.map +1 -0
  91. package/dist/gate/checks/secret/entropy-detector.js +76 -0
  92. package/dist/gate/checks/secret/git-scanner.d.ts +36 -0
  93. package/dist/gate/checks/secret/git-scanner.d.ts.map +1 -0
  94. package/dist/gate/checks/secret/git-scanner.js +90 -0
  95. package/dist/gate/checks/secret/secret-patterns.d.ts +42 -0
  96. package/dist/gate/checks/secret/secret-patterns.d.ts.map +1 -0
  97. package/dist/gate/checks/secret/secret-patterns.js +137 -0
  98. package/dist/gate/checks/secret.check.d.ts +56 -0
  99. package/dist/gate/checks/secret.check.d.ts.map +1 -0
  100. package/dist/gate/checks/secret.check.js +245 -0
  101. package/dist/gate/config/command-safety-config.d.ts +5 -0
  102. package/dist/gate/config/command-safety-config.d.ts.map +1 -0
  103. package/dist/gate/config/command-safety-config.js +69 -0
  104. package/dist/gate/config/index.d.ts +2 -0
  105. package/dist/gate/config/index.d.ts.map +1 -0
  106. package/dist/gate/config/index.js +1 -0
  107. package/dist/gate/formatters/command-safety-formatter.d.ts +10 -0
  108. package/dist/gate/formatters/command-safety-formatter.d.ts.map +1 -0
  109. package/dist/gate/formatters/command-safety-formatter.js +64 -0
  110. package/dist/gate/formatters/index.d.ts +2 -0
  111. package/dist/gate/formatters/index.d.ts.map +1 -0
  112. package/dist/gate/formatters/index.js +1 -0
  113. package/dist/gate/gate-config.d.ts +44 -0
  114. package/dist/gate/gate-config.d.ts.map +1 -0
  115. package/dist/gate/gate-config.js +334 -0
  116. package/dist/gate/gate-runner.d.ts +160 -0
  117. package/dist/gate/gate-runner.d.ts.map +1 -0
  118. package/dist/gate/gate-runner.js +531 -0
  119. package/dist/gate/index.d.ts +20 -0
  120. package/dist/gate/index.d.ts.map +1 -0
  121. package/dist/gate/index.js +14 -0
  122. package/dist/gate/parsers/command-parser.d.ts +18 -0
  123. package/dist/gate/parsers/command-parser.d.ts.map +1 -0
  124. package/dist/gate/parsers/command-parser.js +363 -0
  125. package/dist/gate/parsers/index.d.ts +2 -0
  126. package/dist/gate/parsers/index.d.ts.map +1 -0
  127. package/dist/gate/parsers/index.js +1 -0
  128. package/dist/gate/policy/index.d.ts +12 -0
  129. package/dist/gate/policy/index.d.ts.map +1 -0
  130. package/dist/gate/policy/index.js +10 -0
  131. package/dist/gate/rules/default-filesystem-rules.d.ts +3 -0
  132. package/dist/gate/rules/default-filesystem-rules.d.ts.map +1 -0
  133. package/dist/gate/rules/default-filesystem-rules.js +201 -0
  134. package/dist/gate/rules/default-git-rules.d.ts +3 -0
  135. package/dist/gate/rules/default-git-rules.d.ts.map +1 -0
  136. package/dist/gate/rules/default-git-rules.js +192 -0
  137. package/dist/gate/rules/index.d.ts +5 -0
  138. package/dist/gate/rules/index.d.ts.map +1 -0
  139. package/dist/gate/rules/index.js +3 -0
  140. package/dist/gate/rules/rule-matcher.d.ts +27 -0
  141. package/dist/gate/rules/rule-matcher.d.ts.map +1 -0
  142. package/dist/gate/rules/rule-matcher.js +228 -0
  143. package/dist/gate/rules/types.d.ts +250 -0
  144. package/dist/gate/rules/types.d.ts.map +1 -0
  145. package/dist/gate/rules/types.js +1 -0
  146. package/dist/index.d.ts +19 -0
  147. package/dist/index.d.ts.map +1 -0
  148. package/dist/index.js +35 -0
  149. package/dist/types/gate.types.d.ts +42 -0
  150. package/dist/types/gate.types.d.ts.map +1 -0
  151. package/dist/types/gate.types.js +94 -0
  152. package/dist/watch/debouncer.d.ts +90 -0
  153. package/dist/watch/debouncer.d.ts.map +1 -0
  154. package/dist/watch/debouncer.js +135 -0
  155. package/dist/watch/file-watcher.d.ts +73 -0
  156. package/dist/watch/file-watcher.d.ts.map +1 -0
  157. package/dist/watch/file-watcher.js +121 -0
  158. package/dist/watch/git-status.d.ts +98 -0
  159. package/dist/watch/git-status.d.ts.map +1 -0
  160. package/dist/watch/git-status.js +266 -0
  161. package/dist/watch/index.d.ts +16 -0
  162. package/dist/watch/index.d.ts.map +1 -0
  163. package/dist/watch/index.js +15 -0
  164. package/dist/watch/orchestrator.d.ts +113 -0
  165. package/dist/watch/orchestrator.d.ts.map +1 -0
  166. package/dist/watch/orchestrator.js +409 -0
  167. package/dist/watch/types.d.ts +190 -0
  168. package/dist/watch/types.d.ts.map +1 -0
  169. package/dist/watch/types.js +76 -0
  170. 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,2 @@
1
+ export { loadCommandSafetyRules, resolveCommandSafetyConfig, DEFAULT_COMMAND_SAFETY_CONFIG, } from './command-safety-config.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -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';