# XShop – Security ## 1. Principles - Sanitize in, validate, escape out. - Nonce + capability for every state-changing request. - Use WP/WC APIs; no hand-rolled auth/crypto. ## 2. Input - `sanitize_text_field`, `sanitize_textarea_field`, `sanitize_email`, `wc_clean`, `absint`, `esc_url_raw` per type. - Validate: enums via allow-list, URLs via `wp_http_validate_url` where remote, dates via `DateTimeImmutable`. - No `$_GET`/`$_POST` without sanitization. No `extract()`. ## 3. Output - `esc_html`, `esc_attr`, `esc_url`, `esc_html__`, `wp_kses_post` (only for intentionally HTML fields with allow-list), `wp_kses` for narrow HTML. - No `echo $raw`. ## 4. CSRF - Every POST/AJAX/REST mutation: `check_ajax_referer` / `wp_verify_nonce` / `X-WP-Nonce` header for REST. - REST: `permission_callback` checks nonce and capability. ## 5. AuthZ - Admin screens: `current_user_can('manage_options')` or `manage_woocommerce` where appropriate (documented per screen). - Q&A: `edit_posts` for moderation; user can delete own question via `delete_post` cap check. - Banners: `manage_options`. ## 6. XSS - No inline `onclick` with untrusted data. No `innerHTML` with unsanitized server data – DOM via `textContent` or sanitized fragment. - Stored XSS: banner CTA/link, Q&A body – sanitized on save, escaped on render. ## 7. SQLi - No raw SQL unless via `$wpdb->prepare`. Prefer `WP_Query`/`WC` CRUD. No string-concatenated queries. ## 8. Special Cases - **File uploads**: use `wp_handle_upload` + `wp_check_filetype`, no arbitrary file execution, no path traversal (`sanitize_file_name`, `realpath` check). - **Redirects**: `wp_safe_redirect` + `wp_validate_redirect` allow-list. - **Unserialize**: never `unserialize` user data; use JSON. - **Secrets**: no inline secrets; no committed `.env`. - **Remote import**: validate MIME, size cap, timeout, no SSRF (allow-list demo asset host if any). ## 9. Headers (theme-level) - Theme does not override server headers; document recommended headers for host (CSP, etc.) in `docs/security.md`. ## 10. Checklist per Feature - [ ] Inputs sanitized + validated - [ ] Nonce verified - [ ] Capability checked - [ ] Outputs escaped - [ ] No raw SQL - [ ] No unsafe redirect/unserialize/upload ## 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.