feat: implement WooCommerce AJAX product search
This commit is contained in:
+10
-1
@@ -58,7 +58,16 @@
|
||||
- [ ] No raw SQL
|
||||
- [ ] No unsafe redirect/unserialize/upload
|
||||
|
||||
## 11. Review Process
|
||||
## 11. Search Security Review (M3 — 2026-09-13)
|
||||
|
||||
- **Endpoint**: `GET /wp-json/xshop/v1/search` `permission_callback __return_true` — public read-only, no auth needed (visitors must search). No private data: only `id/title/url/image/price_html/type/in_stock` from published products; no `post_password`, no customer/order/user, no draft/private (`post_status=publish` enforced), no internal meta beyond `_sku` LIKE. Verified via direct REST: draft products never returned.
|
||||
- **Sanitization**: `search` via `sanitize_text_field` + `wp_strip_all_tags` + `mb_substr 100` + `trim`; `limit` via `absint` 1..20; bounded length prevents DOS via huge query.
|
||||
- **Validation**: `search` max 100 chars, `limit` validate 1..20 else 400 (WP REST validation). Minimum length 2 enforced in handler returns empty 200 without DB query.
|
||||
- **Escaping**: `title` via `html_entity_decode` + JS `textContent`, `url` via `get_permalink` + `esc_url` in handler `price_html` from Woo (trusted but passed as HTML; frontend injects via `innerHTML` after ensuring no script — price_html contains only Woo spans, no user input).
|
||||
- **Abuse**: public endpoint can be crawled; mitigated via `Cache-Control: public, max-age=60`, limited `posts_per_page` (`limit*2` max 40), `no_found_rows:true`, no full table scan (indexed `s` + `meta_query` on `_sku` with LIKE). No N+1 (IDs then single `wc_get_product` per result, max 6). Recommend rate limiting at reverse proxy for high traffic.
|
||||
- **XSS**: `search` never reflected unescaped; response JSON; frontend uses `textContent` for title.
|
||||
|
||||
## 12. Review Process
|
||||
|
||||
- PHPCS `WordPress.Security` + manual review before each release.
|
||||
- Security note in CHANGELOG.md.
|
||||
|
||||
Reference in New Issue
Block a user