---
title: Hardening and threat model
order: 2
description: The threat model of the BxLens console in plain words: what it can do, what it can never do, what leaves the server, how data and secrets are protected, and a hardening list for operators.
icon: lucide:shield-check
---

# Hardening and threat model

This page is the short version of [Security](../security.md), written for the person who has to say yes to running BxLens on a server. It says what the console can do, what it never does, what leaves the machine, and what you should still do yourself.

## What BxLens is, security wise

BxLens shows the inside of a running application. That is its job, and it is also the risk. Someone who gets into the console sees SQL text, URLs, error messages, thread stacks, settings, and (on a full collect level) headers and the callers of queries. So the console is built like an admin tool: off by default, closed to everyone but allowed addresses, behind a password, and quiet about itself.

## What the console can do

| Who | Can |
|---|---|
| Anyone not allowed by `console.access`, `access.allowedHosts` or `access.requireHeader` | Nothing. A plain 404, so the console does not say it exists. |
| Anyone allowed, without a password | See the login page. Nothing else. With no password set at all: the setup page, and 503 for the rest. |
| A **viewer** (BoxLang+) | Read requests, queries, errors, reports, HTTP, executors, tasks, datasources, applications, watchers, cache statistics, modules, Mail without message text. Chat with Lensy with the tools a viewer may use. Change nothing. |
| An **admin** | Everything above, plus read logs, environment, system and threads. Change live settings (BoxLang+), passwords and secrets. Run, pause and reload tasks, clear caches, restart an application, start and stop a watcher, requeue mail, run GC. With BoxLang+: download logs, thread dumps and the diagnostic bundle, read a cache value. With `console.allowHeapDump`: take a heap dump. Approve what Lensy proposes. |

`console.readOnly` removes every change. `console.actions: false` removes the actions on the server. Both are in `boxlang.json` only.

There are no user names. The password decides the role, so two admins share one password, and the audit log names the **role and the address**, not the person.

## What the console can never do

- **Reach another site.** The console and the bar load nothing from outside: scripts, styles, fonts and icons come from your own server. See [what leaves the server](#what-leaves-the-server) for the few calls the *server* makes.
- **Change who can reach it.** The Settings page and Lensy cannot change `console.*`, `access.*`, `bar.access`, `store.*` or `secrets.dir`. The server refuses those keys.
- **Show a secret.** No route, answer, audit line, log line, bundle, prompt or Lensy tool returns a stored password, hash, key, API key, SMTP password or channel address. Pages show `set`, `not set` or the host.
- **Run SQL.** Lensy's database tools read JDBC metadata only. No tool takes SQL text.
- **Stop or restart the server.** There is no such tool or button. (An application can be restarted by name, with BoxLang+ and a typed confirmation, and not the one that serves the console.)
- **Read arbitrary files.** The Logs page reads the BoxLang logs directory only, and refuses names outside it and links that point outside.
- **Act on another server.** The pages of another server are read only, and the server refuses a change aimed at one.
- **Keep bound query values unless you ask.** Statement text is kept with literals masked. Bound values need `collectors.queries.includeParams`, and are never kept at `collect.level` `light`.

## What leaves the server

BxLens makes no call for telemetry or for licensing. The license is checked inside the runtime through `bx-plus`. These are the only outgoing connections, and each one needs somebody to switch it on:

| Connection | When | Goes to |
|---|---|---|
| Registration form | Once, when someone submits the Register page on Free or Trial. From the **server**, https only, no redirects. Sends the form and the versions of BxLens, BoxLang and Java, nothing about your application. Checking the key is offline | `registration.endpoint` (`https://bxlens-register.boxlang.io`) |
| SMTP | Alerts, reports, test mails (BoxLang+) | The SMTP server an admin typed |
| Channels | Alerts and reports to Slack, Teams, Telegram, or a webhook (BoxLang+) | The https address an admin saved. Public addresses only, checked again before every send. See [Channels](../security.md#channels) |
| AI provider | Only with `ai.enabled`, BoxLang+, and `bx-ai`. Redacted prompts and tool results | The provider an admin chose. A local model keeps it inside your network |
| MCP servers | Only if an admin adds one and Lensy needs it. Public https addresses, no credentials | The address an admin saved. See [MCP servers](../security.md#mcp-servers) |

The buttons Ask ChatGPT and Ask Claude send nothing from the server: your browser opens the site and you paste.

## How data and secrets are protected

- **Redaction before storing.** Values under keys such as `password`, `token`, `secret`, `apikey`, `authorization`, `cookie` and `credential` are masked on the server before they reach a page or a store. Add your own names to `redact.keys`. Query strings, URL credentials and `-Dname=value` pairs are masked too. See [Redaction](../security.md#redaction).
- **Not redacted:** the Logs page shows lines as written, exception messages are kept as the application produced them, and the Environment page hides by name only (a secret under a harmless name shows). Treat the console like the log files.
- **Passwords** set in the console are salted PBKDF2-HMAC-SHA256 hashes (310000 rounds), compared in constant time. Failed logins are counted per address (and per proxy connection), with a lockout. Sessions are in memory, with an idle and a 12 hour limit.
- **The Secret Store** seals the secrets BxLens keeps (the SMTP password, the AI key, channel addresses and tokens, password hashes) with AES-256-GCM. By default the key sits beside the data. That protects against a copied `secrets.json` and a backup that holds only the data. It does not protect against someone who has the whole folder or runs as the same user. Set `LENS_SECRETS_KEY` from your platform's secret store to keep the key off the volume.
- **The store** holds redacted data. See [The store](../reference/store.md) and [Backup, retention and disk usage](backup-and-retention.md).
- **The audit log** records logins, denials and every change, with the role and address, never a password or key.

## The threat model in one table

| Threat | What stops it | What is left for you |
|---|---|---|
| A stranger finds the console | Closed by default, plain 404 for callers who are not allowed, no listing | Use a tight `console.access`, `access.allowedHosts` and, if you can, `access.requireHeader` set by your proxy |
| Password guessing | Lockout per address and per proxy connection, a 10 to 256 character password with a slow hash, a login header | Use a long random password. Set `access.proxyPeers` right so the address is real |
| Sniffing the password | `console.requireHttps`, Secure cookie | Terminate TLS properly, and require it |
| A web page attacks the console from a browser (CSRF, DNS rebinding) | `X-Lens-CSRF` on every change, `X-Lens-Login` on sign in, SameSite=Strict cookie, Host check for a `local` rule | Keep `access.allowedHosts` as short as you can |
| Script injection in the page | Everything from your app is shown as text, no `x-html`, a policy that allows only your own server as a source | The policy is **not strict**: it allows `unsafe-eval` for scripts and `unsafe-inline` for styles. Do not rely on it alone. Keep the console behind your VPN or proxy |
| A viewer or a leaked viewer password | A viewer cannot change anything and does not see logs, environment, system, threads or the admin tabs | Request data is still sensitive. Keep the viewer password private too |
| A stolen admin session | 12 hour limit, idle timeout (`console.sessionMinutes`), a password change ends the other sessions of the role | Shorten `sessionMinutes` for a production console |
| Secrets in a backup or a screenshot | Sealed store, secrets never shown | Keep the key out of the backup that holds the data |
| The AI sends your data away | Off by default, redacted prompts, approval for every action, a local model possible | Use a local model, or accept the provider's terms |
| A channel or MCP address points inside your network (SSRF) | https only, public addresses only, checked again before each connection, no redirects | Do not add hosts you do not control to `notifications.channelHosts` |
| Damage by a mistake of an admin | `console.readOnly`, `console.actions`, a typed name for an application restart, an audit line for each action | Turn them on in production |
| A heap dump leaks everything | Off by default, admin only, a confirmation, deleted after the download | Turn `console.allowHeapDump` on only for the time you need it |

## A hardening list

In order of value:

1. Put the console behind your VPN or an authenticating proxy. Then BxLens is the second lock, not the only one.
2. `console.access` to the VPN range. `access.allowedHosts` to your name. `access.requireHeader` if the proxy can add a secret header.
3. `console.requireHttps: true`, and `access.proxyPeers` to your proxy addresses only.
4. A long random admin password, kept out of files you commit (`${env.NAME}`, `bxsecret:` or set in Settings, Security). A viewer password for everyone who only looks. Change them when someone leaves.
5. `console.sessionMinutes` at 15 or less. `console.readOnly: true` and `console.actions: false` where nobody needs to act.
6. `collect.level: "light"`. `bar.enabled: false`. Keep `collectors.queries.includeParams` off.
7. `console.allowHeapDump: false`. `ai.enabled: false` or a local provider.
8. `LENS_SECRETS_KEY` from your secret manager, and a note in your restore procedure about where the key is.
9. Permissions on `store.dir`, `secrets.dir` and the logs directory: the user that runs BoxLang, and your backup, and nobody else.
10. Read `bxlens-audit.log` or ship it. With BoxLang+ switch on the **Security** alert.
11. Upgrade. See [Upgrading](upgrading.md).

## Known limits

- The Content-Security-Policy needs `unsafe-eval` (the Alpine.js build evaluates expressions) and `unsafe-inline` for styles. A move to the Alpine CSP build is planned.
- Shared passwords, no single sign on, no per person accounts, no second factor. Use your proxy for that.
- Sessions are in memory: a restart signs everyone out.
- Anything an application puts into a log line or an error message is shown as it was written.
- The check between a name lookup and the connection for channels and MCP servers is very short but not zero, see [Channels](../security.md#channels).

To report a problem, see [Troubleshooting](../troubleshooting.md#report-a-problem).
