A PII Data Privacy Vault REST API built in Go. Instead of storing raw sensitive data (emails, phone numbers, card numbers) in your application database, you send them to AEGIS, get back an opaque token, and store that token. Only authorized roles can retrieve the real data — and every access is logged.
cp .env.example .env
# Edit .env: set JWT_SECRET (32+ chars) and VAULT_MASTER_KEY (exactly 32 chars)
docker-compose up --buildThat's it. Postgres starts, migrations run, API is live at http://localhost:8080.
Full step-by-step guide with curl examples for every endpoint → docs/USAGE_GUIDE.md
API reference → docs/API.md
Swagger UI (GitHub Pages) → https://anandukch.github.io/aegis/swagger.html
| Role | phone | card_number | aadhaar | pan | name | dob | |
|---|---|---|---|---|---|---|---|
| ADMIN | FULL | FULL | FULL | FULL | FULL | FULL | FULL |
| ANALYST | MASKED | MASKED | DENIED | MASKED | MASKED | MASKED | MASKED |
| SERVICE | MASKED | MASKED | FULL | MASKED | MASKED | MASKED | MASKED |
| VIEWER | MASKED | MASKED | MASKED | MASKED | MASKED | MASKED | MASKED |
Masking examples:
- email:
john@example.com→j***@example.com - phone:
9876543210→******3210 - card:
4111111111114242→****-****-****-4242 - aadhaar:
123456781234→XXXX-XXXX-1234 - pan:
ABCDE1234F→ABCDE****F - name:
John Doe→J*** D*** - dob:
1990-07-15→****-**-15
This is the right question to ask about any vault system.
Attacker gets rows of enc_value + nonce — both base64-encoded ciphertext. Without VAULT_MASTER_KEY, these are computationally infeasible to reverse. AES-256-GCM with a random nonce per record gives no pattern to exploit. Raw PII remains safe.
This is the single critical secret. If it leaks alongside the database, every record can be decrypted. This is game over for all stored data.
Mitigation for production:
- Never store the master key in a
.envfile on the server - Use a dedicated secrets manager: AWS KMS, HashiCorp Vault, or GCP Secret Manager
- Ideally use envelope encryption — the master key itself is encrypted by a hardware-backed KMS key that never leaves the HSM
- Rotate the master key periodically (requires re-encrypting all vault records)
Attacker can forge valid JWTs with any role (including ADMIN), then call /detokenize to retrieve raw values — provided the API is reachable and the DB is accessible. Effectively full read access.
Mitigation: short JWT_EXPIRY_HOURS, rotate the secret immediately on suspicion, use RS256 (asymmetric) JWTs in production so the signing key never needs to be shared.
Every detokenize call is logged with actor_id, ip_address, and timestamp — even denied ones. Anomalous patterns (high volume from one actor, unusual IPs, DENIED spikes) signal an active breach before full exfiltration completes.
| What leaks | Impact | Mitigation |
|---|---|---|
| Database only | None — ciphertext is opaque | AES-256-GCM (built in) |
VAULT_MASTER_KEY |
Full decryption of all records | Store in KMS, never in env files |
JWT_SECRET |
Attacker can impersonate any role | Short expiry, rotate immediately, use RS256 |
| Both key + DB | Total breach | Defense in depth — restrict DB network access, monitor audit logs. Production-grade mitigation: use Customer-Managed Keys (CMK) where the KEK lives in the customer's own AWS KMS account — even a full vendor-side compromise cannot decrypt data without the customer's key. |
AES-256-GCM — authenticated encryption. GCM provides both confidentiality and integrity: any tamper with the ciphertext causes decryption to fail rather than silently return garbage. A fresh 12-byte random nonce per record ensures identical values produce different ciphertexts.
Soft deletes — deleted_at timestamp instead of hard delete. Audit logs reference tokens; hard-deleting vault records would leave dangling audit entries. Soft delete preserves the audit trail while making the data inaccessible through normal API flows.
Append-only audit logs — no update or delete endpoint for audit records. The audit trail's value is in its immutability. Every STORE, DETOKENIZE, and DELETE is logged regardless of outcome.
Role embedded in JWT — no DB lookup per request for role resolution. Role changes only take effect on next login (short JWT expiry mitigates stale role window).
go test ./internal/crypto/... ./internal/vault/... -vSee internal/llmproxy/README.md — planned LLM proxy that detokenizes PII before sending prompts to external models, preventing raw PII from ever reaching OpenAI/Anthropic.
| Variable | Description |
|---|---|
APP_PORT |
HTTP listen port (default: 8080) |
APP_ENV |
development or production |
DB_HOST |
Postgres host |
DB_PORT |
Postgres port |
DB_USER |
Postgres user |
DB_PASSWORD |
Postgres password |
DB_NAME |
Database name |
JWT_SECRET |
HMAC secret for JWT signing (min 32 chars) |
JWT_EXPIRY_HOURS |
JWT TTL in hours (default: 24) |
VAULT_MASTER_KEY |
AES-256 master key — must be exactly 32 bytes |
