# Allow and Deny Lists

This page explains how the two permanent lists (Allow and Deny) are loaded, queried, written, and removed.

## Comparison

| Item | Allow | Deny |
|---|---|---|
| Access | `IPGuardian.Manager.Allow` | `IPGuardian.Manager.Deny` |
| On hit | `200`, skips every later check | `403`, `Device is banned, IP: {ip}` |
| Precedence | Highest; passes even when also blocked or denied | Checked after Block |
| Redis key | `allow:{ip}`, no expiry | `deny:{ip}`, no expiry |
| List file | `Filepath.WhiteList`, default `./whiteList.json` | `Filepath.BlackList`, default `./blackList.json` |
| Notification on add | None | Sends email asynchronously when `Email` is set; see [Email Alerts](/email-alerts) |

## Loading at Startup

`New()` reads each list file and writes every entry to both the memory cache and Redis (pipeline):

| Case | Behavior |
|---|---|
| File missing | Skipped |
| Read or JSON parse fails | Logs the error; `New()` still succeeds |
| Redis write fails | Logs the error; the memory cache is already loaded |

List file format (an `IPItem` array):

```json
[
  {"ip": "203.0.113.10", "reason": "office gateway", "added_at": 1735689600}
]
```

## Lookup

`Check(ip)` asks Redis with `EXISTS` first and returns `true` on a hit; on a miss or Redis error it falls back to the memory cache. Matching is exact string equality, so different IPv6 spellings (for example with zeros compressed) count as different IPs.

## Adding

```go
if err := sentry.Manager.Allow.Add("203.0.113.10", "office gateway"); err != nil {
	log.Printf("allow: %v", err)
}
if err := sentry.Manager.Deny.Add("198.51.100.7", "credential stuffing"); err != nil {
	log.Printf("deny: %v", err)
}
```

`Add` writes the memory cache, then Redis, then overwrites the list file with **this instance's memory cache**.

## Removing

There is no removal API. Because `Check` queries Redis first, editing the file alone has no effect; do all of the following:

1. Delete the Redis key: `redis-cli DEL allow:203.0.113.10`
2. Remove the entry from the list file
3. Restart every instance to clear the memory caches

## Multi-Instance Boundaries

| Case | Behavior |
|---|---|
| Instance A calls `Add` | Redis applies to every instance at once; only A's memory cache and list file have the entry |
| Several instances share one list file | Each `Add` overwrites the whole file from its own cache, erasing entries other instances added |
| Restart after Redis is flushed | Only entries written to the list file come back |

When consistency across instances matters, treat Redis as the source of truth and the list file as a single-instance backup.
