Status note (2026-04-15): this file is a historical planning document and no longer reflects the live implementation details in several areas (naming, auth/session model, and route surface). For current behavior, use
README.md,apps/api/README.md,apps/ui/README.md,LOCAL_TESTING.md,CHECKLIST.md, andREMAINING_BLOCKERS.md.
We are building a lean Privileged Access Management (PAM) system that centralizes access to infrastructure assets (Linux servers, databases, Redis, file transfer endpoints). Users authenticate into PAM, which brokers all connections — users never receive raw credentials. Every session is audited. The system ships as a modular monolith: Go backend, React+TailAdmin UI, and a desktop connector/launcher.
Development mode (current): local auth provider first, with LDAP deferred but planned as an additional provider. Authorization, session handling, policy enforcement, and audit remain provider-agnostic from day one.
- Local authentication provider for development/POC
- Local user/group/role model
- Asset inventory (servers, databases, Redis instances, file transfer endpoints)
- Access policy: assign assets to users/groups
- Central credential vault (encrypted at rest in PostgreSQL)
- Access table UI — user sees their assigned assets with launch actions
- SSH gateway/proxy with full session recording (keystroke + output)
- SFTP/file transfer managed path with file-operation logging
- DB access brokered through PAM (DBeaver enforced), connection-level audit guaranteed
- Redis managed shell with connection-level audit (command logging included only if simple within proxy design)
- Audit trail: who, when, what asset, session duration, activity
- Session history and review UI
- Desktop connector/launcher for client enforcement
- LDAP integration is deferred during foundation and early feature slices.
- Auth is implemented as a provider model:
localfirst,ldaplater. - Authorization is unchanged and fully enforced server-side:
- users, groups, roles, access grants
- policy checks
- session token lifecycle
- audit event model
- Seed a default development admin user on first bootstrap/seed path.
- LDAP onboarding later must plug into the same auth-provider interface without rewriting authorization, policy, or session/audit logic.
See section 3.
- RDP / Windows remote desktop
- Browser-based terminal / web shell
- Video replay of sessions
- Approval workflows / request-based access
- Secret rotation engine
- Multi-tenancy
- Microservices architecture
- Kubernetes-specific control plane
- SAML / OIDC federation
- Compliance framework automation (SOX, PCI checklists, etc.)
- High-availability / clustering (single-instance deployment for v1)
- DB statement-level capture (connection/session audit only in v1)
- Connector device registration / admin approval
- Full LDAP sync engine
- External KMS / HashiCorp Vault integration (v1 uses app-level master key)
- Mobile UI
- API for third-party integrations
pam/
├── apps/
│ ├── api/ # Go backend (HTTP API + SSH proxy + DB broker + audit)
│ │ ├── cmd/ # Entry points (server, migrate, seed)
│ │ ├── internal/
│ │ │ ├── auth/ # Auth providers (local first, LDAP later), session/token management
│ │ │ ├── user/ # User/group/role domain
│ │ │ ├── asset/ # Asset inventory domain
│ │ │ ├── policy/ # Access assignments, policy evaluation
│ │ │ ├── vault/ # Credential storage and retrieval
│ │ │ ├── proxy/ # SSH proxy, DB broker, Redis proxy, SFTP relay
│ │ │ ├── audit/ # Audit log writes, session recording storage
│ │ │ ├── api/ # HTTP handlers, middleware, routes
│ │ │ └── db/ # PostgreSQL queries, migrations
│ │ ├── migrations/
│ │ └── go.mod
│ ├── ui/ # React + TailAdmin
│ │ ├── src/
│ │ │ ├── pages/ # Login, Dashboard, Assets, Sessions, Admin
│ │ │ ├── components/
│ │ │ ├── api/ # API client (generated or hand-written from contracts)
│ │ │ ├── hooks/
│ │ │ └── types/ # TypeScript types (from contracts)
│ │ ├── package.json
│ │ └── vite.config.ts
│ └── connector/ # Desktop launcher/helper (Go binary)
│ ├── cmd/
│ ├── internal/
│ │ ├── launch/ # Client launcher (PuTTY, terminal, DBeaver, FileZilla)
│ │ ├── config/ # Local config, API endpoint discovery
│ │ └── auth/ # Token forwarding to API
│ └── go.mod
├── packages/
│ └── contracts/ # Shared API contracts
│ ├── api.yaml # OpenAPI spec (or similar)
│ └── types/ # Shared type definitions
├── scripts/ # Dev tooling, docker-compose, etc.
├── deploy/ # Systemd unit files, production config templates
├── docker-compose.yml # PostgreSQL (+ optional LDAP for later integration tests)
├── Makefile
├── PLAN.md
├── CHECKLIST.md
└── README.md
Go modules: apps/api and apps/connector are separate Go modules. They share contracts via the OpenAPI spec / generated types, not via Go imports.
┌─────────────┐ ┌──────────────────────────────────────────┐
│ Browser │◄─────►│ PAM API (Go, single binary) │
│ React UI │ HTTP │ │
└─────────────┘ │ ┌──────────┐ ┌────────┐ ┌───────────┐ │
│ │ Auth │ │ Policy │ │ Vault │ │
┌─────────────┐ │ │Provider │ │ Engine │ │ (creds) │ │
│ Connector │◄─────►│ └──────────┘ └────────┘ └───────────┘ │
│ (desktop) │ HTTP │ │
└─────────────┘ │ ┌──────────────────────────────────────┐│
│ │ Proxy Layer ││
│ │ ┌─────┐ ┌──────┐ ┌─────┐ ┌──────┐ ││
│ │ │ SSH │ │ DB │ │SFTP │ │Redis │ ││
│ │ │Proxy│ │Broker│ │Relay│ │Proxy │ ││
│ │ └──┬──┘ └──┬───┘ └──┬──┘ └──┬───┘ ││
│ └─────┼───────┼────────┼───────┼──────┘│
│ │ │ │ │ │
│ ┌─────┴───────┴────────┴───────┴──────┐│
│ │ Audit Engine (writes all events) ││
│ └─────────────────────────────────────┘│
│ │
│ ┌──────────────┐ │
│ │ PostgreSQL │ │
│ └──────────────┘ │
└──────────────────────────────────────────┘
│ │ │ │
┌───────┴───┐ ┌─┴──────┐ │ ┌────┴────┐
│ Linux VMs │ │ DBs │ │ │ Redis │
│ (SSH) │ │(PG/My) │ │ │instances│
└───────────┘ └────────┘ │ └─────────┘
┌────┴────┐
│ SFTP │
│endpoints│
└─────────┘
Key architectural decisions:
- Single Go binary serves HTTP API + SSH proxy + DB broker + SFTP relay + Redis proxy on different ports.
- Connector is a thin desktop binary. It authenticates to PAM API, receives launch instructions, and spawns the correct local client with injected credentials. It does NOT hold credentials persistently.
- All proxy traffic routes through PAM — this is the enforcement point, not the UI or connector.
- PostgreSQL is the single data store — config, audit, session recordings (v1). Session recordings stored as compressed blobs or files referenced from PG.
- Provider-oriented authentication (
localin dev mode,ldaplater) - JWT token issuance (short-lived access + refresh)
- Session management
- Middleware for route protection
- Default seeded admin user for development/POC bootstrap
- LDAP provider later: minimal attribute sync on login (username, email, display name, group membership). Not a full sync engine.
- CRUD for users, groups, roles
- Group membership
- Role definitions:
admin,operator,viewer(v1 roles) - Provider metadata bookkeeping (including future LDAP sync fields)
- Asset CRUD (servers, databases, Redis, file-transfer endpoints)
- Asset types:
ssh,database,redis,sftp - Asset metadata: host, port, engine type (for DB), description, tags
- Asset health/status (basic ping, optional in v1)
- Access assignment rules: user/group → asset(s) with allowed actions
- Policy evaluation: given a user + asset, return allowed/denied + permitted actions
- Server-side enforcement — every proxy connection checks policy before proceeding
- Credential storage: encrypted at rest using AES-256-GCM
- v1 key management (temporary): single application master key loaded from
ACCESSD_VAULT_KEYenvironment variable. This is explicitly a temporary v1 approach — documented as requiring migration to external KMS or HashiCorp Vault-backed key management in hardening/post-v1. - Credential types: SSH key pair, SSH password, DB username/password, Redis password, SFTP credentials
- Credential retrieval: internal only, never exposed to API consumers
SSH proxy (highest-fidelity recording path in v1):
- Implements an SSH server that clients connect to
- On connection: authenticate user via keyboard-interactive SSH auth method (locked for v1). The session token is passed through the keyboard-interactive challenge-response. This avoids abusing the username field and is well-supported by PuTTY, OpenSSH, and other clients.
- Establishes upstream SSH connection to target using stored credential
- Relays traffic bidirectionally
- Records full session (stdin/stdout as asciicast-compatible stream)
- Logs connect, disconnect, duration
DB broker:
- Receives launch request from connector
- Checks policy, retrieves DB credential from vault
- Returns a short-lived launch payload to the connector
- Connector creates temporary local connection profile/material for DBeaver, launches DBeaver pointed at PAM's DB proxy port
- Temporary connection material is cleaned up after session ends
- PAM proxies the DB connection to the real target
- Logs: connection open/close, database name, user, duration
- Statement-level capture: deferred beyond v1. v1 guarantees connection/session metadata audit only.
SFTP relay:
- PAM runs an SFTP server (or proxies SFTP protocol)
- Connector launches FileZilla/WinSCP pointed at PAM's SFTP endpoint
- PAM relays to target, logs file operations (upload, download, delete, rename with paths)
Redis proxy:
- TCP proxy that authenticates via PAM, connects to target Redis using stored credential
- v1 guarantees connection/session-level audit (connect, disconnect, duration)
- RESP command-level logging is included in v1 only if it remains simple within the chosen proxy design; otherwise deferred to post-v1
- Structured audit log writes to PostgreSQL
- Event types: login, logout, session_start, session_end, policy_check, credential_access, file_operation, admin_action
- Session recording storage: compressed session data in filesystem, referenced from PG
- Query interface for session history
- RESTful HTTP API
- Routes grouped:
/auth/*,/users/*,/assets/*,/policies/*,/sessions/*,/admin/* - JSON request/response
- Middleware: auth, RBAC, request logging, CORS
- SQL migrations (golang-migrate or similar)
- Query layer (sqlc or hand-written)
- Connection pooling
| Page | Path | Purpose |
|---|---|---|
| Login | /login |
Username/password form (provider-backed; local in dev mode) |
| Dashboard | / |
Overview: recent sessions, assigned asset count, alerts |
| My Assets | /assets |
Table of assets assigned to current user with launch actions |
| Asset Detail | /assets/:id |
Asset info, connection history for that asset |
| Sessions | /sessions |
Session history table with filters (user, asset, date range) |
| Session Detail | /sessions/:id |
Session replay (SSH text replay), metadata, timeline |
| Admin: Users | /admin/users |
User list, group assignment, role management |
| Admin: Assets | /admin/assets |
Asset CRUD, credential assignment |
| Admin: Policies | /admin/policies |
Access assignment rules |
| Admin: Audit Log | /admin/audit |
Full audit event log with search/filter |
- Access table: sortable/filterable table showing asset name, type, host, allowed actions, launch button
- Launch button: triggers connector protocol handler or direct proxy connection
- Session player: text-based replay of SSH sessions (asciicast/xterm.js based)
- Policy editor: assign users/groups to assets with action scoping
The connector is a lightweight desktop binary (Go, cross-compiled for Windows/macOS/Linux).
What it does:
- Authenticates to PAM API (stores JWT locally, refreshes as needed)
- Receives launch instructions from API (which client to open, what proxy endpoint to connect to)
- Launches the approved client application:
- SSH: PuTTY (Windows) or native terminal with
ssh(macOS/Linux) pointed at PAM's SSH proxy - DB: DBeaver pointed at PAM's DB proxy port with connection parameters
- SFTP: FileZilla (macOS/Linux) or WinSCP (Windows) pointed at PAM's SFTP endpoint
- Redis: Terminal with
redis-clipointed at PAM's Redis proxy
- SSH: PuTTY (Windows) or native terminal with
- Passes credentials ephemerally (env vars, temp config files deleted after launch, or protocol-level injection)
- Reports launch status back to API
What it does NOT do:
- Store credentials persistently
- Make policy decisions
- Bypass the proxy layer
- Provide a shell or terminal of its own
Registration flow (v1 — locked):
- No device registration or admin approval in v1
- Connector authenticates using the user's JWT (same token as UI login)
- Connector calls
POST /sessions/launchwith JWT to receive launch payload - This is a pure JWT-authenticated launch/session model — no device identity layer yet
User → UI login form → POST /auth/login {username, password}
→ API authenticates via active provider (dev mode: local provider)
→ On success: load user/groups/roles from local DB
→ Issue JWT (access token: 15min, refresh token: 8h)
→ Return tokens to UI
→ UI stores tokens, redirects to dashboard
User → UI loads /assets → GET /assets/mine (with JWT)
→ API evaluates policy: which assets is this user assigned to?
→ Return asset list with allowed actions per asset
→ UI renders access table
User clicks "Connect" on an asset in UI
→ UI calls POST /sessions/launch {asset_id, action}
→ API checks policy (server-side, authoritative)
→ API retrieves target credential from vault
→ API prepares proxy endpoint (if not already listening)
→ API creates session record (status: pending)
→ API returns launch instructions {proxy_host, proxy_port, session_token, client_hint}
→ UI passes launch instructions to connector (via localhost API or protocol handler)
→ Connector launches approved client pointed at proxy endpoint with session token
Connector launches: ssh -p <pam_ssh_port> user@<pam_host>
→ PAM SSH proxy accepts connection
→ Prompts for session token via keyboard-interactive auth
→ Connector (or user) provides session token through challenge-response
→ Validates session token, maps to user + asset + policy
→ Retrieves target SSH credential from vault
→ Opens upstream SSH connection to target host
→ Relays bidirectional traffic
→ Records full I/O stream (stdin + stdout + stderr)
→ On disconnect: finalizes session record, compresses recording
This is the highest-fidelity recording path in v1. PAM is a full man-in-the-middle for SSH, so we capture everything the user types and everything the server returns.
Connector receives short-lived launch payload for DB asset
→ Connector creates temporary local DBeaver connection profile
(host=pam_host, port=proxy_port, db=target_db, session_token as auth)
→ Connector launches DBeaver pointed at this temporary profile
→ DBeaver connects to PAM's DB proxy port
→ PAM proxy authenticates via session token, connects upstream to real DB
→ Relays TCP traffic
→ Logs: connection open/close, duration, database name
→ On session end: connector cleans up temporary connection material
→ Statement-level capture: deferred beyond v1
Connector launches FileZilla/WinSCP pointed at PAM's SFTP endpoint
→ PAM SFTP relay accepts connection, authenticates via session token
→ Connects upstream to target SFTP server using stored credentials
→ Relays SFTP operations
→ Logs: file uploads, downloads, deletes, renames (with full paths and sizes)
→ On disconnect: finalizes session record
Connector launches terminal with: redis-cli -h <pam_host> -p <pam_redis_port> -a <session_token>
→ PAM Redis proxy accepts connection
→ Authenticates session token, connects upstream to target Redis using stored credential
→ Relays RESP protocol traffic
→ If protocol parsing implemented: logs individual commands
→ Otherwise: logs connection-level events (connect, disconnect, bytes)
| Capability | Coverage |
|---|---|
| Login/logout events | All users, all sessions |
| Session start/end | All connection types |
| Session duration | All connection types |
| User ↔ asset mapping | All sessions |
| SSH full I/O recording | Complete keystroke + output capture |
| SFTP file operation log | File paths, operations, sizes |
| DB connection audit | Connect/disconnect, database, duration |
| Redis connection audit | Connect/disconnect, duration |
| Admin action audit | All CRUD operations on users, assets, policies |
| Capability | Notes |
|---|---|
| Redis command logging | RESP protocol is simple to parse. Included in v1 only if it remains straightforward within the chosen proxy design. Otherwise connection-level audit only. |
| Capability | Notes |
|---|---|
| DB statement capture (any engine) | Wire protocol parsing (PG, MySQL) is non-trivial and out of v1 scope. v1 guarantees brokered access + connection/session metadata audit only. |
| DB result capture | Very expensive in bandwidth/storage. Deferred. |
- Full video-style replay of DB or file transfer sessions
- Behavioral analytics or anomaly detection
- Real-time alerting on suspicious commands
- Tamper-proof audit log (append-only / signed — desirable but deferred)
- SSH recordings: stored as asciicast v2 files (JSON lines with timestamps), compressed with gzip, saved to local filesystem, referenced from
sessionstable in PG. - Audit events: rows in
audit_eventstable in PG. - v1 storage is local disk + PG. Object storage (S3) deferred.
- Credentials stored in
credentialstable in PostgreSQL - Encrypted at rest using AES-256-GCM
- Encryption key: loaded from environment variable or config file at startup
- Each credential row:
{id, asset_id, credential_type, encrypted_blob, created_at, updated_at} - Credential types:
ssh_password,ssh_keypair,db_password,redis_password,sftp_password
- Credentials are never returned via any API endpoint
- Only the proxy layer retrieves credentials internally to establish upstream connections
- Vault module provides:
GetCredential(assetID) → plaintext(internal only) - All credential retrievals are audit-logged
- Admin creates/updates credentials via Admin UI → API → vault
- No automatic rotation in v1 (manual rotation only)
- Credential deletion is soft-delete with audit trail
This is explicitly a temporary v1 design. It must be replaced by external KMS or HashiCorp Vault-backed key management in hardening or post-v1.
- Single application master key loaded from
ACCESSD_VAULT_KEYenv var - Key rotation: re-encrypt all credentials with new key (manual process in v1)
- The env var must be protected by OS-level access controls (file permissions, systemd
LoadCredential, etc.) - Migration path: post-v1, integrate with HashiCorp Vault or cloud KMS (AWS KMS, GCP KMS) for envelope encryption
| Table | Purpose |
|---|---|
users |
Local user records (id, username, email, display_name, auth_provider, external_subject, status, created/updated) |
groups |
Groups (id, name, description, external_ref, created/updated) |
user_groups |
Many-to-many user ↔ group membership |
roles |
Role definitions (admin, operator, viewer) |
user_roles |
User ↔ role assignment |
assets |
Infrastructure assets (id, name, type, host, port, engine, description, status, created/updated) |
credentials |
Encrypted credentials per asset (id, asset_id, type, encrypted_blob, created/updated) |
access_policies |
Assignment rules: (id, subject_type[user/group], subject_id, asset_id, allowed_actions[], created/updated) |
sessions |
Session records (id, user_id, asset_id, type, status, started_at, ended_at, recording_path, metadata) |
audit_events |
Immutable audit log (id, timestamp, user_id, event_type, resource_type, resource_id, detail_json, ip_address) |
audit_events: index on (timestamp), (user_id, timestamp), (event_type)sessions: index on (user_id, started_at), (asset_id, started_at)access_policies: index on (subject_type, subject_id), (asset_id)
audit_eventsshould be append-only in application logic (no UPDATE/DELETE in code)sessions.recording_pathpoints to filesystem location of recording filecredentials.encrypted_blobcontains JSON-structured credential data, encrypted as a single blob- Consider partitioning
audit_eventsby month if volume is high (v1: simple table, partition later)
- User ↔ PAM UI/API: untrusted. All requests authenticated and authorized.
- Connector ↔ PAM API: semi-trusted. Connector authenticates with user's JWT. API validates every request.
- PAM proxy ↔ target infrastructure: trusted network path assumed in v1. (TLS to targets where supported.)
- PAM API ↔ PostgreSQL: trusted. Same host or private network in v1.
- PAM API ↔ external auth provider: trusted network path when enabled (e.g., LDAP over LDAPS/StartTLS).
- PAM server is deployed in a secured network segment
- PostgreSQL is not publicly accessible
- The
ACCESSD_VAULT_KEYenvironment variable is protected by OS-level access controls - PAM authentication is provider-based. In development mode, PAM stores local password hashes; with LDAP enabled, LDAP becomes external source of truth for identity verification.
- JWT tokens are signed with a strong secret/key pair; short-lived access tokens minimize impact of theft
- The connector is installed on authorized workstations; it is not a security boundary itself (the proxy is)
- All proxy connections require a valid, unexpired session token — even if someone reaches the proxy port directly
- Authentication on every API call and every proxy connection
- Policy check before every session launch
- Credential isolation — users cannot extract credentials through any path
- Audit logging of all security-relevant events
| Risk / Hard Part | Severity | Mitigation |
|---|---|---|
| SSH proxy implementation complexity | High | Use golang.org/x/crypto/ssh — well-proven. Start here, it's the highest-value path. |
| DB wire protocol parsing for statement capture | Medium | Deferred beyond v1. v1 ships connection/session audit only. |
| Connector cross-platform support | Medium | Go cross-compilation helps. Windows client launching (PuTTY, DBeaver, WinSCP) needs testing on real Windows machines. |
| Credential security in transit to clients | Medium | Prefer session tokens over credential forwarding. For DBeaver/FileZilla: use PAM proxy so credentials never leave the server. |
| SFTP relay complexity | Medium | Use pkg/sftp Go library. Proxying SFTP is well-understood but needs careful testing. |
| Session recording storage growth | Low (v1) | Compress recordings. Monitor disk usage. Object storage deferred. |
| Single encryption key for vault | Medium | Temporary v1 approach, clearly documented. Upgrade to external KMS/Vault required post-v1. |
| Auth provider expansion complexity | Low | Keep provider interface explicit from the start (local + future ldap), so adding providers does not impact authorization/session/policy paths. |
| Redis RESP parsing for command logging | Low | Include if simple within proxy design; otherwise defer. Connection audit guaranteed. |
| Connector deployment/updates | Medium | Ship as a single binary. Auto-update mechanism deferred to post-v1. |
- Monolith over microservices: faster to build, deploy, and debug. Can be split later if needed.
- PostgreSQL for everything: credentials, audit, config in one DB. Simpler ops. Acceptable at v1 scale.
- Proxy everything through PAM: increases latency slightly but is the only way to guarantee credential isolation and audit. Non-negotiable.
- Asciicast for SSH recording over video: lightweight, text-searchable, good enough for v1. Video replay deferred.
- Keyboard-interactive for SSH session auth: well-supported across all target clients, avoids username-field hacks.
- Systemd on Linux VM for production: simple, proven, single-server deployment. Docker Compose for dev/test only.
- Provider-first auth architecture: local provider now for rapid development/POC, LDAP added later on same interface without changing authorization/session/policy internals.
- Temporary app-level vault key: ships fast, clearly documented as requiring KMS migration post-v1.
- Monorepo setup, build tooling, CI skeleton
- PostgreSQL schema + migrations
- Backend bootstrap (HTTP server, config, DB connection, middleware)
- UI bootstrap (React + TailAdmin, routing, auth context)
- Shared contracts (API spec)
- Local auth provider + JWT auth flow
- User/group/role model + admin UI
- Asset inventory CRUD + UI
- Access policy model + assignment UI
- LDAP provider integration on same auth-provider interface (after local auth is stable)
- Credential storage with encryption
- Admin UI for credential management
- SSH proxy server implementation
- Session token flow (launch → proxy auth → upstream)
- Full I/O session recording
- Connector: SSH launch path (macOS/Linux first, then Windows/PuTTY)
- Session history UI + text replay
- DB TCP proxy + DBeaver launch path
- SFTP relay + FileZilla/WinSCP launch path
- Redis proxy + terminal launch path
- Audit events for all connection types
- Audit log UI with search/filter
- Dashboard page
- Security hardening (input validation, rate limiting, token rotation)
- Error handling, edge cases, reconnection behavior
- Documentation
- Auth and inventory are prerequisites for everything
- SSH proxy is the highest-value, highest-fidelity path — prove the core model here first
- DB/SFTP/Redis paths reuse the same session token + policy + audit infrastructure built for SSH
- Polish last, not first
- A user can log in to PAM using the active auth provider (development mode: local auth)
- An admin can add/edit/remove users, groups, and roles
- An admin can add/edit/remove infrastructure assets (SSH, DB, SFTP, Redis)
- An admin can assign access: user/group → asset with allowed actions
- An admin can store credentials for each asset (encrypted at rest)
- A user sees only their assigned assets in the access table
- A user can launch an SSH session to an assigned server — connection is proxied through PAM
- The SSH session is fully recorded (keystrokes + output) and stored
- A user can launch a DBeaver session to an assigned database — connection is brokered through PAM
- A user can launch a file transfer session (FileZilla/WinSCP) to an assigned endpoint — traffic relayed through PAM
- A user can launch a Redis CLI session — connection proxied through PAM
- All session events (start, end, duration, user, asset) are recorded in the audit log
- An admin can view the audit log with search and filters
- An admin can view session history and replay SSH sessions as text
- The connector correctly launches PuTTY (Windows), native terminal (macOS/Linux), DBeaver, FileZilla/WinSCP, and redis-cli
- No user ever sees or receives raw target credentials
- All policy enforcement happens server-side — bypassing the UI does not grant access
- The system runs as a single deployable unit (one Go binary + static UI assets + PostgreSQL) deployed via systemd on a Linux VM
- Credential encryption uses AES-256-GCM with documented temporary key management approach
- LDAP can be introduced as an additional auth provider without reworking authorization, policy enforcement, session handling, or audit model