Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion docs/features/storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,14 @@ $cache = new ApcuCache();
$config = new Config($cache);
```

### With Custom Namespace

Every key is stored under a namespace prefix (default `Phirewall:`) so Phirewall's entries do not collide with other code sharing the same APCu segment, and `clear()` deletes only that namespace instead of wiping the whole shared segment:

```php
$cache = new ApcuCache('myapp:firewall:');
```

### Characteristics

- Shared memory within a single PHP-FPM pool
Expand Down Expand Up @@ -439,7 +447,7 @@ phirewall.track.api-calls.<sha256 of "user-123">
Use `$config->setKeyPrefix('myapp')` to change the prefix and avoid collisions when sharing a cache instance.

::: tip Key segments join with `.`
`CacheKeyGenerator` (and the trusted-bot rDNS cache) join key segments with `.` rather than `:`. PSR-16 reserves `:` for the *cache implementation*, not its callers, so joining with `.` keeps Phirewall's own keys spec-compliant. `RedisCache`'s own namespace prefix (default `Phirewall:`) is the backend's keyspace, applied *after* the public key, and is unaffected.
`CacheKeyGenerator` (and the trusted-bot rDNS cache) join key segments with `.` rather than `:`. PSR-16 reserves `:` for the *cache implementation*, not its callers, so joining with `.` keeps Phirewall's own keys spec-compliant. `RedisCache`'s and `ApcuCache`'s own namespace prefix (default `Phirewall:` for both) is the backend's keyspace, applied *after* the public key, and is unaffected.
:::

See [Discriminator Normalizer](/advanced/discriminator-normalizer) for details on how keys are sanitized.
Expand Down
1 change: 1 addition & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,7 @@ new TrustedProxyResolver(
- **The default header is a single header.** `allowedHeaders` defaults to `['X-Forwarded-For']` only. If your stack emits the RFC 7239 `Forwarded` header instead, pass it explicitly: `new TrustedProxyResolver([...], ['Forwarded'])`, or `['Forwarded', 'X-Forwarded-For']` for both, so the header the resolver trusts is visible at the call site rather than inferred.
- **Proxy headers are read only when the direct peer is trusted.** The resolver consults `X-Forwarded-For` (or `Forwarded`) only when `REMOTE_ADDR`, the address that connected, is itself in your trusted-proxy list. A request arriving directly from an untrusted client has its forwarded headers ignored and is keyed on `REMOTE_ADDR`.
- **All header instances are folded into one chain.** Whether intermediaries keep `X-Forwarded-For` as separate lines or fold them into one comma-separated value (the nginx default), the resolver flattens them and walks the chain right to left, returning the first hop that is not in your trusted-proxy list. The protection is this trusted-hop walk, not the number or order of header instances: a client-prepended value sits to the left of the addresses your proxies append, so it is returned only if every hop to its right is trusted. Correct trusted ranges are therefore essential, and stripping or overwriting the inbound header at the edge prevents spoofing outright.
- **An unparsable hop stops the walk.** If a chain entry cannot be parsed as an IP address - the literal `for=unknown`, an RFC 7239 obfuscated identifier (`for=_hidden`), or a malformed value - the walk treats it as terminal and falls back to `REMOTE_ADDR` rather than stepping past it. An unidentifiable hop breaks the verifiable chain, so entries further left cannot be trusted either.
- **IPv6 is canonicalized.** An IPv4-mapped IPv6 peer (`::ffff:203.0.113.7`) collapses to its embedded IPv4 form, so a plain IPv4 rule or CIDR matches it and an attacker cannot bypass an IPv4 rule by presenting the mapped form. Alternate *genuine*-IPv6 spellings (expanded `2001:0db8::1` vs compressed `2001:db8::1`, mixed case) are also treated as one identity by `ip()` / CIDR **list** matching, which compares the raw binary address. When a `TrustedProxyResolver` is set via `setIpResolver`, the resolver canonicalizes the address it returns, so per-client keys stay stable regardless of the spelling the client presents; the consistent-spelling caveat applies only to the raw `REMOTE_ADDR` peer address (read via `$request->getServerParams()['REMOTE_ADDR']`) or a custom resolver that does not canonicalize.

## First Test
Expand Down
Loading