Files
mataba/shop/docs/SECURITY.md
T

3.8 KiB
Raw Blame History

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.