configuration.md

Every environment variable, config.json, and the precedence rules

Configuration

Configuration comes from .env (see .env.example) plus config.json for feed URLs and the technology filter. Values in config.json support ${ENV_VAR} substitution, and a few keys (CRON_SCHEDULE, SLACK_ENABLED) can be overridden from the environment.

DATABASE_URL is the one variable Atalaia cannot start without. Everything else has a default or an off switch.

Optional integrations ship commented out, and should stay that way until you have real values. A variable set here beats the console, so a placeholder webhook or SMTP host is not harmless: Atalaia treats it as deliberate configuration, greys out the matching console section, and fails every Send test against it. ./scripts/atalaia.sh doctor flags any value in .env that is still the one from .env.example.

Core

Variable Default Description
API_KEY — Required. Key for every /api/v1/* request (X-API-Key header).
PORT 3000 API port.
HOST 0.0.0.0 API bind address.
NODE_ENV — production disables the ngrok/Slack dev bootstrap.
LOG_LEVEL info Pino level: trace…fatal.
DATABASE_URL — Required. Any Postgres 13+. Use the session connection on port 5432, not a 6543 transaction pooler: pgbouncer in transaction mode breaks prepared statements and LISTEN, and the queue needs both. Keep the host's address here (127.0.0.1 for a database on this machine) — the launcher translates it to host.docker.internal for the containers, so one value serves the host tools and the containers alike.
DATABASE_POOL_MAX 10 Connections per process.
PGBOSS_SCHEMA pgboss Schema the queue keeps its own tables in.
CRON_SCHEDULE 0 * * * * Monitoring cycle; overrides config.json. Registered in the database by the worker, so a change takes effect when the worker restarts.
CORS_ORIGINS http://localhost:3000 Comma-separated allowed origins.

Console

Variable Default Description
UI_PORT 3001 Console port.
UI_HOST 0.0.0.0 Console bind address.
ATALAIA_API_URL http://localhost:3000 API base URL the console proxies to — and the one the terminal client uses.
BFF_TIMEOUT_MS 120000 Upstream timeout — a repository scan can take minutes.
BFF_URL http://localhost:3001 Vite dev-server proxy target. Development only.

UI_PASSWORD and UI_SESSION_SECRET are gone. The console signs people in with passkeys and signs nothing itself; UI_PASSWORD is still read, but only as a fallback name for SETUP_PASSWORD below.

Sign-in

Read by the API, and describing the console's address — the ceremony happens in the browser talking to the console, and the API verifies it. The service refuses to start on a value the browser would reject. See Authentication.

Variable Default Description
SETUP_PASSWORD — Creates the first console account, then stops granting access. Generated by the launcher on first run.
WEBAUTHN_RP_ID localhost The domain passkeys belong to. Bare domain: no scheme, no port. Changing it invalidates every passkey.
WEBAUTHN_ORIGINS http://localhost:3001 Comma-separated console origins, in full. https unless loopback, and each must sit under WEBAUTHN_RP_ID.
WEBAUTHN_RP_NAME Atalaia Console What the browser's prompt calls this service.
WEBAUTHN_REQUIRE_UV false Require a PIN or fingerprint. Off, so authenticators that cannot do it still work.
SESSION_TTL_HOURS 720 Session lifetime.
CHALLENGE_TTL_SECONDS 120 How long a ceremony may take.
AUTH_ALLOW_BREAKGLASS false Lets SETUP_PASSWORD enroll a passkey for an existing account. A recovery mechanism; leave it off in production.
AUTH_SWEEP_CRON 17 * * * * When spent challenges and week-old sessions are deleted.
MCP_API_KEY — A key that opens /mcp and nothing else. Set it, and the REST key stops opening /mcp. Unset, agents use API_KEY and can reach everything it can.
TRUST_PROXY — true, a hop count, or a subnet. Off by default: without a proxy in front, believing X-Forwarded-For lets any caller claim any address.

Chat integrations

Variable Default Description
SLACK_ENABLED false Master switch; overrides config.json.
SLACK_WEBHOOK_URL — Incoming webhook for alerts.
SLACK_SIGNING_SECRET — Verifies interactive button callbacks. Required for Acknowledge/Resolve.
SLACK_APP_TOKEN — Dev only: lets Atalaia update the app's Request URL.
SLACK_APP_ID — Dev only: the app to update.
TEAMS_WEBHOOK_URL — Microsoft Teams Workflows webhook. Pins the integration to the environment.
TEAMS_ENABLED — Forces Teams delivery on or off wherever it is configured.
DISCORD_WEBHOOK_URL — Discord channel webhook. Pins the integration to the environment.
DISCORD_ENABLED — Forces Discord delivery on or off wherever it is configured.
TELEGRAM_BOT_TOKEN — Bot token from @BotFather. Pins the integration to the environment.
TELEGRAM_CHAT_ID — Where alerts go: a group (-100…), a channel (@name) or a person's chat.
TELEGRAM_ENABLED — Forces Telegram delivery on or off wherever it is configured.

Callbacks and tunnels

Slack and Telegram call back — a button pressed in a chat has to arrive somewhere. A deployment with a hostname sets PUBLIC_URL and needs nothing else; a laptop borrows one.

Variable Default Description
PUBLIC_URL — Where this API answers from, as the internet sees it. Wins over any tunnel.
TUNNEL_PROVIDER auto auto, ngrok, cloudflared or none. auto takes ngrok when it has a token, Cloudflare's quick tunnel otherwise. Outside production a tunnel is opened even when this is unset.
NGROK_AUTH_TOKEN — Required by ngrok; without it auto skips to Cloudflare.
NGROK_REGION auto ngrok region.

Cloudflare's quick tunnel needs no account and no token, and downloads its binary on first use — so the first start is slower, and a machine with no outbound network cannot use it.

Feeds and scanning

Variable Default Description
VULN_MAX_AGE_DAYS 7 How recently an advisory must have been published to earn an alert. Most sources serve a catalogue rather than a window — CISA's KEV list goes back to 2021 and arrives whole on every fetch — so the cutoff is applied to all of them here rather than per feed. An advisory with no publication date is discarded, not assumed recent. 0 disables the cutoff.
MAX_ALERTS_PER_CYCLE 20 Alerts one cycle may send. Past it the findings are still stored and visible in the console; only the message is dropped. Worst first: exploited, then severity, then score, then newest.
ALERT_DELAY_MS 1000 Pause between alerts. Telegram accepts about twenty messages a minute to a group and answers the rest with 429.
FEED_TIMEOUT_MS 15000 Per-feed HTTP timeout.
FEED_DELAY_MS 2000 Pause between feeds in a cycle — keeps scraped sources happy.
FEED_HEALTH_TTL_MS 60000 Cache TTL for /api/v1/feeds/health.
OPENCVE_API_URL — OpenCVE instance for vendor/product lookup.
OPENCVE_API_TOKEN — OpenCVE token. Without it the source reports NOT_CONFIGURED instead of being called.
NVD_API_KEY — Free NVD key. Lifts the limit from five requests per thirty seconds to fifty; without it a busy cycle gets 503 from NVD.
GITHUB_TOKEN — Fallback token for the GHSA feed and for repository scanning when an organization has none of its own.
TOKEN_ENCRYPTION_KEY falls back to API_KEY Key used to encrypt organization tokens at rest. Change it and the stored tokens become unreadable.
LLM_TIMEOUT_MS 90000 local, 20000 hosted How long a model has to answer. Ollama unloads an idle model and the cycle runs hourly, so nearly every call is a cold start: a 12B writing a 300-word guide measured 21s warm and 30.3s cold.
MITRE_MAX_RECORDS 25 CVE records fetched per cycle from the MITRE delta — one request each.
REDHAT_PAGE_SIZE 100 CVEs per Red Hat Security Data page.
DEBIAN_LIMIT 10 Debian advisories read per list, newest first. One kernel advisory can name three hundred CVEs.
USN_LIMIT 10 Ubuntu notices per cycle. A single kernel notice can carry hundreds of CVEs.
REGISTRY_CONCURRENCY 6 Registry lookups at once during a freshness check.
SCAN_CONCURRENCY 10 Repositories scanned at once — by a fleet sweep and by one-off scan jobs alike. Parallelism does not reduce the number of GitHub requests, only the rate, and a token allows 5000 an hour: raise it for a fleet in the tens, lower it if GitHub starts refusing.

LLM summaries

Normally configured from the console (Settings → Model). These still work and take precedence — setting LLM_PROVIDER turns the console section read-only.

Variable Default Description
LLM_PROVIDER — ollama, lmstudio, openai, anthropic, gemini, openrouter, groq or custom. Unset leaves it to the console.
OPENAI_API_KEY — Key for whichever hosted provider is selected.
OPENAI_MODEL per provider Model name.
OLLAMA_URL http://localhost:11434 Local Ollama endpoint.
OLLAMA_MODEL llama3.1 Ollama model.

Weekly email report

Delivery is normally configured from the console (Settings → Email), which stores the provider and its credential in the database. These variables still work and take precedence — set SMTP_HOST and the console section turns read-only, so a deployment that pins credentials in the environment keeps behaving exactly as before.

Variable Default Description
SMTP_HOST — SMTP server. Setting it pins the whole email configuration to the environment.
SMTP_PORT 587 SMTP port.
SMTP_USER / SMTP_PASS — SMTP credentials.
EMAIL_FROM atalaia@localhost Sender address.
EMAIL_RECIPIENTS — Comma-separated recipients.
EMAIL_TEMPLATE professional professional or minimal.
WEEKLY_REPORT_CRON 0 9 * * 1 Digest schedule — Mondays at 09:00.

config.json

Key Description
cronSchedule Monitoring interval. CRON_SCHEDULE wins.
slack.enabled / slack.webhookUrl Slack switch and webhook (env-substituted).
feeds.* Source URLs for CISA, Snyk, VulDB.
opencve.* OpenCVE API URL and token (env-substituted).
providers[] Git providers (org key, type, token) pinned in configuration. Organizations registered in the console take precedence over an entry with the same key.
repositories.autoScan / scanCron Scheduled dependency scanning.
repositories.autoFilterFromDeps Extend the technology filter from scanned dependencies.
filterSettings.enabled / technologies[] The stack filter applied to every feed item.

Rules that hold everywhere

The same handful of decisions recur across every feature; knowing them explains most of the behaviour without reading the code.

Environment beats database beats file. Anything set as an environment variable wins and turns the matching console field read-only — a deployment can always pin a value, and a write that would have no effect is refused with 409 rather than silently stored. Next comes what the console wrote to the database, and last config.json, which is the committed default.

Secrets are encrypted at rest and never come back. GitHub tokens, SMTP passwords, Slack credentials and LLM keys are stored with AES-256-GCM keyed by TOKEN_ENCRYPTION_KEY (or API_KEY). The API returns whether one is held and its last four characters, never the value. Changing provider drops the stored key — a SendGrid key is not a Mailgun password.

Reads only, on everything external. GitHub, the vulnerability feeds and the package registries are read and never written to.

Long work runs detached. A fleet scan, a version check or a monitoring cycle answers 202 immediately, refuses a second concurrent run with 409, and reports progress on a GET at the same path. Nothing that outlives an HTTP timeout is run inside a request.

Deleting is soft, and stays. Repositories, organizations, owners and dependencies are marked deleted rather than removed, and an import will not resurrect one behind your back — only asking for it by name will.

Enabled is the operator's switch. Re-importing or re-scanning never flips it, and a disabled repository stops counting towards exposure and relevance.

Counts and filters share one definition. Where a header states a number — repositories exposed, CVEs affecting you, dependencies behind — the same SQL backs the number and the rows beneath it, so the two cannot disagree.

Nothing is claimed that was not verified. A source that answers with zero items is reported as EMPTY, not healthy; a version that cannot be compared answers unknown with the reason instead of guessing; a repository that was never scanned says so instead of showing as clean.