Centralized security report aggregation server for collecting vulnerability scan results from multiple hosts.
- Python 3.7+
- PyYAML
pip install -r requirements.txtBREAKING CHANGE: As of version 1.1.0, authentication is mandatory. The server will not start without auth files.
Create two authentication files:
1. Token file (YAML format)
cat > tokens.yaml <<EOF
tokens:
- hb_token_$(openssl rand -hex 16)
- hb_token_$(openssl rand -hex 16)
EOF2. Password file (plaintext)
openssl rand -base64 24 > password.txt./honeybadger_server.py \
--token-file tokens.yaml \
--dashboard-password-file password.txtThe server will:
- Load authentication credentials
- Load configuration from
config.yaml(or use--configto specify location) - Start listening on the configured port (default: 7123)
The server uses two separate authentication mechanisms:
All report submission endpoints require a valid Bearer token in the Authorization header.
Token File Format (tokens.yaml):
tokens:
- hb_production_token_abc123
- hb_staging_token_xyz789- Multiple tokens supported (for different clients/environments)
- Whitespace is automatically trimmed
- Tokens are validated using constant-time comparison (timing-attack resistant)
Web dashboard access requires HTTP Basic Authentication.
Password File Format (password.txt):
my_secure_password_123
- Single password for all dashboard users
- Only first line is used (extra lines logged as warning)
- Whitespace is automatically trimmed
- Username is ignored - any username works with the correct password
Submit individual reports with Bearer token authentication:
# Neofetch (system info)
curl -X POST http://server:7123/ \
-H "Authorization: Bearer hb_production_token_abc123" \
-H "Content-Type: application/json" \
-H "X-Hostname: $(hostname)" \
-H "X-Username: $(whoami)" \
-H "X-Report-Type: neofetch" \
-d @neofetch-report.json
# Lynis (hardening audit)
curl -X POST http://server:7123/ \
-H "Authorization: Bearer hb_production_token_abc123" \
-H "Content-Type: application/json" \
-H "X-Hostname: $(hostname)" \
-H "X-Username: $(whoami)" \
-H "X-Report-Type: lynis" \
-d @lynis-report.json
# Trivy (vulnerability scanner)
curl -X POST http://server:7123/ \
-H "Authorization: Bearer hb_production_token_abc123" \
-H "Content-Type: application/json" \
-H "X-Hostname: $(hostname)" \
-H "X-Username: $(whoami)" \
-H "X-Report-Type: trivy" \
-d @trivy-report.json
# Vulnix (NixOS vulnerability scanner)
curl -X POST http://server:7123/ \
-H "Authorization: Bearer hb_production_token_abc123" \
-H "Content-Type: application/json" \
-H "X-Hostname: $(hostname)" \
-H "X-Username: $(whoami)" \
-H "X-Report-Type: vulnix" \
-d @vulnix-report.jsonSubmit multiple reports in a single request:
# Create archive with all reports
tar -czf reports.tar.gz \
neofetch-report.json \
lynis-report.json \
trivy-report.json
# Submit archive
curl -X POST http://server:7123/submit-tar \
-H "Authorization: Bearer hb_production_token_abc123" \
-H "X-Hostname: $(hostname)" \
-H "X-Username: $(whoami)" \
--data-binary @reports.tar.gzMissing token:
{"error": "Missing Authorization header"}Invalid token:
{"error": "Invalid authentication token"}Malformed header:
{"error": "Invalid Authorization header format. Expected: Bearer <token>"}- Open
http://server:7123/in your browser - Browser will show a login dialog
- Enter any username and the password from
password.txt - Dashboard displays compliance status for all systems
Browser Behavior:
- Login prompt appears automatically on first visit
- Browser caches credentials for the session
- To log out: close browser or use browser's "forget password" feature
# View dashboard HTML
curl -u admin:your_password http://server:7123/
# Download report
curl -u admin:your_password \
http://server:7123/reports/2026-03/hostname-user/lynis-report.json \
-o lynis-report.jsonThe /health endpoint is unauthenticated for monitoring systems:
curl http://server:7123/healthReturns:
{
"status": "ok",
"http_code": 200,
"service": "honeybadger-server",
"uptime": {"seconds": 3600, "human_readable": "1h 0m"},
"statistics": {
"total_report_directories": 42,
"unique_hosts": 10,
"reports_by_type": {"lynis": 40, "neofetch": 42}
}
}usage: honeybadger_server [-h] [--config PATH] --token-file PATH
--dashboard-password-file PATH [--version]
Required Arguments:
--token-file PATH Path to YAML file containing API tokens
--dashboard-password-file PATH Path to plaintext file with dashboard password
Optional Arguments:
--config PATH Path to configuration file
(default: search working dir, script dir, /etc/honeybadger/)
--version Show version and exit
-h, --help Show this help message
[Unit]
Description=Honeybadger Security Report Server
After=network.target
[Service]
Type=simple
User=honeybadger
WorkingDirectory=/opt/honeybadger
ExecStart=/opt/honeybadger/honeybadger_server.py \
--config /etc/honeybadger/config.yaml \
--token-file /run/agenix/honeybadger-tokens.yaml \
--dashboard-password-file /run/agenix/honeybadger-password.txt
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target{ config, pkgs, ... }:
{
age.secrets = {
honeybadger-tokens = {
file = ./secrets/honeybadger-tokens.yaml.age;
owner = "honeybadger";
};
honeybadger-password = {
file = ./secrets/honeybadger-password.txt.age;
owner = "honeybadger";
};
};
systemd.services.honeybadger = {
description = "Honeybadger Security Report Server";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
User = "honeybadger";
WorkingDirectory = "/var/lib/honeybadger";
ExecStart = ''
${pkgs.python3}/bin/python3 /opt/honeybadger/honeybadger_server.py \
--config /etc/honeybadger/config.yaml \
--token-file ${config.age.secrets.honeybadger-tokens.path} \
--dashboard-password-file ${config.age.secrets.honeybadger-password.path}
'';
Restart = "on-failure";
};
};
}What changed:
--token-fileand--dashboard-password-fileCLI arguments are now required- Server will not start without authentication files
- All POST endpoints require Bearer token authentication
- All GET endpoints (except
/health) require Basic Auth
Migration steps:
-
Generate authentication files (before upgrading):
# Create token file cat > /etc/honeybadger/tokens.yaml <<EOF tokens: - hb_$(openssl rand -hex 16) EOF # Create password file openssl rand -base64 24 > /etc/honeybadger/password.txt chmod 600 /etc/honeybadger/tokens.yaml /etc/honeybadger/password.txt chown honeybadger:honeybadger /etc/honeybadger/*.{yaml,txt}
-
Update systemd unit or deployment scripts to include auth arguments
-
Update client scripts to include
Authorization: Bearer <token>header -
Distribute tokens to all client systems
-
Share dashboard password with team via password manager
-
Upgrade server - it will now enforce authentication
-
Test authentication:
# Test API (should return 401 without token) curl -X POST http://server:7123/ # Test API with token (should work) curl -X POST http://server:7123/ \ -H "Authorization: Bearer your_token" \ -H "X-Hostname: test" \ -H "X-Username: test" \ -H "X-Report-Type: neofetch" \ -d '{"os": "NixOS"}' # Test dashboard (should prompt for password) curl http://server:7123/ # Test health check (should work without auth) curl http://server:7123/health
Rollback: If you need to rollback, downgrade to version 1.0.x and remove auth arguments from systemd unit.
| Type | Purpose | Required |
|---|---|---|
| Neofetch | System metadata (hostname, OS, kernel) | Always |
| Lynis | System hardening audit | Always |
| Trivy | Container/OS vulnerability scanner | One of Trivy or Vulnix |
| Vulnix | NixOS vulnerability scanner | One of Trivy or Vulnix |
A system is marked "Complete" when it has:
- Neofetch (identity)
- Lynis (hardening audit)
- Trivy OR Vulnix (vulnerability scan)
See config.yaml for storage location, network port, and compliance settings.
- Token storage: Tokens are loaded into memory at startup and never written to logs
- Password storage: Password stored in plaintext file (protect with OS file permissions)
- Timing attacks: Token/password comparisons use
secrets.compare_digest() - Token rotation: Restart server after updating
tokens.yaml - No rate limiting: Deploy behind firewall or reverse proxy with rate limiting
- File permissions: Ensure auth files are readable only by server user (chmod 600)
Error: "Token file not found"
- Check path in
--token-fileargument - Verify file exists and is readable by server user
Error: "Token file missing 'tokens' key"
- Ensure YAML file has
tokens:key with list of tokens - Check YAML syntax with
python3 -c "import yaml; yaml.safe_load(open('tokens.yaml'))"
Error: "Dashboard password file is empty"
- Ensure password.txt contains at least one non-whitespace character
API returns 401
- Verify token in
Authorization: Bearer <token>header - Check token exists in tokens.yaml (case-sensitive, no extra whitespace)
- Check server logs for "Loaded N token(s)" message at startup
Dashboard prompts for password repeatedly
- Verify password matches first line of password.txt (case-sensitive)
- Check browser isn't blocking cookies
- Try different username (username is ignored, but required by Basic Auth)
Health check returns 401
- Health check should work without auth - this indicates a bug
- Check you're requesting
/healthnot/or/status
See LICENSE file.