# Security & Validation Standards

## FormRequests
- **Text Fields:** MUST use `regex:SecurityPatterns::SANITIZED_STRING`.
- **JSON Fields:** MUST have `max` size limits (e.g., 50KB).
- **URLs:** MUST use `regex:SecurityPatterns::HTTPS_URL`.
- **Control Logic:** Use `in:whitelist` for fields like `operator` or `type`.

## Validation-First Execution Order

**Rule:** `$request->validate()` MUST be the very first statement in any controller method or private helper that processes user-supplied input. Raw `$request->input()` extraction, logging, and all business logic MUST occur after validation passes.

**Why:** Extracting raw inputs before validation and then passing them to business logic (e.g., path normalization, logging, crypto operations) means malformed or malicious data reaches internal functions before it has been vetted. If a downstream function throws on invalid input, error messages may leak internal system details (file paths, config keys) derived from the unvalidated value.

```php
// ❌ BAD — raw input extracted and logged before validation
$path = $request->input('path');
Log::info('Processing upload', ['path' => $path]); // logs unvalidated user data
$request->validate(['path' => 'nullable|string']);
$normalized = PathHelper::normalize($path); // unvalidated path reaches business logic

// ✅ GOOD — validate first, then extract, then act
$request->validate(['path' => 'nullable|string', 'file' => 'required|file']);
$path = $request->input('path');
Log::info('Processing upload', ['path' => $path]); // logs validated data only
$normalized = PathHelper::normalize($path);
```

**Enforcement:** Any controller method that calls `$request->input()` or `$request->file()` before `$request->validate()` (or before a FormRequest is resolved) is a validation-order violation and is an auto-fail in code review.

## Schema-First Validation
1. **Inspect Schema:** Before writing rules, check the DB table definition.
2. **Align Rules:** Validation MUST match DB constraints exactly (length, nullability). If a boolean is `NOT NULL` in DB, it must be `required|boolean`. Do not assume `0` or `false` default values blindly—extract them from the DB schema.
3. **Pre-Normalization Flexibility:** When processing bulk upserts for currencies/rates, initial validation should allow broader types (e.g., `string|max:20`) to prevent early validation failure from commas/symbols. Strict `numeric` casting should happen during service normalization.
4. **No Copying:** DO NOT copy rules from other services without verifying schema.
5. **Rule: Never use date-parsing functions for format validation.** `strtotime()` and `Carbon::parse()` are date-interpretation functions — they accept natural language expressions like `"next friday"` or `"+1 day"`. For format validation in canonical pipelines, use `\DateTimeImmutable::createFromFormat()` with an explicit format string and a roundtrip equality check (`$dt->format($format) === $value`). The roundtrip check is critical: `createFromFormat('Y-m-d', '2024-02-30')` returns a DateTime object (overflow to March 1), but `format()` returns `"2024-03-01"` — correctly rejecting the impossible date.
6. **Rule 6 — Multi-Stage Pipeline Contract Alignment:** If Stage N accepts input format X (e.g., RFC 3339 with optional fractional seconds), every downstream stage must either accept the same superset OR normalize the input before validating. A downstream stage that silently rejects a value accepted upstream creates a DLQ trap with no user-visible error. Use normalization (`str_replace`, `preg_replace`) to bridge the gap between upstream acceptance and downstream strictness.

## Sensitive Data & Secrets
- **Redaction:** Any process capturing metrics, errors, or creating logs MUST redact sensitive keys (password, api_key, token, etc.) before inserting data into the database.
- **Payload Limits:** Truncate large JSON dumps in logs/metrics (e.g., limit payloads to 50KB or array sampling) to prevent DB ballooning.
- **Implicit Decryption Ban:** Eloquent Models MUST NOT expose decrypted data automatically via `getAttribute()`. Encrypted properties should return null, blank `""`, or masked values `***` on passive invocations (like `$model->toArray()`). Decrypted data MUST only be accessed explicitly via semantic methods (e.g. `getDecryptedSecret()`) exclusively for backend interactions.
- **Empty Form Preservation:** UI Views MUST NEVER inject obfuscated values into sensitive empty form fields (`value=""`). Controllers updating records MUST NOT overwrite or re-encrypt the database field if the form request arrives blank.
- **Log Allowlist (CRITICAL):** `$request->all()` and `$request->toArray()` are NEVER safe to pass as log context. Logging the full request object captures binary file contents, credentials, and arbitrary user-supplied data verbatim. Every logging call that includes request data MUST use an explicit allowlist of safe scalar fields (e.g., `$request->only(['filename', 'size', 'mime_type', 'provider'])`). Redacting only specific keys (passwords, tokens) is insufficient — the prohibition is on logging the entire request payload.

## Security Gate Normalization
- **Empty String Falsy Bypass:** PHP treats `''` (empty string) as falsy. Any conditional security check of the form `if ($value && condition)` is silently skipped when `$value` is an empty string, even if the caller explicitly passed one. Before any authorization gate, access check, or security assertion that operates on a string parameter sourced from user input, normalize empty strings to `null` and use strict null comparison (`!== null`):
    ```php
    // ❌ BAD — '' is falsy; gate skipped; code proceeds unprotected
    if ($folderId && $this->isRootJailEnabled()) {
        $this->assertFolderWithinRootJail($folderId);
    }

    // ✅ GOOD — normalize first, then strict-compare
    $folderId = $folderId ?: null;
    if ($folderId !== null && $this->isRootJailEnabled()) {
        $this->assertFolderWithinRootJail($folderId);
    }
    ```

## Frontend Security (Console Logs)
- **Production Logs:** Console logging (`console.log`, `console.error`) in JavaScript MUST be guarded or removed for production builds. 
- **Pattern:** Use `if (process.env.NODE_ENV !== 'production') { console.error('...'); }`. Exposing error messages in prod is a security risk.

## Mass Assignment Protection
**Architecture:** We use TWO patterns based on schema type.

### Pattern 1: Static Schema (System Models)
**Use `$fillable` array** for models with fixed schemas (e.g., `User`, `Account`).

### Pattern 2: Dynamic Schema (Tenant Models)
**Use `$guarded = ['field']`** for tenant models that support custom fields. Tenant database tables are dynamically modified when custom fields are assigned, making `$fillable` impractical.
**⚠️ DO NOT flag `$guarded` as a mass-assignment risk on tenant models in `src/Domain/`.**

## Authorization & Access Logic (RBAC)
- **Flat validations over heavy abstractions:** When building Middlewares, FormRequests, or early controller access validations, prefer direct boolean checks (`auth()->user()->hasPermission('can_manage_x')`) instead of relying on heavy abstracted Laravel Policies, especially if the access logic is universally unilinear. 
- **Mandatory 403 Interception Logs:** Any authorization failure check MUST trigger a `Log::warning` explicitly capturing the attempted action, target resource ID, and the intercepting User ID/IP, exactly BEFORE throwing `abort(403)`.

## IDOR & Tenancy (CRITICAL)
**Architecture:** We use **Database-Level Tenancy** (separate DBs via connection), NOT Row-Level Tenancy.

### ❌ BAD (Do NOT do this)
- **Do NOT** add `where('tenant_id', ...)` to queries. These columns DO NOT EXIST on tenant tables.
- **Do NOT** use generic `Model::find($id)` unless the model uses the Tenant Trait.

### ✅ GOOD (The Safe Pattern)
1.  **The Trait:** All Tenant Models MUST use `App\Traits\UsesTenantConnection`.
2.  **The Connection:** The trait automatically forces `$connection = 'tenant_connection'`.
3.  **Dynamic Models:** If instantiating a dynamic class, you MUST use the `GetTenantModel($modelName)` helper function which handles runtime security validation for you.

```php
// Safe because Post uses UsesTenantConnection
$post = Post::findOrFail($id);

// Safe Dynamic Usage
$modelClass = GetTenantModel($modelName); // Validates and throws 500 if unsafe
$record = $modelClass::findOrFail($id);
```
