DEV Community

Marcin
Marcin

Posted on

Self-hosting Agentless EDR -bladedr 0.9.0: what works and what is still beta


description: "A practical look at bladedr, its agentless Linux scanning model, current limits, and a Docker Compose deployment."

This post covers
v0.9.0, released on
July 27, 2026. I pulled the published image, ran its key generator, started the
server, checked its health endpoints, and logged in. I also checked the commands
below against the tagged repository.

I have not audited the code or run bladedr on a production fleet. The eBPF
sensor and response actions are marked Beta by the project. Risk scoring is
experimental.

What bladedr does

bladedr scans Linux hosts over SSH. The
server uploads a static binary called bladedr-probe, runs it on the target,
and receives the findings as JSON. The probe reads a snapshot of system state,
including data from /proc, and evaluates detection rules written in YAML and
CEL.

There is no daemon running on the target between agentless scans. The uploaded
probe is cached under /tmp/.bladedr/, so "agentless" does not mean that the
scan leaves no file behind. It means there is no resident process, package, or
kernel module in the default scanning mode.

The optional eBPF tier is different. bladedr-sensor is a long-running service
that wraps Tetragon. It requires root, Docker, and a kernel that supports BTF.

The project has four main binaries:

  • bladedr-server: API, web console, inventory, scan scheduling, and storage;
  • bladedr-probe: the program executed during an SSH scan;
  • bladectl: a command-line API client;
  • bladedr-sensor: the optional Tetragon event forwarder.

The mar0ls/bladedr:0.9.0 image supports linux/amd64 and linux/arm64. It
contains probe and sensor binaries for both architectures. The server can
therefore scan either architecture without a Go toolchain on the control-plane
host. Version 0.9.0 reports 87 built-in detection rules at startup.

The rules are data rather than compiled Go code. Filesystem and database rules
can extend or override the built-in YAML and CEL rules. The server also tracks
per-host baselines, reports drift, calculates fleet rarity, supports triage and
audit logging, and exports ECS/JSON records.

The limit of an SSH snapshot

An SSH scan only sees state that exists while the probe is running. A short
process or network connection can start and stop between scans without being
observed. Continuous telemetry needs the optional sensor tier or another
runtime monitoring tool.

The project publishes a
coverage matrix.
It lists implemented detections and known gaps. A successful installation does
not imply complete detection coverage.

The SSH account also affects visibility. The probe can run without root, but an
unprivileged account cannot read all system data. Giving the control plane root
SSH credentials improves visibility while increasing the impact of a control
plane compromise.

What is not stable yet

The project's
stability document
uses three levels:

  • agentless SSH scanning, storage, authentication, the rule engine, and the v1 API are listed as GA;
  • the eBPF sensor, server-push sensor deployment, response actions, and bladectl are Beta;
  • learned risk scoring, attack-emulation training, and retention are experimental.

The image does not ship with Tetragon policies. /etc/bladedr/policies is
empty. Enabling the sensor requires an operator-supplied TracingPolicy
bundle.

Response actions run fixed, allowlisted playbooks instead of accepting arbitrary
shell commands. They require approval from a second administrator by default.
They are still Beta, and the project says they have not been proven on a real
fleet. A bad isolate_host configuration can cut a machine off from the
control plane.

Risk scoring only changes the order of findings. It does not generate
detections or suppress an observation.

There is one stale sentence in the tagged stability document. It says the
release workflow does not publish an image and that Docker Hub is not wired up.
The public 0.9.0 image exists and can be pulled, so that note no longer matches
the release.

Deploying the control plane

The repository includes a Compose file for the server and its database. This
setup uses:

  • mar0ls/bladedr:0.9.0;
  • paradedb/paradedb:0.23.5;
  • a named volume for the database;
  • a server port bound to 127.0.0.1.

ParadeDB is used because observation search depends on the BM25 index provided
by pg_search. Stock PostgreSQL is not the documented production backend.
Without BLADEDR_DATABASE_URL, bladedr uses an in-memory store and loses its
state on restart.

Download the tagged Compose file

mkdir bladedr-deploy
cd bladedr-deploy

curl -fsSLo compose.yml \
  https://raw.githubusercontent.com/mar0ls/bladedr/v0.9.0/docs/compose.example.yml
Enter fullscreen mode Exit fullscreen mode

The file pins version 0.9.0. The public image index had this digest when I
checked it:

sha256:78533b520303373beb9d97b382d4aee9d173242428f5d67aa07dd19817041368
Enter fullscreen mode Exit fullscreen mode

A version tag makes upgrades explicit. A digest is stricter because a registry
owner can move a tag, while the digest identifies the exact image content.
latest is fine for a temporary test, but I would not use it for this control
plane.

Generate the node key

docker run --rm mar0ls/bladedr:0.9.0 -keygen
Enter fullscreen mode Exit fullscreen mode

The command prints BLADEDR_NODE_KEY and a public key. Save
BLADEDR_NODE_KEY. It decrypts stored SSH credentials. Losing it makes those
credentials unrecoverable, even if the database is intact. A database copy on
its own does not contain enough information to decrypt them.

Create the environment file

Generate a database password that is safe to place directly in a PostgreSQL
connection string:

openssl rand -hex 32
install -m 600 /dev/null .env
Enter fullscreen mode Exit fullscreen mode

Edit .env:

BLADEDR_NODE_KEY=VALUE_PRINTED_BY_KEYGEN
POSTGRES_PASSWORD=RANDOM_HEX_PASSWORD
Enter fullscreen mode Exit fullscreen mode

Do not commit this file. Keep a backup of the node key separately from the
database dump.

Start the stack

docker compose -f compose.yml up -d
docker compose -f compose.yml ps
Enter fullscreen mode Exit fullscreen mode

The database port is not published on the host. The web server listens on
127.0.0.1:8080, using plain HTTP in this example.

Check readiness:

curl -fsS http://127.0.0.1:8080/readyz
Enter fullscreen mode Exit fullscreen mode

Expected response:

{"status":"ready"}
Enter fullscreen mode Exit fullscreen mode

/readyz also checks the data store. /healthz and /metrics are available
without authentication. Keep all three endpoints internal unless there is a
reason to expose them.

Get the initial admin password

On a fresh database, the server creates an admin account. If
BLADEDR_ADMIN_PASSWORD is not set, it generates a password and writes it to
the startup log:

docker compose -f compose.yml logs server
Enter fullscreen mode Exit fullscreen mode

Open:

http://127.0.0.1:8080/ui/login
Enter fullscreen mode Exit fullscreen mode

The generated password must be changed after the first login. It remains in the
container logs, so those logs should be treated as sensitive.

For a remote control-plane host, an SSH tunnel avoids publishing the plain HTTP
port:

ssh -N -L 8080:127.0.0.1:8080 operator@control-plane-host
Enter fullscreen mode Exit fullscreen mode

The console will then be available on the local machine at
http://127.0.0.1:8080/ui/login.

For permanent access, terminate TLS in bladedr or at a reverse proxy. Direct TLS
uses BLADEDR_TLS_CERT and BLADEDR_TLS_KEY. When TLS terminates at a proxy,
set BLADEDR_SECURE_COOKIES=1. Configure BLADEDR_TRUSTED_PROXY_CIDRS as well;
otherwise forwarded client IP headers are ignored and login rate limiting and
audit records see the proxy address.

Adding a Linux host

The control plane needs outbound SSH access to the target. In the web console:

  1. Open Hosts and expand Add host.
  2. Enter the IP address, SSH port, username, and either a password or private key.
  3. Select amd64 or arm64.
  4. Keep the mode set to scan_only unless you have supplied Tetragon policies.
  5. Add the host and click Scan.

The SSH secret is sealed with the node key before it is stored. The server pins
the SSH host key on first use. That is TOFU, so the first connection should take
place over a path you trust.

No repository checkout or compiler is required on the target. The server
uploads the probe included in its container image.

Backup and upgrades

Dump the database before an upgrade:

docker compose -f compose.yml exec -T db \
  pg_dump -U bladedr bladedr > bladedr-$(date +%F).sql
Enter fullscreen mode Exit fullscreen mode

Back up BLADEDR_NODE_KEY separately. The dump and node key together are the
documented complete backup.

Database migrations run when the server starts. Downgrading to a version that
predates an applied migration is not supported. Read the
CHANGELOG.md
before changing the image version.

The 0.9.0 upgrade invalidates existing sessions and replaces the old shared
sensor token with per-host tokens. Its release notes say that hosts, scans,
observations, triage state, rules, baselines, credentials, and audit history are
preserved.

My take

Version 0.9.0 is usable for testing central SSH-based scans, writing CEL rules,
and checking how agentless detection fits a Linux environment. The published
image removes the need to compile the server and its architecture-specific
probes.

I would test it on disposable or non-production hosts first. The control plane
holds SSH access to the fleet, agentless scans have gaps between snapshots, and
the continuous sensor path does not yet include a policy bundle. Those are
important design and maturity limits, not installation details.

The source, tagged binaries, SBOM, checksums, and container image are public.
Start with the coverage matrix and compare it with the threats that matter in
your environment before granting access to production machines.

References

Top comments (0)