hackmyagent-core 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/dist/checker/check-skill.d.ts +48 -0
- package/dist/checker/check-skill.d.ts.map +1 -0
- package/dist/checker/check-skill.js +105 -0
- package/dist/checker/check-skill.js.map +1 -0
- package/dist/checker/check-skill.test.d.ts +2 -0
- package/dist/checker/check-skill.test.d.ts.map +1 -0
- package/dist/checker/check-skill.test.js +83 -0
- package/dist/checker/check-skill.test.js.map +1 -0
- package/dist/checker/index.d.ts +12 -0
- package/dist/checker/index.d.ts.map +1 -0
- package/dist/checker/index.js +16 -0
- package/dist/checker/index.js.map +1 -0
- package/dist/checker/permission-analyzer.d.ts +12 -0
- package/dist/checker/permission-analyzer.d.ts.map +1 -0
- package/dist/checker/permission-analyzer.js +84 -0
- package/dist/checker/permission-analyzer.js.map +1 -0
- package/dist/checker/permission-analyzer.test.d.ts +2 -0
- package/dist/checker/permission-analyzer.test.d.ts.map +1 -0
- package/dist/checker/permission-analyzer.test.js +87 -0
- package/dist/checker/permission-analyzer.test.js.map +1 -0
- package/dist/checker/publisher-verifier.d.ts +34 -0
- package/dist/checker/publisher-verifier.d.ts.map +1 -0
- package/dist/checker/publisher-verifier.js +121 -0
- package/dist/checker/publisher-verifier.js.map +1 -0
- package/dist/checker/publisher-verifier.test.d.ts +2 -0
- package/dist/checker/publisher-verifier.test.d.ts.map +1 -0
- package/dist/checker/publisher-verifier.test.js +171 -0
- package/dist/checker/publisher-verifier.test.js.map +1 -0
- package/dist/checker/skill-identifier.d.ts +14 -0
- package/dist/checker/skill-identifier.d.ts.map +1 -0
- package/dist/checker/skill-identifier.js +55 -0
- package/dist/checker/skill-identifier.js.map +1 -0
- package/dist/checker/skill-identifier.test.d.ts +2 -0
- package/dist/checker/skill-identifier.test.d.ts.map +1 -0
- package/dist/checker/skill-identifier.test.js +64 -0
- package/dist/checker/skill-identifier.test.js.map +1 -0
- package/dist/hardening/index.d.ts +7 -0
- package/dist/hardening/index.d.ts.map +1 -0
- package/dist/hardening/index.js +9 -0
- package/dist/hardening/index.js.map +1 -0
- package/dist/hardening/scanner.d.ts +85 -0
- package/dist/hardening/scanner.d.ts.map +1 -0
- package/dist/hardening/scanner.js +3410 -0
- package/dist/hardening/scanner.js.map +1 -0
- package/dist/hardening/scanner.test.d.ts +2 -0
- package/dist/hardening/scanner.test.d.ts.map +1 -0
- package/dist/hardening/scanner.test.js +1103 -0
- package/dist/hardening/scanner.test.js.map +1 -0
- package/dist/hardening/security-check.d.ts +56 -0
- package/dist/hardening/security-check.d.ts.map +1 -0
- package/dist/hardening/security-check.js +6 -0
- package/dist/hardening/security-check.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/scanner/external-scanner.d.ts +13 -0
- package/dist/scanner/external-scanner.d.ts.map +1 -0
- package/dist/scanner/external-scanner.js +299 -0
- package/dist/scanner/external-scanner.js.map +1 -0
- package/dist/scanner/external-scanner.test.d.ts +2 -0
- package/dist/scanner/external-scanner.test.d.ts.map +1 -0
- package/dist/scanner/external-scanner.test.js +302 -0
- package/dist/scanner/external-scanner.test.js.map +1 -0
- package/dist/scanner/index.d.ts +6 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +9 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/scanner/types.d.ts +32 -0
- package/dist/scanner/types.d.ts.map +1 -0
- package/dist/scanner/types.js +6 -0
- package/dist/scanner/types.js.map +1 -0
- package/package.json +37 -0
|
@@ -0,0 +1,1103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const vitest_1 = require("vitest");
|
|
37
|
+
const scanner_1 = require("./scanner");
|
|
38
|
+
const fs = __importStar(require("fs/promises"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const os = __importStar(require("os"));
|
|
41
|
+
(0, vitest_1.describe)('HardeningScanner', () => {
|
|
42
|
+
let scanner;
|
|
43
|
+
let tempDir;
|
|
44
|
+
(0, vitest_1.beforeEach)(async () => {
|
|
45
|
+
scanner = new scanner_1.HardeningScanner();
|
|
46
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hackmyagent-test-'));
|
|
47
|
+
});
|
|
48
|
+
(0, vitest_1.afterEach)(async () => {
|
|
49
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
50
|
+
});
|
|
51
|
+
(0, vitest_1.describe)('scan', () => {
|
|
52
|
+
(0, vitest_1.it)('returns a scan result with findings', async () => {
|
|
53
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
54
|
+
(0, vitest_1.expect)(result).toMatchObject({
|
|
55
|
+
timestamp: vitest_1.expect.any(Date),
|
|
56
|
+
platform: vitest_1.expect.any(String),
|
|
57
|
+
findings: vitest_1.expect.any(Array),
|
|
58
|
+
score: vitest_1.expect.any(Number),
|
|
59
|
+
maxScore: vitest_1.expect.any(Number),
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
(0, vitest_1.it)('calculates security score based on findings', async () => {
|
|
63
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
64
|
+
(0, vitest_1.expect)(result.score).toBeGreaterThanOrEqual(0);
|
|
65
|
+
(0, vitest_1.expect)(result.score).toBeLessThanOrEqual(result.maxScore);
|
|
66
|
+
});
|
|
67
|
+
(0, vitest_1.it)('groups findings by category', async () => {
|
|
68
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
69
|
+
const findings = result.findings;
|
|
70
|
+
// Each finding should have a category
|
|
71
|
+
for (const finding of findings) {
|
|
72
|
+
(0, vitest_1.expect)(finding.category).toBeDefined();
|
|
73
|
+
(0, vitest_1.expect)(typeof finding.category).toBe('string');
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
(0, vitest_1.describe)('credential exposure checks', () => {
|
|
78
|
+
(0, vitest_1.it)('detects API keys in config files', async () => {
|
|
79
|
+
// Create a config file with exposed API key
|
|
80
|
+
const configPath = path.join(tempDir, 'config.json');
|
|
81
|
+
await fs.writeFile(configPath, JSON.stringify({
|
|
82
|
+
apiKey: 'sk-ant-api03-xxxxxxxxxxxxxxxxxxxxx',
|
|
83
|
+
name: 'test',
|
|
84
|
+
}));
|
|
85
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
86
|
+
const credFindings = result.findings.filter((f) => f.category === 'credentials');
|
|
87
|
+
(0, vitest_1.expect)(credFindings.some((f) => !f.passed)).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
(0, vitest_1.it)('detects OpenAI API keys', async () => {
|
|
90
|
+
const configPath = path.join(tempDir, '.env');
|
|
91
|
+
await fs.writeFile(configPath, 'OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxxxxxxxxxxxxx\n');
|
|
92
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
93
|
+
const finding = result.findings.find((f) => f.checkId === 'CRED-001' && !f.passed);
|
|
94
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
95
|
+
(0, vitest_1.expect)(finding?.details?.keys).toContain('OPENAI_API_KEY');
|
|
96
|
+
});
|
|
97
|
+
(0, vitest_1.it)('detects AWS credentials', async () => {
|
|
98
|
+
const configPath = path.join(tempDir, 'config.yaml');
|
|
99
|
+
await fs.writeFile(configPath, 'aws_access_key_id: AKIAIOSFODNN7EXAMPLE\naws_secret_access_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\n');
|
|
100
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
101
|
+
const finding = result.findings.find((f) => f.checkId === 'CRED-001' && !f.passed);
|
|
102
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
103
|
+
});
|
|
104
|
+
(0, vitest_1.it)('passes when no credentials exposed', async () => {
|
|
105
|
+
const configPath = path.join(tempDir, 'config.json');
|
|
106
|
+
await fs.writeFile(configPath, JSON.stringify({
|
|
107
|
+
name: 'safe-config',
|
|
108
|
+
apiKey: '${ANTHROPIC_API_KEY}', // env var reference, not actual key
|
|
109
|
+
}));
|
|
110
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
111
|
+
const credFindings = result.findings.filter((f) => f.category === 'credentials' && !f.passed);
|
|
112
|
+
(0, vitest_1.expect)(credFindings).toHaveLength(0);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
(0, vitest_1.describe)('file permission checks', () => {
|
|
116
|
+
(0, vitest_1.it)('detects world-readable sensitive files', async () => {
|
|
117
|
+
const secretPath = path.join(tempDir, 'secrets.json');
|
|
118
|
+
await fs.writeFile(secretPath, '{"secret": "value"}');
|
|
119
|
+
await fs.chmod(secretPath, 0o644); // world-readable
|
|
120
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
121
|
+
const permFindings = result.findings.filter((f) => f.category === 'permissions' && !f.passed);
|
|
122
|
+
// Should detect overly permissive files
|
|
123
|
+
(0, vitest_1.expect)(permFindings.length).toBeGreaterThanOrEqual(0);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
(0, vitest_1.describe)('claude code specific checks', () => {
|
|
127
|
+
(0, vitest_1.it)('detects CLAUDE.md with sensitive content', async () => {
|
|
128
|
+
const claudeMdPath = path.join(tempDir, 'CLAUDE.md');
|
|
129
|
+
await fs.writeFile(claudeMdPath, '# Instructions\n\nAPI Key: sk-ant-api03-testsecretkey1234567890abc\n\nDo not share this.');
|
|
130
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
131
|
+
const finding = result.findings.find((f) => f.checkId === 'CLAUDE-001' && !f.passed);
|
|
132
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
133
|
+
(0, vitest_1.expect)(finding?.message).toContain('CLAUDE.md');
|
|
134
|
+
});
|
|
135
|
+
(0, vitest_1.it)('passes for safe CLAUDE.md', async () => {
|
|
136
|
+
const claudeMdPath = path.join(tempDir, 'CLAUDE.md');
|
|
137
|
+
await fs.writeFile(claudeMdPath, '# Instructions\n\nUse environment variables for API keys.\n');
|
|
138
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
139
|
+
const finding = result.findings.find((f) => f.checkId === 'CLAUDE-001');
|
|
140
|
+
(0, vitest_1.expect)(finding?.passed).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
(0, vitest_1.describe)('MCP configuration checks', () => {
|
|
144
|
+
(0, vitest_1.it)('detects insecure MCP server configurations', async () => {
|
|
145
|
+
const mcpConfigPath = path.join(tempDir, 'mcp.json');
|
|
146
|
+
await fs.writeFile(mcpConfigPath, JSON.stringify({
|
|
147
|
+
servers: {
|
|
148
|
+
filesystem: {
|
|
149
|
+
command: 'mcp-server-filesystem',
|
|
150
|
+
args: ['/'], // Root access - dangerous
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
}));
|
|
154
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
155
|
+
const finding = result.findings.find((f) => f.checkId === 'MCP-001' && !f.passed);
|
|
156
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
157
|
+
});
|
|
158
|
+
(0, vitest_1.it)('detects shell MCP server without restrictions', async () => {
|
|
159
|
+
const mcpConfigPath = path.join(tempDir, 'mcp.json');
|
|
160
|
+
await fs.writeFile(mcpConfigPath, JSON.stringify({
|
|
161
|
+
servers: {
|
|
162
|
+
shell: {
|
|
163
|
+
command: 'mcp-server-shell',
|
|
164
|
+
// No command restrictions
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
}));
|
|
168
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
169
|
+
const finding = result.findings.find((f) => f.checkId === 'MCP-002' && !f.passed);
|
|
170
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
(0, vitest_1.describe)('auto-fix capabilities', () => {
|
|
174
|
+
(0, vitest_1.it)('can fix file permission issues', async () => {
|
|
175
|
+
const secretPath = path.join(tempDir, 'secrets.json');
|
|
176
|
+
await fs.writeFile(secretPath, '{"secret": "value"}');
|
|
177
|
+
await fs.chmod(secretPath, 0o644);
|
|
178
|
+
const result = await scanner.scan({
|
|
179
|
+
targetDir: tempDir,
|
|
180
|
+
autoFix: true,
|
|
181
|
+
});
|
|
182
|
+
// Check if fix was attempted for permission issues
|
|
183
|
+
const permFinding = result.findings.find((f) => f.category === 'permissions' && f.fixable);
|
|
184
|
+
if (permFinding && permFinding.fixed) {
|
|
185
|
+
const stats = await fs.stat(secretPath);
|
|
186
|
+
const mode = stats.mode & 0o777;
|
|
187
|
+
(0, vitest_1.expect)(mode).toBe(0o600);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
(0, vitest_1.it)('reports which fixes were applied', async () => {
|
|
191
|
+
const result = await scanner.scan({
|
|
192
|
+
targetDir: tempDir,
|
|
193
|
+
autoFix: true,
|
|
194
|
+
});
|
|
195
|
+
for (const finding of result.findings) {
|
|
196
|
+
if (finding.fixable && !finding.passed) {
|
|
197
|
+
(0, vitest_1.expect)(finding.fixed).toBeDefined();
|
|
198
|
+
if (finding.fixed) {
|
|
199
|
+
(0, vitest_1.expect)(finding.fixMessage).toBeDefined();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
(0, vitest_1.it)('does not auto-fix when disabled', async () => {
|
|
205
|
+
const secretPath = path.join(tempDir, 'secrets.json');
|
|
206
|
+
await fs.writeFile(secretPath, '{"secret": "value"}');
|
|
207
|
+
await fs.chmod(secretPath, 0o644);
|
|
208
|
+
const result = await scanner.scan({
|
|
209
|
+
targetDir: tempDir,
|
|
210
|
+
autoFix: false,
|
|
211
|
+
});
|
|
212
|
+
const fixedFindings = result.findings.filter((f) => f.fixed);
|
|
213
|
+
(0, vitest_1.expect)(fixedFindings).toHaveLength(0);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
(0, vitest_1.describe)('score calculation', () => {
|
|
217
|
+
(0, vitest_1.it)('gives higher score when more checks pass', async () => {
|
|
218
|
+
// Safe directory
|
|
219
|
+
const safeDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hackmyagent-safe-'));
|
|
220
|
+
await fs.writeFile(path.join(safeDir, 'config.json'), JSON.stringify({ name: 'safe' }));
|
|
221
|
+
// Unsafe directory
|
|
222
|
+
const unsafeDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hackmyagent-unsafe-'));
|
|
223
|
+
await fs.writeFile(path.join(unsafeDir, 'config.json'), JSON.stringify({ apiKey: 'sk-ant-api03-secretkey1234567890def' }));
|
|
224
|
+
const safeResult = await scanner.scan({ targetDir: safeDir });
|
|
225
|
+
const unsafeResult = await scanner.scan({ targetDir: unsafeDir });
|
|
226
|
+
(0, vitest_1.expect)(safeResult.score).toBeGreaterThanOrEqual(unsafeResult.score);
|
|
227
|
+
await fs.rm(safeDir, { recursive: true, force: true });
|
|
228
|
+
await fs.rm(unsafeDir, { recursive: true, force: true });
|
|
229
|
+
});
|
|
230
|
+
(0, vitest_1.it)('weights critical issues higher than low', async () => {
|
|
231
|
+
// The score should penalize critical issues more than low issues
|
|
232
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
233
|
+
// Verify score calculation logic exists
|
|
234
|
+
(0, vitest_1.expect)(result.maxScore).toBeGreaterThan(0);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
(0, vitest_1.describe)('Git security checks', () => {
|
|
239
|
+
let scanner;
|
|
240
|
+
let tempDir;
|
|
241
|
+
(0, vitest_1.beforeEach)(async () => {
|
|
242
|
+
scanner = new scanner_1.HardeningScanner();
|
|
243
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hackmyagent-test-'));
|
|
244
|
+
});
|
|
245
|
+
(0, vitest_1.afterEach)(async () => {
|
|
246
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
247
|
+
});
|
|
248
|
+
(0, vitest_1.it)('detects missing .gitignore', async () => {
|
|
249
|
+
// No .gitignore file
|
|
250
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
251
|
+
const finding = result.findings.find((f) => f.checkId === 'GIT-001');
|
|
252
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
253
|
+
(0, vitest_1.expect)(finding?.passed).toBe(false);
|
|
254
|
+
});
|
|
255
|
+
(0, vitest_1.it)('detects .gitignore missing sensitive patterns', async () => {
|
|
256
|
+
// Create .gitignore without .env pattern
|
|
257
|
+
await fs.writeFile(path.join(tempDir, '.gitignore'), 'node_modules/\n');
|
|
258
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
259
|
+
const finding = result.findings.find((f) => f.checkId === 'GIT-002');
|
|
260
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
261
|
+
(0, vitest_1.expect)(finding?.passed).toBe(false);
|
|
262
|
+
(0, vitest_1.expect)(finding?.message).toContain('.env');
|
|
263
|
+
});
|
|
264
|
+
(0, vitest_1.it)('passes when .gitignore has all sensitive patterns', async () => {
|
|
265
|
+
await fs.writeFile(path.join(tempDir, '.gitignore'), '.env\n.env.*\nsecrets.json\n*.pem\n*.key\n');
|
|
266
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
267
|
+
const finding = result.findings.find((f) => f.checkId === 'GIT-002');
|
|
268
|
+
(0, vitest_1.expect)(finding?.passed).toBe(true);
|
|
269
|
+
});
|
|
270
|
+
(0, vitest_1.it)('detects .env file when .gitignore missing .env pattern', async () => {
|
|
271
|
+
await fs.writeFile(path.join(tempDir, '.gitignore'), 'node_modules/\n');
|
|
272
|
+
await fs.writeFile(path.join(tempDir, '.env'), 'SECRET=value\n');
|
|
273
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
274
|
+
const finding = result.findings.find((f) => f.checkId === 'GIT-003');
|
|
275
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
276
|
+
(0, vitest_1.expect)(finding?.passed).toBe(false);
|
|
277
|
+
(0, vitest_1.expect)(finding?.severity).toBe('critical');
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
(0, vitest_1.describe)('Network security checks', () => {
|
|
281
|
+
let scanner;
|
|
282
|
+
let tempDir;
|
|
283
|
+
(0, vitest_1.beforeEach)(async () => {
|
|
284
|
+
scanner = new scanner_1.HardeningScanner();
|
|
285
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hackmyagent-test-'));
|
|
286
|
+
});
|
|
287
|
+
(0, vitest_1.afterEach)(async () => {
|
|
288
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
289
|
+
});
|
|
290
|
+
(0, vitest_1.it)('detects MCP server bound to 0.0.0.0', async () => {
|
|
291
|
+
await fs.writeFile(path.join(tempDir, 'mcp.json'), JSON.stringify({
|
|
292
|
+
servers: {
|
|
293
|
+
myserver: {
|
|
294
|
+
command: 'mcp-server',
|
|
295
|
+
args: ['--host', '0.0.0.0'],
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
}));
|
|
299
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
300
|
+
const finding = result.findings.find((f) => f.checkId === 'NET-001');
|
|
301
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
302
|
+
(0, vitest_1.expect)(finding?.passed).toBe(false);
|
|
303
|
+
(0, vitest_1.expect)(finding?.severity).toBe('critical');
|
|
304
|
+
});
|
|
305
|
+
(0, vitest_1.it)('detects remote MCP server without TLS', async () => {
|
|
306
|
+
await fs.writeFile(path.join(tempDir, 'mcp.json'), JSON.stringify({
|
|
307
|
+
servers: {
|
|
308
|
+
remote: {
|
|
309
|
+
url: 'http://api.example.com/mcp',
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
}));
|
|
313
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
314
|
+
const finding = result.findings.find((f) => f.checkId === 'NET-002');
|
|
315
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
316
|
+
(0, vitest_1.expect)(finding?.passed).toBe(false);
|
|
317
|
+
(0, vitest_1.expect)(finding?.severity).toBe('high');
|
|
318
|
+
});
|
|
319
|
+
(0, vitest_1.it)('passes for remote MCP server with HTTPS', async () => {
|
|
320
|
+
await fs.writeFile(path.join(tempDir, 'mcp.json'), JSON.stringify({
|
|
321
|
+
servers: {
|
|
322
|
+
remote: {
|
|
323
|
+
url: 'https://api.example.com/mcp',
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
}));
|
|
327
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
328
|
+
const finding = result.findings.find((f) => f.checkId === 'NET-002');
|
|
329
|
+
(0, vitest_1.expect)(finding?.passed).toBe(true);
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
(0, vitest_1.describe)('Additional MCP checks', () => {
|
|
333
|
+
let scanner;
|
|
334
|
+
let tempDir;
|
|
335
|
+
(0, vitest_1.beforeEach)(async () => {
|
|
336
|
+
scanner = new scanner_1.HardeningScanner();
|
|
337
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hackmyagent-test-'));
|
|
338
|
+
});
|
|
339
|
+
(0, vitest_1.afterEach)(async () => {
|
|
340
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
341
|
+
});
|
|
342
|
+
(0, vitest_1.it)('detects secrets passed as environment variables to MCP', async () => {
|
|
343
|
+
await fs.writeFile(path.join(tempDir, 'mcp.json'), JSON.stringify({
|
|
344
|
+
servers: {
|
|
345
|
+
myserver: {
|
|
346
|
+
command: 'mcp-server',
|
|
347
|
+
env: {
|
|
348
|
+
API_KEY: 'sk-ant-api03-hardcodedsecretkey1234567890',
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
}));
|
|
353
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
354
|
+
const finding = result.findings.find((f) => f.checkId === 'MCP-003');
|
|
355
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
356
|
+
(0, vitest_1.expect)(finding?.passed).toBe(false);
|
|
357
|
+
(0, vitest_1.expect)(finding?.severity).toBe('critical');
|
|
358
|
+
});
|
|
359
|
+
(0, vitest_1.it)('passes when env vars use references', async () => {
|
|
360
|
+
await fs.writeFile(path.join(tempDir, 'mcp.json'), JSON.stringify({
|
|
361
|
+
servers: {
|
|
362
|
+
myserver: {
|
|
363
|
+
command: 'mcp-server',
|
|
364
|
+
env: {
|
|
365
|
+
API_KEY: '${ANTHROPIC_API_KEY}',
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
}));
|
|
370
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
371
|
+
const finding = result.findings.find((f) => f.checkId === 'MCP-003');
|
|
372
|
+
(0, vitest_1.expect)(finding?.passed).toBe(true);
|
|
373
|
+
});
|
|
374
|
+
(0, vitest_1.it)('detects database MCP server with default credentials', async () => {
|
|
375
|
+
await fs.writeFile(path.join(tempDir, 'mcp.json'), JSON.stringify({
|
|
376
|
+
servers: {
|
|
377
|
+
postgres: {
|
|
378
|
+
command: 'mcp-server-postgres',
|
|
379
|
+
args: ['--password', 'postgres'],
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
}));
|
|
383
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
384
|
+
const finding = result.findings.find((f) => f.checkId === 'MCP-004');
|
|
385
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
386
|
+
(0, vitest_1.expect)(finding?.passed).toBe(false);
|
|
387
|
+
});
|
|
388
|
+
(0, vitest_1.it)('detects MCP server allowing all tools', async () => {
|
|
389
|
+
await fs.writeFile(path.join(tempDir, 'mcp.json'), JSON.stringify({
|
|
390
|
+
servers: {
|
|
391
|
+
myserver: {
|
|
392
|
+
command: 'mcp-server',
|
|
393
|
+
allowedTools: ['*'],
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
}));
|
|
397
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
398
|
+
const finding = result.findings.find((f) => f.checkId === 'MCP-005');
|
|
399
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
400
|
+
(0, vitest_1.expect)(finding?.passed).toBe(false);
|
|
401
|
+
(0, vitest_1.expect)(finding?.severity).toBe('high');
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
(0, vitest_1.describe)('Claude Code additional checks', () => {
|
|
405
|
+
let scanner;
|
|
406
|
+
let tempDir;
|
|
407
|
+
(0, vitest_1.beforeEach)(async () => {
|
|
408
|
+
scanner = new scanner_1.HardeningScanner();
|
|
409
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hackmyagent-test-'));
|
|
410
|
+
});
|
|
411
|
+
(0, vitest_1.afterEach)(async () => {
|
|
412
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
413
|
+
});
|
|
414
|
+
(0, vitest_1.it)('detects overly permissive allowed commands', async () => {
|
|
415
|
+
await fs.mkdir(path.join(tempDir, '.claude'), { recursive: true });
|
|
416
|
+
await fs.writeFile(path.join(tempDir, '.claude', 'settings.json'), JSON.stringify({
|
|
417
|
+
permissions: {
|
|
418
|
+
allow: ['Bash(*)', 'Read(*)', 'Write(*)'],
|
|
419
|
+
},
|
|
420
|
+
}));
|
|
421
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
422
|
+
const finding = result.findings.find((f) => f.checkId === 'CLAUDE-002');
|
|
423
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
424
|
+
(0, vitest_1.expect)(finding?.passed).toBe(false);
|
|
425
|
+
(0, vitest_1.expect)(finding?.severity).toBe('high');
|
|
426
|
+
});
|
|
427
|
+
(0, vitest_1.it)('detects dangerous Bash patterns', async () => {
|
|
428
|
+
await fs.mkdir(path.join(tempDir, '.claude'), { recursive: true });
|
|
429
|
+
await fs.writeFile(path.join(tempDir, '.claude', 'settings.json'), JSON.stringify({
|
|
430
|
+
permissions: {
|
|
431
|
+
allow: ['Bash(rm -rf *)'],
|
|
432
|
+
},
|
|
433
|
+
}));
|
|
434
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
435
|
+
const finding = result.findings.find((f) => f.checkId === 'CLAUDE-003');
|
|
436
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
437
|
+
(0, vitest_1.expect)(finding?.passed).toBe(false);
|
|
438
|
+
(0, vitest_1.expect)(finding?.severity).toBe('critical');
|
|
439
|
+
});
|
|
440
|
+
(0, vitest_1.it)('passes for scoped permissions', async () => {
|
|
441
|
+
await fs.mkdir(path.join(tempDir, '.claude'), { recursive: true });
|
|
442
|
+
await fs.writeFile(path.join(tempDir, '.claude', 'settings.json'), JSON.stringify({
|
|
443
|
+
permissions: {
|
|
444
|
+
allow: ['Read(./src/**)', 'Write(./src/**)'],
|
|
445
|
+
deny: ['Bash(rm *)'],
|
|
446
|
+
},
|
|
447
|
+
}));
|
|
448
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
449
|
+
const finding = result.findings.find((f) => f.checkId === 'CLAUDE-002');
|
|
450
|
+
(0, vitest_1.expect)(finding?.passed).toBe(true);
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
(0, vitest_1.describe)('Cursor configuration checks', () => {
|
|
454
|
+
let scanner;
|
|
455
|
+
let tempDir;
|
|
456
|
+
(0, vitest_1.beforeEach)(async () => {
|
|
457
|
+
scanner = new scanner_1.HardeningScanner();
|
|
458
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hackmyagent-test-'));
|
|
459
|
+
});
|
|
460
|
+
(0, vitest_1.afterEach)(async () => {
|
|
461
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
462
|
+
});
|
|
463
|
+
(0, vitest_1.it)('detects credentials in Cursor rules', async () => {
|
|
464
|
+
await fs.mkdir(path.join(tempDir, '.cursor'), { recursive: true });
|
|
465
|
+
await fs.writeFile(path.join(tempDir, '.cursor', 'rules'), 'Use API key sk-ant-api03-secretkey1234567890xyz for all requests\n');
|
|
466
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
467
|
+
const finding = result.findings.find((f) => f.checkId === 'CURSOR-001');
|
|
468
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
469
|
+
(0, vitest_1.expect)(finding?.passed).toBe(false);
|
|
470
|
+
(0, vitest_1.expect)(finding?.severity).toBe('critical');
|
|
471
|
+
});
|
|
472
|
+
(0, vitest_1.it)('detects credentials in .cursorrules', async () => {
|
|
473
|
+
await fs.writeFile(path.join(tempDir, '.cursorrules'), 'API_KEY=sk-proj-xxxxxxxxxxxxxxxxxxxxxxxxxxxx\n');
|
|
474
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
475
|
+
const finding = result.findings.find((f) => f.checkId === 'CURSOR-001');
|
|
476
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
477
|
+
(0, vitest_1.expect)(finding?.passed).toBe(false);
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
(0, vitest_1.describe)('VSCode configuration checks', () => {
|
|
481
|
+
let scanner;
|
|
482
|
+
let tempDir;
|
|
483
|
+
(0, vitest_1.beforeEach)(async () => {
|
|
484
|
+
scanner = new scanner_1.HardeningScanner();
|
|
485
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hackmyagent-test-'));
|
|
486
|
+
});
|
|
487
|
+
(0, vitest_1.afterEach)(async () => {
|
|
488
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
489
|
+
});
|
|
490
|
+
(0, vitest_1.it)('detects credentials in VSCode MCP config', async () => {
|
|
491
|
+
await fs.mkdir(path.join(tempDir, '.vscode'), { recursive: true });
|
|
492
|
+
await fs.writeFile(path.join(tempDir, '.vscode', 'mcp.json'), JSON.stringify({
|
|
493
|
+
servers: {
|
|
494
|
+
myserver: {
|
|
495
|
+
apiKey: 'sk-ant-api03-exposedsecretkey1234567890',
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
}));
|
|
499
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
500
|
+
const finding = result.findings.find((f) => f.checkId === 'VSCODE-001');
|
|
501
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
502
|
+
(0, vitest_1.expect)(finding?.passed).toBe(false);
|
|
503
|
+
(0, vitest_1.expect)(finding?.severity).toBe('critical');
|
|
504
|
+
});
|
|
505
|
+
(0, vitest_1.it)('detects overly permissive VSCode MCP config', async () => {
|
|
506
|
+
await fs.mkdir(path.join(tempDir, '.vscode'), { recursive: true });
|
|
507
|
+
await fs.writeFile(path.join(tempDir, '.vscode', 'mcp.json'), JSON.stringify({
|
|
508
|
+
servers: {
|
|
509
|
+
filesystem: {
|
|
510
|
+
command: 'mcp-server-filesystem',
|
|
511
|
+
args: ['/'],
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
}));
|
|
515
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
516
|
+
const finding = result.findings.find((f) => f.checkId === 'VSCODE-002');
|
|
517
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
518
|
+
(0, vitest_1.expect)(finding?.passed).toBe(false);
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
(0, vitest_1.describe)('Additional credential checks', () => {
|
|
522
|
+
let scanner;
|
|
523
|
+
let tempDir;
|
|
524
|
+
(0, vitest_1.beforeEach)(async () => {
|
|
525
|
+
scanner = new scanner_1.HardeningScanner();
|
|
526
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hackmyagent-test-'));
|
|
527
|
+
});
|
|
528
|
+
(0, vitest_1.afterEach)(async () => {
|
|
529
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
530
|
+
});
|
|
531
|
+
(0, vitest_1.it)('detects private keys in directory', async () => {
|
|
532
|
+
await fs.writeFile(path.join(tempDir, 'server.key'), '-----BEGIN RSA PRIVATE KEY-----\nMIIBOgIBAAJBALRi\n-----END RSA PRIVATE KEY-----\n');
|
|
533
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
534
|
+
const finding = result.findings.find((f) => f.checkId === 'CRED-002');
|
|
535
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
536
|
+
(0, vitest_1.expect)(finding?.passed).toBe(false);
|
|
537
|
+
(0, vitest_1.expect)(finding?.severity).toBe('critical');
|
|
538
|
+
});
|
|
539
|
+
(0, vitest_1.it)('detects .pem files', async () => {
|
|
540
|
+
await fs.writeFile(path.join(tempDir, 'cert.pem'), '-----BEGIN CERTIFICATE-----\nMIIBOgIBAAJBALRi\n-----END CERTIFICATE-----\n');
|
|
541
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
542
|
+
const finding = result.findings.find((f) => f.checkId === 'CRED-002');
|
|
543
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
544
|
+
});
|
|
545
|
+
(0, vitest_1.it)('detects hardcoded secrets in package.json', async () => {
|
|
546
|
+
await fs.writeFile(path.join(tempDir, 'package.json'), JSON.stringify({
|
|
547
|
+
name: 'test',
|
|
548
|
+
scripts: {
|
|
549
|
+
deploy: 'API_KEY=sk-ant-api03-secretkey12345678901234abc npm run build',
|
|
550
|
+
},
|
|
551
|
+
}));
|
|
552
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
553
|
+
const finding = result.findings.find((f) => f.checkId === 'CRED-003');
|
|
554
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
555
|
+
(0, vitest_1.expect)(finding?.passed).toBe(false);
|
|
556
|
+
});
|
|
557
|
+
(0, vitest_1.it)('detects JWT secrets in config', async () => {
|
|
558
|
+
await fs.writeFile(path.join(tempDir, 'config.json'), JSON.stringify({
|
|
559
|
+
jwt: {
|
|
560
|
+
secret: 'super-secret-jwt-key-that-should-not-be-here',
|
|
561
|
+
},
|
|
562
|
+
}));
|
|
563
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
564
|
+
const finding = result.findings.find((f) => f.checkId === 'CRED-004');
|
|
565
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
566
|
+
(0, vitest_1.expect)(finding?.passed).toBe(false);
|
|
567
|
+
});
|
|
568
|
+
});
|
|
569
|
+
(0, vitest_1.describe)('Permission boundary checks', () => {
|
|
570
|
+
let scanner;
|
|
571
|
+
let tempDir;
|
|
572
|
+
(0, vitest_1.beforeEach)(async () => {
|
|
573
|
+
scanner = new scanner_1.HardeningScanner();
|
|
574
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hackmyagent-test-'));
|
|
575
|
+
});
|
|
576
|
+
(0, vitest_1.afterEach)(async () => {
|
|
577
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
578
|
+
});
|
|
579
|
+
(0, vitest_1.it)('detects executable config files', async () => {
|
|
580
|
+
const configPath = path.join(tempDir, 'config.json');
|
|
581
|
+
await fs.writeFile(configPath, '{}');
|
|
582
|
+
await fs.chmod(configPath, 0o755);
|
|
583
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
584
|
+
const finding = result.findings.find((f) => f.checkId === 'PERM-002');
|
|
585
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
586
|
+
(0, vitest_1.expect)(finding?.passed).toBe(false);
|
|
587
|
+
});
|
|
588
|
+
(0, vitest_1.it)('detects group-writable sensitive files', async () => {
|
|
589
|
+
const envPath = path.join(tempDir, '.env');
|
|
590
|
+
await fs.writeFile(envPath, 'SECRET=value');
|
|
591
|
+
await fs.chmod(envPath, 0o664);
|
|
592
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
593
|
+
const finding = result.findings.find((f) => f.checkId === 'PERM-003');
|
|
594
|
+
(0, vitest_1.expect)(finding).toBeDefined();
|
|
595
|
+
(0, vitest_1.expect)(finding?.passed).toBe(false);
|
|
596
|
+
});
|
|
597
|
+
});
|
|
598
|
+
(0, vitest_1.describe)('Auto-fix: Git security', () => {
|
|
599
|
+
let scanner;
|
|
600
|
+
let tempDir;
|
|
601
|
+
(0, vitest_1.beforeEach)(async () => {
|
|
602
|
+
scanner = new scanner_1.HardeningScanner();
|
|
603
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hackmyagent-test-'));
|
|
604
|
+
});
|
|
605
|
+
(0, vitest_1.afterEach)(async () => {
|
|
606
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
607
|
+
});
|
|
608
|
+
(0, vitest_1.it)('creates .gitignore when missing', async () => {
|
|
609
|
+
await scanner.scan({ targetDir: tempDir, autoFix: true });
|
|
610
|
+
const gitignoreExists = await fs.access(path.join(tempDir, '.gitignore')).then(() => true).catch(() => false);
|
|
611
|
+
(0, vitest_1.expect)(gitignoreExists).toBe(true);
|
|
612
|
+
const content = await fs.readFile(path.join(tempDir, '.gitignore'), 'utf-8');
|
|
613
|
+
(0, vitest_1.expect)(content).toContain('.env');
|
|
614
|
+
(0, vitest_1.expect)(content).toContain('*.pem');
|
|
615
|
+
});
|
|
616
|
+
(0, vitest_1.it)('adds missing patterns to existing .gitignore', async () => {
|
|
617
|
+
await fs.writeFile(path.join(tempDir, '.gitignore'), 'node_modules/\n');
|
|
618
|
+
await scanner.scan({ targetDir: tempDir, autoFix: true });
|
|
619
|
+
const content = await fs.readFile(path.join(tempDir, '.gitignore'), 'utf-8');
|
|
620
|
+
(0, vitest_1.expect)(content).toContain('node_modules/');
|
|
621
|
+
(0, vitest_1.expect)(content).toContain('.env');
|
|
622
|
+
(0, vitest_1.expect)(content).toContain('*.pem');
|
|
623
|
+
});
|
|
624
|
+
(0, vitest_1.it)('reports fix was applied for .gitignore', async () => {
|
|
625
|
+
const result = await scanner.scan({ targetDir: tempDir, autoFix: true });
|
|
626
|
+
const gitFinding = result.findings.find(f => f.checkId === 'GIT-001');
|
|
627
|
+
(0, vitest_1.expect)(gitFinding?.fixed).toBe(true);
|
|
628
|
+
});
|
|
629
|
+
});
|
|
630
|
+
(0, vitest_1.describe)('Auto-fix: Credential replacement', () => {
|
|
631
|
+
let scanner;
|
|
632
|
+
let tempDir;
|
|
633
|
+
(0, vitest_1.beforeEach)(async () => {
|
|
634
|
+
scanner = new scanner_1.HardeningScanner();
|
|
635
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hackmyagent-test-'));
|
|
636
|
+
});
|
|
637
|
+
(0, vitest_1.afterEach)(async () => {
|
|
638
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
639
|
+
});
|
|
640
|
+
(0, vitest_1.it)('replaces Anthropic API key with env var reference in config.json', async () => {
|
|
641
|
+
const configPath = path.join(tempDir, 'config.json');
|
|
642
|
+
await fs.writeFile(configPath, JSON.stringify({
|
|
643
|
+
apiKey: 'sk-ant-api03-abcdefghijklmnopqrstuvwxyz',
|
|
644
|
+
name: 'test'
|
|
645
|
+
}, null, 2));
|
|
646
|
+
await scanner.scan({ targetDir: tempDir, autoFix: true });
|
|
647
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
648
|
+
(0, vitest_1.expect)(content).not.toContain('sk-ant-api03');
|
|
649
|
+
(0, vitest_1.expect)(content).toContain('${ANTHROPIC_API_KEY}');
|
|
650
|
+
});
|
|
651
|
+
(0, vitest_1.it)('replaces OpenAI API key with env var reference', async () => {
|
|
652
|
+
const configPath = path.join(tempDir, 'config.json');
|
|
653
|
+
await fs.writeFile(configPath, JSON.stringify({
|
|
654
|
+
openaiKey: 'sk-proj-abcdefghijklmnopqrstuvwxyz123456',
|
|
655
|
+
}, null, 2));
|
|
656
|
+
await scanner.scan({ targetDir: tempDir, autoFix: true });
|
|
657
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
658
|
+
(0, vitest_1.expect)(content).not.toContain('sk-proj-');
|
|
659
|
+
(0, vitest_1.expect)(content).toContain('${OPENAI_API_KEY}');
|
|
660
|
+
});
|
|
661
|
+
(0, vitest_1.it)('creates .env.example with placeholder', async () => {
|
|
662
|
+
const configPath = path.join(tempDir, 'config.json');
|
|
663
|
+
await fs.writeFile(configPath, JSON.stringify({
|
|
664
|
+
apiKey: 'sk-ant-api03-abcdefghijklmnopqrstuvwxyz',
|
|
665
|
+
}, null, 2));
|
|
666
|
+
await scanner.scan({ targetDir: tempDir, autoFix: true });
|
|
667
|
+
const envExamplePath = path.join(tempDir, '.env.example');
|
|
668
|
+
const envExists = await fs.access(envExamplePath).then(() => true).catch(() => false);
|
|
669
|
+
(0, vitest_1.expect)(envExists).toBe(true);
|
|
670
|
+
const content = await fs.readFile(envExamplePath, 'utf-8');
|
|
671
|
+
(0, vitest_1.expect)(content).toContain('ANTHROPIC_API_KEY=');
|
|
672
|
+
});
|
|
673
|
+
});
|
|
674
|
+
(0, vitest_1.describe)('Auto-fix: Network security', () => {
|
|
675
|
+
let scanner;
|
|
676
|
+
let tempDir;
|
|
677
|
+
(0, vitest_1.beforeEach)(async () => {
|
|
678
|
+
scanner = new scanner_1.HardeningScanner();
|
|
679
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hackmyagent-test-'));
|
|
680
|
+
});
|
|
681
|
+
(0, vitest_1.afterEach)(async () => {
|
|
682
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
683
|
+
});
|
|
684
|
+
(0, vitest_1.it)('changes 0.0.0.0 to 127.0.0.1 in mcp.json', async () => {
|
|
685
|
+
const mcpPath = path.join(tempDir, 'mcp.json');
|
|
686
|
+
await fs.writeFile(mcpPath, JSON.stringify({
|
|
687
|
+
servers: {
|
|
688
|
+
myserver: {
|
|
689
|
+
command: 'node',
|
|
690
|
+
args: ['server.js', '--host', '0.0.0.0', '--port', '3000']
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}, null, 2));
|
|
694
|
+
await scanner.scan({ targetDir: tempDir, autoFix: true });
|
|
695
|
+
const content = await fs.readFile(mcpPath, 'utf-8');
|
|
696
|
+
(0, vitest_1.expect)(content).not.toContain('0.0.0.0');
|
|
697
|
+
(0, vitest_1.expect)(content).toContain('127.0.0.1');
|
|
698
|
+
});
|
|
699
|
+
(0, vitest_1.it)('reports fix was applied for network binding', async () => {
|
|
700
|
+
const mcpPath = path.join(tempDir, 'mcp.json');
|
|
701
|
+
await fs.writeFile(mcpPath, JSON.stringify({
|
|
702
|
+
servers: {
|
|
703
|
+
myserver: {
|
|
704
|
+
command: 'node',
|
|
705
|
+
args: ['--host', '0.0.0.0']
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}, null, 2));
|
|
709
|
+
const result = await scanner.scan({ targetDir: tempDir, autoFix: true });
|
|
710
|
+
const netFinding = result.findings.find(f => f.checkId === 'NET-001');
|
|
711
|
+
(0, vitest_1.expect)(netFinding?.fixed).toBe(true);
|
|
712
|
+
});
|
|
713
|
+
});
|
|
714
|
+
(0, vitest_1.describe)('Auto-fix: MCP filesystem access', () => {
|
|
715
|
+
let scanner;
|
|
716
|
+
let tempDir;
|
|
717
|
+
(0, vitest_1.beforeEach)(async () => {
|
|
718
|
+
scanner = new scanner_1.HardeningScanner();
|
|
719
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hackmyagent-test-'));
|
|
720
|
+
});
|
|
721
|
+
(0, vitest_1.afterEach)(async () => {
|
|
722
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
723
|
+
});
|
|
724
|
+
(0, vitest_1.it)('changes root "/" to "./data" in mcp.json', async () => {
|
|
725
|
+
const mcpPath = path.join(tempDir, 'mcp.json');
|
|
726
|
+
await fs.writeFile(mcpPath, JSON.stringify({
|
|
727
|
+
servers: {
|
|
728
|
+
filesystem: {
|
|
729
|
+
command: 'mcp-server-filesystem',
|
|
730
|
+
args: ['/']
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}, null, 2));
|
|
734
|
+
await scanner.scan({ targetDir: tempDir, autoFix: true });
|
|
735
|
+
const content = await fs.readFile(mcpPath, 'utf-8');
|
|
736
|
+
const config = JSON.parse(content);
|
|
737
|
+
(0, vitest_1.expect)(config.servers.filesystem.args).not.toContain('/');
|
|
738
|
+
(0, vitest_1.expect)(config.servers.filesystem.args).toContain('./data');
|
|
739
|
+
});
|
|
740
|
+
(0, vitest_1.it)('changes home "~" to "./" in mcp.json', async () => {
|
|
741
|
+
const mcpPath = path.join(tempDir, 'mcp.json');
|
|
742
|
+
await fs.writeFile(mcpPath, JSON.stringify({
|
|
743
|
+
servers: {
|
|
744
|
+
filesystem: {
|
|
745
|
+
command: 'mcp-server-filesystem',
|
|
746
|
+
args: ['~']
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}, null, 2));
|
|
750
|
+
await scanner.scan({ targetDir: tempDir, autoFix: true });
|
|
751
|
+
const content = await fs.readFile(mcpPath, 'utf-8');
|
|
752
|
+
const config = JSON.parse(content);
|
|
753
|
+
(0, vitest_1.expect)(config.servers.filesystem.args).not.toContain('~');
|
|
754
|
+
(0, vitest_1.expect)(config.servers.filesystem.args).toContain('./');
|
|
755
|
+
});
|
|
756
|
+
});
|
|
757
|
+
(0, vitest_1.describe)('Auto-fix: MCP hardcoded secrets', () => {
|
|
758
|
+
let scanner;
|
|
759
|
+
let tempDir;
|
|
760
|
+
(0, vitest_1.beforeEach)(async () => {
|
|
761
|
+
scanner = new scanner_1.HardeningScanner();
|
|
762
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hackmyagent-test-'));
|
|
763
|
+
});
|
|
764
|
+
(0, vitest_1.afterEach)(async () => {
|
|
765
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
766
|
+
});
|
|
767
|
+
(0, vitest_1.it)('replaces hardcoded API key in MCP env with reference', async () => {
|
|
768
|
+
const mcpPath = path.join(tempDir, 'mcp.json');
|
|
769
|
+
await fs.writeFile(mcpPath, JSON.stringify({
|
|
770
|
+
servers: {
|
|
771
|
+
myserver: {
|
|
772
|
+
command: 'node',
|
|
773
|
+
env: {
|
|
774
|
+
API_KEY: 'sk-ant-api03-secretkeyhere1234567890'
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}, null, 2));
|
|
779
|
+
await scanner.scan({ targetDir: tempDir, autoFix: true });
|
|
780
|
+
const content = await fs.readFile(mcpPath, 'utf-8');
|
|
781
|
+
(0, vitest_1.expect)(content).not.toContain('sk-ant-api03');
|
|
782
|
+
(0, vitest_1.expect)(content).toContain('${ANTHROPIC_API_KEY}');
|
|
783
|
+
});
|
|
784
|
+
});
|
|
785
|
+
(0, vitest_1.describe)('Platform detection', () => {
|
|
786
|
+
let scanner;
|
|
787
|
+
let tempDir;
|
|
788
|
+
(0, vitest_1.beforeEach)(async () => {
|
|
789
|
+
scanner = new scanner_1.HardeningScanner();
|
|
790
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hackmyagent-test-'));
|
|
791
|
+
});
|
|
792
|
+
(0, vitest_1.afterEach)(async () => {
|
|
793
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
794
|
+
});
|
|
795
|
+
(0, vitest_1.it)('detects Claude Code environment', async () => {
|
|
796
|
+
await fs.writeFile(path.join(tempDir, 'CLAUDE.md'), '# Claude Instructions');
|
|
797
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
798
|
+
(0, vitest_1.expect)(result.platform).toContain('claude');
|
|
799
|
+
});
|
|
800
|
+
(0, vitest_1.it)('detects Cursor environment', async () => {
|
|
801
|
+
await fs.mkdir(path.join(tempDir, '.cursor'), { recursive: true });
|
|
802
|
+
await fs.writeFile(path.join(tempDir, '.cursor', 'rules'), 'cursor rules');
|
|
803
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
804
|
+
(0, vitest_1.expect)(result.platform).toContain('cursor');
|
|
805
|
+
});
|
|
806
|
+
(0, vitest_1.it)('detects MCP configuration', async () => {
|
|
807
|
+
await fs.writeFile(path.join(tempDir, 'mcp.json'), JSON.stringify({ servers: {} }));
|
|
808
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
809
|
+
(0, vitest_1.expect)(result.platform).toContain('mcp');
|
|
810
|
+
});
|
|
811
|
+
(0, vitest_1.it)('returns generic for unknown platform', async () => {
|
|
812
|
+
const result = await scanner.scan({ targetDir: tempDir });
|
|
813
|
+
(0, vitest_1.expect)(result.platform).toBeDefined();
|
|
814
|
+
});
|
|
815
|
+
});
|
|
816
|
+
(0, vitest_1.describe)('Backup and rollback', () => {
|
|
817
|
+
let scanner;
|
|
818
|
+
let tempDir;
|
|
819
|
+
(0, vitest_1.beforeEach)(async () => {
|
|
820
|
+
scanner = new scanner_1.HardeningScanner();
|
|
821
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hackmyagent-test-'));
|
|
822
|
+
});
|
|
823
|
+
(0, vitest_1.afterEach)(async () => {
|
|
824
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
825
|
+
});
|
|
826
|
+
(0, vitest_1.it)('creates backup before auto-fix', async () => {
|
|
827
|
+
// Create a config file with exposed credentials
|
|
828
|
+
const configPath = path.join(tempDir, 'config.json');
|
|
829
|
+
await fs.writeFile(configPath, JSON.stringify({ apiKey: 'sk-ant-api03-secretkey12345678901234' }));
|
|
830
|
+
// Run with auto-fix
|
|
831
|
+
const result = await scanner.scan({ targetDir: tempDir, autoFix: true });
|
|
832
|
+
// Check backup was created
|
|
833
|
+
const backupDir = path.join(tempDir, '.hackmyagent-backup');
|
|
834
|
+
const backupExists = await fs
|
|
835
|
+
.access(backupDir)
|
|
836
|
+
.then(() => true)
|
|
837
|
+
.catch(() => false);
|
|
838
|
+
(0, vitest_1.expect)(backupExists).toBe(true);
|
|
839
|
+
// Check backup contains the original file
|
|
840
|
+
const backups = await fs.readdir(backupDir);
|
|
841
|
+
(0, vitest_1.expect)(backups.length).toBeGreaterThan(0);
|
|
842
|
+
// Read the backup and verify it has original content
|
|
843
|
+
const backupContent = await fs.readFile(path.join(backupDir, backups[0], 'config.json'), 'utf-8');
|
|
844
|
+
(0, vitest_1.expect)(backupContent).toContain('sk-ant-api03');
|
|
845
|
+
});
|
|
846
|
+
(0, vitest_1.it)('backup contains timestamp in folder name', async () => {
|
|
847
|
+
const configPath = path.join(tempDir, 'config.json');
|
|
848
|
+
await fs.writeFile(configPath, JSON.stringify({ apiKey: 'sk-ant-api03-secretkey12345678901234' }));
|
|
849
|
+
await scanner.scan({ targetDir: tempDir, autoFix: true });
|
|
850
|
+
const backupDir = path.join(tempDir, '.hackmyagent-backup');
|
|
851
|
+
const backups = await fs.readdir(backupDir);
|
|
852
|
+
// Backup folder should have timestamp format: YYYY-MM-DD-HHMMSS
|
|
853
|
+
(0, vitest_1.expect)(backups[0]).toMatch(/^\d{4}-\d{2}-\d{2}-\d{6}$/);
|
|
854
|
+
});
|
|
855
|
+
(0, vitest_1.it)('can rollback to previous state', async () => {
|
|
856
|
+
const configPath = path.join(tempDir, 'config.json');
|
|
857
|
+
const originalContent = JSON.stringify({ apiKey: 'sk-ant-api03-secretkey12345678901234' });
|
|
858
|
+
await fs.writeFile(configPath, originalContent);
|
|
859
|
+
// Run with auto-fix (modifies the file)
|
|
860
|
+
await scanner.scan({ targetDir: tempDir, autoFix: true });
|
|
861
|
+
// Verify file was modified
|
|
862
|
+
const modifiedContent = await fs.readFile(configPath, 'utf-8');
|
|
863
|
+
(0, vitest_1.expect)(modifiedContent).not.toContain('sk-ant-api03');
|
|
864
|
+
// Rollback
|
|
865
|
+
await scanner.rollback(tempDir);
|
|
866
|
+
// Verify file is restored
|
|
867
|
+
const restoredContent = await fs.readFile(configPath, 'utf-8');
|
|
868
|
+
(0, vitest_1.expect)(restoredContent).toBe(originalContent);
|
|
869
|
+
});
|
|
870
|
+
(0, vitest_1.it)('rollback restores multiple files', async () => {
|
|
871
|
+
// Create multiple files
|
|
872
|
+
await fs.writeFile(path.join(tempDir, 'config.json'), JSON.stringify({ key: 'sk-ant-api03-secret1key1234567890abc' }));
|
|
873
|
+
await fs.writeFile(path.join(tempDir, 'mcp.json'), JSON.stringify({
|
|
874
|
+
mcpServers: {
|
|
875
|
+
test: { env: { API_KEY: 'sk-ant-api03-secret2key1234567890abc' } },
|
|
876
|
+
},
|
|
877
|
+
}));
|
|
878
|
+
// Run auto-fix
|
|
879
|
+
await scanner.scan({ targetDir: tempDir, autoFix: true });
|
|
880
|
+
// Rollback
|
|
881
|
+
await scanner.rollback(tempDir);
|
|
882
|
+
// Check both files restored
|
|
883
|
+
const config = await fs.readFile(path.join(tempDir, 'config.json'), 'utf-8');
|
|
884
|
+
const mcp = await fs.readFile(path.join(tempDir, 'mcp.json'), 'utf-8');
|
|
885
|
+
(0, vitest_1.expect)(config).toContain('sk-ant-api03-secret1key1234567890abc');
|
|
886
|
+
(0, vitest_1.expect)(mcp).toContain('sk-ant-api03-secret2');
|
|
887
|
+
});
|
|
888
|
+
(0, vitest_1.it)('rollback removes newly created files', async () => {
|
|
889
|
+
// Create a file with credentials (no .gitignore exists)
|
|
890
|
+
await fs.writeFile(path.join(tempDir, 'config.json'), JSON.stringify({ key: 'sk-ant-api03-secret' }));
|
|
891
|
+
// Run auto-fix (creates .gitignore and .env.example)
|
|
892
|
+
await scanner.scan({ targetDir: tempDir, autoFix: true });
|
|
893
|
+
// Verify new files were created
|
|
894
|
+
const gitignoreExists = await fs
|
|
895
|
+
.access(path.join(tempDir, '.gitignore'))
|
|
896
|
+
.then(() => true)
|
|
897
|
+
.catch(() => false);
|
|
898
|
+
(0, vitest_1.expect)(gitignoreExists).toBe(true);
|
|
899
|
+
// Rollback
|
|
900
|
+
await scanner.rollback(tempDir);
|
|
901
|
+
// Verify .gitignore is removed (it didn't exist before)
|
|
902
|
+
const gitignoreAfterRollback = await fs
|
|
903
|
+
.access(path.join(tempDir, '.gitignore'))
|
|
904
|
+
.then(() => true)
|
|
905
|
+
.catch(() => false);
|
|
906
|
+
(0, vitest_1.expect)(gitignoreAfterRollback).toBe(false);
|
|
907
|
+
});
|
|
908
|
+
(0, vitest_1.it)('returns backup path in scan result when autoFix is true', async () => {
|
|
909
|
+
await fs.writeFile(path.join(tempDir, 'config.json'), JSON.stringify({ key: 'sk-ant-api03-secret' }));
|
|
910
|
+
const result = await scanner.scan({ targetDir: tempDir, autoFix: true });
|
|
911
|
+
(0, vitest_1.expect)(result.backupPath).toBeDefined();
|
|
912
|
+
(0, vitest_1.expect)(result.backupPath).toContain('.hackmyagent-backup');
|
|
913
|
+
});
|
|
914
|
+
(0, vitest_1.it)('does not create backup when autoFix is false', async () => {
|
|
915
|
+
await fs.writeFile(path.join(tempDir, 'config.json'), JSON.stringify({ key: 'sk-ant-api03-secret' }));
|
|
916
|
+
const result = await scanner.scan({ targetDir: tempDir, autoFix: false });
|
|
917
|
+
(0, vitest_1.expect)(result.backupPath).toBeUndefined();
|
|
918
|
+
const backupDir = path.join(tempDir, '.hackmyagent-backup');
|
|
919
|
+
const backupExists = await fs
|
|
920
|
+
.access(backupDir)
|
|
921
|
+
.then(() => true)
|
|
922
|
+
.catch(() => false);
|
|
923
|
+
(0, vitest_1.expect)(backupExists).toBe(false);
|
|
924
|
+
});
|
|
925
|
+
(0, vitest_1.it)('throws error when no backup exists for rollback', async () => {
|
|
926
|
+
await (0, vitest_1.expect)(scanner.rollback(tempDir)).rejects.toThrow(/no backup found/i);
|
|
927
|
+
});
|
|
928
|
+
});
|
|
929
|
+
(0, vitest_1.describe)('Dry-run mode', () => {
|
|
930
|
+
let scanner;
|
|
931
|
+
let tempDir;
|
|
932
|
+
(0, vitest_1.beforeEach)(async () => {
|
|
933
|
+
scanner = new scanner_1.HardeningScanner();
|
|
934
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hackmyagent-test-'));
|
|
935
|
+
});
|
|
936
|
+
(0, vitest_1.afterEach)(async () => {
|
|
937
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
938
|
+
});
|
|
939
|
+
(0, vitest_1.it)('shows what would be fixed without modifying files', async () => {
|
|
940
|
+
const configPath = path.join(tempDir, 'config.json');
|
|
941
|
+
const originalContent = JSON.stringify({ apiKey: 'sk-ant-api03-secretkey12345678901234' });
|
|
942
|
+
await fs.writeFile(configPath, originalContent);
|
|
943
|
+
const result = await scanner.scan({
|
|
944
|
+
targetDir: tempDir,
|
|
945
|
+
autoFix: true,
|
|
946
|
+
dryRun: true,
|
|
947
|
+
});
|
|
948
|
+
// Should report what would be fixed
|
|
949
|
+
const credFinding = result.findings.find((f) => f.checkId === 'CRED-001');
|
|
950
|
+
(0, vitest_1.expect)(credFinding?.wouldFix).toBe(true);
|
|
951
|
+
// File should NOT be modified
|
|
952
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
953
|
+
(0, vitest_1.expect)(content).toBe(originalContent);
|
|
954
|
+
});
|
|
955
|
+
(0, vitest_1.it)('does not create backup in dry-run mode', async () => {
|
|
956
|
+
await fs.writeFile(path.join(tempDir, 'config.json'), JSON.stringify({ apiKey: 'sk-ant-api03-secretkey1234567890def' }));
|
|
957
|
+
const result = await scanner.scan({
|
|
958
|
+
targetDir: tempDir,
|
|
959
|
+
autoFix: true,
|
|
960
|
+
dryRun: true,
|
|
961
|
+
});
|
|
962
|
+
(0, vitest_1.expect)(result.backupPath).toBeUndefined();
|
|
963
|
+
const backupDir = path.join(tempDir, '.hackmyagent-backup');
|
|
964
|
+
const backupExists = await fs
|
|
965
|
+
.access(backupDir)
|
|
966
|
+
.then(() => true)
|
|
967
|
+
.catch(() => false);
|
|
968
|
+
(0, vitest_1.expect)(backupExists).toBe(false);
|
|
969
|
+
});
|
|
970
|
+
(0, vitest_1.it)('reports all fixable issues in dry-run', async () => {
|
|
971
|
+
// Create multiple fixable issues
|
|
972
|
+
await fs.writeFile(path.join(tempDir, 'config.json'), JSON.stringify({ key: 'sk-ant-api03-secret1key1234567890abc' }));
|
|
973
|
+
// No .gitignore (fixable)
|
|
974
|
+
const result = await scanner.scan({
|
|
975
|
+
targetDir: tempDir,
|
|
976
|
+
autoFix: true,
|
|
977
|
+
dryRun: true,
|
|
978
|
+
});
|
|
979
|
+
const wouldFixFindings = result.findings.filter((f) => f.wouldFix);
|
|
980
|
+
(0, vitest_1.expect)(wouldFixFindings.length).toBeGreaterThanOrEqual(2);
|
|
981
|
+
});
|
|
982
|
+
(0, vitest_1.it)('returns dryRun flag in result', async () => {
|
|
983
|
+
const result = await scanner.scan({
|
|
984
|
+
targetDir: tempDir,
|
|
985
|
+
autoFix: true,
|
|
986
|
+
dryRun: true,
|
|
987
|
+
});
|
|
988
|
+
(0, vitest_1.expect)(result.dryRun).toBe(true);
|
|
989
|
+
});
|
|
990
|
+
});
|
|
991
|
+
(0, vitest_1.describe)('Atomic auto-fix', () => {
|
|
992
|
+
let scanner;
|
|
993
|
+
let tempDir;
|
|
994
|
+
(0, vitest_1.beforeEach)(async () => {
|
|
995
|
+
scanner = new scanner_1.HardeningScanner();
|
|
996
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hackmyagent-test-'));
|
|
997
|
+
});
|
|
998
|
+
(0, vitest_1.afterEach)(async () => {
|
|
999
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
1000
|
+
});
|
|
1001
|
+
(0, vitest_1.it)('rolls back all changes if any fix fails', async () => {
|
|
1002
|
+
// Create a config file that can be fixed
|
|
1003
|
+
const configPath = path.join(tempDir, 'config.json');
|
|
1004
|
+
const originalContent = JSON.stringify({ apiKey: 'sk-ant-api03-secretkey1234567890def' });
|
|
1005
|
+
await fs.writeFile(configPath, originalContent);
|
|
1006
|
+
// Create a read-only directory to cause .gitignore creation to fail
|
|
1007
|
+
const readOnlyDir = path.join(tempDir, 'readonly');
|
|
1008
|
+
await fs.mkdir(readOnlyDir);
|
|
1009
|
+
// Mock a scenario where fix would fail by making config.json read-only after backup
|
|
1010
|
+
// For this test, we'll verify the rollback mechanism exists
|
|
1011
|
+
// The actual failure simulation would require more complex setup
|
|
1012
|
+
const result = await scanner.scan({
|
|
1013
|
+
targetDir: tempDir,
|
|
1014
|
+
autoFix: true,
|
|
1015
|
+
});
|
|
1016
|
+
// If fixes succeeded, verify atomicity flag is set
|
|
1017
|
+
(0, vitest_1.expect)(result.atomicFix).toBeDefined();
|
|
1018
|
+
});
|
|
1019
|
+
(0, vitest_1.it)('sets atomicFix to true when all fixes succeed', async () => {
|
|
1020
|
+
await fs.writeFile(path.join(tempDir, 'config.json'), JSON.stringify({ apiKey: 'sk-ant-api03-secretkey1234567890def' }));
|
|
1021
|
+
const result = await scanner.scan({
|
|
1022
|
+
targetDir: tempDir,
|
|
1023
|
+
autoFix: true,
|
|
1024
|
+
});
|
|
1025
|
+
(0, vitest_1.expect)(result.atomicFix).toBe(true);
|
|
1026
|
+
});
|
|
1027
|
+
(0, vitest_1.it)('provides rollback instructions on partial failure', async () => {
|
|
1028
|
+
// Create fixable content
|
|
1029
|
+
await fs.writeFile(path.join(tempDir, 'config.json'), JSON.stringify({ apiKey: 'sk-ant-api03-secretkey1234567890def' }));
|
|
1030
|
+
const result = await scanner.scan({
|
|
1031
|
+
targetDir: tempDir,
|
|
1032
|
+
autoFix: true,
|
|
1033
|
+
});
|
|
1034
|
+
// When autoFix is used, backupPath should be available for manual rollback
|
|
1035
|
+
if (result.findings.some((f) => f.fixed)) {
|
|
1036
|
+
(0, vitest_1.expect)(result.backupPath).toBeDefined();
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
});
|
|
1040
|
+
(0, vitest_1.describe)('Ignore checks', () => {
|
|
1041
|
+
let scanner;
|
|
1042
|
+
let tempDir;
|
|
1043
|
+
(0, vitest_1.beforeEach)(async () => {
|
|
1044
|
+
scanner = new scanner_1.HardeningScanner();
|
|
1045
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hackmyagent-test-'));
|
|
1046
|
+
});
|
|
1047
|
+
(0, vitest_1.afterEach)(async () => {
|
|
1048
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
1049
|
+
});
|
|
1050
|
+
(0, vitest_1.it)('ignores specific check IDs', async () => {
|
|
1051
|
+
// Create file with exposed credential
|
|
1052
|
+
await fs.writeFile(path.join(tempDir, 'config.json'), JSON.stringify({ apiKey: 'sk-ant-api03-secretkey1234567890def' }));
|
|
1053
|
+
// Scan without ignore - should find CRED-001
|
|
1054
|
+
const resultWithCheck = await scanner.scan({ targetDir: tempDir });
|
|
1055
|
+
(0, vitest_1.expect)(resultWithCheck.findings.some((f) => f.checkId === 'CRED-001')).toBe(true);
|
|
1056
|
+
// Scan with ignore - should NOT find CRED-001
|
|
1057
|
+
const resultIgnored = await scanner.scan({
|
|
1058
|
+
targetDir: tempDir,
|
|
1059
|
+
ignore: ['CRED-001'],
|
|
1060
|
+
});
|
|
1061
|
+
(0, vitest_1.expect)(resultIgnored.findings.some((f) => f.checkId === 'CRED-001')).toBe(false);
|
|
1062
|
+
});
|
|
1063
|
+
(0, vitest_1.it)('ignore is case-insensitive', async () => {
|
|
1064
|
+
await fs.writeFile(path.join(tempDir, 'config.json'), JSON.stringify({ apiKey: 'sk-ant-api03-secretkey1234567890def' }));
|
|
1065
|
+
const result = await scanner.scan({
|
|
1066
|
+
targetDir: tempDir,
|
|
1067
|
+
ignore: ['cred-001'], // lowercase
|
|
1068
|
+
});
|
|
1069
|
+
(0, vitest_1.expect)(result.findings.some((f) => f.checkId === 'CRED-001')).toBe(false);
|
|
1070
|
+
});
|
|
1071
|
+
(0, vitest_1.it)('returns list of ignored checks in result', async () => {
|
|
1072
|
+
const result = await scanner.scan({
|
|
1073
|
+
targetDir: tempDir,
|
|
1074
|
+
ignore: ['CRED-001', 'GIT-001'],
|
|
1075
|
+
});
|
|
1076
|
+
(0, vitest_1.expect)(result.ignored).toBeDefined();
|
|
1077
|
+
(0, vitest_1.expect)(result.ignored).toContain('CRED-001');
|
|
1078
|
+
(0, vitest_1.expect)(result.ignored).toContain('GIT-001');
|
|
1079
|
+
});
|
|
1080
|
+
(0, vitest_1.it)('ignores multiple check IDs', async () => {
|
|
1081
|
+
await fs.writeFile(path.join(tempDir, 'config.json'), JSON.stringify({ apiKey: 'sk-ant-api03-secretkey1234567890def' }));
|
|
1082
|
+
const result = await scanner.scan({
|
|
1083
|
+
targetDir: tempDir,
|
|
1084
|
+
ignore: ['CRED-001', 'GIT-001', 'GIT-002'],
|
|
1085
|
+
});
|
|
1086
|
+
(0, vitest_1.expect)(result.findings.some((f) => f.checkId === 'CRED-001')).toBe(false);
|
|
1087
|
+
(0, vitest_1.expect)(result.findings.some((f) => f.checkId === 'GIT-001')).toBe(false);
|
|
1088
|
+
(0, vitest_1.expect)(result.findings.some((f) => f.checkId === 'GIT-002')).toBe(false);
|
|
1089
|
+
});
|
|
1090
|
+
(0, vitest_1.it)('score calculation excludes ignored checks', async () => {
|
|
1091
|
+
await fs.writeFile(path.join(tempDir, 'config.json'), JSON.stringify({ apiKey: 'sk-ant-api03-secretkey1234567890def' }));
|
|
1092
|
+
// Without ignore
|
|
1093
|
+
const resultFull = await scanner.scan({ targetDir: tempDir });
|
|
1094
|
+
// With ignore (ignoring critical check means fewer findings)
|
|
1095
|
+
const resultIgnored = await scanner.scan({
|
|
1096
|
+
targetDir: tempDir,
|
|
1097
|
+
ignore: ['CRED-001'],
|
|
1098
|
+
});
|
|
1099
|
+
// Should have fewer findings when ignoring a check
|
|
1100
|
+
(0, vitest_1.expect)(resultIgnored.findings.length).toBeLessThan(resultFull.findings.length);
|
|
1101
|
+
});
|
|
1102
|
+
});
|
|
1103
|
+
//# sourceMappingURL=scanner.test.js.map
|