Hardening and threat model
On this page
Hardening and threat model
This page is the short version of Security, 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 for the few calls the server makes.
- Change who can reach it. The Settings page and Lensy cannot change
console.*,access.*,bar.access,store.*orsecrets.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 setor 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 atcollect.levellight.
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 |
| 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 |
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,cookieandcredentialare masked on the server before they reach a page or a store. Add your own names toredact.keys. Query strings, URL credentials and-Dname=valuepairs are masked too. See 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.jsonand a backup that holds only the data. It does not protect against someone who has the whole folder or runs as the same user. SetLENS_SECRETS_KEYfrom your platform's secret store to keep the key off the volume. - The store holds redacted data. See The store and Backup, retention and disk usage.
- 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:
- Put the console behind your VPN or an authenticating proxy. Then BxLens is the second lock, not the only one.
console.accessto the VPN range.access.allowedHoststo your name.access.requireHeaderif the proxy can add a secret header.console.requireHttps: true, andaccess.proxyPeersto your proxy addresses only.- 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. console.sessionMinutesat 15 or less.console.readOnly: trueandconsole.actions: falsewhere nobody needs to act.collect.level: "light".bar.enabled: false. Keepcollectors.queries.includeParamsoff.console.allowHeapDump: false.ai.enabled: falseor a local provider.LENS_SECRETS_KEYfrom your secret manager, and a note in your restore procedure about where the key is.- Permissions on
store.dir,secrets.dirand the logs directory: the user that runs BoxLang, and your backup, and nobody else. - Read
bxlens-audit.logor ship it. With BoxLang+ switch on the Security alert. - Upgrade. See Upgrading.
Known limits
- The Content-Security-Policy needs
unsafe-eval(the Alpine.js build evaluates expressions) andunsafe-inlinefor 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.
To report a problem, see Troubleshooting.