This document describes the security model, controls, and known limitations.
- Prevent unauthorized access to a user's GitPins account.
- Protect GitHub OAuth tokens at rest and in transit.
- Prevent cross-site request forgery for state-changing actions.
- Ensure admin access is explicit, revocable, and auditable.
- Ensure GitPins cannot delete GitHub repositories (no such permission and no such code path).
Main components:
- Browser (untrusted environment, user-controlled).
- GitPins server (trusted).
- Postgres database (trusted storage, treat as sensitive).
- GitHub (external system, trusted for identity but not under our control).
Files:
src/app/api/auth/login/route.tssrc/app/api/auth/callback/route.tssrc/lib/session.tssrc/lib/github.ts
Controls:
- OAuth
statetoken is generated server-side and stored in an HTTP-only cookie. returnTois sanitized to internal paths only to prevent open redirects.- Banned users are blocked during OAuth callback (see
users.isBanned).
Files:
src/lib/session.ts
Design:
- Session is a signed JWT in an HTTP-only cookie (
gitpins_session). - The session stores only identifiers:
userId,githubId,username. - Tokens and admin status are not stored in the session cookie.
Most API routes enforce:
- A valid session.
- Per-user rate limiting.
Files:
src/lib/admin.tsprisma/schema.prisma(admin_accounts)
Design:
- Admin access is granted by inserting a GitHub id into
admin_accountswithrevokedAt IS NULL. - Allowlist can be revoked (
revokedAtset). - DB allowlist is the single runtime source of truth. Bootstrap/recovery should be done with the admin CLI or direct DB access.
Auditing:
- Admin actions write to
admin_logs.
Files:
src/lib/security.ts(validateOrigin)src/lib/session.ts(CSRF token cookie)src/app/api/auth/csrf/route.ts
Controls:
- State-changing routes validate Origin/Referer against
NEXT_PUBLIC_APP_URL. - Destructive routes also require a CSRF token:
- Server issues a CSRF token and stores it in an HTTP-only cookie.
- Client sends the token back in
X-CSRF-Token. - Server verifies the header token matches the cookie.
Notes:
- GET routes generally do not require CSRF tokens.
- Origin validation allows local dev hosts and the configured app URL.
Files:
src/lib/rate-limit.tssrc/lib/security.ts
Current implementation:
- In-memory fixed window rate limiter.
- Appropriate for single-instance dev and small deployments.
Limitations:
- Not shared across instances.
- Not persistent across restarts.
Recommendation for production:
- Replace with Redis or similar shared storage.
Files:
src/lib/crypto.tssrc/lib/github.tssrc/app/api/auth/callback/route.ts
At rest:
- Access tokens (and refresh tokens if present) are stored encrypted in
user_tokens. - Encryption uses AES-256-GCM authenticated encryption.
- Each token encryption uses a unique random salt and IV.
- Only server-side code can decrypt tokens.
In transit:
- Tokens never leave the server after OAuth callback.
- Tokens are not put into the session cookie.
- Tokens are not included in privacy exports.
Expiry/refresh:
- If GitHub provides
expires_in, it is stored asuser_tokens.expiresAt. ensureValidToken()refreshes when close to expiry (5 minute window) and when forced.- If refresh is not possible, the user is required to log in again.
Files:
prisma/schema.prisma(repo_orders.syncSecret)src/app/api/sync/route.tssrc/app/api/sync/[secret]/route.ts
Design:
- Sync endpoint is authenticated by a per-user UUID secret.
- An external scheduler calls
POST /api/syncand provides the secret inX-GitPins-Sync-Secret. /api/syncproxies internally to/api/sync/[secret].
Security properties:
- Secret is high-entropy (UUIDv4) and stored server-side.
- Sync endpoint is rate-limited per secret (10/hour) to reduce abuse.
Files:
src/lib/sudo.tssrc/app/api/privacy/delete/route.ts
Design:
- Deleting an account requires recent reauthentication.
- The app issues a short-lived
gitpins_sudocookie only after completing a fresh OAuth flow initiated withsudo=1.
This reduces risk from:
- Stolen long-lived session cookies.
- Shared computers.
Files:
src/lib/privacy-audit.tsprisma/schema.prisma(privacy_events,account_deletion_audits)
Design:
- Privacy/security events are recorded in
privacy_events. - Events are pseudonymized via
subjectHashso they can survive deletion without keeping direct user identifiers. - IP and user agent can be captured in a privacy-preserving way (hashed IP, trimmed UA).
GitPins should request only what it needs.
Required in practice for temporary-ref touch:
- Repository contents read/write (to create commits).
- Repository metadata read (to list repos and read default branch).
Non-goals:
- Delete repositories.
- Modify settings, collaborators, issues, or pull requests.
Commit cleanup rewrites git history. Any history rewrite has inherent risk:
- Collaborators must re-clone or hard reset.
- Forks will diverge.
- Branch protection may block force-updates.
If you keep the cleanup feature, documentation and UI should always describe it as "history rewrite" and require explicit confirmation.
- Set
JWT_SECRETandENCRYPTION_SECRETto strong unique values. - Keep
GITHUB_APP_PRIVATE_KEYsecret and rotated if leaked. - Ensure
NEXT_PUBLIC_APP_URLmatches the deployed origin exactly. - Run behind HTTPS in production.
- Enable database backups and access controls.
- Consider replacing in-memory rate limiting with Redis.