acidtest 0.8.0 → 1.0.1
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/.github/workflows/acidtest-pr-comment.yml +219 -0
- package/README.md +176 -36
- package/dist/analysis/dataflow-graph.d.ts +19 -0
- package/dist/analysis/dataflow-graph.d.ts.map +1 -0
- package/dist/analysis/dataflow-graph.js +365 -0
- package/dist/analysis/dataflow-graph.js.map +1 -0
- package/dist/analysis/dataflow-types.d.ts +86 -0
- package/dist/analysis/dataflow-types.d.ts.map +1 -0
- package/dist/analysis/dataflow-types.js +8 -0
- package/dist/analysis/dataflow-types.js.map +1 -0
- package/dist/analysis/dataflow.test.d.ts +7 -0
- package/dist/analysis/dataflow.test.d.ts.map +1 -0
- package/dist/analysis/dataflow.test.js +257 -0
- package/dist/analysis/dataflow.test.js.map +1 -0
- package/dist/analysis/taint-propagation.d.ts +30 -0
- package/dist/analysis/taint-propagation.d.ts.map +1 -0
- package/dist/analysis/taint-propagation.js +207 -0
- package/dist/analysis/taint-propagation.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/layers/code.d.ts +1 -1
- package/dist/layers/code.d.ts.map +1 -1
- package/dist/layers/code.js +247 -3
- package/dist/layers/code.js.map +1 -1
- package/dist/layers/code.test.js +196 -0
- package/dist/layers/code.test.js.map +1 -1
- package/dist/layers/crossref.d.ts.map +1 -1
- package/dist/layers/crossref.js +7 -0
- package/dist/layers/crossref.js.map +1 -1
- package/dist/layers/dataflow.d.ts +29 -0
- package/dist/layers/dataflow.d.ts.map +1 -0
- package/dist/layers/dataflow.js +217 -0
- package/dist/layers/dataflow.js.map +1 -0
- package/dist/layers/injection.d.ts.map +1 -1
- package/dist/layers/injection.js +8 -1
- package/dist/layers/injection.js.map +1 -1
- package/dist/layers/permissions.d.ts.map +1 -1
- package/dist/layers/permissions.js +7 -0
- package/dist/layers/permissions.js.map +1 -1
- package/dist/mcp-server.js +1 -1
- package/dist/parsers/parser-interface.d.ts +31 -0
- package/dist/parsers/parser-interface.d.ts.map +1 -0
- package/dist/parsers/parser-interface.js +6 -0
- package/dist/parsers/parser-interface.js.map +1 -0
- package/dist/parsers/parsers.test.d.ts +5 -0
- package/dist/parsers/parsers.test.d.ts.map +1 -0
- package/dist/parsers/parsers.test.js +111 -0
- package/dist/parsers/parsers.test.js.map +1 -0
- package/dist/parsers/python-parser.d.ts +18 -0
- package/dist/parsers/python-parser.d.ts.map +1 -0
- package/dist/parsers/python-parser.js +120 -0
- package/dist/parsers/python-parser.js.map +1 -0
- package/dist/parsers/typescript-parser.d.ts +16 -0
- package/dist/parsers/typescript-parser.d.ts.map +1 -0
- package/dist/parsers/typescript-parser.js +112 -0
- package/dist/parsers/typescript-parser.js.map +1 -0
- package/dist/patterns/dangerous-calls-python.json +220 -0
- package/dist/patterns/dangerous-imports-python.json +256 -0
- package/dist/patterns/insecure-crypto.json +163 -0
- package/dist/patterns/prototype-pollution.json +72 -0
- package/dist/patterns/python-deserialization.json +94 -0
- package/dist/patterns/regex-dos.json +50 -0
- package/dist/patterns/sql-injection.json +91 -0
- package/dist/patterns/xss-injection.json +115 -0
- package/dist/reporter.d.ts.map +1 -1
- package/dist/reporter.js +6 -0
- package/dist/reporter.js.map +1 -1
- package/dist/scanner.d.ts +1 -1
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +48 -5
- package/dist/scanner.js.map +1 -1
- package/dist/scanner.test.js +31 -0
- package/dist/scanner.test.js.map +1 -1
- package/dist/schemas/pattern.schema.json +139 -0
- package/dist/test-corpus/validate-corpus.d.ts +7 -0
- package/dist/test-corpus/validate-corpus.d.ts.map +1 -0
- package/dist/test-corpus/validate-corpus.js +341 -0
- package/dist/test-corpus/validate-corpus.js.map +1 -0
- package/dist/types.d.ts +4 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/validation/pattern-validator.d.ts +34 -0
- package/dist/validation/pattern-validator.d.ts.map +1 -0
- package/dist/validation/pattern-validator.js +168 -0
- package/dist/validation/pattern-validator.js.map +1 -0
- package/dist/validation/pattern-validator.test.d.ts +5 -0
- package/dist/validation/pattern-validator.test.d.ts.map +1 -0
- package/dist/validation/pattern-validator.test.js +222 -0
- package/dist/validation/pattern-validator.test.js.map +1 -0
- package/dist/validation/validate-patterns.d.ts +6 -0
- package/dist/validation/validate-patterns.d.ts.map +1 -0
- package/dist/validation/validate-patterns.js +55 -0
- package/dist/validation/validate-patterns.js.map +1 -0
- package/package.json +11 -4
- package/test-fixtures/fixture-no-manifest-node/README.md +4 -0
- package/test-fixtures/fixture-no-manifest-node/index.js +24 -0
- package/test-fixtures/fixture-no-manifest-python/README.md +4 -0
- package/test-fixtures/fixture-no-manifest-python/app.py +24 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema-based validator for AcidTest pattern files
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { createRequire } from 'module';
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
// Load dependencies via require to avoid ESM issues
|
|
11
|
+
const Ajv = require('ajv');
|
|
12
|
+
const addFormats = require('ajv-formats');
|
|
13
|
+
// Load the JSON Schema
|
|
14
|
+
const schemaPath = path.join(__dirname, '../schemas/pattern.schema.json');
|
|
15
|
+
const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf-8'));
|
|
16
|
+
// Initialize AJV with options
|
|
17
|
+
const ajv = new Ajv({
|
|
18
|
+
allErrors: true,
|
|
19
|
+
verbose: true,
|
|
20
|
+
strict: true,
|
|
21
|
+
allowUnionTypes: true,
|
|
22
|
+
});
|
|
23
|
+
// Add format validation
|
|
24
|
+
addFormats(ajv);
|
|
25
|
+
// Compile the schema
|
|
26
|
+
const validate = ajv.compile(schema);
|
|
27
|
+
/**
|
|
28
|
+
* Validate a single pattern file
|
|
29
|
+
* @param filePath Path to the pattern JSON file
|
|
30
|
+
* @returns ValidationResult with validation status and errors
|
|
31
|
+
*/
|
|
32
|
+
export function validatePattern(filePath) {
|
|
33
|
+
try {
|
|
34
|
+
// Read and parse the file
|
|
35
|
+
const fileContent = fs.readFileSync(filePath, 'utf-8');
|
|
36
|
+
let data;
|
|
37
|
+
try {
|
|
38
|
+
data = JSON.parse(fileContent);
|
|
39
|
+
}
|
|
40
|
+
catch (parseError) {
|
|
41
|
+
return {
|
|
42
|
+
valid: false,
|
|
43
|
+
errors: [
|
|
44
|
+
{
|
|
45
|
+
message: `JSON Parse Error: ${parseError instanceof Error ? parseError.message : 'Invalid JSON'}`,
|
|
46
|
+
path: filePath,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
file: filePath,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// Validate against schema
|
|
53
|
+
const isValid = validate(data);
|
|
54
|
+
if (!isValid && validate.errors) {
|
|
55
|
+
const errors = validate.errors.map((error) => ({
|
|
56
|
+
message: formatErrorMessage(error),
|
|
57
|
+
path: error.instancePath || '/',
|
|
58
|
+
keyword: error.keyword,
|
|
59
|
+
params: error.params,
|
|
60
|
+
}));
|
|
61
|
+
return {
|
|
62
|
+
valid: false,
|
|
63
|
+
errors,
|
|
64
|
+
file: filePath,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
// Additional validation: Check for duplicate pattern IDs
|
|
68
|
+
const patternData = data;
|
|
69
|
+
const seenIds = new Set();
|
|
70
|
+
const duplicateErrors = [];
|
|
71
|
+
patternData.patterns.forEach((pattern, index) => {
|
|
72
|
+
if (seenIds.has(pattern.id)) {
|
|
73
|
+
duplicateErrors.push({
|
|
74
|
+
message: `Duplicate pattern ID "${pattern.id}" found at index ${index}`,
|
|
75
|
+
path: `/patterns/${index}/id`,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
seenIds.add(pattern.id);
|
|
79
|
+
// Validate regex patterns can compile
|
|
80
|
+
if (pattern.match.type === 'regex') {
|
|
81
|
+
try {
|
|
82
|
+
new RegExp(pattern.match.value, pattern.match.flags || '');
|
|
83
|
+
}
|
|
84
|
+
catch (regexError) {
|
|
85
|
+
duplicateErrors.push({
|
|
86
|
+
message: `Invalid regex pattern at /patterns/${index}: ${regexError instanceof Error ? regexError.message : 'Invalid regex'}`,
|
|
87
|
+
path: `/patterns/${index}/match/value`,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
if (duplicateErrors.length > 0) {
|
|
93
|
+
return {
|
|
94
|
+
valid: false,
|
|
95
|
+
errors: duplicateErrors,
|
|
96
|
+
file: filePath,
|
|
97
|
+
patternCount: patternData.patterns.length,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
valid: true,
|
|
102
|
+
errors: [],
|
|
103
|
+
file: filePath,
|
|
104
|
+
patternCount: patternData.patterns.length,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
return {
|
|
109
|
+
valid: false,
|
|
110
|
+
errors: [
|
|
111
|
+
{
|
|
112
|
+
message: `Unexpected error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
113
|
+
path: filePath,
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
file: filePath,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Validate all pattern files in a directory
|
|
122
|
+
* @param patternsDir Directory containing pattern JSON files
|
|
123
|
+
* @returns Array of ValidationResult for each file
|
|
124
|
+
*/
|
|
125
|
+
export function validateAllPatterns(patternsDir) {
|
|
126
|
+
try {
|
|
127
|
+
const files = fs.readdirSync(patternsDir).filter((f) => f.endsWith('.json'));
|
|
128
|
+
return files.map((file) => validatePattern(path.join(patternsDir, file)));
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
return [
|
|
132
|
+
{
|
|
133
|
+
valid: false,
|
|
134
|
+
errors: [
|
|
135
|
+
{
|
|
136
|
+
message: `Failed to read patterns directory: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
137
|
+
path: patternsDir,
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Format AJV error into human-readable message
|
|
146
|
+
*/
|
|
147
|
+
function formatErrorMessage(error) {
|
|
148
|
+
const path = error.instancePath || '/';
|
|
149
|
+
switch (error.keyword) {
|
|
150
|
+
case 'required':
|
|
151
|
+
return `Missing required field: ${error.params?.missingProperty} at ${path}`;
|
|
152
|
+
case 'enum':
|
|
153
|
+
return `Invalid value at ${path}. Must be one of: ${JSON.stringify(error.params?.allowedValues)}`;
|
|
154
|
+
case 'pattern':
|
|
155
|
+
return `Value at ${path} does not match required pattern: ${error.params?.pattern}`;
|
|
156
|
+
case 'minLength':
|
|
157
|
+
return `Value at ${path} is too short (minimum length: ${error.params?.limit})`;
|
|
158
|
+
case 'minItems':
|
|
159
|
+
return `Array at ${path} has too few items (minimum: ${error.params?.limit})`;
|
|
160
|
+
case 'type':
|
|
161
|
+
return `Invalid type at ${path}. Expected: ${error.params?.type}`;
|
|
162
|
+
case 'additionalProperties':
|
|
163
|
+
return `Unexpected property "${error.params?.additionalProperty}" at ${path}`;
|
|
164
|
+
default:
|
|
165
|
+
return `${error.message || 'Validation error'} at ${path}`;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=pattern-validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pattern-validator.js","sourceRoot":"","sources":["../../src/validation/pattern-validator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pattern-validator.test.d.ts","sourceRoot":"","sources":["../../src/validation/pattern-validator.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for pattern validation
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
5
|
+
import { mkdirSync, writeFileSync, rmSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { validatePattern, validateAllPatterns } from './pattern-validator.js';
|
|
8
|
+
describe('Pattern Validator', () => {
|
|
9
|
+
const testDir = join(process.cwd(), 'test-validation-temp');
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
mkdirSync(testDir, { recursive: true });
|
|
12
|
+
});
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
15
|
+
});
|
|
16
|
+
it('should validate a valid pattern file', () => {
|
|
17
|
+
const validPattern = {
|
|
18
|
+
category: 'test-patterns',
|
|
19
|
+
patterns: [
|
|
20
|
+
{
|
|
21
|
+
id: 'tp-001',
|
|
22
|
+
name: 'test-pattern',
|
|
23
|
+
description: 'A test pattern',
|
|
24
|
+
severity: 'HIGH',
|
|
25
|
+
match: {
|
|
26
|
+
type: 'regex',
|
|
27
|
+
value: 'test.*pattern',
|
|
28
|
+
flags: 'i'
|
|
29
|
+
},
|
|
30
|
+
layer: 'markdown'
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
};
|
|
34
|
+
const filePath = join(testDir, 'valid-pattern.json');
|
|
35
|
+
writeFileSync(filePath, JSON.stringify(validPattern, null, 2));
|
|
36
|
+
const result = validatePattern(filePath);
|
|
37
|
+
expect(result.valid).toBe(true);
|
|
38
|
+
expect(result.errors).toHaveLength(0);
|
|
39
|
+
expect(result.patternCount).toBe(1);
|
|
40
|
+
});
|
|
41
|
+
it('should fail validation when required field is missing (name)', () => {
|
|
42
|
+
const invalidPattern = {
|
|
43
|
+
category: 'test-patterns',
|
|
44
|
+
patterns: [
|
|
45
|
+
{
|
|
46
|
+
id: 'tp-001',
|
|
47
|
+
// name is missing
|
|
48
|
+
severity: 'HIGH',
|
|
49
|
+
match: {
|
|
50
|
+
type: 'regex',
|
|
51
|
+
value: 'test.*pattern'
|
|
52
|
+
},
|
|
53
|
+
layer: 'markdown'
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
};
|
|
57
|
+
const filePath = join(testDir, 'missing-name.json');
|
|
58
|
+
writeFileSync(filePath, JSON.stringify(invalidPattern, null, 2));
|
|
59
|
+
const result = validatePattern(filePath);
|
|
60
|
+
expect(result.valid).toBe(false);
|
|
61
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
62
|
+
expect(result.errors[0].message).toMatch(/name/i);
|
|
63
|
+
});
|
|
64
|
+
it('should fail validation with invalid severity enum', () => {
|
|
65
|
+
const invalidPattern = {
|
|
66
|
+
category: 'test-patterns',
|
|
67
|
+
patterns: [
|
|
68
|
+
{
|
|
69
|
+
id: 'tp-001',
|
|
70
|
+
name: 'test-pattern',
|
|
71
|
+
severity: 'INVALID_SEVERITY', // Invalid severity
|
|
72
|
+
match: {
|
|
73
|
+
type: 'regex',
|
|
74
|
+
value: 'test.*pattern'
|
|
75
|
+
},
|
|
76
|
+
layer: 'markdown'
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
};
|
|
80
|
+
const filePath = join(testDir, 'invalid-severity.json');
|
|
81
|
+
writeFileSync(filePath, JSON.stringify(invalidPattern, null, 2));
|
|
82
|
+
const result = validatePattern(filePath);
|
|
83
|
+
expect(result.valid).toBe(false);
|
|
84
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
85
|
+
expect(result.errors[0].message).toMatch(/(severity|enum)/i);
|
|
86
|
+
});
|
|
87
|
+
it('should fail validation with invalid match type', () => {
|
|
88
|
+
const invalidPattern = {
|
|
89
|
+
category: 'test-patterns',
|
|
90
|
+
patterns: [
|
|
91
|
+
{
|
|
92
|
+
id: 'tp-001',
|
|
93
|
+
name: 'test-pattern',
|
|
94
|
+
severity: 'HIGH',
|
|
95
|
+
match: {
|
|
96
|
+
type: 'invalid-type', // Invalid match type
|
|
97
|
+
value: 'test.*pattern'
|
|
98
|
+
},
|
|
99
|
+
layer: 'markdown'
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
};
|
|
103
|
+
const filePath = join(testDir, 'invalid-match-type.json');
|
|
104
|
+
writeFileSync(filePath, JSON.stringify(invalidPattern, null, 2));
|
|
105
|
+
const result = validatePattern(filePath);
|
|
106
|
+
expect(result.valid).toBe(false);
|
|
107
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
108
|
+
expect(result.errors[0].message).toMatch(/(match|type|enum)/i);
|
|
109
|
+
});
|
|
110
|
+
it('should validate all current pattern files in src/patterns/', () => {
|
|
111
|
+
const patternsDir = join(process.cwd(), 'src', 'patterns');
|
|
112
|
+
const results = validateAllPatterns(patternsDir);
|
|
113
|
+
// All pattern files should be valid
|
|
114
|
+
const allValid = results.every((result) => result.valid);
|
|
115
|
+
expect(allValid).toBe(true);
|
|
116
|
+
// Should have validated at least 6 files (based on current patterns)
|
|
117
|
+
expect(results.length).toBeGreaterThanOrEqual(6);
|
|
118
|
+
// Display any errors for debugging
|
|
119
|
+
const errors = results.filter((r) => !r.valid);
|
|
120
|
+
if (errors.length > 0) {
|
|
121
|
+
console.error('Pattern validation errors:');
|
|
122
|
+
errors.forEach((error) => {
|
|
123
|
+
console.error(` File: ${error.file}`);
|
|
124
|
+
error.errors.forEach((e) => {
|
|
125
|
+
console.error(` - ${e.message}`);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
it('should detect duplicate pattern IDs in the same file', () => {
|
|
131
|
+
const duplicatePattern = {
|
|
132
|
+
category: 'test-patterns',
|
|
133
|
+
patterns: [
|
|
134
|
+
{
|
|
135
|
+
id: 'tp-001',
|
|
136
|
+
name: 'first-pattern',
|
|
137
|
+
severity: 'HIGH',
|
|
138
|
+
match: {
|
|
139
|
+
type: 'regex',
|
|
140
|
+
value: 'test1'
|
|
141
|
+
},
|
|
142
|
+
layer: 'markdown'
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: 'tp-001', // Duplicate ID
|
|
146
|
+
name: 'second-pattern',
|
|
147
|
+
severity: 'HIGH',
|
|
148
|
+
match: {
|
|
149
|
+
type: 'regex',
|
|
150
|
+
value: 'test2'
|
|
151
|
+
},
|
|
152
|
+
layer: 'markdown'
|
|
153
|
+
}
|
|
154
|
+
]
|
|
155
|
+
};
|
|
156
|
+
const filePath = join(testDir, 'duplicate-ids.json');
|
|
157
|
+
writeFileSync(filePath, JSON.stringify(duplicatePattern, null, 2));
|
|
158
|
+
const result = validatePattern(filePath);
|
|
159
|
+
expect(result.valid).toBe(false);
|
|
160
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
161
|
+
expect(result.errors[0].message).toMatch(/duplicate/i);
|
|
162
|
+
});
|
|
163
|
+
it('should detect invalid regex patterns', () => {
|
|
164
|
+
const invalidRegexPattern = {
|
|
165
|
+
category: 'test-patterns',
|
|
166
|
+
patterns: [
|
|
167
|
+
{
|
|
168
|
+
id: 'tp-001',
|
|
169
|
+
name: 'invalid-regex',
|
|
170
|
+
severity: 'HIGH',
|
|
171
|
+
match: {
|
|
172
|
+
type: 'regex',
|
|
173
|
+
value: '[invalid(regex' // Invalid regex
|
|
174
|
+
},
|
|
175
|
+
layer: 'markdown'
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
};
|
|
179
|
+
const filePath = join(testDir, 'invalid-regex.json');
|
|
180
|
+
writeFileSync(filePath, JSON.stringify(invalidRegexPattern, null, 2));
|
|
181
|
+
const result = validatePattern(filePath);
|
|
182
|
+
expect(result.valid).toBe(false);
|
|
183
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
184
|
+
expect(result.errors[0].message).toMatch(/regex/i);
|
|
185
|
+
});
|
|
186
|
+
it('should validate remediation structure when present', () => {
|
|
187
|
+
const patternWithRemediation = {
|
|
188
|
+
category: 'test-patterns',
|
|
189
|
+
patterns: [
|
|
190
|
+
{
|
|
191
|
+
id: 'tp-001',
|
|
192
|
+
name: 'test-pattern',
|
|
193
|
+
severity: 'HIGH',
|
|
194
|
+
match: {
|
|
195
|
+
type: 'regex',
|
|
196
|
+
value: 'test'
|
|
197
|
+
},
|
|
198
|
+
layer: 'code',
|
|
199
|
+
remediation: {
|
|
200
|
+
title: 'Fix this issue',
|
|
201
|
+
suggestions: [
|
|
202
|
+
'Do this',
|
|
203
|
+
'Then do that'
|
|
204
|
+
],
|
|
205
|
+
autofix: true,
|
|
206
|
+
fixAction: {
|
|
207
|
+
type: 'replace',
|
|
208
|
+
pattern: 'old',
|
|
209
|
+
replacement: 'new'
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
]
|
|
214
|
+
};
|
|
215
|
+
const filePath = join(testDir, 'with-remediation.json');
|
|
216
|
+
writeFileSync(filePath, JSON.stringify(patternWithRemediation, null, 2));
|
|
217
|
+
const result = validatePattern(filePath);
|
|
218
|
+
expect(result.valid).toBe(true);
|
|
219
|
+
expect(result.errors).toHaveLength(0);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
//# sourceMappingURL=pattern-validator.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pattern-validator.test.js","sourceRoot":"","sources":["../../src/validation/pattern-validator.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-patterns.d.ts","sourceRoot":"","sources":["../../src/validation/validate-patterns.ts"],"names":[],"mappings":";AAEA;;GAEG"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI script to validate all AcidTest pattern files
|
|
4
|
+
*/
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { validateAllPatterns } from './pattern-validator.js';
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const patternsDir = path.join(__dirname, '../patterns');
|
|
10
|
+
console.log('Validating AcidTest pattern files...\n');
|
|
11
|
+
// Validate all patterns
|
|
12
|
+
const results = validateAllPatterns(patternsDir);
|
|
13
|
+
// Track statistics
|
|
14
|
+
let totalFiles = 0;
|
|
15
|
+
let validFiles = 0;
|
|
16
|
+
let totalPatterns = 0;
|
|
17
|
+
let hasErrors = false;
|
|
18
|
+
// Process and display results
|
|
19
|
+
for (const result of results) {
|
|
20
|
+
totalFiles++;
|
|
21
|
+
if (result.valid) {
|
|
22
|
+
validFiles++;
|
|
23
|
+
totalPatterns += result.patternCount || 0;
|
|
24
|
+
const fileName = result.file ? path.basename(result.file) : 'unknown';
|
|
25
|
+
console.log(`✓ ${fileName} (${result.patternCount || 0} pattern${result.patternCount !== 1 ? 's' : ''})`);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
hasErrors = true;
|
|
29
|
+
const fileName = result.file ? path.basename(result.file) : 'unknown';
|
|
30
|
+
console.error(`✗ ${fileName}\n`);
|
|
31
|
+
// Display all errors for this file
|
|
32
|
+
for (const error of result.errors) {
|
|
33
|
+
console.error(` [Error] ${error.message}`);
|
|
34
|
+
if (error.path && error.path !== fileName) {
|
|
35
|
+
console.error(` Path: ${error.path}`);
|
|
36
|
+
}
|
|
37
|
+
console.error('');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Display summary
|
|
42
|
+
console.log(`${'─'.repeat(50)}`);
|
|
43
|
+
console.log('Validation Results:');
|
|
44
|
+
console.log(` Files checked: ${totalFiles}`);
|
|
45
|
+
console.log(` Valid files: ${validFiles}`);
|
|
46
|
+
console.log(` Patterns validated: ${totalPatterns}`);
|
|
47
|
+
if (hasErrors) {
|
|
48
|
+
console.log(` Status: ✗ FAILED\n`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.log(` Status: ✓ PASSED\n`);
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=validate-patterns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-patterns.js","sourceRoot":"","sources":["../../src/validation/validate-patterns.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "acidtest",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Security scanner for AI agent skills. Scan before you install.",
|
|
6
6
|
"bin": {
|
|
7
7
|
"acidtest": "./dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"validate": "
|
|
11
|
-
"
|
|
10
|
+
"validate:patterns": "tsx src/validation/validate-patterns.ts",
|
|
11
|
+
"prebuild": "npm run validate:patterns",
|
|
12
|
+
"build": "tsc && mkdir -p dist/patterns dist/schemas && cp src/patterns/*.json dist/patterns/ && cp src/schemas/*.json dist/schemas/",
|
|
12
13
|
"dev": "npm run build && node dist/index.js",
|
|
13
14
|
"watch": "tsc --watch",
|
|
14
15
|
"test": "vitest run",
|
|
15
16
|
"test:watch": "vitest",
|
|
16
17
|
"test:ui": "vitest --ui",
|
|
17
|
-
"test:coverage": "vitest run --coverage"
|
|
18
|
+
"test:coverage": "vitest run --coverage",
|
|
19
|
+
"test:corpus": "tsx src/test-corpus/validate-corpus.ts"
|
|
18
20
|
},
|
|
19
21
|
"files": [
|
|
20
22
|
"dist",
|
|
@@ -41,17 +43,22 @@
|
|
|
41
43
|
"homepage": "https://acidtest.dev",
|
|
42
44
|
"dependencies": {
|
|
43
45
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
46
|
+
"ajv": "^8.17.1",
|
|
47
|
+
"ajv-formats": "^3.0.1",
|
|
44
48
|
"chalk": "^5.3.0",
|
|
45
49
|
"chokidar": "^5.0.0",
|
|
46
50
|
"cli-table3": "^0.6.5",
|
|
47
51
|
"glob": "^10.3.10",
|
|
48
52
|
"gray-matter": "^4.0.3",
|
|
49
53
|
"ora": "^9.3.0",
|
|
54
|
+
"tree-sitter": "^0.21.1",
|
|
55
|
+
"tree-sitter-python": "^0.21.0",
|
|
50
56
|
"typescript": "^5.3.3"
|
|
51
57
|
},
|
|
52
58
|
"devDependencies": {
|
|
53
59
|
"@types/node": "^20.11.0",
|
|
54
60
|
"@vitest/ui": "^4.0.18",
|
|
61
|
+
"tsx": "^4.21.0",
|
|
55
62
|
"vitest": "^4.0.18"
|
|
56
63
|
},
|
|
57
64
|
"engines": {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Simple Express.js-style application without SKILL.md
|
|
2
|
+
const express = require('express');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
const app = express();
|
|
6
|
+
const PORT = process.env.PORT || 3000;
|
|
7
|
+
|
|
8
|
+
// Some potentially risky patterns to detect
|
|
9
|
+
app.get('/data', (req, res) => {
|
|
10
|
+
// Reading from filesystem
|
|
11
|
+
const filePath = `/tmp/${req.query.file}`;
|
|
12
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
13
|
+
res.send(content);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
app.get('/eval', (req, res) => {
|
|
17
|
+
// Code injection vulnerability
|
|
18
|
+
const result = eval(req.query.code);
|
|
19
|
+
res.json({ result });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
app.listen(PORT, () => {
|
|
23
|
+
console.log(`Server running on port ${PORT}`);
|
|
24
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Simple Flask application without SKILL.md
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
from flask import Flask, request
|
|
6
|
+
|
|
7
|
+
app = Flask(__name__)
|
|
8
|
+
|
|
9
|
+
@app.route('/exec')
|
|
10
|
+
def execute_command():
|
|
11
|
+
# Shell execution vulnerability
|
|
12
|
+
cmd = request.args.get('cmd')
|
|
13
|
+
result = subprocess.check_output(cmd, shell=True)
|
|
14
|
+
return result
|
|
15
|
+
|
|
16
|
+
@app.route('/env')
|
|
17
|
+
def get_env():
|
|
18
|
+
# Access environment variables
|
|
19
|
+
api_key = os.environ.get('API_KEY')
|
|
20
|
+
secret = os.environ.get('SECRET_TOKEN')
|
|
21
|
+
return {'api_key': api_key, 'secret': secret}
|
|
22
|
+
|
|
23
|
+
if __name__ == '__main__':
|
|
24
|
+
app.run(debug=True)
|