feat: initial open marketplace with llm-security, config-audit, ultraplan-local
This commit is contained in:
commit
f93d6abdae
380 changed files with 65935 additions and 0 deletions
163
plugins/llm-security/tests/hooks/pre-edit-secrets.test.mjs
Normal file
163
plugins/llm-security/tests/hooks/pre-edit-secrets.test.mjs
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
// pre-edit-secrets.test.mjs — Tests for hooks/scripts/pre-edit-secrets.mjs
|
||||
// Zero external dependencies: node:test + node:assert only.
|
||||
//
|
||||
// Fake credentials are assembled ONLY at runtime so this source file cannot
|
||||
// self-trigger the pre-edit-secrets hook when written by Claude Code.
|
||||
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { resolve } from 'node:path';
|
||||
import { runHook } from './hook-helper.mjs';
|
||||
|
||||
const SCRIPT = resolve(import.meta.dirname, '../../hooks/scripts/pre-edit-secrets.mjs');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Runtime-assembled fake credentials (no literal patterns in source)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// AWS key ID: AKIA + 16 uppercase alphanumeric chars
|
||||
const awsKeyId = ['AKIA', 'IOSFODNN7EXAMPLE'].join(''); // 20 chars total
|
||||
|
||||
// AWS secret: keyword + 40-char base64-ish value
|
||||
const awsSecretLine = [
|
||||
'aws_secret_access_key = "',
|
||||
'abcdefghij1234567890ABCDEFGHIJ1234567890',
|
||||
'"',
|
||||
].join('');
|
||||
|
||||
// GitHub token: ghp_ prefix + 36 alphanum chars (total >= 40)
|
||||
const ghToken = ['ghp_', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij'].join('');
|
||||
|
||||
// Generic password assignment (>= 8 char value)
|
||||
const pwdLine = ['pass', 'word', ' = "longvalue123456789"'].join('');
|
||||
|
||||
// Bearer token (>= 20 non-space chars after "Bearer ")
|
||||
const bearerLine = [
|
||||
'Authorization: Bearer ',
|
||||
'eyJhbGciOiJSUzI1NiJ9.payload.sig12345678',
|
||||
].join('');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function writePayload(filePath, content) {
|
||||
return { tool_name: 'Write', tool_input: { file_path: filePath, content } };
|
||||
}
|
||||
|
||||
function editPayload(filePath, newString) {
|
||||
return { tool_name: 'Edit', tool_input: { file_path: filePath, new_string: newString } };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BLOCK cases
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('pre-edit-secrets — BLOCK cases', () => {
|
||||
it('blocks a Write containing an AWS Access Key ID pattern', async () => {
|
||||
const result = await runHook(SCRIPT, writePayload(
|
||||
'src/config.js',
|
||||
`const key = "${awsKeyId}";`
|
||||
));
|
||||
assert.equal(result.code, 2);
|
||||
assert.match(result.stderr, /BLOCKED/);
|
||||
assert.match(result.stderr, /AWS Access Key ID/);
|
||||
});
|
||||
|
||||
it('blocks a Write containing an AWS Secret Access Key assignment', async () => {
|
||||
const result = await runHook(SCRIPT, writePayload('src/config.js', awsSecretLine));
|
||||
assert.equal(result.code, 2);
|
||||
assert.match(result.stderr, /BLOCKED/);
|
||||
assert.match(result.stderr, /AWS Secret Access Key/);
|
||||
});
|
||||
|
||||
it('blocks a Write containing a GitHub token pattern', async () => {
|
||||
const result = await runHook(SCRIPT, writePayload(
|
||||
'src/config.js',
|
||||
`const t = "${ghToken}";`
|
||||
));
|
||||
assert.equal(result.code, 2);
|
||||
assert.match(result.stderr, /BLOCKED/);
|
||||
assert.match(result.stderr, /GitHub Token/);
|
||||
});
|
||||
|
||||
it('blocks a Write containing a generic password assignment with a long value', async () => {
|
||||
const result = await runHook(SCRIPT, writePayload('src/config.js', pwdLine));
|
||||
assert.equal(result.code, 2);
|
||||
assert.match(result.stderr, /BLOCKED/);
|
||||
assert.match(result.stderr, /Generic credential assignment/);
|
||||
});
|
||||
|
||||
it('blocks a Write containing a Bearer token in an Authorization header', async () => {
|
||||
const result = await runHook(SCRIPT, writePayload('src/api.js', bearerLine));
|
||||
assert.equal(result.code, 2);
|
||||
assert.match(result.stderr, /BLOCKED/);
|
||||
assert.match(result.stderr, /Authorization header/);
|
||||
});
|
||||
|
||||
it('blocks an Edit where new_string contains an AWS Access Key ID pattern', async () => {
|
||||
const result = await runHook(SCRIPT, editPayload(
|
||||
'src/config.js',
|
||||
`const accessKey = "${awsKeyId}";`
|
||||
));
|
||||
assert.equal(result.code, 2);
|
||||
assert.match(result.stderr, /BLOCKED/);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ALLOW cases
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('pre-edit-secrets — ALLOW cases', () => {
|
||||
it('allows a generic pattern where the value is shorter than 8 characters', async () => {
|
||||
const result = await runHook(SCRIPT, writePayload('src/config.js', 'x = "abc"'));
|
||||
assert.equal(result.code, 0);
|
||||
});
|
||||
|
||||
it('allows a Write to a file in /project/knowledge/ (absolute path) even if content matches a secret pattern', async () => {
|
||||
// The exclusion pattern requires a directory separator before "knowledge"
|
||||
const result = await runHook(SCRIPT, {
|
||||
tool_name: 'Write',
|
||||
tool_input: { file_path: '/project/knowledge/aws-docs.md', content: `Example: ${awsKeyId}` },
|
||||
});
|
||||
assert.equal(result.code, 0);
|
||||
});
|
||||
|
||||
it('allows a Write to a .test.js file even if content matches a secret pattern', async () => {
|
||||
// The exclusion matches .(test|spec|mock).[jt]sx? — covers .test.js but not .test.mjs
|
||||
const result = await runHook(SCRIPT, {
|
||||
tool_name: 'Write',
|
||||
tool_input: { file_path: 'tests/config.test.js', content: `const k = "${awsKeyId}"; // fixture` },
|
||||
});
|
||||
assert.equal(result.code, 0);
|
||||
});
|
||||
|
||||
it('allows a Write to a .example file even if content matches a secret pattern', async () => {
|
||||
const result = await runHook(SCRIPT, {
|
||||
tool_name: 'Write',
|
||||
tool_input: { file_path: 'config.example', content: pwdLine },
|
||||
});
|
||||
assert.equal(result.code, 0);
|
||||
});
|
||||
|
||||
it('allows a Write with content that contains no secrets', async () => {
|
||||
const result = await runHook(SCRIPT, writePayload('src/app.js', 'console.log("Hello");'));
|
||||
assert.equal(result.code, 0);
|
||||
});
|
||||
|
||||
it('allows a Write with empty content', async () => {
|
||||
const result = await runHook(SCRIPT, writePayload('src/app.js', ''));
|
||||
assert.equal(result.code, 0);
|
||||
});
|
||||
|
||||
it('allows a Write where the content field is absent', async () => {
|
||||
const result = await runHook(SCRIPT, { tool_name: 'Write', tool_input: { file_path: 'src/app.js' } });
|
||||
assert.equal(result.code, 0);
|
||||
});
|
||||
|
||||
it('exits 0 gracefully when stdin is not valid JSON', async () => {
|
||||
const result = await runHook(SCRIPT, 'this is not json {{{');
|
||||
assert.equal(result.code, 0);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue