# Security Skill ## Purpose Use this skill for input validation, output escaping, passwords, authentication, authorization, sessions, CSRF, secrets, error disclosure, dangerous functions, serialization, and file/path safety. --- ## Security Baseline Treat all external data as untrusted. Untrusted data includes: - `$_GET` - `$_POST` - `$_REQUEST` - `$_COOKIE` - `$_SERVER` - uploaded files - request bodies - session values - database values originally supplied by users - third-party API responses --- ## Input Validation Validate on input. Use `Core\Validator` for field validation — it is a fluent chain that collects all errors before returning. ### Validator API | Method | Description | |--------|-------------| | `required(field, value, message?)` | Fails if value is null or blank | | `maxLength(field, value, max, message?)` | Fails if string length exceeds max | | `minLength(field, value, min, message?)` | Fails if string length is below min | | `numeric(field, value, message?)` | Fails if value is not numeric | | `min(field, value, min, message?)` | Fails if numeric value is below min | | `max(field, value, max, message?)` | Fails if numeric value exceeds max | | `email(field, value, message?)` | Fails if non-empty value is not a valid email | | `date(field, value, format?, message?)` | Fails if non-empty value does not match the date format (default `Y-m-d`) | | `in(field, value, allowed[], message?)` | Fails if value is not in the allowed list (strict comparison) | | `passes()` | Returns `true` when no errors were collected | | `fails()` | Returns `true` when any errors were collected | | `errors()` | Returns `array>` of field errors | `email()`, `date()`, `min()`, and `max()` skip empty or non-numeric values respectively — pair them with `required()` or `numeric()` when the field is mandatory. Example: ```php $validator = new Validator(); $validator ->required('email', $form['email'], 'Email is required.') ->maxLength('email', $form['email'], 255) ->email('email', $form['email'], 'Enter a valid email address.') ->required('start_date', $form['start_date'], 'Start date is required.') ->date('start_date', $form['start_date'], 'Y-m-d', 'Enter a valid start date.'); if ($validator->fails()) { // $validator->errors() returns field => [messages] map } ``` Rules: - Validate type, range, length, format, and allowed values. - Validate server-side even when client-side validation exists. - Reject unexpected fields when appropriate. - Normalize data intentionally, not accidentally. - Do not reimplement email or date validation inline in controllers — use the Validator methods. --- ## Output Escaping Escape on output based on context. For HTML output: ```php function e(string $value): string { return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } ``` Usage: ```php

name()) ?>

``` Rules: - Escape based on context: HTML, attribute, JavaScript, CSS, URL, SQL, shell. - Do not use the same escaping function for every context. - Prefer template engines with automatic escaping only when appropriate for the project. - Avoid allowing raw HTML from users. If required, sanitize with a proven whitelist sanitizer. - Use `escapeshellarg()` when passing controlled values to shell commands, and avoid shell execution when possible. - Never trust file paths supplied by users. - Reject path traversal values such as `../`, `/`, `\`, and null bytes when user-provided paths are not allowed. --- ## Passwords and Authentication Never store plain-text passwords. Use PHP’s password API: ```php $hash = password_hash($plainPassword, PASSWORD_DEFAULT); if (! password_verify($plainPassword, $hash)) { throw new RuntimeException('Invalid credentials.'); } ``` Rules: - Use `password_hash()` for new password hashes. - Use `password_verify()` for login checks. - Use `password_needs_rehash()` when algorithm/cost settings change. - Do not create your own password hashing algorithm. - Do not use general-purpose hashes like `md5`, `sha1`, or raw `sha256` for passwords. - Rate-limit login attempts. - Regenerate session IDs after login. - Use secure, HTTP-only, SameSite cookies for sessions. --- ## Authorization - Check authorization separately from authentication. - Do not assume logged-in means allowed. - Enforce permissions server-side. - Avoid hiding buttons as the only authorization control. - Prefer explicit permission checks near protected actions or service boundaries. --- ## CSRF Use CSRF protection for state-changing forms and unsafe HTTP methods. State-changing actions include: - Create - Update - Delete - Login/logout state changes - Password changes - Email changes - Permission changes When using `_method` override to tunnel PUT, PATCH, or DELETE through a POST form, always include a CSRF token. The override is only honoured for POST requests, and only for the values `PUT`, `PATCH`, and `DELETE` — all other values are rejected by the framework. In MindVisionCode PHP, use the built-in helpers from `core/helpers.php`: | Helper | Purpose | |--------|---------| | `csrf_token()` | Generates and persists the token in the session | | `csrf_field()` | Outputs a hidden `` carrying the token — use in every state-changing form | | `verify_csrf_token(string $token)` | Returns `bool` — call before any business logic in POST actions | **Always verify CSRF before field validation and business logic.** A token failure is a security event, not a form validation error. Use a dedicated private method that returns `?Response` and short-circuits the action: ```php private function verifyCsrf(Request $request): ?Response { if (!verify_csrf_token((string) $request->input('_token', ''))) { return new Response('Your session has expired. Please go back and try again.', 419); } return null; } ``` Call it as the first thing in the action: ```php public function store(): Response { $request = Request::capture(); if ($guard = $this->verifyCsrf($request)) { return $guard; } // field validation and business logic follow } ``` --- ## Serialization and Data Exchange Do not call `unserialize()` on untrusted data. Prefer JSON for data exchange: ```php $data = json_decode($json, true, flags: JSON_THROW_ON_ERROR); $json = json_encode($data, JSON_THROW_ON_ERROR); ``` Rules: - Use `JSON_THROW_ON_ERROR` for new code. - Validate decoded data before using it. - Avoid PHP serialization for data that crosses trust boundaries. --- ## Configuration and Secrets Rules: - Keep secrets out of source control. - Do not commit passwords, API keys, private keys, tokens, or production DSNs. - Store configuration outside the public web root. - Use environment variables or ignored local config files for secrets. - Provide a safe example file such as `.env.example`. Example `.gitignore` entries: ```text .env .env.local /config/local.php /var/cache/ /var/log/ /vendor/ ``` --- ## Error Handling and Logging Development: - Show errors locally. - Log errors. - Use Xdebug when debugging complex issues. Production: - Do not display errors to users. - Log errors to a secure log destination. - Return safe, generic error messages. - Preserve enough context in logs for troubleshooting. Do not leak: - stack traces to users - SQL statements with secrets - environment variables - full filesystem paths - tokens or passwords Example: ```php try { $service->handle($request); } catch (Throwable $e) { $logger->error('Order processing failed.', [ 'exception' => $e, 'requestId' => $requestId, ]); http_response_code(500); echo 'An unexpected error occurred.'; } ``` --- ## Security Checklist Before completing any feature, verify: - [ ] All external input is validated. - [ ] All output is escaped for the correct context. - [ ] SQL uses prepared statements or safe query builders. - [ ] Authentication and authorization are checked server-side. - [ ] Secrets are not committed. - [ ] Errors are not exposed in production responses. - [ ] File uploads validate size, extension, MIME type, and storage path. - [ ] Passwords use `password_hash()` and `password_verify()`. - [ ] CSRF protection exists for state-changing requests. - [ ] Dangerous functions are avoided or justified: `eval`, `exec`, `shell_exec`, `system`, `passthru`, `unserialize`. - [ ] Dependencies have no known vulnerabilities according to `composer audit`.