Skip to content
This repository was archived by the owner on Feb 15, 2026. It is now read-only.
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
5 changes: 5 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,8 @@ jobs:

- name: Show coverage
run: go tool cover -func=coverage.out | tail -1

- name: Vulnerability check
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./... || echo "::warning::govulncheck found vulnerabilities — review output above"
43 changes: 28 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,21 @@ This microservice isolates the encryption key from the main backend:
## Architecture

```
┌─────────────────┐ gRPC (mTLS) ┌─────────────────────┐
│ keyway-backend │ ◄────────────────────► │ keyway-crypto │
│ (Node.js) │ :50051 │ (Go) │
└─────────────────┘ └─────────────────────┘
ENCRYPTION_KEY
(env, never logged)
Internet (untrusted)
┌─────────────────┐ gRPC (private network) ┌─────────────────────┐
│ keyway-backend │ ◄──────────────────────────► │ keyway-crypto │
│ (Node.js) │ :50051 │ (Go) │
└─────────────────┘ └─────────────────────┘
ENCRYPTION_KEY
(env, never logged)
```

> **Important**: keyway-crypto must run on an isolated private network (Docker internal network, private VPC, or Kubernetes pod network). It must never be exposed to the public internet. See [SECURITY.md](./SECURITY.md) for the full threat model.

## Encryption Details

| Property | Value |
Expand Down Expand Up @@ -67,8 +72,11 @@ ENCRYPTION_KEY=<64-hex-chars> make run

| Variable | Description | Required |
|----------|-------------|----------|
| `ENCRYPTION_KEY` | AES-256 key in hex (64 chars) | Yes |
| `PORT` | gRPC server port (default: 50051) | No |
| `ENCRYPTION_KEY` | Single AES-256 key in hex (64 chars) | Yes* |
| `ENCRYPTION_KEYS` | Versioned keys for rotation (e.g., `1:key1,2:key2`) | Yes* |
| `GRPC_PORT` | gRPC server port (default: 50051) | No |

\* Either `ENCRYPTION_KEY` or `ENCRYPTION_KEYS` must be set. `ENCRYPTION_KEYS` takes priority if both are set.

### Generating a secure key

Expand Down Expand Up @@ -161,12 +169,17 @@ CRYPTO_SERVICE_URL=localhost:50051 pnpm run dev
CRYPTO_SERVICE_URL=crypto:50051
```

## Security Considerations
## Security

See [SECURITY.md](./SECURITY.md) for the full threat model and vulnerability disclosure policy.

Key points:

1. **Key management**: Never commit `ENCRYPTION_KEY` to version control
2. **Network**: Deploy in a private network, use mTLS in production
3. **Logging**: The service never logs plaintext or keys
4. **Memory**: Sensitive data is not retained after request completion
1. **Network isolation required**: This service must run on a private network (Docker network, VPC). Never expose port 50051 to the internet.
2. **Key management**: Never commit `ENCRYPTION_KEY` to version control. Use a secrets manager in production.
3. **Key rotation**: Use `ENCRYPTION_KEYS` with versioned keys (e.g., `1:oldkey,2:newkey`) for zero-downtime rotation.
4. **Logging**: The service never logs plaintext, ciphertext, IVs, auth tags, or keys.
5. **mTLS**: Optional but recommended for high-security environments. Network isolation provides the baseline transport security.

## Make Commands

Expand Down
118 changes: 118 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Security Policy

## Threat Model

keyway-crypto is an **internal encryption microservice** that handles AES-256-GCM encryption and decryption for the Keyway platform. It is designed to run on an **isolated private network** and must never be exposed directly to the public internet.

### What this service protects against

- **Database compromise**: Secrets are encrypted at rest. An attacker who gains access to the database only sees ciphertext that cannot be decrypted without the encryption key held by this service.
- **Backend compromise**: The encryption key never touches the Node.js backend. Compromising the backend does not expose the key material.
- **Ciphertext tampering**: AES-256-GCM's authenticated encryption detects any modification to the ciphertext, IV, or authentication tag.

### What this service does NOT protect against

- **Network-level attacks on the internal network**: Communication between the backend and this service is currently unencrypted (plaintext gRPC). An attacker with access to the internal network could intercept traffic. This is mitigated by network isolation (Docker network, private VPC). mTLS support is planned.
- **Host compromise**: If an attacker gains access to the host running this service, they can read the encryption key from the process environment.
- **Key compromise**: If the encryption key is leaked, all data encrypted with that key version can be decrypted. Use key rotation (`ENCRYPTION_KEYS` with versioned keys) to limit the blast radius.

### Trust boundaries

```
Internet (untrusted)
┌──────────────────┐
│ Caddy / TLS │ ← TLS terminates here
└──────────────────┘
┌──────────────────┐
│ keyway-backend │ ← Handles auth, rate limiting, access control
└──────────────────┘
▼ Private network only (Docker network / VPC)
┌──────────────────┐
│ keyway-crypto │ ← Holds encryption key, never exposed to internet
└──────────────────┘
```

### Deployment requirements

- **MUST** run on an isolated network (Docker internal network, private VPC, Kubernetes pod network)
- **MUST NOT** be exposed to the public internet
- **MUST NOT** have the gRPC port (50051) accessible from outside the private network
- **SHOULD** use mTLS for service-to-service communication in high-security environments
- **SHOULD** use a secrets manager (Kubernetes Secrets, AWS Secrets Manager, etc.) to inject `ENCRYPTION_KEY`

## Cryptographic Details

| Property | Value |
|----------|-------|
| Algorithm | AES-256-GCM (NIST SP 800-38D) |
| Key size | 256 bits (32 bytes) |
| IV/Nonce | 12 bytes, cryptographically random (`crypto/rand`) |
| Authentication tag | 16 bytes (128 bits) |
| Key rotation | Supported via versioned keys (`ENCRYPTION_KEYS`) |
| Crypto library | Go standard library (`crypto/aes`, `crypto/cipher`, `crypto/rand`) |

The Go standard library crypto packages were [audited by Trail of Bits](https://go.dev/blog/tob-crypto-audit) in 2025.

## What we log (and what we don't)

**Never logged**: plaintext values, ciphertext, IVs, authentication tags, encryption keys.

**Logged**: request sizes (in bytes), key version numbers, operation success/failure status.

## Supported Versions

| Version | Supported |
|---------|-----------|
| latest | Yes |

## Reporting a Vulnerability

If you discover a security vulnerability, please report it responsibly:

1. **Email**: [security@keyway.sh](mailto:security@keyway.sh)
2. **Do NOT** open a public GitHub issue for security vulnerabilities
3. Include a description of the vulnerability and steps to reproduce

### Response timeline

- **Acknowledgment**: Within 48 hours
- **Assessment and timeline**: Within 5 business days
- **Fix and disclosure**: Coordinated with reporter

### What to expect

- We will acknowledge your report promptly
- We will work with you to understand and validate the issue
- We will develop and test a fix
- We will coordinate disclosure timing with you
- We will credit you in the security advisory (unless you prefer anonymity)

## Security Design Decisions

### Why a separate service?

Isolating encryption into a dedicated microservice means:

1. The encryption key is only accessible to this service, not the main backend
2. The attack surface for key extraction is reduced to ~300 lines of Go code
3. The service can be deployed with stricter network policies than the backend
4. Code audits are simpler due to the small, focused codebase

### Why AES-256-GCM?

- NIST-approved and widely adopted across the industry
- Provides both confidentiality and integrity (authenticated encryption)
- Hardware-accelerated on modern CPUs via AES-NI
- Quantum-resistant at the 256-bit level (Grover's algorithm only provides quadratic speedup)

### Why Go standard library?

- Audited by third-party security firms
- Maintained by the Go security team
- No external crypto dependencies = minimal supply chain risk
- CGO disabled = no C memory management vulnerabilities
Loading