A modern, secure NIP-46 Nsec Bunker management tool built with TypeScript.
| Login | Dashboard |
|---|---|
![]() |
![]() |
| Connections | Keys |
|---|---|
![]() |
![]() |
Screenshots are captured with Playwright (WebP). The connections screenshot uses mocked API data. To regenerate: run pnpm dev in one terminal, then pnpm run e2e:screenshots.
| Layer | Technology |
|---|---|
| Frontend | Vue 3, Vite 7, Tailwind CSS v4, shadcn-vue, nanostores, localforage |
| Backend | NestJS 11, Fastify, Prisma ORM 7 (driver adapter), nostr-tools |
| Database | PostgreSQL 17 |
| Auth | JWT + Argon2, TOTP (otplib), WebAuthn/Passkeys (@simplewebauthn) |
| Testing | Vitest, Playwright, eslint-plugin-security |
| Infra | Docker, Docker Compose, Node 24, pnpm 10, Redis (optional, for live updates) |
bunker46/
├── apps/
│ ├── server/ # NestJS + Fastify backend
│ │ ├── src/
│ │ │ ├── auth/ # JWT, TOTP, WebAuthn, session management
│ │ │ ├── bunker/ # NIP-46 RPC handler, relay pool, URI parsing
│ │ │ ├── connections/ # CRUD for bunker connections & permissions
│ │ │ ├── events/ # Redis pub/sub, SSE stream (live dashboard/connections)
│ │ │ ├── logging/ # Signing logs & dashboard stats
│ │ │ ├── users/ # User management
│ │ │ ├── prisma/ # Database service
│ │ │ └── common/ # Encryption, guards, interceptors
│ │ └── prisma/ # Schema & migrations
│ └── web/ # Vue 3 SPA
│ ├── src/
│ │ ├── components/ # UI components (shadcn-style)
│ │ ├── views/ # Route views
│ │ ├── stores/ # nanostores + localforage
│ │ ├── router/ # Vue Router
│ │ └── lib/ # API client, utilities
│ └── e2e/ # Playwright E2E tests
├── packages/
│ ├── shared-types/ # Zod schemas & TypeScript types
│ ├── config/ # Environment config & constants
│ ├── tsconfig/ # Shared TypeScript configs
│ └── eslint-config/ # Shared ESLint configs
├── docker-compose.yml # Production Docker Compose
└── docker-compose.dev.yml # Dev services (DB, Redis)
- Node.js >= 24
- pnpm >= 10
- Docker & Docker Compose (for database)
# Install dependencies
pnpm install
# Start database services
docker compose -f docker-compose.dev.yml up -d
# Copy environment config
cp .env.example .env
# Optional: set REDIS_URL=redis://localhost:6379 in .env for live dashboard/connections updates
# Optional: set ALLOW_REGISTRATION=false to disable new user sign-ups (backend returns 403; frontend hides register link)
# Optional: set TRUST_PROXY=true when the API is behind a reverse proxy (e.g. Caddy) so session IPs and rate limiting use the real client IP
# Generate Prisma client (required; runs automatically on pnpm install) & run migrations
pnpm db:generate
pnpm db:migrate
# Optional: seed the database (Prisma 7 does not auto-seed after migrate)
# pnpm --filter @bunker46/server run db:seed
# Start dev servers (backend + frontend)
pnpm devThe frontend will be available at http://localhost:5173 and the API at http://localhost:3000. With Redis running and REDIS_URL set, the dashboard and connections list refresh automatically when activity occurs.
If ports 3000 or 5173 are already in use (e.g. a previous dev run didn’t exit cleanly), free them and restart:
pnpm kill-port
pnpm dev# Run all unit/integration tests
pnpm test
# Run with coverage
pnpm test:coverage
# Run E2E tests (Playwright; starts full stack from repo root so API works)
pnpm e2e
# If you already have `pnpm dev` running, reuse it to avoid port conflicts:
# CI= pnpm e2e
# Run E2E with UI
pnpm e2e:ui
# When the server is started by Playwright (pnpm dev), rate limiting is skipped for localhost in non-production so E2E does not hit limits. For E2E_USE_PREVIEW, start the server yourself; localhost requests are also skipped when NODE_ENV is not production.
# Lint (includes security rules)
pnpm lint
# Security-specific lint
pnpm lint:security# Build and run all services
docker compose up --build
# Access the application
open http://localhost:8080When running behind a reverse proxy (e.g. Caddy, Nginx), set TRUST_PROXY=true (or 1 / yes) in the server environment so that session IPs and throttling use the client IP from X-Forwarded-For / X-Real-IP. Only enable this when the app is only reachable through a trusted proxy.
This tool implements the full NIP-46 Nostr Remote Signing specification:
- URI Support: Parse and generate both
bunker://andnostrconnect://URIs - All RPC Methods:
connect,sign_event,ping,get_public_key,nip04_encrypt/decrypt,nip44_encrypt/decrypt,switch_relays - Fine-grained Permissions: Per-connection method and event kind restrictions
- NIP-44 Encryption: All communication encrypted using NIP-44
- Auth Challenges: Support for out-of-band authentication
- Relay Management: Configurable relays per connection with automatic reconnection
- Private keys (nsecs) are encrypted at rest using AES-256-GCM
- Passwords hashed with Argon2
- 2FA via TOTP and WebAuthn/Passkeys
- JWT with short-lived access tokens and refresh token rotation
- Session management: list active sessions (with IP and user-agent), revoke a session, or log out all other sessions
- HTTP-only secure cookies option
- Security ESLint rules enforced
- Non-root Docker containers
CORS / CSRF: Keep CORS_ORIGINS strict: use explicit origins (e.g. http://localhost:5173 or your frontend URL), never * when credentials are used. Avoid overly broad origins to reduce cross-site request risk; consider SameSite cookie attributes and CSRF tokens for critical state-changing actions.
To check for outdated packages across the monorepo:
pnpm outdated -rSeveral dependencies have newer major versions available (e.g. Prisma 7, Vitest 4, Vue Router 5, Zod 4). Upgrade when ready and run tests; pin to current majors if you prefer stability.
MIT




