Credential-Aware Reverse Proxy / Secret Injection Gateway in Node.js + TypeScript.
103
Β Β

Credential-Aware Reverse Proxy / Secret Injection Gateway in Node.js + TypeScript.
English | EspaΓ±olβ
π New here? Follow the step-by-step tutorial (for dummies): docs/TUTORIAL.mdβ
Your applications need to call third-party APIs (OpenAI, Stripe, AWS...), which means scattering real credentials across repos, environment variables, and services. Rotating a key means touching everything; auditing who uses it is impossible; leaking it is a disaster.
Applications call Llavero with an internal token (llv_*).
Llavero authenticates the consumer, checks permissions, decrypts the real credential in memory,
signs/modifies the request with an auth driver, and forwards it to the provider.
The real secret never leaves Llavero: not in responses, not in logs, not in the API.
Application
β Authorization: Bearer llv_dev_xxx
βΌ
Llavero ββ validate token ββ policy (deny by default) ββ resolve service/environment
βββ decrypt credential (AES-256-GCM) ββ auth driver (bearer/oauth2/aws-sigv4...)
βΌ
External API (the real secret only travels to the registered hostname)
bearer, api-key, basic, cookie, oauth2 (refresh flow with cache), aws-sigv4 β hot enable/disable.LLAVERO_MASTER_KEY).llv_<env>_* β SHA-256 only in DB; shown once.openai-current can retarget from staging to production without touching clients.RateLimitPlugin.Node.js 24+ Β· TypeScript Β· Fastify Β· SQLite (better-sqlite3) Β· Drizzle ORM Β· node:crypto Β· Undici Β· Pino Β· Zod Β· ws Β· Docker
Requires: Node.js 22+ (24 recommended).
npm install
# 1. Master key (required in production; ephemeral one is generated in dev)
export LLAVERO_MASTER_KEY=$(openssl rand -hex 32)
npm run dev # http://localhost:3000
ADMIN=localhost:3000/admin
# Service (upstream)
curl -s -X POST $ADMIN/services -H 'content-type: application/json' -d '{
"name": "OpenAI", "slug": "openai",
"baseUrl": "https://api.openai.com", "authDriver": "bearer"
}'
# β keep the returned "id"
# Environment
curl -s -X POST $ADMIN/environments -H 'content-type: application/json' \
-d '{"serviceId":"<service-id>","name":"staging"}'
# β keep the environment "id"
# Credential (auto-encrypted; never shown again)
curl -s -X POST $ADMIN/credentials -H 'content-type: application/json' \
-d '{"serviceId":"<service-id>","name":"primary","secret":{"token":"sk-real-openai-key"}}'
# β keep the credential "id"
Attach the credential to the environment from the dashboard (/dashboard/services/<id>),
the CLI (llavero environments attach), or the API (POST /admin/environments/:id/credential).
# Client (your consumer application)
curl -s -X POST $ADMIN/clients -H 'content-type: application/json' -d '{"name":"my-backend"}'
# Internal token β SHOWN ONLY ONCE
curl -s -X POST $ADMIN/clients/<client-id>/tokens -H 'content-type: application/json' \
-d '{"environment":"stg"}'
# β {"token":"llv_stg_...", ...} save it
# Policy (without an explicit policy EVERYTHING is rejected: deny by default)
curl -s -X POST $ADMIN/policies -H 'content-type: application/json' -d '{
"clientId":"<client-id>", "serviceId":"<service-id>",
"environments":["staging"], "methods":["POST"], "paths":["/v1/*"]
}'
curl -X POST localhost:3000/gateway/openai/v1/responses \
-H 'Authorization: Bearer llv_stg_...' \
-H 'x-llavero-env: staging' \
-H 'content-type: application/json' \
-d '{"model":"gpt-5","input":"hello"}'
Llavero turns it into https://api.openai.com/v1/responses with
Authorization: Bearer sk-real-openai-key β your app never knows that key.
Alternative to the header: create an alias (
openai-currentβ openai/staging) and call/gateway/openai-current/v1/responseswith nox-llavero-env.
export LLAVERO_MASTER_KEY=$(openssl rand -hex 32)
export LLAVERO_ADMIN_KEY=$(openssl rand -hex 24) # enables /admin and /dashboard in prod
docker compose up -d
SQLite persists in the llavero-data volume (/data/llavero.sqlite).
Migrations are applied automatically on startup.
http://localhost:3000/dashboard β minimal SSR over the same managers as the API:
Services (create, environments, credentials), Aliases (create/retarget), Clients (tokens),
Policies, Audit.
Dashboard access: login form with LLAVERO_ADMIN_KEY or an llv_* token from a client
marked as admin (isAdmin). Successful login sets an HttpOnly cookie (the credential
never travels in URLs). A token from a non-admin client is rejected. In production without
LLAVERO_ADMIN_KEY the whole control plane returns 503.
LlaveroClient to operate the full control plane (Admin API)
and execute through the gateway. Zero dependencies.llavero config (saves
url + admin key in ~/.llavero/config.json), commands services|environments| credentials|aliases|clients|policies|audit|drivers|plugins and call (gateway, streaming to stdout).cd cli && npm install
node cli.mjs config
node cli.mjs call openai /v1/models --env dev --token llv_dev_...
Minimal project consuming the gateway with an llv_* token
(zero dependencies, native fetch):
cd examples/client-demo
npm run provision # creates services/clients/policies/token via Admin API
npm run demo:openai # OpenAI (bearer)
npm run demo:mercadopago # MercadoPago (bearer)
npm run demo:google # Firebase/Identity Toolkit + userinfo (oauth2)
npm run demo:stripe # Stripe (bearer)
npm run demo:aws-s3 # PUT/GET object signed (aws-sigv4)
npm run demo:api-key # httpbin reflects the injected header (api-key)
See examples/client-demo/README.mdβ .
| Variable | Description | Default |
|---|---|---|
LLAVERO_MASTER_KEY | AES-256-GCM key, 64 hex (openssl rand -hex 32). Never persisted | ephemeral in dev/test, required in prod |
LLAVERO_ADMIN_KEY | Admin API/Dashboard key (min 16). Empty β open in dev/test, 503 in prod | β |
LLAVERO_PORT | HTTP port | 3000 |
LLAVERO_HOST | Listen host | 0.0.0.0 |
DATABASE_URL | SQLite path (:memory: for tests) | ./data/llavero.sqlite |
LOG_LEVEL | Pino level | info |
NODE_ENV | development | test | production | development |
secret field)| Driver | authDriver | secret |
|---|---|---|
| Bearer | bearer | { "token": "sk-..." } |
| API Key | api-key | { "apiKey": "..." } + driverConfig { "location": "header|query", "name": "X-API-Key" } |
| Basic | basic | { "username": "...", "password": "..." } |
| Cookie | cookie | { "cookie": "session=..." } |
| OAuth2 | oauth2 | { "clientId", "clientSecret", "refreshToken", "tokenEndpoint" } |
| AWS SigV4 | aws-sigv4 | { "accessKeyId", "secretAccessKey", "region", "service" } |
npm run seed # creates openai, stripe, mercadopago, google (oauth2) and aws-s3
# with dev/staging/production environments and PLACEHOLDER credentials
# (idempotent; replace placeholders with real secrets)
service.baseUrl; paths pointing to another origin β 400.llv_* token) with HttpOnly cookie; Admin API via x-llavero-admin-key header. No credentials in URLs.authorization, cookie, set-cookie, x-api-key, *_secret, *_token, password in all logs.| Phase | Content | Status |
|---|---|---|
| 1 | Bootstrap: TypeScript, Fastify, Drizzle, SQLite, Pino, Zod, Vitest, Docker, /health /ready, migrations | β |
| 2 | Services, Environments, Credentials, AES-256-GCM, Repositories | β |
| 3 | Clients, llv_* tokens, Policy Engine | β |
| 4 | AuthDriver interface, Registry, Bearer/ApiKey/Basic | β |
| 5 | HTTP Gateway, Route Resolver, Header Sanitizer, Upstream Client, Streaming | β |
| 6 | WebSocket proxy | β |
| 7 | Cookie/OAuth2/AWS SigV4 drivers | β |
| 8 | Plugin interface, PluginManager | β |
| 9 | Admin API, minimal Dashboard | β |
| 10 | Docs, seeds, Docker Compose β 0.1.0 release | β |
Full design and rules in docs/master-prompt.mdβ (Spanish).
src/
config/ConfigService.ts # Centralized config (env + Zod)
core/
logger.ts # Pino with secret redaction
errors/index.ts # Domain errors (AppError, NotFound, 401/403/429/5xx)
auth-drivers/ # Interface + Registry + 6 swappable drivers
gateway/
HttpGateway.ts # auth β policy β route β credential β driver β upstream β audit
WebSocketGateway.ts # Driver handshake + transparent tunnel
RouteResolver.ts # Alias or slug + x-llavero-env
UpstreamUrlBuilder.ts # SSRF guard (origin = registered baseUrl)
HeaderSanitizer.ts # Drop hop-by-hop, auth, cookie, host
UpstreamClient.ts # Interface (+ UndiciUpstreamClient, no redirects)
services/ # ServiceManager, EnvironmentManager, AliasManager
credentials/ # CredentialManager + EncryptionProvider (AES-GCM)
clients/ # ClientManager, TokenManager (llv_*)
policies/PolicyEngine.ts # deny-by-default, '*' and prefixes
plugins/ # PluginManager + RateLimitPlugin
audit/AuditService.ts # Per-request metadata; never secrets
admin/ # Admin REST API + SSR Dashboard (same manager layer)
database/ # DatabaseProvider, schema, repositories, migrations
bootstrap.ts # AppContainer (manual DI)
app.ts / server.ts
scripts/seed-examples.ts # Example seeds (npm run seed)
sdk/ # Client SDK (LlaveroClient)
cli/ # CLI (Commander + SDK)
examples/client-demo/ # Gateway consumption demo
tests/ # Vitest (79 tests)
| Command | Description |
|---|---|
npm run dev | Dev with watch (tsx) |
npm run build | Compile to dist/ + copy SQL migrations |
npm start | Run dist/server.js |
npm test | Vitest (79 tests) |
npm run typecheck | TypeScript, no emit |
npm run db:generate | Generate migration after schema changes |
npm run seed | Example seeds (idempotent) |
curl localhost:3000/health # {"status":"ok"} β process alive
curl localhost:3000/ready # checks: database, masterKey, core β 200 or 503
MIT
Content type
Image
Digest
sha256:b0b824a1fβ¦
Size
84.7 MB
Last updated
about 1 month ago
docker pull leganuxservices/llavero-keychain