XC_VM uses a two-layer defense for incoming request data. First, a **global sanitization** pass strips dangerous content from all PHP superglobals during bootstrap, before any application code runs. Second, an **action-level validation** layer checks that required fields are present before business logic executes.
Sanitization runs automatically during bootstrap. When `LegacyInitializer::initCore()` is called (in `src/Core/Init/LegacyInitializer.php`), it performs the following steps before any controller or service code:
After this sequence, all raw superglobals have been sanitized in-place, and the merged/cleaned GET+POST data is available through `RequestManager`.
The streaming context (`LegacyInitializer::initStreaming()`) performs the same sanitization sequence using the `Request` class, which provides equivalent methods for the streaming bootstrap path.
### cleanGlobals(&$rData, $rIteration = 0)
Recursively walks the given superglobal array and removes dangerous content. Applied to `$_GET`, `$_POST`, `$_SESSION`, and `$_COOKIE`.
Recursively walks GET and POST data, applying key and value sanitization to every leaf. For arrays, it recurses up to 20 levels deep. For scalar values, it applies `parseCleanKey()` to the key and `parseCleanValue()` to the value.
The merged result (GET first, then POST overlaid) is stored in `RequestManager` for use throughout the request lifecycle.
### parseCleanKey($rKey)
Sanitizes array keys to prevent injection through key names:
1. URL-decodes and HTML-escapes the key (`htmlspecialchars(urldecode(...))`)
2. Removes double-dot sequences (`..` -> `''`)
3. Strips `__dunder__`-style markers via regex
4. Validates against allowed character set: word characters, dots, hyphens, underscores
### parseCleanValue($rValue)
Sanitizes scalar values with multiple passes:
| Step | What it does |
| --- | --- |
| Unescape | `stripslashes()` and restore ` ` to space |
Checks that the minimum required fields are present for a given action. Returns `true` if data is acceptable, `false` if required fields are missing or malformed. Controllers should call this before forwarding data to service/repository layers.
Filters an array to contain only positive integer IDs. Any value where `intval($id) <= 0` is dropped. Used extensively across the codebase (30+ call sites) wherever user-supplied ID lists need to be sanitized before database queries.
Actions not explicitly listed in the `switch` statement fall through to `return true`, meaning they always pass validation. This is intentional -- these actions either have no required fields at the gate level, or perform their own validation deeper in the business logic layer.
| `is_array(json_decode($rData['field'] ?? '', true))` | JSON string that must decode to an array | `is_array(json_decode($rData['streams'] ?? '', true))` |
| `isset($rData['field'])` | Field presence check (value can be empty/falsy) | `isset($rData['review'])` |
- Validate only the minimum required inputs at this layer. Keep domain-specific rules (format validation, business constraints, uniqueness checks) in the service layer.
- Use `!empty()` for required scalars, `is_numeric()` for numeric fields, and `is_array(json_decode(..., true))` for JSON array payloads.
- For actions that accept file uploads as an alternative to form fields, include `isset($_FILES['field'])` as an OR condition.
- If the action needs no gate-level validation, add it to the explicit pass-through block with `return true` so future maintainers know the omission is intentional rather than accidental.