# 02 — `.htaccess`: hardening e performance na conta

> **Arquivo:** `public/.htaccess` (pasta pública do domínio).
> **Cópia literal:** [`arquivos/public.htaccess`](arquivos/public.htaccess)
> SHA-256: `94df1c888c182db36077fded2e3a297e218f5beb2920920d305803432ec5d0ef`
>
> **NÃO edite o `.htaccess` da RAIZ da conta**: ele é gerado pelo cPanel.
> O arquivo deste documento é o da **pasta pública** (`public/.htaccess`).

---

## 1. Contexto e restrições do servidor

O ambiente é **Apache 2.4 + EasyApache 4 (cPanel)**. A pesquisa empírica
(`_pesquisa/security/`) mostrou que:

| Recurso | Funciona na conta? | Observação |
|---|---|---|
| `Header` cruas | **Sim** | **Nunca** envolver em `<IfModule mod_headers.c>` — causa HTTP 500 neste servidor. |
| `mod_expires` | Sim | Wrapper `<IfModule mod_expires.c>` é seguro (se não houver, só perde cache). |
| `FilesMatch`, `FileETag` | Sim | Usados para cache de estáticos e ETag. |
| `Rewrite*` | Sim | Inclui `THE_REQUEST` para inspeção da linha original. |
| `mod_deflate`/`mod_brotli` | **Não** | Sobrepujos pelo vhost do EA4. Compressão é server-side (WHM/cPanel). |
| `opcache.*`/`realpath_cache_*` | **Não** | `PHP_INI_SYSTEM`; ignorados na conta. |
| `RedirectMatch`/`REQUEST_URI` | Limitado | Neste servidor, usar `THE_REQUEST` para artefatos sensíveis. |

### Regra de ouro

> **Se for `Header`, escreva “cru”. Se for compressão/OPcache, não escreva aqui.**

### 1.1 Semântica das diretivas usadas

| Diretiva | Onde atua | Observações |
|---|---|---|
| `Header always set` | Tabela `always`, inclusive respostas de erro | Sobrevive a `ErrorDocument`; é o que garante headers em 403/404. |
| `Header set` | Tabela normal (`onsuccess`) | Não é aplicado em respostas de erro. Usado no `Cache-Control` de estáticos. |
| `Header edit` | Edita valor existente | Se o header não existir ou o regex não casar, é **no-op**. |
| `FilesMatch` | Por nome de arquivo | Blocos são aplicados na ordem do arquivo; o **último `Header set` vence** para o mesmo header. |
| `FileETag MTime Size` | Geração de ETag do core | Não remove ETag; define os componentes. `Header unset ETag` desligaria. |
| `ExpiresByType` | `mod_expires` | Gera `Expires` e `Cache-Control: max-age`; o `Header set Cache-Control` explícito pode sobrescrever. |
| `RewriteCond %{THE_REQUEST}` | Linha HTTP original | Mais fiel que `REQUEST_URI` neste host para inspeção de path bruto. |
| `RewriteCond %{REQUEST_URI}` | URI já reescrita | Pode não refletir o path original após regras anteriores. |
| `<IfModule mod_headers.c>` | Wrapper condicional | **Proibido neste host** (HTTP 500); as diretivas `Header` ficam cruas. |
| `<IfModule mod_expires.c>` | Wrapper condicional | Seguro: se o módulo não existir, apenas perde o cache. |

**Ordem de precedência prática** para um mesmo header:

1. `Header always set`/`set` no `.htaccess` da conta (mais específico) é aplicado
   depois da configuração do vhost;
2. entre blocos do mesmo `.htaccess`, vence o último `set` que casar;
3. `Header edit` é aplicado sobre o valor corrente; se um `set` posterior
   redefinir o header, o `edit` anterior é perdido.

> Isso explica por que `[L0.3b]` (`sw.js`/`manifest`) precisa ficar **depois** de
> `[L0.3]`: ambos usam `Header set Cache-Control`; o último vence.

---

## 2. Mapa dos blocos

| Bloco | Linha de defesa | O que faz |
|---|---|---|
| `[H1]` | Hardening | `Options -Indexes` (sem listagem de diretório). |
| `RewriteEngine` | Roteamento | Front controller Laravel + trailling slash + robots/sitemap. |
| `[SEC-5]` | Segurança | Redirect 301 HTTP→HTTPS, aware de `X-Forwarded-Proto`. |
| `[H2]` | Hardening | Bloqueia métodos `TRACE`/`TRACK`. |
| `[H3]` | Virtual patch | Bloqueia `/init` (rota de bootstrap com admin hardcoded). |
| `[H4]` | Virtual patch | Bloqueia padrões SQLi na query string. |
| `[H8]` | Hardening | Bloqueia `.env`, `.git`, `.user.ini`, `composer.*`, `phpunit.xml`, `web.config`, `error_log`. |
| `[H5]` | Segurança | Headers: XFO, XCTO, XSS, Referrer, Permissions, HSTS, Server, canário. |
| `[SEC-2]` | Isolamento | `Cross-Origin-Resource-Policy` + `Cross-Origin-Opener-Policy`. |
| `[H7]` | Cookies | `SameSite=Lax` em `Set-Cookie`. |
| `[H9]` | Performance | `mod_expires` para estáticos (1 ano). |
| `[L5]` | MIME | `AddType image/webp` / `image/avif`. |
| `[L0.3]` | Performance | `Cache-Control: public, max-age=31536000, immutable` para assets. |
| `[L0.3b]` | PWA | `sw.js`/`manifest` com `max-age=0, must-revalidate`. |
| `[L0.3c]` | Cache | Remove `User-Agent` do `Vary`; preserva `Accept-Encoding`. |
| `[L0.4]` | Revalidação | `FileETag MTime Size`. |

---

## 3. Explicação bloco a bloco

### 3.1 `[H1]` — sem listagem de diretórios

```apache
Options -Indexes
```

Evita que o Apache exponha diretórios sem `index`. Defesa simples e de baixo risco.

### 3.2 `[SEC-5]` — HTTP → HTTPS com proxy reverso

```apache
RewriteCond %{HTTPS} off
RewriteCond %{HTTP:X-Forwarded-Proto} !https [NC]
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
```

Só redireciona quando **não** é HTTPS e o proto encaminhado **não** é `https`.
Isso evita loop quando há proxy/CDN terminando TLS e enviando
`X-Forwarded-Proto: https`.

### 3.3 `[H2]` — TRACE/TRACK

```apache
RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK)$ [NC]
RewriteRule .* - [F]
```

Defesa em profundidade ao `TraceEnable` do servidor.

### 3.4 `[H3]` — virtual patch `/init`

```apache
RewriteCond %{THE_REQUEST} \s/init[/?\s] [NC]
RewriteRule ^ - [F,L]
```

Usa `THE_REQUEST` (linha original `GET /init HTTP/1.1`) porque este servidor
entrega URI interna reescrita ao rewrite; `path`/`REQUEST_URI` não são fiéis.

### 3.5 `[H4]` — anti-SQLi

```apache
RewriteCond %{QUERY_STRING} union.+select [NC,OR]
RewriteCond %{QUERY_STRING} information_schema [NC,OR]
RewriteCond %{QUERY_STRING} pg_sleep [NC,OR]
RewriteCond %{QUERY_STRING} sleep [NC,OR]
RewriteCond %{QUERY_STRING} benchmark [NC,OR]
RewriteCond %{QUERY_STRING} load_file [NC,OR]
RewriteCond %{QUERY_STRING} concat [NC]
RewriteRule ^ - [F,L]
```

Várias condições curtas com `[OR]`. O PCRE deste servidor rejeita grupos com
muitas alternâncias; esta forma foi validada linha a linha no alvo. É um
**virtual patch**, não substitui prepared statements.

### 3.6 `[H8]` — artefatos sensíveis

```apache
RewriteCond %{THE_REQUEST} \s/\.env[/?\s] [NC,OR]
RewriteCond %{THE_REQUEST} \s/\.git[/?\s] [NC,OR]
RewriteCond %{THE_REQUEST} \s/\.user\.ini[/?\s] [NC,OR]
RewriteCond %{THE_REQUEST} \s/composer\.json[/?\s] [NC,OR]
RewriteCond %{THE_REQUEST} \s/composer\.lock[/?\s] [NC,OR]
RewriteCond %{THE_REQUEST} \s/package-lock\.json[/?\s] [NC,OR]
RewriteCond %{THE_REQUEST} \s/package\.json[/?\s] [NC,OR]
RewriteCond %{THE_REQUEST} \s/phpunit\.xml[/?\s] [NC,OR]
RewriteCond %{THE_REQUEST} \s/web\.config[/?\s] [NC,OR]
RewriteCond %{THE_REQUEST} \s/error_log[/?\s] [NC]
RewriteRule ^ - [F,L]
```

`RedirectMatch` não dispara neste servidor; por isso o blocklist vive dentro do
`mod_rewrite`, via `THE_REQUEST`. Retorna **403**.

### 3.7 `[H5]` — cabeçalhos de segurança (crus)

```apache
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set X-XSS-Protection "0"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=(), usb=()"
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
Header always unset X-Powered-By
Header always set Server "MaximaTecnologia"
ServerSignature Off
Header always set X-Hardening-Version "2026-08-maxima"
```

- `X-XSS-Protection: 0` é a recomendação atual (OWASP/MDN); CSP é o controle real.
- `Strict-Transport-Security` com 2 anos, `includeSubDomains`, `preload`.
- `Server` é sobrescrito porque `unset` no header `Server` do core é ignorado no
  Apache 2.4.
- `X-Hardening-Version` é o **canário de deploy**: se não aparecer no `curl -I`,
  o arquivo servido não é este.

### 3.8 `[SEC-2]` — isolamento de origem

```apache
Header always set Cross-Origin-Resource-Policy "same-origin"
Header always set Cross-Origin-Opener-Policy "same-origin"
```

Reduz leitura cruzada de recursos e isolamento de browsing context.

### 3.9 `[H7]` — `SameSite=Lax` em cookies

```apache
Header always edit Set-Cookie "^(.+)$" "$1; SameSite=Lax" env=h7https
Header always edit Set-Cookie "^(.+)$" "$1; SameSite=Lax" env=!h7https
```

Funciona como diretiva crua (sem `IfModule`). O flag `Secure` **não** sai deste
arquivo: o cookie de sessão do Laravel é controlado por `config/session.php`
(`SESSION_SECURE_COOKIE=true`). `session.cookie_secure` do php.ini vale só para
sessões PHP nativas.

> **Correção técnica (revisão de 2026-10-20):** o snapshot **não define**
> `h7https` em lugar nenhum (não há `SetEnvIf`/`SetEnv`). Portanto a linha
> `env=h7https` é inerte e apenas `env=!h7https` casa — ou seja, a segunda linha
> é a que efetivamente atua. Além disso, ela **anexa** `SameSite=Lax` sem checar
> se o cookie já possui o atributo, o que pode gerar `SameSite` duplicado. A
> fonte autoritativa continua sendo `config/session.php` (`secure`, `http_only`,
> `same_site`); o H7 é apenas defesa em profundidade. Se for mantido, prefira uma
> condição negativa:
>
> ```apache
> Header always edit Set-Cookie "^(?!.*SameSite)(.+)$" "$1; SameSite=Lax"
> ```
>
> (PCRE com negative lookahead; validar no Apache alvo antes de publicar).

### 3.10 `[H9]` — `mod_expires`

```apache
<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType text/css "access plus 1 year"
    ExpiresByType text/javascript "access plus 1 year"
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/png "access plus 1 year"
    ExpiresByType image/webp "access plus 1 year"
    ExpiresByType image/svg+xml "access plus 1 year"
    ExpiresByType image/gif "access plus 1 year"
    ExpiresByType image/avif "access plus 1 year"
    ExpiresByType font/woff2 "access plus 1 year"
    ExpiresByType font/woff "access plus 1 year"
    ExpiresByType font/ttf "access plus 1 year"
    ExpiresByType font/otf "access plus 1 year"
    ExpiresByType application/font-ttf "access plus 1 year"
    ExpiresByType application/font-woff "access plus 1 year"
    ExpiresByType application/javascript "access plus 1 year"
</IfModule>
```

Wrapper seguro: se `mod_expires` não existir, o custo é apenas perder o cache.

### 3.11 `[L5]` — MIME de imagens modernas

```apache
AddType image/webp .webp
AddType image/avif .avif
```

Sem isso, o Apache serve `.webp` sem `Content-Type` e o navegador (com
`X-Content-Type-Options: nosniff`) recusa o `<source>` do `<picture>`.

### 3.12 `[L0.3]` — cache imutável de assets

```apache
<FilesMatch "\.(css|js|jpg|jpeg|png|webp|avif|gif|svg|woff2?|ttf|otf|ico)$">
    Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
```

`Header` **crua** (nunca `<IfModule mod_headers.c>`). Aplica-se a estáticos
versionados (Mix hash). A invalidação é por mudança de URL.

> ⚠️ **Achado de revisão (F-02):** o `FilesMatch` é **amplo**. Ele casa qualquer
> `.jpg/.png/.webp/.css/.js` sob `public/`, incluindo uploads servidos por
> `/storage/...` (symlink para `storage/app/public`) e arquivos de
> `public/img/...` que **não** têm hash no nome. Se o CMS substituir uma imagem
> mantendo o mesmo caminho, navegador e CDN podem continuar servindo a versão
> antiga por até 1 ano (`immutable` + `Expires 1 year`). O mesmo vale para o
> bloco `[H9]`.
>
> **Patch recomendado (não aplicado):** limitar `immutable` ao diretório
> versionado e dar cache curto/revalidação a uploads:
>
> ```apache
> # Assets versionados (Mix hash ou path com versão)
> <If "%{REQUEST_URI} =~ m#^/assets/#">
>     <FilesMatch "\.(css|js|jpg|jpeg|png|webp|avif|gif|svg|woff2?|ttf|otf|ico)$">
>         Header set Cache-Control "public, max-age=31536000, immutable"
>     </FilesMatch>
> </If>
>
> # Uploads do CMS: nome pode ser reutilizado; revalidar sempre
> <If "%{REQUEST_URI} =~ m#^/storage/#">
>     <FilesMatch "\.(jpg|jpeg|png|webp|avif|gif|svg)$">
>         Header set Cache-Control "public, max-age=300, must-revalidate"
>     </FilesMatch>
> </If>
> ```
>
> `mod_expires` também deve ser escopado ou substituído por `Cache-Control`
> explícito; `Expires` de 1 ano em upload não versionado tem o mesmo risco.
> Validar a sintaxe `<If>` no Apache 2.4 do alvo (módulo `mod_headers` já é
> obrigatório para o restante do arquivo).

### 3.13 `[L0.3b]` — exceção do Service Worker

```apache
<FilesMatch "(sw\.js|manifest\.webmanifest)$">
    Header set Cache-Control "public, max-age=0, must-revalidate"
</FilesMatch>
```

**Ordem importa:** este bloco fica **depois** de `[L0.3]` de propósito. O
`mod_headers` aplica os `Header set` na ordem em que aparecem; o **último vence**.
Antes de `[L0.3]`, a exceção era sobrescrita e `/sw.js` saía `immutable` (bug
reproduzido no canário).

### 3.14 `[L0.3c]` — limpeza do `Vary`

```apache
Header edit Vary "^(?i)\s*Accept-Encoding\s*,\s*User-Agent\s*$" "Accept-Encoding"
Header edit Vary "^(?i)\s*User-Agent\s*,\s*Accept-Encoding\s*$" "Accept-Encoding"
Header edit Vary "^(?i)\s*User-Agent\s*$" "Accept-Encoding"
```

O HTML público não varia por `User-Agent` (o md5 do corpo é idêntico entre UAs).
Remover `User-Agent` do `Vary` evita fragmentar o cache de borda. `Header edit` sem
`always` edita a tabela normal, onde o `Vary` dos módulos vive; se o valor não casar
ou o header não existir, é **no-op**.

> **Lacuna conhecida:** não é possível alterar a camada WHM/vhost daqui. Só
> produção confirma se o `Header edit` do `.htaccess` vence o include global.

### 3.15 `[L0.4]` — ETag em estáticos

```apache
FileETag MTime Size
```

Revalidação condicional (ETag/304) em estáticos. O `CacheControl` usa ETag fraco
no HTML; estáticos usam ETag do Apache.

### 3.16 `[L0.2]` — compressão NÃO fica aqui

O arquivo termina com um comentário explícito:

```apache
# [L0.2] COMPRESSÃO é SERVER-SIDE (cPanel "Optimize Website" / mod_deflate global +
#        mod_brotli no WHM EasyApache4). NÃO colocar mod_deflate/brotli aqui: o
#        AddOutputFilterByType da conta é sobreposto pelo vhost e é ineficaz neste
#        cPanel. Veja wiki/infra-update/WHM.md (P0-A).
```

---

## 4. Validação

```bash
# 1) Canário de deploy: o arquivo servido é este?
curl -sI https://SEU_HOST/ | grep -i x-hardening-version
# esperado: X-Hardening-Version: 2026-08-maxima

# 2) Estático: immutable + ETag
curl -sI https://SEU_HOST/assets/css/app.css \
  | grep -iE 'cache-control|etag|x-hardening-version'
# esperado:
#   Cache-Control: public, max-age=31536000, immutable
#   ETag: "..."
#   X-Hardening-Version: 2026-08-maxima

# 3) Service Worker: NÃO pode ser immutable
curl -sI https://SEU_HOST/sw.js | grep -i cache-control
# esperado: public, max-age=0, must-revalidate

# 4) Headers de segurança
curl -sI https://SEU_HOST/ \
  | grep -iE 'x-frame-options|x-content-type|referrer-policy|permissions-policy|strict-transport|cross-origin|server'
# esperado: todos presentes; Server: MaximaTecnologia

# 5) Artefatos sensíveis
curl -s -o /dev/null -w '%{http_code}\n' https://SEU_HOST/.env
curl -s -o /dev/null -w '%{http_code}\n' https://SEU_HOST/.git/config
# esperado: 403

# 6) SQLi/TRACE (não deve retornar 200)
curl -s -o /dev/null -w '%{http_code}\n' 'https://SEU_HOST/?x=union+select+1'
curl -s -o /dev/null -w '%{http_code}\n' -X TRACE https://SEU_HOST/
# esperado: 403 / 405
```

> Para features do Laravel (`CacheControl`, `PageCache`, `HtmlMinify`), use **GET**.
> `curl -sI` envia HEAD e pode parecer “sem efeito”.

---

## 5. Erros comuns e como diagnosticar

| Sintoma | Causa provável | Correção |
|---|---|---|
| HTTP 500 em todas as páginas após editar `.htaccess` | `<IfModule mod_headers.c>` ou diretiva inválida | Remover wrapper; validar sintaxe no Apache. |
| `X-Hardening-Version` ausente | Arquivo errado (raiz em vez de `public/`) ou não salvo | Conferir caminho e cache do servidor. |
| `.webp` não renderiza | Falta `AddType image/webp` | Aplicar `[L5]`. |
| `sw.js` preso em versão antiga | `immutable` vencendo a exceção | Garantir `[L0.3b]` **depois** de `[L0.3]`. |
| Cache de borda fragmentado | `Vary` com `User-Agent` | Aplicar `[L0.3c]` e confirmar no `curl`. |
| Compressão não funciona | Bloco `mod_deflate` no `.htaccess` | Remover e usar cPanel/WHM. |
| OPcache não atualiza | `validate_timestamps=0` sem reset | Reiniciar/reload do PHP-FPM. |
| `Set-Cookie` em página pública | `CacheControl` não rodou (HEAD, rota não-Portal, status ≥400) | Testar GET real; conferir ordem do Kernel. |

---

## 6. Anexo A — conteúdo literal de `public/.htaccess`

> Cópia exata do commit `ed413d8`. O arquivo em `arquivos/public.htaccess` é a
> referência canônica; o bloco abaixo reproduz o mesmo conteúdo para leitura.

```apache
# ======================================================================
# HARDENING COMPLETO — portais v5/v6 · Apache 2.4 / EasyApache4 (cPanel)
# ---------------------------------------------------------------------
# COLOQUE ESTE ARQUIVO EM: public/.htaccess  (na pasta pública do domínio)
# O .htaccess da RAIZ da conta é gerado pelo cPanel: NÃO edite.
# Arquivo mantido em sincronia com _pesquisa/security/htaccess.txt
# (inclui hardening H1-H9 + performance L0.2-L0.4 da tarefa #23410).
# ======================================================================

# [H1] Sem listagem de diretórios (v5 F-4)
Options -Indexes

<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews
    </IfModule>

    RewriteEngine On

    # [SEC-5] Redirect HTTP -> HTTPS (defesa em profundidade; aware de proxy reverso).
    #          So redireciona quando NAO e HTTPS e o proto encaminhado nao e https:
    #          atras de proxy que termina SSL (X-Forwarded-Proto=https) nao ha loop.
    RewriteCond %{HTTPS} off
    RewriteCond %{HTTP:X-Forwarded-Proto} !https [NC]
    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

    # [H2] TRACE/TRACK recusados (defesa em profundidade ao TraceEnable do servidor)
    RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK)$ [NC]
    RewriteRule .* - [F]

    # [H3] Virtual patch: rota de bootstrap com admin hardcoded
    #      Via THE_REQUEST (linha original "GET /init HTTP/1.1") — este servidor
    #      entrega URI interna reescrita ao rewrite; path/REQUEST_URI não são fiéis.
    RewriteCond %{THE_REQUEST} \s/init[/?\s] [NC]
    RewriteRule ^ - [F,L]

    # [H4] Virtual patch anti-SQLi — várias condições curtas com [OR].
    #       O PCRE deste servidor rejeita grupos com muitas alternâncias; esta
    #       forma (cada condição minúscula) foi validada linha a linha no alvo.
    RewriteCond %{QUERY_STRING} union.+select [NC,OR]
    RewriteCond %{QUERY_STRING} information_schema [NC,OR]
    RewriteCond %{QUERY_STRING} pg_sleep [NC,OR]
    RewriteCond %{QUERY_STRING} sleep [NC,OR]
    RewriteCond %{QUERY_STRING} benchmark [NC,OR]
    RewriteCond %{QUERY_STRING} load_file [NC,OR]
    RewriteCond %{QUERY_STRING} concat [NC]
    RewriteRule ^ - [F,L]

    # [H8] Artefatos sensíveis -> 403 — via THE_REQUEST (RedirectMatch e
    #      REQUEST_URI não funcionam neste servidor). Condições curtas individuais.
    RewriteCond %{THE_REQUEST} \s/\.env[/?\s] [NC,OR]
    RewriteCond %{THE_REQUEST} \s/\.git[/?\s] [NC,OR]
    RewriteCond %{THE_REQUEST} \s/\.user\.ini[/?\s] [NC,OR]
    RewriteCond %{THE_REQUEST} \s/composer\.json[/?\s] [NC,OR]
    RewriteCond %{THE_REQUEST} \s/composer\.lock[/?\s] [NC,OR]
    RewriteCond %{THE_REQUEST} \s/package-lock\.json[/?\s] [NC,OR]
    RewriteCond %{THE_REQUEST} \s/package\.json[/?\s] [NC,OR]
    RewriteCond %{THE_REQUEST} \s/phpunit\.xml[/?\s] [NC,OR]
    RewriteCond %{THE_REQUEST} \s/web\.config[/?\s] [NC,OR]
    RewriteCond %{THE_REQUEST} \s/error_log[/?\s] [NC]
    RewriteRule ^ - [F,L]

    # Redirect Trailing Slashes If Not A Folder...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_URI} (.+)/$
    RewriteRule ^ %1 [L,R=301]

    # Force robots.txt to be handled by Laravel (sitemap dinâmico + cache headers)
    RewriteRule ^robots\.txt$ index.php [L]

    # Force sitemap.xml to be handled by Laravel (XML dinâmico por host; o
    # arquivo estático legado public/sitemap.xml sombrearia a rota)
    RewriteRule ^sitemap\.xml$ index.php [L]

    # Handle Front Controller...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]

    # Handle Authorization Header
    RewriteCond %{HTTP:Authorization} .
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
</IfModule>

# [H5] Cabeçalhos de segurança
#      IMPORTANTE: NÃO usar <IfModule mod_headers.c> neste servidor — a tag causa 500.
#      As diretivas Header ficam "cruas" (funcionam sem o wrapper).
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
# X-XSS-Protection deprecated: "0" é a recomendação atual (OWASP/MDN); CSP é o controle real.
Header always set X-XSS-Protection "0"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=(), usb=()"
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
Header always unset X-Powered-By
# Remove a informacao "Apache" do header Server (reduz fingerprinting).
# No Apache 2.4 o header Server e fixo do core e "unset" e ignorado; a
# unica forma via .htaccess e sobrescrever com valor vazio ("set").
Header always set Server "MaximaTecnologia"
# Tira a assinatura do servidor das paginas de erro (ex.: 403/404).
ServerSignature Off
# CANARIO de deploy: se este header NAO aparecer no curl -I, o arquivo servido
# nao e este (edicao em local errado / nao salva).
Header always set X-Hardening-Version "2026-08-maxima"

# [SEC-2] Isolamento de origem (defesa contra leitura cruzada de recursos)
Header always set Cross-Origin-Resource-Policy "same-origin"
Header always set Cross-Origin-Opener-Policy "same-origin"

# [SEC-1a] Reporting-Endpoints interno p/ coleta de violacoes de CSP: definido via
#          middleware CspNonce (PHP) usando a URL exata da requisicao — este Apache
#          nao expande %{VAR} em valores de "Header", e o host e dinamico.

# [H7] Cookies: SameSite=Lax nos Set-Cookie via "Header edit". Funciona como
#      diretiva "crua" — NÃO usar <IfModule mod_headers.c> (tag causa 500 neste
#      servidor). O flag SECURE NÃO sai deste arquivo: o cookie de sessão do
#      LARAVEL é controlado por config/session.php — defina SESSION_SECURE_COOKIE=true
#      no .env do app (ou cPanel › Environment Variables). O session.cookie_secure
#      do php.ini vale só para sessões PHP nativas.
Header always edit Set-Cookie "^(.+)$" "$1; SameSite=Lax" env=h7https
Header always edit Set-Cookie "^(.+)$" "$1; SameSite=Lax" env=!h7https

# [H8] OBS: o blocklist de artefatos sensíveis foi MIGRADO para dentro do bloco
#      mod_rewrite (acima) — RedirectMatch não dispara neste servidor e o
#      wrapper <IfModule mod_alias.c> é descartável aqui.

# [H9] Cache de estáticos (performance — opcional). Mantido com wrapper:
#      se este IfModule não for suportado, o custo é só perder o cache.
<IfModule mod_expires.c>
    ExpiresActive On
    # CSS
    ExpiresByType text/css "access plus 1 year"
    # JavaScript (moderno)
    ExpiresByType text/javascript "access plus 1 year"
    # Imagens
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/png "access plus 1 year"
    ExpiresByType image/webp "access plus 1 year"
    ExpiresByType image/svg+xml "access plus 1 year"
    ExpiresByType image/gif "access plus 1 year"
    ExpiresByType image/avif "access plus 1 year"
    # Fontes
    ExpiresByType font/woff2 "access plus 1 year"
    ExpiresByType font/woff "access plus 1 year"
    ExpiresByType font/ttf "access plus 1 year"
    ExpiresByType font/otf "access plus 1 year"
    ExpiresByType application/font-ttf "access plus 1 year"
    ExpiresByType application/font-woff "access plus 1 year"
    ExpiresByType application/javascript "access plus 1 year"
</IfModule>

# [L5] Mapeamento de MIME para formatos de imagem modernos (mod_mime sempre
#      disponível). Sem isso o Apache servia .webp sem Content-Type e o navegador
#      (X-Content-Type-Options: nosniff) recusava o <source> do <picture>.
AddType image/webp .webp
AddType image/avif .avif

# [L0.3] Cache-Control explícito + immutable para assets versionados (Mix hash)
#        Header CRUA (NUNCA <IfModule mod_headers.c> — causa 500 neste servidor).
#        Complementa o H9 acima para fechar cache_policy/immutable da tarefa #23410.
<FilesMatch "\.(css|js|jpg|jpeg|png|webp|avif|gif|svg|woff2?|ttf|otf|ico)$">
    Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>

# [L0.3b] PWA: sw.js (e manifest estatico, se houver) NAO podem ser immutable —
#          isso quebraria a atualizacao do Service Worker. Fica DEPOIS de L0.3
#          de proposito: o mod_headers aplica os "Header set" na ordem em que
#          aparecem, entao o ULTIMO vence. Antes de L0.3 a excecao era
#          sobrescrita e /sw.js saia immutable (bug reproduzido no canario).
<FilesMatch "(sw\.js|manifest\.webmanifest)$">
    Header set Cache-Control "public, max-age=0, must-revalidate"
</FilesMatch>

# [L0.3c] A4 / Multi-CDN: remove "User-Agent" do Vary em QUALQUER resposta.
#         O HTML publico ja e normalizado no middleware CacheControl, mas o
#         header reaparecia em estaticos e em respostas geradas pelo Apache/WHM
#         (ex.: config global de cPanel/mod_deflate), fragmentando o cache de
#         borda. "Header edit" (sem "always") edita a tabela normal, onde o
#         Vary dos modulos vive; se o valor nao casar ou o header nao existir, a
#         diretiva e um no-op — portanto NAO altera o funcionamento retroativo.
#         As tres linhas cobrem "Accept-Encoding, User-Agent", a ordem inversa e
#         "User-Agent" sozinho; em todos os casos o resultado preserva
#         "Accept-Encoding" (obrigatorio para compressao negociavel).
Header edit Vary "^(?i)\s*Accept-Encoding\s*,\s*User-Agent\s*$" "Accept-Encoding"
Header edit Vary "^(?i)\s*User-Agent\s*,\s*Accept-Encoding\s*$" "Accept-Encoding"
Header edit Vary "^(?i)\s*User-Agent\s*$" "Accept-Encoding"

# [L0.4] Revalidação condicional (ETag / 304) em estáticos
FileETag MTime Size

# [L0.2] COMPRESSÃO é SERVER-SIDE (cPanel "Optimize Website" / mod_deflate global +
#        mod_brotli no WHM EasyApache4). NÃO colocar mod_deflate/brotli aqui: o
#        AddOutputFilterByType da conta é sobreposto pelo vhost e é ineficaz neste
#        cPanel. Veja wiki/infra-update/WHM.md (P0-A).

```
