📦 deps(thirdparty): update snapshots

This commit is contained in:
ci[bot]
2026-07-18 00:02:59 +00:00
parent 82f7c6e56a
commit 47ce7f78dc
1446 changed files with 141041 additions and 6442 deletions
@@ -0,0 +1,88 @@
# WP Guard — Internationalization Reference
## Contents
- Wrapper selection
- Text domain rules
- Placeholders and translator comments
- Plurals
- Sentence assembly
- JavaScript i18n
- Dates, numbers, RTL
- Multilingual-plugin gotchas
## Wrapper selection
| Situation | Use |
|---|---|
| Return a translated string | `__( 'Text', 'my-plugin' )` |
| Echo a translated string | `_e( 'Text', 'my-plugin' )` — or better, `esc_html_e()` |
| Translate + escape for HTML | `esc_html__()` / `esc_html_e()` |
| Translate + escape for attribute | `esc_attr__()` / `esc_attr_e()` |
| Ambiguous word needing context | `_x( 'Post', 'verb', 'my-plugin' )` |
| Plural | `_n( '%s item', '%s items', $count, 'my-plugin' )` |
| Plural + context | `_nx()` |
When a string is both translated and output, the combined wrappers (`esc_html__`) are required — translations are untrusted input like any other (a compromised translation file is an XSS vector).
## Text domain rules
- Literal string, always: `__( 'Text', 'my-plugin' )`. Never `__( 'Text', PLUGIN_DOMAIN )`, never a variable — static analysis tools and translate.wordpress.org both fail on non-literal domains.
- Must match the plugin slug exactly (the WordPress.org directory enforces this).
- One domain per plugin/theme. AI agents copying snippets from other projects routinely import foreign text domains — grep for domains that don't match the project's.
## Placeholders and translator comments
```php
/* translators: 1: customer name, 2: order number. */
$message = sprintf(
__( 'Hi %1$s, your order #%2$d is on its way.', 'my-plugin' ),
$customer_name,
$order_id
);
```
- Numbered placeholders (`%1$s`) whenever there is more than one — translators reorder words.
- A `/* translators: … */` comment on every string with placeholders, immediately above the line.
- Never put variables or HTML soup inside the translatable string when it can sit outside.
## Plurals
`_n()` exists because languages have between one and six plural forms. Never:
```php
// Wrong — English-only logic.
$label = $count === 1 ? __( 'item', 'my-plugin' ) : __( 'items', 'my-plugin' );
```
Always `_n( '%s item', '%s items', $count, 'my-plugin' )`, then `sprintf()` with `number_format_i18n( $count )`.
## Sentence assembly
Never build sentences by concatenation — word order differs across languages:
```php
// Wrong: translators get fragments they cannot reorder.
echo __( 'Imported', 'my-plugin' ) . ' ' . $count . ' ' . __( 'products', 'my-plugin' );
// Right: one string, one placeholder, full context.
/* translators: %s: number of imported products. */
printf( esc_html__( 'Imported %s products.', 'my-plugin' ), number_format_i18n( $count ) );
```
## JavaScript i18n
- `wp_set_script_translations( 'my-handle', 'my-plugin' )` after enqueueing; use `__()` from `@wordpress/i18n` in the JS.
- Legacy pattern (`wp_localize_script` with pre-translated strings) is acceptable in legacy codebases — match the project.
## Dates, numbers, RTL
- Dates: `wp_date()` / `date_i18n()` with the site's format options — never raw `date()` for display.
- Numbers: `number_format_i18n()`.
- CSS: logical properties (`margin-inline-start`, not `margin-left`) for new styles; don't hand-write directional CSS — build `-rtl.css` files with RTLCSS. Auto-loading them applies only to core styles and block.json-registered styles; classic plugin/theme handles must opt in with `wp_style_add_data( $handle, 'rtl', 'replace' )`.
## Multilingual-plugin gotchas (WPML / Polylang)
- Strings stored in options/meta are NOT translated by `__()` — they need string registration (WPML String Translation / `pll_register_string`). Flag stored user-facing strings during review.
- IDs are language-specific: a hardcoded `page_id` points at one language's page. Resolve through the multilingual plugin's API or filters when the project uses one.
- Queries on multilingual sites are language-filtered by default — explicitly note when a query intentionally crosses languages.
@@ -0,0 +1,70 @@
# WP Guard — Performance Reference
## Contents
- WP_Query discipline
- Meta and term cache priming
- Transients vs object cache
- Options autoload hygiene
- Asset loading
- Cron and background work
- Scaling traps checklist
## WP_Query discipline
- `posts_per_page => -1` is forbidden. There is always a bound; if the caller "needs everything," page through with a loop or use a bounded cap the project agrees on.
- `query_posts()` is forbidden — it clobbers the main query. Use `WP_Query` or `pre_get_posts`.
- Only IDs needed → `'fields' => 'ids'` (skips row hydration and caches).
- Not paginating → `'no_found_rows' => true` (skips `SQL_CALC_FOUND_ROWS`).
- Not using meta/terms in the loop → `'update_post_meta_cache' => false`, `'update_post_term_cache' => false`.
- Avoid `'meta_query'` on unindexed scans for high-traffic paths — meta queries do not scale on large `postmeta` tables; consider a lookup table or taxonomy when the access pattern is hot.
- Never run queries inside `foreach` when one query (or cache priming) can fetch the set.
## Meta and term cache priming
The N+1 killer. When iterating IDs and reading meta per item:
```php
$ids = get_posts( array( 'fields' => 'ids', /* … */ ) );
update_meta_cache( 'post', $ids ); // one query primes the cache
foreach ( $ids as $id ) {
$sku = get_post_meta( $id, '_sku', true ); // served from cache
}
```
`update_meta_cache()` and `update_object_term_cache()` exist for exactly this; on WP ≥ 6.1, `_prime_post_caches()` batches posts, meta, and terms in one call (public API since 6.1 despite the underscore). AI-generated loops skip them every time.
## Transients vs object cache
- `set_transient()` / `get_transient()` — persistent cache with TTL; backed by the object cache when a drop-in exists, by the options table otherwise.
- `wp_cache_get()` / `wp_cache_set()` — request-scope by default, persistent only with an object-cache drop-in (Redis/Memcached). Use cache groups and deliberate TTLs.
- Expensive remote calls (Rule 11) always go behind one of these. Choose TTL consciously; never cache user-specific data in a shared key — include the user/locale in the key when output varies by them.
- On failure paths, decide explicitly whether to cache the failure (short TTL) or retry every request — and say which in a comment.
## Options autoload hygiene
Every `autoload => true` option loads on EVERY request into `alloptions`. Rules:
- `add_option( 'ncs_big_report_cache', $data, '', false )` — large or rarely-read data never autoloads (pass `false` as the third arg of `update_option` too; the parameter exists since WP 4.2).
- Keep individual autoloaded options small; an `alloptions` blob over ~1 MB is a known site-killer on high-traffic sites.
- One serialized settings array per plugin beats twenty separate options.
## Asset loading
- Enqueue only where used: check `is_admin()`, screen IDs (`get_current_screen()`), shortcode presence, or block usage before enqueueing.
- Front-end assets registered on `wp_enqueue_scripts`, admin on `admin_enqueue_scripts` with the `$hook_suffix` check.
- Version assets with the plugin version (cache busting); load non-critical JS with `defer`/`async` strategies (`wp_register_script` args on WP ≥ 6.3).
## Cron and background work
- WP-Cron is traffic-driven and can stack: guard handlers against overlap (a lock transient), keep events idempotent.
- Long or high-volume jobs → Action Scheduler when WooCommerce (or the library) is present.
- Never `sleep()` in a request; never do batch work on `init` for every visitor.
## Scaling traps checklist
- Unbounded `get_users()` / `get_terms()` on large sites — same rules as posts.
- `switch_to_blog()` in loops without batching (multisite).
- Per-request remote HTTP without caching — one slow third party becomes your TTFB.
- Writing to options on front-end requests (cache invalidation storm under traffic).
- Counting rows with `found_posts` when an indexed count query or cached counter would do.
@@ -0,0 +1,57 @@
# WP Guard — Review Checklist
Structured walk for review mode. Lead with findings, not summary. Cite file:line. Prioritize: security → silent breakage → i18n → performance.
## Contents
- Pass 1: Security sweep
- Pass 2: API and hook correctness
- Pass 3: i18n
- Pass 4: Performance
- Pass 5: Packaging hygiene
- Reporting
## Pass 1: Security sweep (must fix)
Grep-driven; check every hit:
- `echo`, `print`, `<?=`, `printf` — every variable escaped with the context-correct `esc_*`/`wp_kses`? (Rule 1)
- `$_POST`, `$_GET`, `$_REQUEST`, `$_SERVER`, `$_COOKIE``wp_unslash()` then sanitized? (Rule 2)
- `add_action( 'wp_ajax_`, `admin_post_`, `rest_api_init` — every state-changing handler has BOTH `current_user_can()` and a nonce check / real `permission_callback`? (Rule 3)
- `$wpdb->` — every variable behind `prepare()` placeholders? `esc_like()` for LIKE? (Rule 4)
- `$_FILES` — handled via `wp_handle_upload()` with type allowlist?
- Secrets: API keys hardcoded? Logged? In autoloaded options when they belong in constants/env?
## Pass 2: API and hook correctness (should fix)
- Every hooked hook and called function exists in supported WP/plugin versions? (Rule 6 — hallucinated hooks fail silently)
- Hook timing right? (No front-end work on admin hooks, no early queries, no `init`-heavy work per request)
- Core API replacements: curl, raw `<script>` echoes, manual cron loops, direct file ops? (Rule 5)
- Prefixes on every public name: functions, options, transients, meta keys, handles, AJAX actions? (Rule 7)
- `ABSPATH` guard present in working files? (Rule 8)
- `wp_safe_redirect()` + `exit` after state changes?
## Pass 3: i18n (should fix; blocking on multilingual projects)
- User-facing strings without wrappers? Wrong/non-literal text domain? (Rule 9)
- Placeholders without translator comments? Plural logic without `_n()`?
- Concatenated sentence fragments?
- Stored user-facing strings (options/meta) that multilingual plugins can't see? (see [i18n.md](i18n.md))
- Raw `date()`/`number_format()` for display?
## Pass 4: Performance (worth noting; blocking on every-request paths)
- `posts_per_page => -1`, `query_posts()`, unbounded `get_users()`/`get_terms()`? (Rule 10)
- Queries or `get_post_meta()` inside loops without cache priming?
- Remote calls without transient/object caching? (Rule 11)
- Large options autoloading? Unconditional asset enqueues?
## Pass 5: Packaging hygiene
- WooCommerce code in scope → hand off to woo-guard when it is installed; otherwise apply WooCommerce's HPOS, CRUD, checkout, and money rules from its developer documentation.
- Activation/deactivation/uninstall: scheduled events cleared, transients cleaned, uninstall removes its data?
- Direct entry files, readme version mismatches, debug code (`error_log`, `var_dump`) left in?
## Reporting
Use the SKILL.md reporting format. Lead with the security findings and an overall verdict (merge / fix first / do not merge). End with at most three positives worth keeping — reviewers who only list faults get ignored.
@@ -0,0 +1,100 @@
# WP Guard — Security Reference
## Contents
- Escaping: context → function
- Sanitization: input type → function
- The unslash-then-sanitize order
- Nonce + capability lifecycle
- REST API permissions
- $wpdb->prepare details
- File uploads
- Common AI-generated violations
## Escaping: context → function
Escape at the moment of output, with the function matching the destination context:
| Output context | Function |
|---|---|
| HTML body text | `esc_html()` |
| HTML attribute | `esc_attr()` |
| URL (href/src/action) | `esc_url()` (display) / `esc_url_raw()` (storage/redirects) |
| Data for inline JS | `wp_json_encode()` + `wp_add_inline_script()`; `esc_js()` is legacy — single-quoted strings in inline attributes only |
| Textarea content | `esc_textarea()` |
| Rich/user HTML | `wp_kses_post()` or `wp_kses()` with an explicit allowlist |
| Translation + output | `esc_html__()`, `esc_html_e()`, `esc_attr__()` — escape and translate in one call |
Trust nothing at output time — not even your own stored options; another plugin or a compromised import may have written them. "Escaped on save" is not a defense.
## Sanitization: input type → function
| Expected input | Function |
|---|---|
| Plain text line | `sanitize_text_field()` |
| Multiline text | `sanitize_textarea_field()` |
| Integer / ID | `absint()` or `intval()` |
| Slug/key | `sanitize_key()` / `sanitize_title()` |
| Email | `sanitize_email()` + `is_email()` check |
| URL | `esc_url_raw()` |
| HTML payload | `wp_kses_post()` / `wp_kses()` |
| File name | `sanitize_file_name()` |
| Anything enumerable | strict allowlist comparison (`in_array( $v, $allowed, true )`) |
## The unslash-then-sanitize order
WordPress adds slashes to superglobals. The correct pipeline is always:
```php
$status = sanitize_key( wp_unslash( $_POST['ncs_status'] ?? '' ) );
```
`wp_unslash()` first, sanitizer second. Sanitizing slashed data corrupts legitimate input and hides bugs.
## Nonce + capability lifecycle
Both checks, always, for any state change. They answer different questions:
- `current_user_can( 'manage_options' )` — is this user *allowed* to do this? (authorization)
- `check_admin_referer( 'ncs_save_settings' )` / `check_ajax_referer( 'ncs_action', 'nonce' )` — did this request *intend* this action? (CSRF protection)
A nonce without a capability check lets any logged-in subscriber fire admin actions. A capability check without a nonce leaves CSRF open. AI-generated handlers routinely have one, the other, or neither — verify both exist on every `admin_post_*`, `wp_ajax_*`, and form handler.
Generate nonces with `wp_nonce_field()` (forms) or `wp_create_nonce()` (AJAX/REST payloads). Nonces are per-user and time-limited; do not cache pages containing them.
## REST API permissions
- Every route registers a real `permission_callback`. `__return_true` is acceptable only for genuinely public read-only data — never for writes.
- Validate and sanitize via the route's `args` schema (`validate_callback`, `sanitize_callback`) instead of manual checks inside the handler.
- Cookie-authenticated REST requests require the `X-WP-Nonce` header (`wp_rest` nonce); custom auth schemes must fail closed.
## $wpdb->prepare details
```php
$row = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}ncs_log WHERE user_id = %d AND event = %s",
$user_id,
$event
)
);
```
- Placeholders: `%s`, `%d`, `%f`; `%i` for table/column identifiers (WP ≥ 6.2).
- `IN ( … )` lists: build placeholders dynamically — `implode( ',', array_fill( 0, count( $ids ), '%d' ) )` — then prepare with the spread array.
- LIKE queries: `$wpdb->esc_like()` the term *before* passing it as a `%s` parameter.
- Never interpolate `$_REQUEST` data anywhere near SQL, even "validated" data.
- Prefer `WP_Query`, `get_posts()`, meta/term/option APIs when they can express the query — they bring caching for free.
## File uploads
Use `wp_handle_upload()` / media APIs — never move `$_FILES` manually. Validate type with `wp_check_filetype_and_ext()` against an allowlist; never trust the client MIME. Store outside executable paths; WordPress handles this when you use its APIs.
## Common AI-generated violations
1. `echo '<div>' . $title . '</div>';` — unescaped output (Rule 1). Published research: XSS appears in 86% of AI generations tested on XSS-prone tasks (see [sources.md](sources.md)).
2. `wp_ajax_` handler with neither `check_ajax_referer()` nor `current_user_can()` (Rule 3).
3. `"SELECT * FROM {$wpdb->posts} WHERE post_title = '$title'"` — interpolated SQL (Rule 4).
4. `permission_callback => '__return_true'` on a POST route (Rule 3).
5. `curl_init()` inside a plugin (Rule 5) — breaks proxies, blocks, and filters that `wp_remote_*` honors.
6. Echoed `<script>` blocks with interpolated PHP — combines Rules 1 and 5; use `wp_enqueue_script()` + `wp_add_inline_script()`/`wp_localize_script()`.
@@ -0,0 +1,27 @@
# WP Guard — Sources
Central bibliography. Operational guidance lives in the other references; read this file only when a source URL is needed.
## Contents
- WordPress handbooks and standards
- Research on AI-generated code defects
## WordPress handbooks and standards
- Plugin Handbook — Security: https://developer.wordpress.org/plugins/security/
- Common APIs Handbook — Sanitizing Data: https://developer.wordpress.org/apis/security/sanitizing/
- Common APIs Handbook — Escaping Data: https://developer.wordpress.org/apis/security/escaping/
- Nonces: https://developer.wordpress.org/apis/security/nonces/
- WordPress Coding Standards (WPCS ruleset): https://github.com/WordPress/WordPress-Coding-Standards
- Plugin Handbook — Internationalization: https://developer.wordpress.org/plugins/internationalization/
- wpdb::prepare() reference: https://developer.wordpress.org/reference/classes/wpdb/prepare/
- REST API Handbook — Adding Custom Endpoints: https://developer.wordpress.org/rest-api/extending-the-rest-api/adding-custom-endpoints/
- Options autoloading (WP 6.6+): https://make.wordpress.org/core/2024/06/18/options-api-disabling-autoload-for-large-options/
- Script loading strategies (WP 6.3+): https://make.wordpress.org/core/2023/07/14/registering-scripts-with-async-and-defer-attributes-in-wordpress-6-3/
## Research on AI-generated code defects
- Veracode, 2025 GenAI Code Security Report — 45% of AI-generated samples contained OWASP Top 10 vulnerabilities; XSS failed in 86% of XSS-prone tasks: https://www.veracode.com/resources/analyst-reports/2025-genai-code-security-report/
- Perry et al., "Do Users Write More Insecure Code with AI Assistants?", ACM CCS 2023: https://arxiv.org/abs/2211.03622
- GitGuardian, State of Secrets Sprawl 2025 — 40% higher secret-leak incidence in Copilot-active repos: https://www.gitguardian.com/state-of-secrets-sprawl-report-2025