Model: gemini-3.1-flash-lite

Rule

Flag ONLY issues that match a bad-pattern below. Ignore style nits, naming, and anything not listed.

System
Code reviewer. Report only critical issues that match the bad-pattern checklist. Be concise.

Checklist by file type

Only sections matching changed files are sent to the model.

WordPress PHP

`.php` under `wp-content/themes`, `plugins`, or `mu-plugins`

WordPress APIs first; escape output, sanitize input, prepare SQL, verify AJAX/REST.

  • BAD query_posts( array( 'posts_per_page' => -1 ) );

    OK pre_get_posts + $query->set( 'posts_per_page', 10 ) on main query only

    Never query_posts(); unbounded -1 is critical perf risk

  • BAD $wpdb->query( "SELECT * FROM {$wpdb->posts} WHERE ID = " . $_GET["id"] );

    OK $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE ID = %d", absint( $_GET["id"] ) ) );

    Raw SQL with request data — SQLi

  • BAD 'posts_per_page' => $_POST['posts_per_page']

    OK 'posts_per_page' => min( 100, absint( $_POST['posts_per_page'] ?? 10 ) )

    User-controlled pagination without absint/cap

  • BAD echo $_POST['title'];

    OK echo esc_html( sanitize_text_field( wp_unslash( $_POST['title'] ) ) );

    Unescaped output — XSS

  • BAD wp_ajax_* handler without check_ajax_referer() / capability check

    OK check_ajax_referer( 'action', 'nonce' ); if ( ! current_user_can( 'edit_posts' ) ) wp_die();

    CSRF / missing auth on AJAX

  • BAD session_start(); or setcookie() in theme template

    OK Avoid frontend sessions; use WP auth/cookies APIs only when required

    Cache bypass / session fixation risk

  • BAD WP_Query / get_posts inside foreach without static/cache

    OK Single query with post__in or batched meta lookup

    N+1 queries in loops

https://developer.wordpress.org/themes/advanced-topics/security/
https://developer.wordpress.org/reference/functions/query_posts/
https://developer.wordpress.org/reference/classes/wpdb/prepare/

PHP

other `.php` files

Prepared statements for SQL; never trust request data in includes/exec.

  • BAD $pdo->query( "SELECT * FROM users WHERE id = " . $_GET["id"] );

    OK $stmt = $pdo->prepare( "SELECT * FROM users WHERE id = ?" ); $stmt->execute( [ (int) $_GET["id"] ] );

    Concatenated SQL — SQLi (OWASP / php.net)

  • BAD $pdo->prepare( "SELECT * FROM users WHERE id = $id" );

    OK Placeholders in SQL string; values only in execute/bind

    Fake prepare — variable still in query string

  • BAD eval( $userInput ); or unserialize( $_COOKIE['x'] );

    Remote code execution / object injection

  • BAD include $_GET['page'] . '.php';

    OK Whitelist allowed templates: in_array( $page, $allowed, true )

    Local file inclusion

  • BAD API keys / passwords committed in source

    Hardcoded secrets

https://www.php.net/manual/en/security.database.sql-injection.php
https://cheatsheetseries.owasp.org/cheatsheets/Injection_Prevention_Cheat_Sheet.html

JavaScript

`.js`, `.ts`, `.jsx`, `.tsx`

No unsanitized HTML sinks; no polling; validate URLs before href/src.

  • BAD element.innerHTML = userInput;

    OK element.textContent = userInput;

    DOM XSS (OWASP) — use textContent or DOMPurify before innerHTML

  • BAD el.insertAdjacentHTML( 'beforeend', `<a href='${url}'>` );

    OK Build with createElement + setAttribute after URL allowlist

    Unsanitized HTML string with user data

  • BAD setInterval( () => fetch( '/api' ), 1000 );

    Polling — perf / self-DDoS risk

  • BAD anchor.href = userValue; // javascript: or data: URI

    OK Allow only http(s): or relative paths after validation

    Unsafe URL in href/src

  • BAD fetch( url, { method: 'POST' } ) for read-only listing

    OK GET + cache headers where data is public

    Cache bypass on read-heavy endpoints (when changed in diff)

https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
https://github.com/mozilla/eslint-plugin-no-unsanitized

CSS / SCSS

`.css`, `.scss`, `.sass`, `.less`

Critical layout/a11y breaks only — not formatting or naming debates.

  • BAD user-scalable=no or maximum-scale=1 in viewport meta (if added in diff)

    Blocks zoom — WCAG / frontend checklist

  • BAD animation on width/height/top/left causing layout thrashing at scale

    OK Prefer transform/opacity for motion

    Perf-critical animation choice

  • BAD Removed focus styles: *:focus { outline: none; } without replacement

    OK Visible :focus-visible styles for keyboard users

    Accessibility regression

  • BAD position: fixed overlay without z-index/stacking context plan breaking header/modal

    Critical UI breakage in changed selectors

  • BAD display:none on .screen-reader-text / skip-link equivalents

    Hides essential a11y elements

https://frontendchecklist.io/rules/css
https://github.com/samuelwill/css-code-review-checklist

Output format

Example assembled prompt

WordPress PHP: wp-content/themes/example/template.php
JavaScript: wp-content/themes/example/assets/app.js
Styles: wp-content/themes/example/assets/app.scss

Flag ONLY issues that match a bad-pattern below. Ignore style nits, naming, and anything not listed.

## Output
- Critical issues only (file:line + short fix), or: None
- Rating 1–10 (10 = safe to merge)
- Confidence: High | Medium | Low

## Checks (only these patterns)
### WordPress PHP
WordPress APIs first; escape output, sanitize input, prepare SQL, verify AJAX/REST.
Patterns:
- BAD: query_posts( array( 'posts_per_page' => -1 ) );
  Why: Never query_posts(); unbounded -1 is critical perf risk
  GOOD: pre_get_posts + $query->set( 'posts_per_page', 10 ) on main query only
- BAD: $wpdb->query( "SELECT * FROM {$wpdb->posts} WHERE ID = " . $_GET["id"] );
  Why: Raw SQL with request data — SQLi
  GOOD: $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE ID = %d", absint( $_GET["id"] ) ) );
- BAD: 'posts_per_page' => $_POST['posts_per_page']
  Why: User-controlled pagination without absint/cap
  GOOD: 'posts_per_page' => min( 100, absint( $_POST['posts_per_page'] ?? 10 ) )
- BAD: echo $_POST['title'];
  Why: Unescaped output — XSS
  GOOD: echo esc_html( sanitize_text_field( wp_unslash( $_POST['title'] ) ) );
- BAD: wp_ajax_* handler without check_ajax_referer() / capability check
  Why: CSRF / missing auth on AJAX
  GOOD: check_ajax_referer( 'action', 'nonce' ); if ( ! current_user_can( 'edit_posts' ) ) wp_die();
- BAD: session_start(); or setcookie() in theme template
  Why: Cache bypass / session fixation risk
  GOOD: Avoid frontend sessions; use WP auth/cookies APIs only when required
- BAD: WP_Query / get_posts inside foreach without static/cache
  Why: N+1 queries in loops
  GOOD: Single query with post__in or batched meta lookup
### JavaScript
No unsanitized HTML sinks; no polling; validate URLs before href/src.
Patterns:
- BAD: element.innerHTML = userInput;
  Why: DOM XSS (OWASP) — use textContent or DOMPurify before innerHTML
  GOOD: element.textContent = userInput;
- BAD: el.insertAdjacentHTML( 'beforeend', `<a href='${url}'>` );
  Why: Unsanitized HTML string with user data
  GOOD: Build with createElement + setAttribute after URL allowlist
- BAD: setInterval( () => fetch( '/api' ), 1000 );
  Why: Polling — perf / self-DDoS risk
- BAD: anchor.href = userValue; // javascript: or data: URI
  Why: Unsafe URL in href/src
  GOOD: Allow only http(s): or relative paths after validation
- BAD: fetch( url, { method: 'POST' } ) for read-only listing
  Why: Cache bypass on read-heavy endpoints (when changed in diff)
  GOOD: GET + cache headers where data is public
### CSS / SCSS
Critical layout/a11y breaks only — not formatting or naming debates.
Patterns:
- BAD: user-scalable=no or maximum-scale=1 in viewport meta (if added in diff)
  Why: Blocks zoom — WCAG / frontend checklist
- BAD: animation on width/height/top/left causing layout thrashing at scale
  Why: Perf-critical animation choice
  GOOD: Prefer transform/opacity for motion
- BAD: Removed focus styles: *:focus { outline: none; } without replacement
  Why: Accessibility regression
  GOOD: Visible :focus-visible styles for keyboard users
- BAD: position: fixed overlay without z-index/stacking context plan breaking header/modal
  Why: Critical UI breakage in changed selectors
- BAD: display:none on .screen-reader-text / skip-link equivalents
  Why: Hides essential a11y elements