Threat Intel — Internal
Purpose
Section titled “Purpose”When WP Luminary Silver/Gold scans find suspicious or flagged content, the wp-luminary-proxy Worker logs anonymized telemetry to two D1 tables. This data:
- Powers the public
/v1/threat-feedendpoint (WAF rule generation) - Builds an aggregate view of which plugins are most commonly flagged
- Enables future CrowdSec/ModSecurity/Cloudflare custom rules feeds
Privacy constraint
Section titled “Privacy constraint”Zero PII stored. Specifically excluded: domain, IP address, full file paths, file content, database option values. The logging code enforces this by construction — only derived/aggregated fields are written.
D1 Schema
Section titled “D1 Schema”threat_reports (per-scan records)
Section titled “threat_reports (per-scan records)”Added in migration 0002_threat_intel.sql.
| Column | Type | Description |
|---|---|---|
id |
TEXT PK | crypto.randomUUID() |
scan_type |
TEXT | 'file' or 'db_option' |
file_ext |
TEXT | File extension (e.g. .php) — null for db_option scans |
plugin_slug |
TEXT | Extracted from wp-content/plugins/{slug}/ path pattern — null if not in plugin dir |
plugin_version |
TEXT | From WordPress plugin registry via get_plugins() — null if unknown; max 20 chars |
wp_version |
TEXT | WordPress major.minor only (e.g. 6.7) — stripped at proxy, not plugin |
risk_level |
INTEGER | 1–5 scale from Anthropic response |
status |
TEXT | 'suspicious' or 'flagged' — only these two trigger logging |
patterns |
TEXT | JSON array of matched pattern names (e.g. ["eval_base64","eval_gzinflate"]) |
summary |
TEXT | Anthropic-generated text summary (no user content quoted) |
wpl_version |
TEXT | WP Luminary plugin version — max 20 chars |
reported_date |
TEXT | YYYY-MM-DD only — no time component |
Records are only written when status === 'suspicious' || status === 'flagged' AND patterns.length > 0.
pattern_stats (aggregated counts)
Section titled “pattern_stats (aggregated counts)”| Column | Type | Description |
|---|---|---|
pattern_name |
TEXT PK+ | Pattern identifier (e.g. eval_base64) |
scan_type |
TEXT PK+ | 'file' or 'db_option' |
occurrences |
INTEGER | Running total — incremented via upsert |
last_seen |
TEXT | Most recent YYYY-MM-DD date for this pattern |
Primary key is (pattern_name, scan_type). Upsert uses ON CONFLICT DO UPDATE SET occurrences = occurrences + 1.
Pattern names
Section titled “Pattern names”Pattern names match the keys in the Silver dynamic scan config returned by /v1/scan-config:
| Key | Pattern |
|---|---|
eval_base64 |
eval(base64_decode(...) |
eval_gzinflate |
eval(gzinflate(...) |
eval_str_rot |
eval(str_rot13(...) |
js_inject |
document.write( in <script> tag |
hidden_iframe |
<iframe> with zero dimensions or display:none |
hex_encoded |
Consecutive \x hex escape sequences |
php_tag_inject |
<?php appearing in non-.php context |
document_write_src |
document.write('<script src= |
curl_exec |
curl_exec( call |
system_call |
system(, exec(, passthru(, shell_exec(, popen( |
/v1/threat-feed query
Section titled “/v1/threat-feed query”The public endpoint queries pattern_stats (all rows) and threat_reports (last 30 days, limited to 100 recent records and top 10 flagged plugins by slug).
No authentication required — this is a public feed designed for WAF rule consumers.