# Decent Billing ERP - Security Posture (S0.1)

Security is baked in from the first commit. This document explains what is
in place today and what belongs to later sub-sprints.

## 1. Transport

- HTTPS everywhere is expected in production. When
  `DECENT_FORCE_HTTPS=true`, `ApplySecurityHeaders` emits an HSTS header
  (`max-age=31536000; includeSubDomains; preload`).
- Session and CSRF cookies are `HttpOnly`, `SameSite=lax`, and `secure` in
  production.

## 2. HTTP response headers

Every response passes through `ApplySecurityHeaders` and carries:

| Header                              | Value                                                     |
|-------------------------------------|-----------------------------------------------------------|
| Content-Security-Policy             | nonce-based; `script-src 'self' 'nonce-...' 'strict-dynamic'` |
| X-Content-Type-Options              | `nosniff`                                                 |
| X-Frame-Options                     | `SAMEORIGIN`                                              |
| Referrer-Policy                     | `strict-origin-when-cross-origin`                         |
| Permissions-Policy                  | Locks camera, microphone, geolocation, payment, USB, MIDI |
| Cross-Origin-Opener-Policy          | `same-origin`                                             |
| Cross-Origin-Resource-Policy        | `same-site`                                               |
| X-Permitted-Cross-Domain-Policies   | `none`                                                    |
| Strict-Transport-Security           | Present only when `DECENT_FORCE_HTTPS=true`               |

The CSP nonce is stored per-request at `decent.security.csp_nonce` so the
Blade layout can emit it on `<script>` and `<style>` tags. Inline scripts
without a matching nonce are refused by the browser.

## 3. CSRF

Enabled by Laravel's default web middleware group. Every state-changing form
must include `@csrf`, and every AJAX POST must send `X-XSRF-TOKEN` or the
`_token` field.

The `preferences.update` endpoint is throttled to `30/minute/ip` on top of
CSRF.

## 4. Authentication and authorisation

Not implemented in S0.1. Auth lands in **S0.2 - Identity & Access**:

- Password login with bcrypt and rate-limited attempts.
- Two-factor authentication (TOTP, WhatsApp OTP).
- Session hardening (rotate on privilege change, IP + UA fingerprint).
- Role-based (RBAC) + attribute-based (ABAC) access control.
- Comprehensive audit + activity logging.

## 5. Input validation

- All controllers receive `FormRequest` objects with explicit rule sets.
- No `$request->all()` reaches models. Only `$request->validated()` is used.
- Domain services throw `ValidationFailedException` when input violates
  business invariants.

## 6. Output escaping

- Blade `{{ }}` escapes by default.
- `{!! !!}` is banned in modules; the reviewer must approve any exception.
- Icons come through the `<x-icon>` component, which renders trusted SVG
  fragments from a fixed dictionary. User-supplied SVG is never rendered.

## 7. Logging and PII

- `RedactSensitiveProcessor` scrubs a fixed list of sensitive context keys
  (`password`, `token`, `gstin`, `pan`, `aadhaar`, `bank_account`, `ifsc`,
  `otp`, `card`, `cvv`, ...) recursively before any log line is written.
- The `redact()` helper is available for one-off string masking (keeps the
  last N characters visible for support).

## 8. Rate limiting

- API endpoints: baseline `60/minute` (`/api/v1/ready`).
- Web preferences endpoint: `30/minute`.
- Business endpoints will publish their own limits per module (auth: `5/min`
  on failed logins, POS: unthrottled to authenticated users, invoices: burst
  smoothing).

## 9. Dependency management

- Composer and npm lockfiles are committed.
- `composer audit` is part of CI. Vulnerable dependencies fail the build.
- Framework version pinning: Laravel `^11.0` in Sprint 0; upgrades happen in
  a dedicated maintenance sprint with a full regression pass.

## 10. Threat model (S0.1 scope)

| Threat                             | Mitigation                                     |
|------------------------------------|------------------------------------------------|
| XSS via inline script injection    | CSP nonce + `strict-dynamic`, no `unsafe-inline` |
| CSRF                               | Laravel CSRF middleware, SameSite cookies      |
| Clickjacking                       | `X-Frame-Options: SAMEORIGIN`, `frame-ancestors 'self'` |
| MIME sniffing                      | `X-Content-Type-Options: nosniff`              |
| Log-based credential leakage       | `RedactSensitiveProcessor`, `redact()` helper  |
| Correlation id spoofing            | Regex-validated `X-Request-Id`, fallback UUID  |
| Uninstalled instance exploration   | `EnsureAppInstalled` middleware serves 503     |

Later sub-sprints extend this table for authentication, tenancy, and business
data.
