Skip to content

Environment variables

Upwarden’s runtime is configured entirely via environment variables. Every variable below has a default unless the row is marked required.

VariableTypeDefaultNotes
PORTint3000
HOSTstring"0.0.0.0"
NODE_ENVstring"development"
UPSTREAM_REGISTRY_URLstring"https://registry.npmjs.org"
DATABASE_URLstring"postgres://vanguard:vanguard@localhost:5432/vanguard"
REDIS_URLstring"redis://localhost:6379"
REDIS_CLUSTER_MODEboolfalse
REDIS_TLSboolfalseForces TLS even when the URL scheme is plain redis://. The URL scheme rediss:// already implies TLS; this is for setups behind a TLS-only LB where the application-side URL is plain.
REDIS_KEY_NAMESPACEstring"vanguard"Key prefix for all vanguard data in Redis. Override per-deployment when sharing a Redis instance with another vanguard tenant — the default “vanguard” prefix avoids collisions but not adversarial co-tenants.
GCS_BUCKETstring"vanguard-tarballs"
FAST_PASS_TIMEOUT_MSint3000
MANIFEST_CACHE_TTL_MSint300_000
STALE_MANIFEST_MAX_AGE_MSint86_400_000
SCANNER_ENABLEDboolfalse
EMBEDDING_ENDPOINTstring"https://api.jina.ai/v1/embeddings"
EMBEDDING_MODELstring"jina-embeddings-v2-base-code"
EMBEDDING_FORMATstring"openai"
SCANNER_RRF_Kint60
SCANNER_TOP_Kint10
SCANNER_BYOS_FALLBACK_MSint8000byosThenInProcess posture: how long to wait for an external (BYOS) callback to clear a quarantine before the built-in scanner takes over.
PRESCAN_MAX_PACKAGESint10Background pre-scan worker bounds (per run): cap how many already-seen packages to check for new versions, and how many new versions per package to scan, so a backlog can’t overwhelm the embedding service. #536 — Lowered from 200 to 10 after production observation. Per-package scan cost is ~10-15 min wall-clock (200 source files / ~490 code chunks per version × embedding round-trips). At 200 packages × 5 versions the design wants ~50 hours per run, but the CronJob’s activeDeadlineSeconds is 50 min — every run was SIGKILL’d mid-flight, marked Failed by K8s, even though real verdicts WERE landing. The watch list is ordered by most-recently-active so a lower cap still keeps the hot tail covered; to raise this safely, optimise per-version scan cost first (batch embed calls, cap chunks per version, etc.) — separate issue.
PRESCAN_MAX_VERSIONS_PER_PACKAGEint5
PRESCAN_MAX_RUNTIME_MSint40 * 60 * 1000#536 — Soft-deadline for the prescan main loop. Checked between packages; on expiry the loop exits cleanly (exit 0, integration tracker records success) rather than waiting for the K8s SIGKILL at activeDeadlineSeconds. Default 40 min leaves 10 min margin from the manifest’s 50-min hard deadline so the in-flight package completes + the shutdown drain runs without truncation.
SCANNER_SIZE_ALLOWLISTstring""#267 Oversized-tarball allow-list. Comma-separated <ecosystem>:<name> entries that are expected to ship artifacts above the 200 MiB decompressed cap (electron-derivatives, bundled browser tooling, large Maven shaded jars). When a prescan hits the cap for a listed package, the row stays QUARANTINED with verdict_source scanner:size-allowed and we log at WARN — no size-anomaly flag is recorded. Maven names are <group>:<artifact> so the full entry is maven:com.google:guava. Stays ConfigMap-driven (per the issue: operators must extend without a code release).
PUBSUB_ENABLEDboolfalse
RATE_LIMIT_RPMint600
TRUSTED_PROXY_HOPSint0Number of trusted proxy hops in front of vanguard. The client IP is derived from X-Forwarded-For by counting this many entries from the right (each trusted proxy appends its caller). Default 0 = trust no proxies; use the rightmost entry. In production behind the GKE Gateway, set to 1.
CALLBACK_MAX_BODY_BYTESint16 * 1024Max request body size, in bytes, for the POST handlers (L4 / #17). Bodies that exceed these get a 413 before being buffered into memory. Callback bodies are tiny JSON payloads (event_id + verdict + scanner_id); advisories/bulk is an opaque passthrough to npm so it gets a larger budget.
ADVISORIES_BULK_MAX_BODY_BYTESint256 * 1024
REQUIRE_PROXY_AUTHboolfalseRequire a per-org API key (Authorization: Bearer vk_… or Basic) on the data-plane proxy routes (npm/PyPI/crates). Default off so local dev and single-tenant installs keep working unauthenticated; the public deployment flips this true BEFORE DNS resolves (Phase E / #51).
VANGUARD_DEFAULT_MIN_AGE_DAYSint0Cooldown: refuse to serve any version younger than this many days. 0 disables the check (historical behavior). A non-zero default applies the cooldown to every org that hasn’t opted out, which closes the freshly-published-malware window that even sub-hour feed sync can’t.
ALLOW_UNSIGNED_CALLBACK_VERDICTboolfalseTransitional: accept the decision fields from the unsigned request body when the signed JWT omits them. Off by default — the signed JWT is authoritative. Intended to be removed once all scanners sign the claims.
DEFAULT_ORG_SLUGstring"default"Tenant resolved for any request whose Host/header doesn’t identify another org. Single-tenant deployments need only this — the bootstrap step inserts a row with this slug if one doesn’t already exist.
ORG_HEADER_TRUSTEDboolfalseWhen true, X-Vanguard-Org: <slug> is trusted as the org hint. Only safe when Upwarden sits behind a gateway that strips/sets the header. Off by default: a public surface must not trust client-supplied org.
COOKIE_SECUREboolprocess.env.NODE_ENV === "production"Whether the dashboard session cookie is marked Secure. Defaults to NODE_ENV === "production" to keep prod safe by default. Set to false for staging environments fronted by HTTP-only LBs (browsers silently drop Secure cookies on http://, which looks like a login failure). Drop the env var once the public ingress is TLS-terminated.
VANGUARD_SLUG_REUSE_BLOCK_MINint30#417 slice 1 — org slug rename, two retention knobs. slugReuseBlockMinutes is the cache-convergence safety guard (David’s Q2 refinement). After A → B, a freshly-issued slug A is rejected for this many minutes so any still-warm cache[A] → tenant(old) entry on a peer pod has time to expire via TTL before the new tenant claims the slug. The org-context / org-switcher caches both run a 60s TTL, so 30 min is comfortably wider than the convergence window in normal operation; operators tuning Redis TTLs upward should bump this proportionally. Clamped to a non-negative minute count at use time. Zero disables the reuse-block window entirely (only the absolute UNIQUE on tenant_slug_history.old_slug guards reuse) — appropriate only for local dev / fixture-replay environments where convergence is instantaneous.
VANGUARD_SLUG_HISTORY_RETENTION_MONTHSint6slugHistoryRetentionMonths is the history-row retention window. pruneSlugHistory (src/jobs/prune-slug-history.ts) deletes rows whose retired_at is older than this many months. Past the retention window the redirect breaks — bookmarks/CI scripts to the retired slug start 404’ing — which is the accepted cost of bounding table size against rename volume we don’t yet have shape for. 6 months matches typical operator-comms churn (post-rename emails, quarterly compliance reviews, etc.) without growing the table unboundedly. Bump upwards (12, 24) if a customer needs longer redirect coverage; the table is narrow and the sweep is cheap.
PASSKEY_RP_IDstring"vg-admin.<your-domain>"
PASSKEY_RP_NAMEstring"Upwarden"
PASSKEY_RP_ORIGINstring"https://vg-admin.<your-domain>"
PASSKEY_ENFORCEMENTboolfalse
AUTH_ALLOW_LEGACY_LOGINboolfalse
SUPPORT_CONTACTstring""Operator-side support contact surface. Surfaced by the account-recovery UI (#403) when a member is stuck mid-flow (e.g. pending email change to an address they can’t access AND can’t reach their current address either). Default empty: the UI hides the “contact support” link and falls back to “ask your org admin” copy. Set to a contact like support@your-org.com or a URL like https://support.your-org.com to surface a direct link.
STYTCH_LOGIN_SEND_MAXint10Stytch magic-link send rate-limit per email. Default 10 per 15min (PR 3b shipped 3 per 15min which proved too tight during initial config + mobile↔desktop testing — every Stytch-dashboard misconfig retry, every already-used magic-link re-click ate a budget slot). Both knobs are env-tunable so we can react to abuse without redeploying.
STYTCH_LOGIN_SEND_WINDOW_SECint15 * 60
OTEL_ENABLEDboolfalse
OTEL_EXPORTER_OTLP_ENDPOINTstring"https://telemetry.googleapis.com"
OTEL_SERVICE_NAMEstring"vanguard"