# Security Documentation

## 1. Security Overview

The Packr registry has been hardened against the OWASP Top 10 and common npm registry attack vectors, including tarball path traversal, package hijacking, brute-force credential attacks, webhook SSRF, and denial-of-service via large uploads or high request volume. This document describes the security controls in place.

---

## 2. Authentication & Authorization

| Endpoint | Auth Method | Description |
|---|---|---|
| `/-/v1/login` | None (public) | User login / registration (disabled by default) |
| `PUT /` (publish) | JWT Bearer token | Package publishing |
| `/-/v1/deprecate` | JWT Bearer token | Deprecate package versions |
| `/-/v1/unpublish` | JWT Bearer token | Unpublish package versions |
| `/api/v1/admin/sessions` | `X-Internal-Secret` header | Create admin session (Next.js -> Go) |
| `/api/v1/admin/*` | Session cookie or Bearer token | Admin dashboard API |
| `/-/search/`, `/agent/` | None (public) | Package search and agent endpoints |

---

## 3. Rate Limiting

| Route | Limit | Window | Purpose |
|---|---|---|---|
| `/-/v1/login` | 10 req | 1 min / IP | Prevent brute-force |
| `/-/v1/password-reset` | 10 req | 1 min / IP | Prevent abuse |
| `PUT /` (publish) | 30 req | 1 min / IP | Prevent flooding |
| `/api/v1/admin/*` | 60 req | 1 min / IP | Prevent admin abuse |
| `/-/search/`, `/agent/` | 30 req | 1 min / IP | Prevent DoS |

IP extraction uses the `Fly-Client-IP` header, which is unforgeable behind the Fly.io proxy. If that header is absent, the implementation falls back to the last IP in `X-Forwarded-For`.

---

## 4. Resource Limits

| Resource | Limit | Configurable |
|---|---|---|
| Tokens per user | 20 | Code constant (`maxTokensPerUser`) |
| Webhooks per user | 10 | Code constant (`maxWebhooksPerUser`) |
| Tarball size | 5 MB | `MAX_TARBALL_SIZE` env var |
| Request body (login/admin) | 1 MB | `SmallBodyLimit` |
| Request body (publish) | 50 MB | `LargeBodyLimit` |
| JWT token expiry | 30 days | Code constant |
| Session expiry | 7 days | Code constant |

---

## 5. Password Requirements

Passwords are validated on registration and password reset. Requirements:

- Minimum 10 characters
- Maximum 128 characters
- At least one uppercase letter
- At least one lowercase letter
- At least one digit
- At least one special character (`!@#$%^&*` etc.)

---

## 6. Input Validation

| Input | Validation | Purpose |
|---|---|---|
| Username | 3-64 chars, alphanumeric + `._-`, must start/end alphanumeric | Prevent injection |
| Package scope | Must start with `@`, lowercase alphanumeric + `._-` | Prevent path traversal |
| Package name | Lowercase alphanumeric + `._-`, start with alphanumeric | Prevent path traversal |
| Version | Strict semver (`X.Y.Z` or `X.Y.Z-prerelease`) | Prevent path traversal |
| Tarball path | Containment check — must stay within data directory | Prevent filesystem escape |

---

## 7. Package Ownership

- **First publish:** any authenticated user with a valid token can publish a new package.
- **Subsequent publishes:** only package owners can publish new versions.
- Ownership is enforced on: publish, deprecate, unpublish, and dist-tag changes.
- Package owners are set automatically on first publish.

---

## 8. Session Security

- Sessions are HMAC-SHA256 signed tokens.
- Session cookies are set with `HttpOnly`, `Secure`, and `SameSite=strict` attributes.
- Sessions expire after 7 days.
- Session creation requires the `INTERNAL_API_SECRET` shared between the Next.js dashboard and the Go backend.

---

## 9. CORS Policy

| Route | CORS Origin |
|---|---|
| `/api/v1/admin/*` | `DASHBOARD_ORIGIN` only (default: `https://packr.blueforge.studio`) |
| All other routes | `*` (wildcard — required for npm CLI compatibility) |

---

## 10. Webhook Security

- Payloads are signed with HMAC-SHA256 and delivered with the header `X-Packr-Signature: sha256=...`.
- SSRF protection is applied at both DNS resolution and TCP connect time, blocking private IP ranges.
- Webhooks targeting unresolvable hostnames are blocked.
- Webhook secrets are redacted in list API responses and are only shown at creation time.
- Webhook delivery has a 10-second timeout.

---

## 11. Infrastructure Security

- The Docker container runs as a non-root user (`packr`).
- HTTP server timeouts: `ReadTimeout` 30s, `WriteTimeout` 120s, `IdleTimeout` 60s.
- `ReadHeaderTimeout` is set to 10s; `MaxHeaderBytes` is limited to 1 MB.
- Token hashing uses full SHA-256 with no truncation.
- Secrets (`JWT_SECRET`, `SESSION_SECRET`) are generated randomly at startup if not configured, with a warning logged. Random secrets are ephemeral and do not persist across restarts.

---

## 12. Environment Variables

| Variable | Required | Default | Description |
|---|---|---|---|
| `JWT_SECRET` | Yes (prod) | Random (ephemeral) | JWT token signing key |
| `SESSION_SECRET` | Yes (prod) | Random (ephemeral) | Session HMAC signing key |
| `INTERNAL_API_SECRET` | Yes (dashboard) | Empty (disabled) | Shared secret for session creation |
| `ALLOW_REGISTRATION` | No | `false` | Enable self-registration |
| `DASHBOARD_ORIGIN` | No | `https://packr.blueforge.studio` | Allowed CORS origin for admin API |
| `MAX_TARBALL_SIZE` | No | `5242880` (5 MB) | Max tarball upload size in bytes |
| `RATE_LIMIT_LOGIN` | No | `10` | Login attempts per minute per IP |
| `RATE_LIMIT_PUBLISH` | No | `30` | Publish requests per minute per IP |

---

## 13. OAuth Device Flow Security

The OAuth device flow (RFC 8628) is implemented with the following security controls:

| Control | Detail |
|---------|--------|
| **Code expiry** | Device codes and user codes expire after **15 minutes**. This limits the phishing window. |
| **Rate limiting** | `POST /-/v1/device/authorize` is rate-limited to **5 requests/min/IP** to prevent code enumeration. |
| **Single-use codes** | Once a device code is exchanged for a token, it is immediately invalidated. Replaying the code returns `expired_token`. |
| **State parameter** | Each authorization request includes a random 32-byte state token to prevent CSRF. |
| **Account linking by email** | OAuth identities are linked to Packr accounts by verified email address. Accounts are not created for unverified emails. |

---

## 14. Keychain Credential Storage

The CLI can store tokens in the system keychain rather than a plain file:

- **macOS:** macOS Keychain (encrypted, locked to user session)
- **Windows:** Windows Credential Manager
- **Linux:** libsecret (GNOME Keyring, KWallet)

Keychain storage is recommended for developer workstations. The file-based store (`~/.packr/credentials.json`) is created with mode `0600` and is acceptable for CI environments where keychain access is unavailable.

---

## 15. Scope-Based Access Control

Tokens carry optional scope restrictions in their JWT claims. When scope restrictions are present:

- The token can only be used for packages within the listed scopes.
- Attempts to publish to, unpublish from, or deprecate packages outside the listed scopes are rejected with `403 Forbidden`.
- Read operations (install, metadata fetch) respect scope restrictions for private scopes.

Empty scope list means the token applies to all packages (backward compatible with tokens created before the permission system).

---

## 16. Token Permission Enforcement

The `RequirePermission` middleware enforces permissions on every protected route. Permission checks happen after JWT validation:

```
Request → JWT validation → Permission check → Scope check → Handler
```

- Missing JWT: `401 Unauthorized`
- Valid JWT, wrong permission: `403 Forbidden`
- Valid JWT, scope mismatch: `403 Forbidden`
- Valid JWT, no permissions claim (legacy): `200 OK` (backward compatible)

Permission types: `read`, `publish`, `unpublish`, `admin`.

---

## 17. Account Linking Security

When a user logs in via OAuth, the registry links the OAuth identity to a Packr account by **verified email address**:

- Only provider-verified emails are used for linking (GitHub: only primary verified email; Google: always verified; GitLab: only verified primary email).
- If no existing Packr account has the email, a new account is created automatically.
- If a Packr account exists with that email, the OAuth identity is linked to it. The user can then log in via password or OAuth interchangeably.
- A single Packr account can have multiple linked OAuth identities (e.g., GitHub + Google with the same email).
- Admins can unlink OAuth identities via the dashboard.

---

## 18. Security Audit History

| Date | Scope | Findings | Status |
|---|---|---|---|
| April 2026 | Comprehensive audit of registry, dashboard, and infrastructure | 24 findings: 4 CRITICAL, 6 HIGH, 8 MEDIUM, 6 LOW | All remediated |

The April 2026 audit covered authentication flows, authorization enforcement, input validation, rate limiting, webhook security, session management, CORS configuration, resource limits, infrastructure hardening, OAuth device flow, and token permission enforcement. All findings have been addressed and verified.
