| 1 |
# SDD: WordPress.Security.ValidatedSanitizedInput |
| 2 |
|
| 3 |
## Goal |
| 4 |
Ensure any input read from GET/POST/COOKIE/SERVER/REQUEST is unslashed, validated, |
| 5 |
and sanitized before use, reducing PHPCS warnings and security risk. |
| 6 |
|
| 7 |
## Scope |
| 8 |
Files in `inc/`, `classes/`, and `views/` that read superglobals or external input. |
| 9 |
|
| 10 |
## Project discovery (before changing) |
| 11 |
1. Map access to `$_GET`, `$_POST`, `$_REQUEST`, and `$_SERVER`. |
| 12 |
2. Reuse existing patterns: |
| 13 |
- `sanitize_text_field( wp_unslash( $_GET['foo'] ) )` |
| 14 |
- `sanitize_email( wp_unslash( $_POST['email'] ) )` |
| 15 |
- `absint( wp_unslash( $_GET['id'] ) )` |
| 16 |
- `sanitize_key()` + allowlist (`in_array`) for filters/ordering |
| 17 |
- `array_map( 'absint', (array) wp_unslash( $_GET['ids'] ) )` |
| 18 |
3. Verify nonces for admin actions (see |
| 19 |
`.aiassistant/specs/phpcs/nonce-verification-recommended.md`). |
| 20 |
|
| 21 |
## Decision (quick tree) |
| 22 |
1. Is the input from a superglobal? If yes, apply `wp_unslash()`. |
| 23 |
2. Is there a clear type/allowlist? Validate before use. |
| 24 |
3. Sanitize with the correct function for the context. |
| 25 |
4. If invalid/missing, use a safe default. |
| 26 |
|
| 27 |
## Implementation patterns |
| 28 |
- **MissingUnslash**: always `wp_unslash()` before `sanitize_*`. |
| 29 |
- **InputNotSanitized**: apply `sanitize_*` or `esc_url_raw()`. |
| 30 |
- **InputNotValidated**: validate with allowlist, `absint`, `filter_var()`, or controlled regex. |
| 31 |
- Prefer `filter_input()`/`filter_input_array()` with `FILTER_SANITIZE_*` when possible. |
| 32 |
- If `filter_input()` returns `null` but `$_GET`/`$_POST` is set, fall back to |
| 33 |
`sanitize_text_field( wp_unslash( $_GET['field'] ) )` for compatibility. |
| 34 |
- Avoid `$_REQUEST`; prefer `$_GET` or `$_POST`. |
| 35 |
- For arrays, sanitize each item with `array_map`. |
| 36 |
- For URLs: `esc_url_raw()` + optionally `filter_var( ..., FILTER_VALIDATE_URL )`. |
| 37 |
- For multiline text: `sanitize_textarea_field()` (do not use `sanitize_text_field`). |
| 38 |
- For allowed HTML: `wp_kses_post()` (only when needed and documented). |
| 39 |
- For boolean flags: `filter_input( INPUT_POST, 'flag', FILTER_VALIDATE_BOOLEAN )` |
| 40 |
or `(bool) filter_input(...)`. |
| 41 |
- For filters/ordering (GET without side effects): validate nonce and allowlist. |
| 42 |
|
| 43 |
## Examples |
| 44 |
```php |
| 45 |
check_admin_referer( 'imagify_settings_save' ); |
| 46 |
$level = isset( $_POST['optimization_level'] ) |
| 47 |
? absint( wp_unslash( $_POST['optimization_level'] ) ) |
| 48 |
: 1; |
| 49 |
``` |
| 50 |
|
| 51 |
```php |
| 52 |
$allowed = [ 'success', 'error', 'already_optimized' ]; |
| 53 |
$status = isset( $_GET['status'] ) |
| 54 |
? sanitize_key( wp_unslash( $_GET['status'] ) ) |
| 55 |
: ''; |
| 56 |
$status = in_array( $status, $allowed, true ) ? $status : ''; |
| 57 |
``` |
| 58 |
|
| 59 |
```php |
| 60 |
$ids = isset( $_GET['ids'] ) |
| 61 |
? array_map( 'absint', (array) wp_unslash( $_GET['ids'] ) ) |
| 62 |
: []; |
| 63 |
``` |
| 64 |
|
| 65 |
## Git Operations |
| 66 |
Do not run `git commit` or `git push`. You may only suggest a commit message. |
| 67 |
|
| 68 |
## Verification |
| 69 |
- Run `composer phpcs` and confirm there are no: |
| 70 |
- `WordPress.Security.ValidatedSanitizedInput.InputNotSanitized` |
| 71 |
- `WordPress.Security.ValidatedSanitizedInput.InputNotValidated` |
| 72 |
- `WordPress.Security.ValidatedSanitizedInput.MissingUnslash` |
| 73 |
|