# SENTINEL — Air-Gapped Encrypted Tokenized Vault

A personal-to-government vault that stores sensitive files and notes **fully
encrypted at rest**, **air-gap ready** (works fully offline, no network calls),
and **tokenized** (each stored item is referenced by an opaque token, so the
real content and even the real filename never appear in listings or logs).

Part of the **SENTINEL Track-Back Defense Platform**. This component is purely
defensive: it stores *your own* data on *your own* machine. It never reaches out
to, touches, or exfiltrates from any other system.

---

## Features

- **Encrypted at rest** — every item is individually encrypted with
  **AES-256-GCM** (authenticated encryption). Tampering is detected on read.
- **Key derivation from a passphrase** — **PBKDF2-HMAC-SHA256** or **scrypt**
  (memory-hard) with a random per-vault salt. The passphrase is never stored.
- **Tokenized** — each item gets an opaque, random token (`tok_...`). Listings
  show only the token, a **masked** filename, size, and timestamp — never the
  plaintext or the real name.
- **Air-gapped** — the code makes **zero network calls**. The web UI binds to
  `127.0.0.1` only and loads no remote assets. It runs with the network cable
  pulled.
- **Three security tiers** — Individual/Family, Business, Government/High-Security,
  with escalating KDF strength and controls (`tiers.py`).
- **Three interfaces** — a core library, a CLI, and a local dark-console web UI.

---

## Files

| File               | Purpose                                                             |
| ------------------ | ------------------------------------------------------------------- |
| `vault.py`         | Core library: `init_vault`, `add_item`, `get_item`, `list_items`, `delete_item`. |
| `tiers.py`         | Tier 1/2/3 config profiles (KDF params + controls).                 |
| `cli.py`           | Command-line interface (`init`, `add`, `list`, `get`, `delete`).    |
| `app.py`           | Flask web UI (unlock, list, add, download, delete). Localhost only. |
| `templates/`       | HTML templates for the web UI (dark security-console style).        |
| `demo_seed.py`     | Populates a demo vault with **synthetic** files.                    |
| `selftest.py`      | Automated roundtrip + tamper + UI-flow tests.                       |
| `requirements.txt` | Python dependencies.                                                |

On-disk vault layout (default `data/vault/`):

```
vault.json                 public, NON-secret metadata (KDF params, salt, tier)
index.enc                  encrypted index (token -> real name/size/time)
vault_store/<token>.enc    one AES-256-GCM ciphertext blob per stored item
```

---

## Setup

Requires Python 3.9+.

```bash
cd sentinel/vault
pip install -r requirements.txt      # flask + cryptography
```

---

## Run

### 1. Web UI (recommended)

```bash
python demo_seed.py          # optional: create a demo vault with synthetic files
python app.py                # then open http://127.0.0.1:5000
```

If you ran `demo_seed.py`, unlock with the demo passphrase: **`demo-pass-123`**.
Otherwise the UI shows a setup screen to create a new vault.

The UI displays a persistent banner:
**"Offline / Air-Gapped Mode — no data leaves this machine."**

### 2. Command line

```bash
python cli.py init --tier tier1          # create a vault (prompts for passphrase)
python cli.py add ./secret.pdf           # -> prints an opaque token
python cli.py list                       # tokens + masked metadata only
python cli.py get tok_XXXX out.pdf       # decrypt an item to a file
python cli.py delete tok_XXXX            # remove an item
```

The vault location defaults to `./data/vault`. Override with `--dir PATH` or the
`SENTINEL_VAULT_DIR` environment variable. Passphrase entry is masked (getpass).

### 3. As a library

```python
import vault as V
V.Vault.init_vault("data/vault", "correct horse battery staple", tier="tier2")
v = V.Vault.open("data/vault")
token = v.add_item("secret.pdf", "correct horse battery staple")
for item in v.list_items("correct horse battery staple"):
    print(item.token, item.masked_name, item.size)
data, real_name = v.get_item(token, "correct horse battery staple")
v.delete_item(token, "correct horse battery staple")
```

---

## Security tiers

| Tier | Audience | KDF | Notable controls |
| ---- | -------- | --- | ---------------- |
| **1 — Individual/Family** | Personal / household | PBKDF2 (600k iters) | Single passphrase, local-only |
| **2 — Business** | SMB with regulated data | scrypt (N=2¹⁷) | Multi-user, retention policy, audit metadata |
| **3 — Government/High-Security** | Classified / high-consequence | scrypt (N=2²⁰) | Hardware key required, two-person delete, data-diode I/O |

Higher tiers = stronger, slower key derivation (more expensive to brute-force) +
more organizational controls. See `tiers.py` for the full definitions and a
detailed note on how **Tier 3 maps to a true physical air-gap** (no NICs/radios,
data diode / sneakernet, hardware root of trust, TEMPEST/SCIF environment).

---

## Air-gap guarantees

- No module in this component imports a networking library or opens a socket.
- The web UI binds to `127.0.0.1` and serves only inlined CSS (no CDN/remote fonts).
- The passphrase-derived key lives only in memory for the duration of an operation.

## Security notice

**This is a PROTOTYPE. It has NOT been independently security-audited.** It
demonstrates sound patterns (AES-256-GCM, PBKDF2/scrypt, per-item encryption,
tokenization) but should not be relied upon to protect real classified or
life-critical data as-is. Passphrases cannot be recovered — if you lose the
passphrase, the data is unrecoverable by design.
