Skip to content

Commit ce9f544

Browse files
committed
Patterns 4, 5, and 6
4. Direct file write with user-controlled path 5. Hardcoded credentials in PHP 6. Insecure data deserialization from superglobals
1 parent ef6fdf4 commit ce9f544

12 files changed

Lines changed: 386 additions & 46 deletions

CHANGELOG.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [1.3.10] - 2026-01-14
11+
12+
### Fixed
13+
- **PHP Security Rules**
14+
- `php-user-controlled-file-write`: Fixed a shell variable interpolation bug in the inline grep patterns that prevented detection when the file path was derived from PHP superglobals (e.g., `$_GET`, `$_POST`). The rule now reliably flags direct file writes with user-controlled paths.
15+
- `spo-003-insecure-deserialization`: Hardened the pattern definitions to avoid accidental expansion of shell special variables while scanning for insecure deserialization of superglobal input.
16+
17+
### Internal
18+
- Added an opt-in `DEBUG_PATTERN=1` environment flag for `dist/bin/check-performance.sh` that prints the resolved grep include arguments, patterns, and paths for pattern-based rules to aid future debugging.
19+
20+
### Documentation
21+
- Updated `PROJECT/1-INBOX/RULES-2026-01-14.md` to:
22+
- Reflect that `php-user-controlled-file-write` is hardened as of v1.3.10.
23+
- Promote `spo-003-insecure-deserialization` to a Tier 1 PHP rule with clear rationale and examples.
24+
- Document the `DEBUG_PATTERN=1` flag as a supported internal tool for auditing Tier 1 pattern behavior.
25+
26+
## [1.3.9] - 2026-01-14
27+
28+
### Added
29+
- **Tier 1 Security Rules (PHP)** - Direct file writes and hardcoded credentials
30+
- New rule: `php-user-controlled-file-write` (**CRITICAL**, security)
31+
- Detects `file_put_contents()`, `fopen()`, and `move_uploaded_file()` calls where the target path is derived directly from PHP superglobals (e.g., `$_GET`, `$_POST`)
32+
- Pattern JSON: `dist/patterns/php-user-controlled-file-write.json`
33+
- Scanner integration: new `run_check` block in `dist/bin/check-performance.sh`
34+
- New fixture: `dist/tests/fixtures/php-user-controlled-file-write.php` with direct file write anti-patterns
35+
- New rule: `php-hardcoded-credentials` (**CRITICAL**, security)
36+
- Detects hardcoded API keys, secrets, tokens, and passwords in PHP variables, constants, and Authorization headers
37+
- Pattern JSON: `dist/patterns/php-hardcoded-credentials.json`
38+
- Scanner integration: new `run_check` block in `dist/bin/check-performance.sh`
39+
- New fixture: `dist/tests/fixtures/php-hardcoded-credentials.php` with representative hardcoded credential patterns
40+
41+
### Changed
42+
- **Severity Configuration** - Updated `dist/config/severity-levels.json`
43+
- Incremented `total_checks` from 36 to 38
44+
- Added severity entries for `php-user-controlled-file-write` and `php-hardcoded-credentials` (both CRITICAL, category: security)
45+
1046
## [1.3.8] - 2026-01-14
1147

1248
### Added

PROJECT/1-INBOX/BACKLOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Backlog - Issues to Investigate
22

33
### Checklist - 2025-01-14
4-
- [ ] Continue with Tier 1 rules
4+
- [ ] Continue with Tier 1 rules - First 5 completed
55
- [ ] Fix tty output for HTML reports (The commit added great new features (init, update, tab completion) with proper TTY detection. However, the original HTML generation code (lines 5848-5863) still writes to /dev/tty unconditionally)
66
- [ ] Make a comment in main script to make rules in external files going forward
77
- [ ] Breakout check-performance.sh into multiple files and external rule files

PROJECT/1-INBOX/RULES-2026-01-14.md

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ passthru($user_input);
7474

7575
---
7676

77-
- [ ] ### 4. Direct file write with user-controlled path
77+
- [x] ### 4. Direct file write with user-controlled path
7878

7979
```php
8080
// CRITICAL - Arbitrary file write → RCE
@@ -87,11 +87,14 @@ fwrite($handle, $data); // when $handle from user input
8787

8888
**False positive rate:** Low (~10%). The pattern of user input → file path is specific.
8989

90-
**Current coverage:** Not covered.
90+
**Current coverage:**
91+
- PHP: `php-user-controlled-file-write` (Tier 1 rule, implemented in `check-performance.sh` v1.3.9; detection hardened in v1.3.10)
92+
93+
**Status:** Implemented in scanner (PHP) as of v1.3.9; detection bug fixed in v1.3.10.
9194

9295
---
9396

94-
- [ ] ### 5. Hardcoded credentials in PHP
97+
- [x] ### 5. Hardcoded credentials in PHP
9598

9699
```php
97100
// CRITICAL - Exposed secrets
@@ -105,7 +108,31 @@ $password = 'admin123';
105108

106109
**False positive rate:** Medium (~25%). Example code and tests may trigger this, but the risk of missing a real exposure is too high.
107110

108-
**Current coverage:** Client-side JS only (`headless-api-key-exposure`). PHP not covered.
111+
**Current coverage:**
112+
- Client-side JS: `headless-api-key-exposure` (existing rule for browser bundles)
113+
- PHP: `php-hardcoded-credentials` (new Tier 1 rule, implemented in `check-performance.sh` v1.3.9)
114+
115+
**Status:** Implemented in scanner (PHP + client-side JS coverage) as of v1.3.9.
116+
117+
---
118+
119+
- [x] ### 6. Insecure data deserialization from superglobals
120+
121+
```php
122+
// CRITICAL - Object injection / RCE via unserialize/json_decode/maybe_unserialize
123+
$data = unserialize($_POST['payload']);
124+
$json = json_decode($_GET['json'], true);
125+
$value = maybe_unserialize($_REQUEST['data']);
126+
```
127+
128+
**Why always critical:** Deserializing attacker-controlled input enables object injection and arbitrary code execution via magic methods, especially in large plugin ecosystems.
129+
130+
**False positive rate:** Low–medium (~15%). Some admin tools may intentionally deserialize trusted payloads, but usage with raw `$_GET` / `$_POST` / `$_REQUEST` is a strong red flag.
131+
132+
**Current coverage:**
133+
- PHP: `spo-003-insecure-deserialization` (Tier 1 rule, implemented in `check-performance.sh` v1.3.10)
134+
135+
**Status:** Implemented in scanner (PHP) as of v1.3.10; focuses on deserialization that starts from superglobals to keep false positives manageable.
109136

110137
---
111138

@@ -114,7 +141,6 @@ $password = 'admin123';
114141
These are also effectively zero-tolerance in modern WordPress/PHP code and should be implemented as dedicated scanner rules:
115142

116143
- `create_function()` (deprecated eval-equivalent, strong malware/legacy indicator)
117-
- `unserialize()` with user-controlled data (object injection → RCE)
118144
- `preg_replace()` with the `/e` modifier (deprecated code execution)
119145
- `assert()` with string arguments (code execution in older PHP configs)
120146

@@ -273,10 +299,11 @@ set_transient($key, $data, HOUR_IN_SECONDS); // ← Cache poisoning?
273299
## Implementation Recommendation
274300

275301
### For Tier 1 (Zero-Tolerance)
276-
- Add PHP patterns to match Node.js coverage (including dynamic include/require, eval/create_function, unserialize, `preg_replace` with `/e`, and `assert` with strings)
302+
- Add PHP patterns to match Node.js coverage (including dynamic include/require, eval/create_function, `preg_replace` with `/e`, and `assert` with strings; insecure `unserialize` from superglobals is covered by `spo-003-insecure-deserialization` as of v1.3.10)
277303
- Build should fail on any match
278304
- Baseline exceptions are **strongly discouraged**; if used, they MUST include explicit justification, be reviewed by a human, and still appear as warnings (e.g., "BASELINED CRITICAL ISSUE")
279305
- These are "stop the line" issues
306+
- Scanner internals: when debugging Tier 1 patterns, `DEBUG_PATTERN=1` can be set when running `check-performance.sh` to log the resolved grep patterns and include arguments without changing behavior.
280307

281308
### For Tier 2 (AI-Assisted)
282309
- Scanner flags as "Needs Review"

dist/PATTERN-LIBRARY.json

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
11
{
22
"version": "1.0.0",
3-
"generated": "2026-01-14T22:17:45Z",
3+
"generated": "2026-01-14T22:35:52Z",
44
"summary": {
5-
"total_patterns": 32,
6-
"enabled": 31,
5+
"total_patterns": 34,
6+
"enabled": 33,
77
"disabled": 1,
88
"by_severity": {
9-
"CRITICAL": 12,
9+
"CRITICAL": 14,
1010
"HIGH": 10,
1111
"MEDIUM": 7,
1212
"LOW": 3
1313
},
1414
"by_category": {
15-
"performance": 9,"duplication": 5,"reliability": 5,"security": 11
15+
"performance": 9,"duplication": 5,"reliability": 5,"security": 13
1616
},
1717
"by_pattern_type": {
18-
"php": 21,
18+
"php": 23,
1919
"headless": 6,
2020
"nodejs": 4,
2121
"javascript": 1
2222
},
2323
"mitigation_detection_enabled": 6,
2424
"heuristic_patterns": 10,
25-
"definitive_patterns": 22
25+
"definitive_patterns": 24
2626
},
2727
"patterns": [
2828
{
@@ -305,6 +305,20 @@
305305
"heuristic": false,
306306
"file": "php-eval-injection.json"
307307
},
308+
{
309+
"id": "php-hardcoded-credentials",
310+
"version": "1.0.0",
311+
"enabled": true,
312+
"category": "security",
313+
"severity": "CRITICAL",
314+
"title": "Hardcoded credentials in PHP",
315+
"description": "Detects API keys, secrets, tokens, or passwords hardcoded into PHP source files.",
316+
"detection_type": "direct",
317+
"pattern_type": "php",
318+
"mitigation_detection": false,
319+
"heuristic": false,
320+
"file": "php-hardcoded-credentials.json"
321+
},
308322
{
309323
"id": "php-shell-exec-functions",
310324
"version": "",
@@ -319,6 +333,20 @@
319333
"heuristic": false,
320334
"file": "php-shell-exec-functions.json"
321335
},
336+
{
337+
"id": "php-user-controlled-file-write",
338+
"version": "1.0.0",
339+
"enabled": true,
340+
"category": "security",
341+
"severity": "CRITICAL",
342+
"title": "Direct file write with user-controlled path",
343+
"description": "Detects file writes where the target path is derived directly from PHP superglobals (user-controlled input). This is a common pattern for arbitrary file write vulnerabilities.",
344+
"detection_type": "direct",
345+
"pattern_type": "php",
346+
"mitigation_detection": false,
347+
"heuristic": false,
348+
"file": "php-user-controlled-file-write.json"
349+
},
322350
{
323351
"id": "superglobal-with-nonce-context",
324352
"version": "1.0.0",

dist/PATTERN-LIBRARY.md

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,43 @@
11
# Pattern Library Registry
22

33
**Auto-generated by Pattern Library Manager**
4-
**Last Updated:** 2026-01-14 22:17:45 UTC
4+
**Last Updated:** 2026-01-14 22:35:52 UTC
55

66
---
77

88
## 📊 Summary Statistics
99

1010
### Total Patterns
11-
- **Total:** 32 patterns
12-
- **Enabled:** 31 patterns
11+
- **Total:** 34 patterns
12+
- **Enabled:** 33 patterns
1313
- **Disabled:** 1 patterns
1414

1515
### By Severity
1616
| Severity | Count | Percentage |
1717
|----------|-------|------------|
18-
| CRITICAL | 12 | 37.5% |
19-
| HIGH | 10 | 31.2% |
20-
| MEDIUM | 7 | 21.9% |
21-
| LOW | 3 | 9.4% |
18+
| CRITICAL | 14 | 41.2% |
19+
| HIGH | 10 | 29.4% |
20+
| MEDIUM | 7 | 20.6% |
21+
| LOW | 3 | 8.8% |
2222

2323
### By Type
2424
| Type | Count | Percentage |
2525
|------|-------|------------|
26-
| Definitive | 22 | 68.8% |
27-
| Heuristic | 10 | 31.2% |
26+
| Definitive | 24 | 70.6% |
27+
| Heuristic | 10 | 29.4% |
2828

2929
### Advanced Features
30-
- **Mitigation Detection Enabled:** 6 patterns (18.8%)
30+
- **Mitigation Detection Enabled:** 6 patterns (17.6%)
3131
- **False Positive Reduction:** 60-70% on mitigated patterns
3232

3333
### By Category
3434
- **performance:** 9 patterns
3535
- **duplication:** 5 patterns
3636
- **reliability:** 5 patterns
37-
- **security:** 11 patterns
37+
- **security:** 13 patterns
3838

3939
### By Pattern Type
40-
- **PHP/WordPress:** 21 patterns
40+
- **PHP/WordPress:** 23 patterns
4141
- **Headless WordPress:** 6 patterns
4242
- **Node.js/Server-Side JS:** 4 patterns
4343
- **Client-Side JavaScript:** 1 patterns
@@ -54,7 +54,9 @@
5454
- **njs-001-eval-injection** - Dangerous eval() or code execution
5555
- **php-dynamic-include** - Dynamic PHP include/require with variables
5656
- **php-eval-injection** - Dangerous eval() usage in PHP
57+
- **php-hardcoded-credentials** - Hardcoded credentials in PHP
5758
- **php-shell-exec-functions** - Shell command execution functions in PHP (shell_exec/exec/system/passthru)
59+
- **php-user-controlled-file-write** - Direct file write with user-controlled path
5860
- **unbounded-wc-get-orders** 🛡️ - Unbounded wc_get_orders()
5961
- **unbounded-wc-get-products** - Unbounded wc_get_products()
6062
- **wp-query-unbounded** 🛡️ - Unbounded WP_Query/get_posts
@@ -100,26 +102,26 @@
100102

101103
### Key Selling Points
102104

103-
1. **Comprehensive Coverage:** 32 detection patterns across 4 categories
104-
2. **Multi-Platform Support:** PHP/WordPress (21), Headless WordPress (6), Node.js (4), JavaScript (1)
105+
1. **Comprehensive Coverage:** 34 detection patterns across 4 categories
106+
2. **Multi-Platform Support:** PHP/WordPress (23), Headless WordPress (6), Node.js (4), JavaScript (1)
105107
3. **Enterprise-Grade Accuracy:** 6 patterns with AI-powered mitigation detection (60-70% false positive reduction)
106-
4. **Severity-Based Prioritization:** 12 CRITICAL + 10 HIGH severity patterns catch the most dangerous issues
107-
5. **Intelligent Analysis:** 22 definitive patterns + 10 heuristic patterns for comprehensive code review
108+
4. **Severity-Based Prioritization:** 14 CRITICAL + 10 HIGH severity patterns catch the most dangerous issues
109+
5. **Intelligent Analysis:** 24 definitive patterns + 10 heuristic patterns for comprehensive code review
108110

109111
### One-Liner Stats
110112

111-
> **32 detection patterns** | **6 with AI mitigation** | **60-70% fewer false positives** | **Multi-platform: PHP, Headless, Node.js, JS**
113+
> **34 detection patterns** | **6 with AI mitigation** | **60-70% fewer false positives** | **Multi-platform: PHP, Headless, Node.js, JS**
112114
113115
### Feature Highlights
114116

115-
-**12 CRITICAL** OOM and security patterns
117+
-**14 CRITICAL** OOM and security patterns
116118
-**10 HIGH** performance and security patterns
117119
-**6 patterns** with context-aware severity adjustment
118120
-**10 heuristic** patterns for code quality insights
119121
-**Multi-platform:** WordPress, Headless, Node.js, JavaScript
120122

121123
---
122124

123-
**Generated:** 2026-01-14 22:17:45 UTC
125+
**Generated:** 2026-01-14 22:35:52 UTC
124126
**Version:** 1.0.0
125127
**Tool:** Pattern Library Manager

dist/bin/check-performance.sh

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ source "$REPO_ROOT/lib/pattern-loader.sh"
6161
# This is the ONLY place the version number should be defined.
6262
# All other references (logs, JSON, banners) use this variable.
6363
# Update this ONE line when bumping versions - never hardcode elsewhere.
64-
SCRIPT_VERSION="1.3.8"
64+
SCRIPT_VERSION="1.3.10"
6565

6666
# Get the start/end line range for the enclosing function/method.
6767
#
@@ -2670,11 +2670,19 @@ run_check() {
26702670

26712671
text_echo "${BLUE}$name ${impact_badge}${NC}"
26722672

2673-
# Run grep with all patterns
2674-
local result
2675-
local finding_count=0
2676-
local severity="error"
2677-
[ "$level" = "WARNING" ] && severity="warning"
2673+
# Run grep with all patterns
2674+
local result
2675+
local finding_count=0
2676+
local severity="error"
2677+
[ "$level" = "WARNING" ] && severity="warning"
2678+
2679+
# Optional debug for pattern-based checks (enable with DEBUG_PATTERN=1)
2680+
if [ "${DEBUG_PATTERN:-0}" = "1" ]; then
2681+
text_echo "DEBUG run_check: rule_id=$rule_id"
2682+
text_echo " include_args: $include_args"
2683+
text_echo " patterns: $patterns"
2684+
text_echo " PATHS: $PATHS"
2685+
fi
26782686

26792687
# SAFEGUARD: "$PATHS" MUST be quoted - paths with spaces will break otherwise (see SAFEGUARDS.md)
26802688
if result=$(grep -rHn $EXCLUDE_ARGS $include_args $patterns "$PATHS" 2>/dev/null); then
@@ -3133,10 +3141,34 @@ run_check "ERROR" "$(get_severity "php-shell-exec-functions" "CRITICAL")" "Shell
31333141

31343142
# Insecure data deserialization
31353143
run_check "ERROR" "$(get_severity "spo-003-insecure-deserialization" "CRITICAL")" "Insecure data deserialization" "spo-003-insecure-deserialization" \
3136-
"-E unserialize[[:space:]]*\\(\\$_" \
3137-
"-E base64_decode[[:space:]]*\\(\\$_" \
3138-
"-E json_decode[[:space:]]*\\(\\$_" \
3139-
"-E maybe_unserialize[[:space:]]*\\(\\$_"
3144+
'-E unserialize[[:space:]]*\(\$_' \
3145+
'-E base64_decode[[:space:]]*\(\$_' \
3146+
'-E json_decode[[:space:]]*\(\$_' \
3147+
'-E maybe_unserialize[[:space:]]*\(\$_'
3148+
3149+
# Direct file write with user-controlled path
3150+
run_check "ERROR" "$(get_severity "php-user-controlled-file-write" "CRITICAL")" "Direct file write with user-controlled path" "php-user-controlled-file-write" \
3151+
'-E file_put_contents[[:space:]]*\([^,]*\$_(GET|POST|REQUEST|COOKIE|SERVER|FILES)' \
3152+
'-E fopen[[:space:]]*\([^,]*\$_(GET|POST|REQUEST|COOKIE|SERVER|FILES)' \
3153+
'-E move_uploaded_file[[:space:]]*\([^,]+,[^,]*\$_(GET|POST|REQUEST|COOKIE|SERVER)'
3154+
3155+
# Hardcoded credentials in PHP
3156+
run_check "ERROR" "$(get_severity "php-hardcoded-credentials" "CRITICAL")" "Hardcoded credentials in PHP" "php-hardcoded-credentials" \
3157+
"-E \\$password[[:space:]]*=[[:space:]]*['\"][^'\"]{8,}['\"]" \
3158+
"-E \\$passwd[[:space:]]*=[[:space:]]*['\"][^'\"]{8,}['\"]" \
3159+
"-E \\$secret[[:space:]]*=[[:space:]]*['\"][^'\"]{8,}['\"]" \
3160+
"-E \\$token[[:space:]]*=[[:space:]]*['\"][^'\"]{8,}['\"]" \
3161+
"-E \\$api_key[[:space:]]*=[[:space:]]*['\"][^'\"]{8,}['\"]" \
3162+
"-E \\$apikey[[:space:]]*=[[:space:]]*['\"][^'\"]{8,}['\"]" \
3163+
"-E \\$auth_token[[:space:]]*=[[:space:]]*['\"][^'\"]{8,}['\"]" \
3164+
"-E \\$PASSWORD[[:space:]]*=[[:space:]]*['\"][^'\"]{8,}['\"]" \
3165+
"-E \\$PASSWD[[:space:]]*=[[:space:]]*['\"][^'\"]{8,}['\"]" \
3166+
"-E \\$SECRET[[:space:]]*=[[:space:]]*['\"][^'\"]{8,}['\"]" \
3167+
"-E \\$TOKEN[[:space:]]*=[[:space:]]*['\"][^'\"]{8,}['\"]" \
3168+
"-E \\$API_KEY[[:space:]]*=[[:space:]]*['\"][^'\"]{8,}['\"]" \
3169+
"-E \\$AUTH_TOKEN[[:space:]]*=[[:space:]]*['\"][^'\"]{8,}['\"]" \
3170+
"-E define[[:space:]]*\\([[:space:]]*['\"][A-Z0-9_]*(SECRET|TOKEN|PASSWORD|KEY|API)[A-Z0-9_]*['\"][[:space:]]*,[[:space:]]*['\"][^'\"]{8,}['\"]" \
3171+
"-E \"Authorization\"[[:space:]]*=>[[:space:]]*\"Bearer[[:space:]]+[A-Za-z0-9._-]{16,}\""
31403172

31413173
# Direct database queries without $wpdb->prepare() (SQL injection risk)
31423174
# Note: This check requires custom implementation because we need to filter out lines

0 commit comments

Comments
 (0)