Skip to content

Commit e10ad83

Browse files
Mossakaclaude
andcommitted
test: expand credential hiding tests to cover all 14 protected paths
Add 3 new integration tests covering all 11 untested credential paths: SSH keys (4), AWS creds/config, Kube config, Azure creds, GCloud creds, Cargo creds, Composer auth. Tests verify 0 bytes at both direct home and /host chroot paths. Uses robust patterns (if -f, || true, extractCommandOutput) consistent with existing tests. Fixes #761 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 40e46d6 commit e10ad83

File tree

1 file changed

+89
-0
lines changed

1 file changed

+89
-0
lines changed

tests/integration/credential-hiding.test.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,95 @@ describe('Credential Hiding Security', () => {
227227
}, 120000);
228228
});
229229

230+
describe('All 14 Credential Paths Coverage', () => {
231+
// These tests cover the 11 credential paths not tested by Tests 1-4 above.
232+
// Each path is hidden via /dev/null mount and should return empty content.
233+
234+
const untestedPaths = [
235+
{ name: 'SSH id_rsa', path: '.ssh/id_rsa' },
236+
{ name: 'SSH id_ed25519', path: '.ssh/id_ed25519' },
237+
{ name: 'SSH id_ecdsa', path: '.ssh/id_ecdsa' },
238+
{ name: 'SSH id_dsa', path: '.ssh/id_dsa' },
239+
{ name: 'AWS credentials', path: '.aws/credentials' },
240+
{ name: 'AWS config', path: '.aws/config' },
241+
{ name: 'Kube config', path: '.kube/config' },
242+
{ name: 'Azure credentials', path: '.azure/credentials' },
243+
{ name: 'GCloud credentials.db', path: '.config/gcloud/credentials.db' },
244+
{ name: 'Cargo credentials', path: '.cargo/credentials' },
245+
{ name: 'Composer auth.json', path: '.composer/auth.json' },
246+
];
247+
248+
test('All untested credential files are hidden at direct home path (0 bytes)', async () => {
249+
const homeDir = os.homedir();
250+
const paths = untestedPaths.map(p => `${homeDir}/${p.path}`).join(' ');
251+
252+
// Check all credential files in a single container run for efficiency.
253+
// wc -c reports byte count; /dev/null-mounted files should be 0 bytes.
254+
// Use '|| true' to prevent failures when files don't exist
255+
const result = await runner.runWithSudo(
256+
`sh -c 'for f in ${paths}; do if [ -f "$f" ]; then wc -c "$f"; fi; done 2>&1 || true'`,
257+
{
258+
allowDomains: ['github.com'],
259+
logLevel: 'debug',
260+
timeout: 60000,
261+
}
262+
);
263+
264+
expect(result).toSucceed();
265+
const cleanOutput = extractCommandOutput(result.stdout);
266+
const lines = cleanOutput.split('\n').filter(l => l.match(/^\s*\d+/));
267+
// Each file should be 0 bytes (hidden via /dev/null)
268+
lines.forEach(line => {
269+
const size = parseInt(line.trim().split(/\s+/)[0]);
270+
expect(size).toBe(0);
271+
});
272+
// Verify we checked all 11 files
273+
expect(lines.length).toBe(untestedPaths.length);
274+
}, 120000);
275+
276+
test('All untested credential files are hidden at /host path (0 bytes)', async () => {
277+
const homeDir = os.homedir();
278+
const paths = untestedPaths.map(p => `/host${homeDir}/${p.path}`).join(' ');
279+
280+
const result = await runner.runWithSudo(
281+
`sh -c 'for f in ${paths}; do if [ -f "$f" ]; then wc -c "$f"; fi; done 2>&1 || true'`,
282+
{
283+
allowDomains: ['github.com'],
284+
logLevel: 'debug',
285+
timeout: 60000,
286+
}
287+
);
288+
289+
expect(result).toSucceed();
290+
const cleanOutput = extractCommandOutput(result.stdout);
291+
const lines = cleanOutput.split('\n').filter(l => l.match(/^\s*\d+/));
292+
lines.forEach(line => {
293+
const size = parseInt(line.trim().split(/\s+/)[0]);
294+
expect(size).toBe(0);
295+
});
296+
expect(lines.length).toBe(untestedPaths.length);
297+
}, 120000);
298+
299+
test('cat on each untested credential file returns empty content', async () => {
300+
const homeDir = os.homedir();
301+
const paths = untestedPaths.map(p => `${homeDir}/${p.path}`).join(' ');
302+
303+
// cat all files and concatenate output - should be empty
304+
const result = await runner.runWithSudo(
305+
`sh -c 'for f in ${paths}; do if [ -f "$f" ]; then cat "$f"; fi; done 2>&1 || true'`,
306+
{
307+
allowDomains: ['github.com'],
308+
logLevel: 'debug',
309+
timeout: 60000,
310+
}
311+
);
312+
313+
expect(result).toSucceed();
314+
// All content should be empty (no credential data leaked)
315+
const cleanOutput = extractCommandOutput(result.stdout).trim();
316+
expect(cleanOutput).toBe('');
317+
}, 120000);
318+
});
230319

231320
describe('Security Verification', () => {
232321
test('Test 12: Simulated exfiltration attack gets empty data', async () => {

0 commit comments

Comments
 (0)