Skip to content
GitHub
documentation

Security

How Bud protects sign-in, Test Stations, tokens and stored data, and how to report a vulnerability.

Bud and Bloom are built to run in production. Runner registration is credential-based and guarded by a separate station enrolment key passed through RUNNER_API_KEY. Secrets stay in the local identity vault, never in your repository. Each test class is isolated in its own process with enforced timeouts, and the daemon control socket is loopback-only by default.

Do not open a public GitHub issue for a suspected security vulnerability. Follow each repository’s SECURITY.md and report it privately, by responsible disclosure, to dev@embedlabs.net.

Please include:

  • the affected deployment or version;
  • reproduction steps or a proof of concept;
  • an impact assessment;
  • any suggested mitigation.

EmbedLabs acknowledges receipt as soon as practical and coordinates next steps over email. Give EmbedLabs reasonable time to investigate and prepare a fix before public disclosure. Security fixes are applied to the latest supported release.

  • A station enrols with its own key, minted by an administrator in Test Stations. There is no shared secret.
  • A key belongs to one station. It pins to the first station that registers with it, and from then on only that station can use it.
  • The key is shown once, on creation, and is never retrievable afterwards. Bud stores it as a SHA-256 digest and lists it by prefix.
  • Only Test Stations submit results and upload artifacts, and only for their own runs. Human sessions, Administrator included, cannot.
  • Removing a station revokes its credentials and keeps every run it produced.

On the station, the runner keeps its machine identity and tokens under ~/.bud/. Keep that directory private and never commit it. See Identity and secrets.

bud_runner imports and executes Python test code from your local workspace. Only run trusted test code: a malicious test class can execute arbitrary code on the host. Each test class runs in its own OS process, so a crash or hang in one test can’t corrupt another. A per-test timeout and a global suite timeout bound execution.

The daemon’s control socket binds to 127.0.0.1 by default. Non-loopback binding is not a LAN-safe deployment mode: the socket protocol has no authentication.

  • Passwords must be at least 12 characters. In production, the first administrator’s password must be at least 16.
  • Changing or resetting a password signs out all existing sessions.
  • One-time links (invitation, email verification, password reset, email change) carry their token only in the URL fragment and are single-use. This keeps tokens out of request targets, server logs, and the Referer header.
  • Revoking an invitation revokes its link.
  • Email changes require the current password, and completing one ends existing sessions.
  • The /setup page stops being reachable the moment the first account exists: nobody else can claim the instance.
  • Auth cookies are sent only over HTTPS in production.

Token lifetimes are set in the Configuration reference.

  • Bloom credential. The Bloom result-sync credential is encrypted at rest under BUD_INTEGRATION_ENCRYPTION_KEY and is never returned through the settings API.
  • Artifacts. Uploads are validated and written while streaming, through a temporary file, and only complete uploads are kept. Runner uploads are scoped to their own run. Size, rate, concurrency, and free-space limits bound what one runner can store.
  • Downloads. Every artifact download is an attachment sent with Content-Security-Policy: sandbox and X-Content-Type-Options: nosniff, so user-supplied HTML or SVG never runs in the Bud origin.

Keep SECRET_KEY and BUD_INTEGRATION_ENCRYPTION_KEY in your secret store, and back them up with the database and the uploads. See Data and backups.

  • The interactive API documentation is disabled by default. Enable it only in a trusted development environment.
  • /api/metrics is unauthenticated. Keep it reachable only from your monitoring network, or disable it with ENABLE_METRICS=false.

Security-relevant events are recorded in the same transaction as the action: sign-in, sign-out, session refresh, password change and reset, email verification and email change; user creation, invitation, role change, activation, and deletion; groups, station registration, enrolment keys, and station removal; configuration changes; deletions, exports, imports, and attachments. A failed sign-in or password reset is recorded even though the request fails. Each event carries the acting user, the action, the target, the request id, the client address and user agent, the outcome, and details without secrets.

The client address is the one the image’s own nginx received. Behind another reverse proxy, such as Cloudron or a load balancer, it is that proxy’s address.

Administrators read the log under Audit log in the sidebar, with event times shown in the viewer’s time zone. No endpoint changes or deletes an event.

Retention is the operator’s choice. The application never deletes events. To remove old ones, run on a schedule:

python -m app.audit prune --older-than-days 365

Every release build publishes an SPDX SBOM alongside the container image. CI runs Bandit, pip-audit, and blocking npm audit checks on every push. Backend dependencies are pinned, so builds are reproducible.