refactor: implement V1.0 contract and unified SDK architecture

- Migrated to HW_ environment variable namespace
- Created installable python-honeywire SDK package
- Standardized sensor templates with Distroless Docker support
- Added CI/CD pipeline for PR security scanning and functional testing
- Updated all internal documentation to match V1.0 specs
This commit is contained in:
andreicscs
2026-04-03 11:38:16 +02:00
parent 5cced8e6a3
commit dbfdf1df90
29 changed files with 892 additions and 393 deletions
+53
View File
@@ -0,0 +1,53 @@
import http.server
import json
import sys
class MockHubHandler(http.server.BaseHTTPRequestHandler):
def do_POST(self):
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
try:
payload = json.loads(post_data.decode('utf-8'))
# 1. Enforce the V1.0 Contract
required_keys = [
"contract_version", "sensor_id", "sensor_type",
"event_type", "severity", "timestamp", "metadata"
]
for key in required_keys:
if key not in payload:
print(f"❌ FAILED: Missing required key '{key}'")
sys.exit(1)
# 2. Enforce severity type
if not isinstance(payload['severity'], int) and payload['severity'] not in ["info", "low", "medium", "high", "critical"]:
print("❌ FAILED: 'severity' must be an int or valid enum.")
sys.exit(1)
print("✅ SUCCESS: Valid HoneyWire V1.0 payload received.")
print(json.dumps(payload, indent=2))
self.send_response(200)
self.end_headers()
# Write a success flag for the CI runner
with open('/tmp/test_passed', 'w') as f:
f.write('success')
except json.JSONDecodeError:
print("❌ FAILED: Payload is not valid JSON.")
sys.exit(1)
except Exception as e:
print(f"❌ FAILED: Unexpected error - {e}")
sys.exit(1)
def log_message(self, format, *args):
# Suppress default HTTP logging to keep CI logs clean
pass
if __name__ == '__main__':
print("🛡️ Mock Hub listening on port 8080...")
server = http.server.HTTPServer(('0.0.0.0', 8080), MockHubHandler)
server.handle_request() # Process exactly one request then exit
-48
View File
@@ -1,48 +0,0 @@
name: Build and Publish HoneyWire
on:
push:
branches: [ "main" ]
release:
types: [published]
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
strategy:
matrix:
component: [Hub, Agent]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# --- THIS IS WHERE THE TAG RULES BELONG ---
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}-${{ matrix.component }}
tags: |
type=ref,event=branch
type=raw,value=latest,enable={{is_default_branch}}
# --- THIS IS THE BUILDER THAT USES THE RULES ---
- name: Build and push
uses: docker/build-push-action@v5
with:
context: ./${{ matrix.component }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+102
View File
@@ -0,0 +1,102 @@
name: Sensor PR Check & Security Scan
on:
pull_request:
paths:
- 'Sensors/community/**'
- 'Sensors/official/**'
jobs:
static-analysis:
name: CodeQL Security Scan
runs-on: ubuntu-latest
permissions:
security-events: write
actions: read
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: python
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
docker-security-scan:
name: Build & Trivy Image Scan
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
# We need to find which sensor changed to build the right Dockerfile
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v42
with:
files: Sensors/**
- name: Build and Scan Changed Sensors
run: |
for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
SENSOR_DIR=$(dirname "$file")
# Only build if there is a Dockerfile
if [ -f "$SENSOR_DIR/Dockerfile" ]; then
echo "Building $SENSOR_DIR..."
docker build -t test-sensor:latest "$SENSOR_DIR"
echo "Running Trivy Vulnerability Scanner..."
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
trivy image --severity HIGH,CRITICAL --exit-code 1 --no-progress test-sensor:latest
fi
done
functional-test:
name: Mock Hub Contract Test
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Start Mock Hub
run: |
python .github/scripts/mock_hub.py &
sleep 3 # Give the server a second to start
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v42
with:
files: Sensors/**
- name: Run Test Mode on Changed Sensors
run: |
for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
SENSOR_DIR=$(dirname "$file")
if [ -f "$SENSOR_DIR/Dockerfile" ]; then
docker build -t test-sensor:latest "$SENSOR_DIR"
# Run the container with test mode enabled
docker run --rm \
-e HW_HUB_ENDPOINT="http://172.17.0.1:8080" \
-e HW_HUB_KEY="mock_key" \
-e HW_SENSOR_ID="ci-test-node" \
-e HW_TEST_MODE="true" \
test-sensor:latest
# Check if the Mock Hub received the success signal
if [ -f /tmp/test_passed ]; then
echo "✅ Sensor successfully communicated with Mock Hub."
rm /tmp/test_passed
else
echo "❌ Sensor failed to contact Mock Hub or sent invalid JSON."
exit 1
fi
fi
done
+19 -3
View File
@@ -1,16 +1,32 @@
# =========================
# Secrets and Config
# =========================
.env
**/ .env
**/.env
# (Note: We do NOT ignore .env.example so users have a template)
# =========================
# Database
# =========================
*.db
*.sqlite3
# =========================
# Python
# =========================
__pycache__/
*.py[cod]
venv/
.venv/
*.egg-info/
build/
dist/
# Docker
.dockerignore
# =========================
# OS & IDE Clutter
# =========================
.DS_Store
Thumbs.db
.vscode/
# (Notice: .dockerignore is intentionally NOT here. It must be tracked by Git!)
-23
View File
@@ -1,23 +0,0 @@
# Where is the Hub located? (Use your Hub's public/LAN IP if on a different machine)
HUB_URL=http://127.0.0.1:8080
# Must match the Hub's secret
API_SECRET=super_secret_key_123
# Unique name and IP for this specific sensor
SENSOR_ID=ubuntu-vps-02
SENSOR_IP=192.168.1.51
# The fake ports this agent will listen on
DECOY_PORTS=21,2222,3306
# Modes: 'echo' (repeat data), 'hold' (do nothing, keep open), 'close' (drop immediately)
TARPIT_MODE=hold
# UI Color Coding: info|low|medium|high|critical
SEVERITY=high
# The fake service banner.
# Example for SSH: SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.1\r\n
# Example for FTP: 220 (vsFTPd 3.0.3)\r\n
TARPIT_BANNER=SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.1\r\n
-26
View File
@@ -1,26 +0,0 @@
# --- Stage 1: Builder ---
FROM python:3.11-slim AS builder
WORKDIR /app
# Create a virtual environment and install dependencies
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
RUN pip install --no-cache-dir requests
# --- Stage 2: Distroless (Production) ---
FROM gcr.io/distroless/python3-debian12
WORKDIR /app
# Copy the virtual environment from the builder
COPY --from=builder /opt/venv /opt/venv
ENV PYTHONPATH="/opt/venv/lib/python3.11/site-packages"
ENV PATH="/opt/venv/bin:$PATH"
# Copy your agent script
COPY agent.py .
# Notice: We intentionally do NOT use a nonroot user here.
# We remain 'root' to allow binding to privileged decoy ports (1-1024).
CMD ["agent.py"]
-222
View File
@@ -1,222 +0,0 @@
"""
HoneyWire Tarpit Agent (Standard Port Tripwire)
Sensor type: tarpit
Environment variables:
HUB_URL Hub base URL (default: http://localhost:8080)
API_SECRET Shared API key (default: super_secret_key_123)
SENSOR_ID Unique name for this node (default: alpha-node-01)
DECOY_PORTS Comma-separated ports (default: 2222,3306)
SEVERITY info|low|medium|high|critical (default: high)
TARPIT_MODE hold | echo | close (default: hold)
"""
import asyncio
import os
import sys
import time
import threading
import requests
# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
HUB_URL = os.getenv("HUB_URL", "http://localhost:8080")
API_SECRET = os.getenv("API_SECRET", "super_secret_key_123")
SENSOR_ID = os.getenv("SENSOR_ID", "alpha-node-01")
SEVERITY = os.getenv("SEVERITY", "high").lower()
raw_ports = os.getenv("DECOY_PORTS", "2222,3306") or "2222,3306"
DECOY_PORTS = [int(p.strip()) for p in raw_ports.split(",") if p.strip()]
TARPIT_MODE = os.getenv("TARPIT_MODE", "hold").lower()
raw_banner = os.getenv("TARPIT_BANNER", "")
TARPIT_BANNER = (
raw_banner.encode("utf-8").decode("unicode_escape").encode("utf-8")
if raw_banner else b""
)
DEFAULT_AGENT_VERSION = "1.0.0"
VERSION_FILE_PATH = os.path.join(os.path.dirname(__file__), "..", "VERSION")
try:
with open(os.path.abspath(VERSION_FILE_PATH), "r") as f:
file_version = f.read().strip()
except FileNotFoundError:
file_version = DEFAULT_AGENT_VERSION
AGENT_VERSION = os.getenv("HONEYWIRE_VERSION", file_version)
MAX_BYTES = 1024 * 50 # 50 KB safety cap per connection
MAX_LINES = 10 # Max lines to store in memory for forensics
MAX_DURATION = 3600 # Force disconnect after 1 hour to free File Descriptors
CONCURRENCY_LIMIT = 1000 # Max simultaneous connections to prevent FD exhaustion
_HEADERS = {"x-api-key": API_SECRET}
# Dynamically set upon startup synchronization
HUB_CONTRACT_VERSION = "unknown"
# Global semaphore to limit concurrent TCP sockets
connection_semaphore = asyncio.Semaphore(CONCURRENCY_LIMIT)
# ---------------------------------------------------------------------------
# Hub Communication
# ---------------------------------------------------------------------------
def sync_hub_version() -> None:
"""Fetches the Hub's contract version on startup to ensure compatibility."""
global HUB_CONTRACT_VERSION
try:
print(f"[*] Synchronizing with Hub at {HUB_URL}...")
resp = requests.get(f"{HUB_URL}/api/v1/version", headers=_HEADERS, timeout=5)
resp.raise_for_status()
HUB_CONTRACT_VERSION = resp.json()["version"]
# Semantic Versioning Check (Major Version)
hub_major = HUB_CONTRACT_VERSION.split('.')[0]
agent_major = AGENT_VERSION.split('.')[0]
if hub_major != agent_major:
print(f"[!] FATAL: Version mismatch. Hub (v{HUB_CONTRACT_VERSION}) vs Agent (v{AGENT_VERSION})")
sys.exit(1)
print(f"[+] Synchronized successfully. Operating on contract v{HUB_CONTRACT_VERSION}")
except requests.exceptions.RequestException as e:
print(f"[!] FATAL: Failed to synchronize with Hub. Is it offline? Details: {e}")
sys.exit(1)
def send_heartbeat() -> None:
"""Pings the Hub every 30 seconds to stay 'online'."""
payload = {
"sensor_id": SENSOR_ID,
"sensor_type": "tarpit",
"metadata": {
"agent_version": AGENT_VERSION,
"contract_version": HUB_CONTRACT_VERSION,
"mode": TARPIT_MODE,
"ports": DECOY_PORTS,
"severity_config": SEVERITY
}
}
while True:
try:
requests.post(f"{HUB_URL}/api/v1/heartbeat", headers=_HEADERS, json=payload, timeout=5)
except Exception as e:
print(f[-] Heartbeat error: {e}")
time.sleep(30)
def report_event(source_ip: str, port: int, duration: float, payload: list[str]) -> None:
"""Reports connection to Hub using the Universal Event Standard."""
event = {
"contract_version": HUB_CONTRACT_VERSION, # Integrated dynamic version
"sensor_id": SENSOR_ID,
"sensor_type": "tarpit",
"event_type": "tcp_connection",
"severity": SEVERITY,
"source": source_ip,
"target": f"Port {port}",
"action_taken": TARPIT_MODE,
"details": {
"duration_sec": round(duration, 2),
"payload": payload,
}
}
try:
requests.post(f"{HUB_URL}/api/v1/event", headers=_HEADERS, json=event, timeout=5)
print(f"[+] Alert sent: {source_ip} -> Port {port} ({SEVERITY.upper()})")
except Exception as e:
print(f"[-] Event report failed: {e}")
# ---------------------------------------------------------------------------
# Core Logic
# ---------------------------------------------------------------------------
async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter, port: int) -> None:
addr = writer.get_extra_info("peername")
source_ip = addr[0] if addr else "Unknown"
start_time = time.time()
payload = []
total_bytes = 0
try:
if TARPIT_BANNER and TARPIT_MODE != "close":
writer.write(TARPIT_BANNER)
await writer.drain()
if TARPIT_MODE != "close":
while total_bytes < MAX_BYTES and (time.time() - start_time) < MAX_DURATION:
try:
# Added a default timeout even for "hold" to allow TTL checks
timeout = 60.0 if TARPIT_MODE == "echo" else 300.0
data = await asyncio.wait_for(reader.read(1024), timeout=timeout)
if not data: break
decoded = data.decode("utf-8", errors="replace").strip()
if decoded:
# 🔒 SECURITY: Only store the first 10 lines to prevent memory bloat
if len(payload) < MAX_LINES:
payload.append(decoded)
total_bytes += len(data)
if TARPIT_MODE == "echo":
writer.write(data)
await writer.drain()
await asyncio.sleep(0.5) # Anti-spam delay
except asyncio.TimeoutError:
if not writer.is_closing():
writer.write(b"\0") # Heartbeat byte to keep connection active
await writer.drain()
continue
except Exception:
pass # Expected when attackers forcefully close the socket
finally:
# 🔒 SECURITY: Try/Except prevents Ghost Bypass on TCP RST
try:
writer.close()
await writer.wait_closed()
except Exception:
pass
duration = time.time() - start_time
await asyncio.to_thread(report_event, source_ip, port, duration, payload)
async def connection_wrapper(reader: asyncio.StreamReader, writer: asyncio.StreamWriter, port: int) -> None:
"""Wraps handle_client in a Semaphore to prevent File Descriptor exhaustion."""
async with connection_semaphore:
await handle_client(reader, writer, port)
async def main() -> None:
print(f"[*] HoneyWire Agent v{AGENT_VERSION} | Severity: {SEVERITY.upper()}")
# 1. Sync version with Hub before opening any ports
sync_hub_version()
# 2. Start heartbeat thread
threading.Thread(target=send_heartbeat, daemon=True).start()
# 3. Start decoy listeners
servers = []
for port in DECOY_PORTS:
server = await asyncio.start_server(lambda r, w, p=port: connection_wrapper(r, w, p), "0.0.0.0", port)
servers.append(server.serve_forever())
print(f"[+] Listening on port {port}")
await asyncio.gather(*servers)
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\n[*] Shutting down HoneyWire Agent...")
-9
View File
@@ -1,9 +0,0 @@
services:
agent:
build: .
container_name: honeywire-agent
restart: unless-stopped
# Binds directly to the host machine's network to catch real port scans
network_mode: "host"
env_file:
- .env
+50
View File
@@ -0,0 +1,50 @@
# Contributing to HoneyWire 🍯🕸️
Welcome to HoneyWire! We are building a centralized, high-fidelity security ecosystem for homelabs and SMBs.
To keep the ecosystem stable, all community-submitted sensors must adhere to a strict set of rules. We treat sensors as **isolated microservices**.
## The Golden Rules of Sensors
1. **Self-Contained (Docker Only):** Every sensor must include a `Dockerfile`. Users should not need to install Python, Node, Go, or Rust on their host machine to run your sensor.
2. **Zero Blast Radius:** Your sensor must not crash the main Hub. All communication must happen via HTTP POST requests containing JSON.
3. **No Hardcoding:** All configurations (Ports, API keys, file paths) must be handled via environment variables inside a `.env` file.
## How to Submit a New Sensor
### 1. Use the Template
Copy the `Sensors/templates/python-sensor/` folder and rename it to your sensor's name inside the `Sensors/community/` directory.
### 2. Follow the JSON Contract (v1.0)
Your sensor must POST a payload to the Hub (`HW_HUB_ENDPOINT`) matching this exact schema:
```json
{
"contract_version": "1.0",
"sensor_id": "provided-by-env",
"sensor_type": "your_sensor_category",
"event_type": "what_just_happened",
"severity": "critical",
"timestamp": "2026-04-03T01:24:18Z",
"action_taken": "logged",
"metadata": {
"ip": "192.168.1.5",
"custom_data": "anything you want"
}
}
```
*(Note: If you use the official HoneyWire SDK provided in the template, this formatting is handled for you automatically).*
### 3. Implement Test Mode (Required for CI/CD)
To ensure your code works before merging, our GitHub Actions will build your Docker container and pass `HW_TEST_MODE=true` as an environment variable.
If this variable is present, your sensor **must immediately send a dummy payload to the Hub and exit**. (The Python SDK handles this out-of-the-box).
### 4. Provide Documentation
Your sensor folder must contain a `README.md` that explicitly lists all required environment variables and provides instructions for the user.
## Review Process
Once you open a Pull Request:
1. **Functional Testing:** GitHub Actions will automatically build your Docker container and test it against a Mock Hub using `HW_TEST_MODE=true`.
2. **Automated Security Scanning:** GitHub Actions will run **Trivy** to scan your Docker image for OS and library vulnerabilities, and **CodeQL** to perform static code analysis for security flaws.
3. **Manual Review:** A core maintainer will manually review the code for malicious intent or blast-radius risks before merging. PRs that fail automated testing or scanning will not be reviewed.
+3 -2
View File
@@ -70,6 +70,7 @@ This document describes the HTTP API for the HoneyWire Hub backend.
```json
[
{
"contract_version": "1.0.0",
"id": 123,
"timestamp": "2026-04-02 15:25:12",
"sensor_id": "alpha-node-01",
@@ -130,6 +131,7 @@ This document describes the HTTP API for the HoneyWire Hub backend.
- Request:
```json
{
"contract_version": "1.0.0",
"sensor_id": "alpha-node-01",
"sensor_type": "tarpit",
"event_type": "tcp_connection",
@@ -139,8 +141,7 @@ This document describes the HTTP API for the HoneyWire Hub backend.
"action_taken": "hold",
"details": {
"duration_sec": 12.3,
"payload_sample": ["sudo rm -rf /"],
"total_lines": 7
"payload": ["sudo rm -rf /"],
}
}
```
+19 -9
View File
@@ -1,12 +1,22 @@
# The port the dashboard will be accessible on
HUB_PORT=8080
# ==========================================
# HONEYWIRE HUB CONFIGURATION
# ==========================================
# The master password for your fleet
API_SECRET=super_secret_key_123
# The secret key your sensors use to talk to the Hub. Make this long and random!
HW_HUB_KEY=change_this_to_a_secure_random_string
# Optional: Get push notifications to your phone
NTFY_URL=https://ntfy.sh/supersecretnotificationendpoint
GOTIFY_URL=http://your-gotify-server.local/message
GOTIFY_TOKEN=AxxBxxCxxDxx
# The password to log into the Web Dashboard
HW_DASHBOARD_PASSWORD=admin
DASHBOARD_PASSWORD=my_secure_password
# Where should the SQLite database be stored? (Inside the container)
HW_DB_PATH=/data/honeywire.db
# ==========================================
# NOTIFICATIONS (Optional)
# ==========================================
# Push alerts directly to your phone via Ntfy (e.g., https://ntfy.sh/my_secret_topic)
HW_NTFY_URL=
# Push alerts to a self-hosted Gotify instance
HW_GOTIFY_URL=
HW_GOTIFY_TOKEN=
+16 -8
View File
@@ -33,14 +33,21 @@ logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(me
log = logging.getLogger("honeywire")
# ---------------------------------------------------------------------------
# Configuration (all values come from environment variables)
# Configuration (all values come from HW_ environment variables)
# ---------------------------------------------------------------------------
API_SECRET = os.getenv("API_SECRET", "super_secret_key_123")
DASHBOARD_PASSWORD = os.getenv("DASHBOARD_PASSWORD", "")
NTFY_URL = os.getenv("NTFY_URL", "")
GOTIFY_URL = os.getenv("GOTIFY_URL", "")
GOTIFY_TOKEN = os.getenv("GOTIFY_TOKEN", "")
DB_PATH = os.getenv("DB_PATH", "/data/honeywire.db")
# The API Key sensors use to authenticate to the Hub
API_SECRET = os.getenv("HW_HUB_KEY", "super_secret_key_123")
# Dashboard UI Password
DASHBOARD_PASSWORD = os.getenv("HW_DASHBOARD_PASSWORD", "")
# Notification Endpoints
NTFY_URL = os.getenv("HW_NTFY_URL", "")
GOTIFY_URL = os.getenv("HW_GOTIFY_URL", "")
GOTIFY_TOKEN = os.getenv("HW_GOTIFY_TOKEN", "")
# Database Location
DB_PATH = os.getenv("HW_DB_PATH", "/data/honeywire.db")
# Versioning: one source of truth stored in root file + env override.
DEFAULT_VERSION = "1.0.0"
@@ -51,7 +58,8 @@ try:
except FileNotFoundError:
FILE_VERSION = DEFAULT_VERSION
HONEYWIRE_VERSION = os.getenv("HONEYWIRE_VERSION", FILE_VERSION)
# Use HW_VERSION to allow users to override it if strictly necessary
HONEYWIRE_VERSION = os.getenv("HW_VERSION", FILE_VERSION)
AUTH_COOKIE_NAME = "hw_auth"
+15 -5
View File
@@ -1,5 +1,15 @@
fastapi
uvicorn
pydantic
requests
jinja2
# Web Framework
fastapi==0.110.0
uvicorn[standard]==0.29.0
# HTML Templating for the Dashboard
jinja2==3.1.3
# Data Validation (Required by FastAPI)
pydantic==2.6.4
# Used for Login Forms (FastAPI OAuth2PasswordRequestForm needs this)
python-multipart==0.0.9
# Used for pushing outbound notifications (Ntfy, Gotify, Discord etc.)
requests==2.31.0
+35 -38
View File
@@ -37,25 +37,26 @@ There are existing lightweight honeypots, but none feature a clean, SaaS-grade d
![Payload Inspector](screenshots/payload-inspector.png)
---
## 🔌 The Universal Event Standard (Bring Your Own Sensor)
The true power of HoneyWire is that the Hub is **completely sensor-agnostic**. You are not limited to the included Tarpit agent.
By adhering to the **HoneyWire Event Standard**, you can write a script in *any* language (Bash, Go, Rust, Python) to monitor *anything*, and the Sentinel UI will dynamically parse, syntax-highlight, and render your forensic data perfectly.
By adhering to the **HoneyWire Event Standard V1.0**, you can write a script in *any* language (Bash, Go, Rust, Python) to monitor *anything*, and the Sentinel UI will dynamically parse, syntax-highlight, and render your forensic data perfectly.
Whether it is a **Deep Packet Inspection (DPI)** engine, a **DNS sinkhole**, a **Canary Token** embedded in a PDF, an **Email Honeypot**, or a simple **TCP Port Tripwire**, just POST this JSON to the Hub:
```json
{
"contract_version": "1.0",
"sensor_id": "core-dpi-engine",
"sensor_type": "deep_packet_inspection",
"event_type": "malformed_jwt_detected",
"severity": "critical",
"source": "104.28.19.12",
"target": "Auth Gateway",
"timestamp": "2026-04-03T11:30:00Z",
"action_taken": "ip_banned",
"details": {
"metadata": {
"source_ip": "104.28.19.12",
"target": "Auth Gateway",
"protocol": "TCP",
"headers_stripped": true,
"payload_sample": [
@@ -65,6 +66,8 @@ Whether it is a **Deep Packet Inspection (DPI)** engine, a **DNS sinkhole**, a *
}
}
```
> Note: If you build your sensor using the official HoneyWire Python SDK, this JSON formatting and delivery is handled for you automatically!
*The Hub's Alpine.js frontend will automatically translate arrays into syntax-highlighted code blocks and primitive values into clean metadata tags.*
---
@@ -87,8 +90,9 @@ Whether it is a **Deep Packet Inspection (DPI)** engine, a **DNS sinkhole**, a *
HoneyWire is split into two independent microservices:
1. **The Hub (`/Hub`)**: The central brain. It runs a FastAPI backend, an SQLite database, and the web dashboard. It runs as a `nonroot` user inside a Distroless container, safely mounting data to a dedicated volume.
2. **The Agent (`/Agent`)**: The decoy sensor (or any custom script you write). It listens on vulnerable ports, traps attackers, and securely POSTs the intrusion data back to the Hub using an `API_SECRET`.
1. `/Hub`: The central brain. It runs a FastAPI backend, an SQLite database, and the web dashboard. It runs as a nonroot user inside a Distroless container, safely mounting data to a dedicated volume.
2. `/Sensors`: The decoy nodes (like the included TCP Tripwire or your custom scripts). They listen on vulnerable ports, trap attackers, and securely POST intrusion data back to the Hub.
3. `/SDKs`: Official libraries (like python-honeywire) that handle secure Hub communication so community developers can easily build new sensors.
---
@@ -113,9 +117,9 @@ services:
env_file:
- .env
agent:
image: ghcr.io/andreicscs/honeywire-agent:latest
container_name: honeywire-agent
tcp-tripwire:
image: ghcr.io/andreicscs/honeywire-tcptripwire:latest
container_name: hw-tcp-tripwire
restart: unless-stopped
network_mode: "host" # Required to accurately capture port scans against the physical machine
env_file:
@@ -130,43 +134,37 @@ volumes:
# HUB CONFIGURATION
# ==========================================
# The master password for your fleet to communicate
API_SECRET=super_secret_key_123
HW_HUB_KEY=super_secret_key_123
# Protect your Web UI (Leave blank for no password)
DASHBOARD_PASSWORD=my_secure_password
HW_DASHBOARD_PASSWORD=my_secure_password
# Optional: Push Notifications
NTFY_URL=https://ntfy.sh/your_private_topic
GOTIFY_URL=https://gotify.yourdomain.com/message
GOTIFY_TOKEN=your_app_token
```
```
HW_NTFY_URL=[https://ntfy.sh/your_private_topic](https://ntfy.sh/your_private_topic)
HW_GOTIFY_URL=[https://gotify.yourdomain.com/message](https://gotify.yourdomain.com/message)
HW_GOTIFY_TOKEN=your_app_token
# ==========================================
# AGENT CONFIGURATION
# SENSOR CONFIGURATION
# ==========================================
# Point this to your Hub's IP address and Port
HUB_URL=http://127.0.0.1:8080
HW_HUB_ENDPOINT=[http://127.0.0.1:8080](http://127.0.0.1:8080)
# Must match the Hub's secret
API_SECRET=super_secret_key_123
# Identify this specific sensor and its IP
SENSOR_ID=dmz-node-01
SENSOR_IP=192.168.1.50
# Identify this specific sensor
HW_SENSOR_ID=dmz-node-01
# A comma-separated list of fake ports to open
DECOY_PORTS=21,22,2222,3306,8080
HW_DECOY_PORTS=21,22,2222,3306,8080
# Tarpit Behavior: 'hold', 'echo', or 'close'
TARPIT_MODE=hold
HW_TARPIT_MODE=hold
# UI Color Coding: info|low|medium|high|critical
SEVERITY=high
HW_SEVERITY=high
# Fake Service Banner (Use \r\n for line breaks)
# Example SSH: SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.1\r\n
# Example FTP: 220 (vsFTPd 3.0.3)\r\n
TARPIT_BANNER=SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.1\r\n
HW_TARPIT_BANNER=SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.1\r\n
```
### 3. Start the Trap
Run the following command to pull the images and start the honeypot:
@@ -210,23 +208,22 @@ nc <agent-ip> 2222
## 📦 Versioning and API reference
- HoneyWire now uses a single source of truth version file: `VERSION` in the repo root.
- Runtime version is exposed via env override: `HONEYWIRE_VERSION` (Hub + Agent), and defaults to `VERSION`.
- Runtime version is exposed via env override: HW_VERSION (Hub + Sensors), and defaults to VERSION.
- `Hub` endpoint:
- `GET /api/v1/version` → returns `{ "version": "1.0.0" }`
- API docs file added: [📖 API.md](./API.md). with full backend route reference and sample payloads.
- API docs file added: [📖 API.md](./Docs/API.md). with full backend route reference and sample payloads.
### API endpoints to know
- `GET /api/v1/system/state` / `PATCH /api/v1/system/state`
- `GET /api/v1/sensors`
- `GET /api/v1/events`
- `PATCH /api/v1/events/read`, `PATCH /api/v1/events/{event_id}/read`, `DELETE /api/v1/events`
- `POST /api/v1/heartbeat` (agent heartbeat)
- `POST /api/v1/event` (agent event reports)
- `POST /api/v1/heartbeat` (Sensor heartbeat)
- `POST /api/v1/event` (Sensor event reports)
---
## 🧪 Operational checklist
- set `API_SECRET` for all components
- set optional `DASHBOARD_PASSWORD`
- optionally set `HONEYWIRE_VERSION` to track deployment version
- set `HW_HUB_KEY` for all components
- set optional `HW_DASHBOARD_PASSWORD`
- build/redeploy containers after any version bump in `VERSION` or env value
+146
View File
@@ -0,0 +1,146 @@
import os
import sys
import time
import threading
import datetime
import requests
from abc import ABC, abstractmethod
class HoneyWireSensor(ABC):
def __init__(self, sensor_type: str):
self.sensor_type = sensor_type
self.hub_endpoint = os.getenv("HW_HUB_ENDPOINT")
self.hub_key = os.getenv("HW_HUB_KEY")
self.sensor_id = os.getenv("HW_SENSOR_ID")
self.test_mode = os.getenv("HW_TEST_MODE", "false").lower() == "true"
self.agent_version = os.getenv("HONEYWIRE_VERSION", "1.0.0")
self.severity = os.getenv("HW_SEVERITY", "4")
if not all([self.hub_endpoint, self.hub_key, self.sensor_id]):
print("[!] FATAL: Missing required environment variables (HW_HUB_ENDPOINT, HW_HUB_KEY, HW_SENSOR_ID).")
sys.exit(1)
self.headers = {
"Authorization": f"Bearer {self.hub_key}",
"Content-Type": "application/json"
}
self.hub_contract_version = "unknown"
def _normalize_severity(self, raw_severity) -> str:
"""Converts 1-5 or strings into the official schema enum."""
mapping = {
"1": "info",
"2": "low",
"3": "medium",
"4": "high",
"5": "critical"
}
val = str(raw_severity).lower().strip()
if val in mapping:
return mapping[val]
elif val in ["info", "low", "medium", "high", "critical"]:
return val
else:
print(f"[!] Warning: Invalid severity '{raw_severity}'. Defaulting to 'info'.")
return "info"
def _sync_hub_version(self) -> None:
"""Fetches the Hub's contract version synchronously on startup."""
print(f"[*] Synchronizing with Hub at {self.hub_endpoint}...")
try:
resp = requests.get(f"{self.hub_endpoint}/api/v1/version", headers=self.headers, timeout=5)
resp.raise_for_status()
self.hub_contract_version = resp.json().get("version", "unknown")
# Semantic Versioning Check
hub_major = str(self.hub_contract_version).split('.')[0]
agent_major = str(self.agent_version).split('.')[0]
if hub_major != agent_major and hub_major != "unknown":
print(f"[!] FATAL: Version mismatch. Hub (v{self.hub_contract_version}) vs Agent (v{self.agent_version})")
sys.exit(1)
print(f"[+] Synchronized successfully. Operating on contract v{self.hub_contract_version}")
except requests.exceptions.RequestException as e:
print(f"[!] FATAL: Failed to synchronize with Hub. Details: {e}")
sys.exit(1)
def _heartbeat_loop(self) -> None:
"""Background thread to ping the Hub every 30 seconds."""
payload = {
"sensor_id": self.sensor_id,
"sensor_type": self.sensor_type,
"metadata": {
"agent_version": self.agent_version,
"contract_version": self.hub_contract_version
}
}
while True:
try:
requests.post(f"{self.hub_endpoint}/api/v1/heartbeat", headers=self.headers, json=payload, timeout=5)
except Exception as e:
print(f"[-] Heartbeat error: {e}")
time.sleep(30)
def report_event(self, event_type: str, severity, metadata: dict, action_taken: str = "logged") -> bool:
"""Formats and sends the payload enforcing the HoneyWire JSON Schema."""
normalized_severity = self._normalize_severity(severity)
payload = {
"contract_version": "1.0",
"sensor_id": self.sensor_id,
"sensor_type": self.sensor_type,
"event_type": event_type,
"severity": normalized_severity,
"timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
"action_taken": action_taken,
"metadata": metadata
}
try:
resp = requests.post(f"{self.hub_endpoint}/api/v1/alert", headers=self.headers, json=payload, timeout=5)
resp.raise_for_status()
print(f"[+] Alert sent: {event_type} (Severity: {normalized_severity})")
return True
except requests.exceptions.RequestException as e:
print(f"[-] Event report failed: {e}")
return False
def _run_test_mode(self):
"""Used by CI/CD to verify sensor works and exits cleanly."""
print("🛠️ TEST MODE ACTIVE: Sending synthetic payload...")
success = self.report_event(
event_type="test_mode_synthetic_alert",
severity="info",
metadata={"test_message": "Automated CI/CD check."},
action_taken="ignored"
)
if success:
print("✅ Test mode complete. Exiting gracefully.")
sys.exit(0)
else:
print("❌ Test mode failed to contact Hub.")
sys.exit(1)
@abstractmethod
async def monitor(self):
"""The specific sensor logic to be implemented by the creator."""
pass
async def start(self):
"""Initializes the sensor, runs background threads, and starts the async monitor."""
self._sync_hub_version()
if self.test_mode:
self._run_test_mode()
# Start heartbeat in a standard daemon thread (avoids blocking the async loop)
threading.Thread(target=self._heartbeat_loop, daemon=True).start()
# Await the creator's async logic
await self.monitor()
+7
View File
@@ -0,0 +1,7 @@
from setuptools import setup, find_packages
setup(
name='honeywire',
version='1.0.0',
py_modules=find_packages(),
)

Before

Width:  |  Height:  |  Size: 368 KiB

After

Width:  |  Height:  |  Size: 368 KiB

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

+29
View File
@@ -0,0 +1,29 @@
# ==========================================
# HONEYWIRE SENSOR: TCP TARPIT
# ==========================================
# --- CORE ECOSYSTEM SETTINGS (Required) ---
# Where is the Hub located? (Use your Hub's IP if on a different machine)
HW_HUB_ENDPOINT=http://127.0.0.1:8080
# Must match the Hub's HW_HUB_KEY
HW_HUB_KEY=super_secret_key_123
# Unique name for this specific sensor
HW_SENSOR_ID=ubuntu-vps-01
# Alert Severity: 1-5 OR info|low|medium|high|critical
HW_SEVERITY=high
# --- SENSOR SPECIFIC SETTINGS ---
# The fake ports this agent will listen on
HW_DECOY_PORTS=21,2222,3306
# Modes: 'echo' (repeat data), 'hold' (do nothing, keep open), 'close' (drop immediately)
HW_TARPIT_MODE=hold
# The fake service banner.
# Example for SSH: SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.1\r\n
# Example for FTP: 220 (vsFTPd 3.0.3)\r\n
HW_TARPIT_BANNER=SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.1\r\n
+36
View File
@@ -0,0 +1,36 @@
# --- Stage 1: Builder ---
FROM python:3.11-slim AS builder
WORKDIR /app
# 1. Install git (Required for pip to pull the HoneyWire SDK from GitHub)
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
# 2. Create a virtual environment
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# 3. Copy requirements and install
# (This fetches requests, aiohttp, and the SDK directly into the venv)
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir -r requirements.txt
# --- Stage 2: Distroless (Production) ---
FROM gcr.io/distroless/python3-debian12
WORKDIR /app
# 1. Copy the compiled virtual environment from the builder
COPY --from=builder /opt/venv /opt/venv
# 2. Set environment variables so Python knows where the packages are
ENV PYTHONPATH="/opt/venv/lib/python3.11/site-packages"
ENV PATH="/opt/venv/bin:$PATH"
# Ensures Python print() statements go straight to Docker logs without delay
ENV PYTHONUNBUFFERED=1
# 3. Copy your specific sensor script
COPY TcpTripWire.py .
# Notice: We intentionally do NOT use a nonroot user here.
# We remain 'root' to allow binding to privileged decoy ports (e.g., Port 22, 80).
CMD ["TcpTripWire.py"]
+70
View File
@@ -0,0 +1,70 @@
# HoneyWire Official Sensor: TCP TripWire 🕸️
The TCP TripWire is a high-fidelity, low-interaction honeypot designed to detect network reconnaissance and brute-force attempts. It acts as a "Tarpit," binding to decoy ports and intentionally stalling attackers to waste their time while silently extracting their IP and payload data to report to the HoneyWire Hub.
## Features
* **Zero-Setup SDK Integration:** Natively built on the HoneyWire Python SDK.
* **Tarpit Modes:** Supports `hold` (silent stall), `echo` (repeat data back), or `close` (immediate drop).
* **Forensic Capture:** Safely buffers up to 10 lines of payload data without risking memory exhaustion.
* **Distroless Container:** Runs as a hardened, Distroless Docker image to prevent container breakouts.
## Configuration
All configuration is handled via Environment Variables. Copy the `.env.example` file to `.env` before running.
### Core Ecosystem Variables (Required)
| Variable | Description | Example |
|---|---|---|
| `HW_HUB_ENDPOINT` | The URL of your central HoneyWire Hub. | `http://192.168.1.100:8080` |
| `HW_HUB_KEY` | The shared secret API key to authenticate with the Hub. | `super_secret_key_123` |
| `HW_SENSOR_ID` | A unique identifier for this specific trap. | `dmz-ssh-tarpit-01` |
### Sensor-Specific Variables
| Variable | Description | Default |
|---|---|---|
| `HW_DECOY_PORTS` | Comma-separated list of TCP ports to monitor. | `2222,3306` |
| `HW_TARPIT_MODE` | The behavior of the trap: `hold`, `echo`, or `close`. | `hold` |
| `HW_SEVERITY` | Alert severity sent to the Hub (`info` to `critical`). | `high` |
| `HW_TARPIT_BANNER` | (Optional) A fake service banner to send on connect. | `SSH-2.0-OpenSSH_8.2p1` |
## Tarpit Modes Explained
* **`hold` (Default):** The sensor accepts the connection but sends nothing. It holds the TCP socket open as long as possible, draining the attacker's resources and slowing down automated scanners like Nmap.
* **`echo`:** The sensor acts as an echo server, repeating whatever the attacker sends back to them. Useful for confusing automated scripts.
* **`close`:** The sensor logs the connection, captures the initial payload, and forcefully closes the socket.
## Deployment
The easiest way to deploy this sensor is via Docker Compose. Note that if you intend to bind to privileged ports (any port under 1024, like Port 22 or 80), the container must run as `root` (which is the default).
### 1. Using Docker Compose
Create a `docker-compose.yml` file:
```yaml
services:
tcp-tripwire:
image: ghcr.io/andreicscs/honeywire-tcptripwire:latest
container_name: hw-tcp-tripwire
restart: unless-stopped
# CRITICAL: Binds directly to the host machine's network.
# 1. Preserves the real source IP of the attacker.
# 2. Automatically exposes whatever ports are set in HW_DECOY_PORTS.
network_mode: "host"
env_file:
- .env
```
Run it in the background:
```Bash
docker-compose up -d
```
### 2. Testing Locally (Build from Source)
If you are developing or testing locally:
```Bash
docker build -t honeywire-tcptripwire .
docker run --rm --network host --env-file .env honeywire-tcptripwire
```
## Security Note
This sensor uses asyncio with strict memory limits (50KB or 10 lines per connection) and a global semaphore to limit concurrent connections. This ensures the sensor cannot be easily DOS'd or used to exhaust the host machine's File Descriptors.
+131
View File
@@ -0,0 +1,131 @@
"""
HoneyWire Tarpit Agent (Standard Port Tripwire)
Sensor type: tarpit
Custom Environment variables:
DECOY_PORTS Comma-separated ports (default: 2222,3306)
TARPIT_MODE hold | echo | close (default: hold)
"""
import asyncio
import os
import sys
import time
# Import the SDK
from honeywire import HoneyWireSensor
class TcpTarpitSensor(HoneyWireSensor):
def __init__(self):
# Initialize the base class with the sensor type
super().__init__(sensor_type="tarpit")
# Sensor-specific configuration
self.tarpit_mode = os.getenv("HW_TARPIT_MODE", "hold").lower()
raw_ports = os.getenv("HW_DECOY_PORTS", "2222,3306")
self.decoy_ports = [int(p.strip()) for p in raw_ports.split(",") if p.strip()]
raw_banner = os.getenv("HW_TARPIT_BANNER", "")
self.tarpit_banner = raw_banner.encode("utf-8").decode("unicode_escape").encode("utf-8") if raw_banner else b""
self.max_bytes = 1024 * 50
self.max_lines = 10
self.max_duration = 3600
self.semaphore = asyncio.Semaphore(1000)
async def handle_client(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, port: int) -> None:
addr = writer.get_extra_info("peername")
source_ip = addr[0] if addr else "Unknown"
start_time = time.time()
payload_data = []
total_bytes = 0
try:
if self.tarpit_banner and self.tarpit_mode != "close":
writer.write(self.tarpit_banner)
await writer.drain()
if self.tarpit_mode != "close":
while total_bytes < self.max_bytes and (time.time() - start_time) < self.max_duration:
try:
timeout = 300.0
data = await asyncio.wait_for(reader.read(1024), timeout=timeout)
if not data: break
decoded = data.decode("utf-8", errors="replace").strip()
if decoded and len(payload_data) < self.max_lines:
payload_data.append(decoded)
total_bytes += len(data)
if self.tarpit_mode == "echo":
writer.write(data)
await writer.drain()
await asyncio.sleep(0.5)
except asyncio.TimeoutError:
if not writer.is_closing():
writer.write(b"\0")
await writer.drain()
continue
except Exception:
pass
finally:
try:
writer.close()
await writer.wait_closed()
except Exception:
pass
duration = time.time() - start_time
metadata = {
"source_ip": source_ip,
"target_port": port,
"duration_sec": round(duration, 2),
"payload": payload_data
}
# Send event!
# Use asyncio.to_thread because report_event uses blocking requests
await asyncio.to_thread(
self.report_event,
event_type="tcp_connection",
severity=self.severity,
metadata=metadata,
action_taken=self.tarpit_mode
)
async def connection_wrapper(self, reader, writer, port):
async with self.semaphore:
await self.handle_client(reader, writer, port)
async def monitor(self):
"""This is the required method from the SDK."""
print(f"[*] HoneyWire Agent | Mode: {self.tarpit_mode.upper()} | Severity: {self.severity}")
servers = []
for port in self.decoy_ports:
server = await asyncio.start_server(
lambda r, w, p=port: self.connection_wrapper(r, w, p),
"0.0.0.0",
port
)
servers.append(server.serve_forever())
print(f"[+] Tarpit listening on port {port}")
await asyncio.gather(*servers)
if __name__ == "__main__":
sensor = TcpTarpitSensor()
try:
# SDK's start() handles heartbeat, version sync, and calls monitor()
asyncio.run(sensor.start())
except KeyboardInterrupt:
print("\n[*] Shutting down HoneyWire Tarpit...")
@@ -0,0 +1,14 @@
services:
tcp-tripwire:
# Use 'build: .' if testing locally, or pull from a registry later
build: .
container_name: hw-tcp-tripwire
restart: unless-stopped
# CRITICAL: Binds directly to the host machine's network.
# 1. Preserves the real source IP of the attacker.
# 2. Automatically exposes whatever ports are set in HW_DECOY_PORTS.
network_mode: "host"
env_file:
- .env
@@ -0,0 +1,6 @@
# requirements.txt
aiohttp==3.9.3
requests==2.31.0
# Install the official HoneyWire SDK directly from the main branch
git+https://github.com/andreicscs/HoneyWire.git@main#subdirectory=SDKs/python-honeywire
@@ -0,0 +1,15 @@
# ==========================================
# HONEYWIRE SENSOR CONFIGURATION
# ==========================================
# --- REQUIRED ECOSYSTEM SETTINGS ---
HW_HUB_ENDPOINT=http://192.168.1.100:8080
HW_HUB_KEY=super_secret_key_123
HW_SENSOR_ID=my-custom-sensor-01
# --- CUSTOM SENSOR SETTINGS ---
# Alert Severity: 1-5 OR info|low|medium|high|critical
HW_SEVERITY=high
# Add your custom variables here:
# HW_TARGET_PATH=/tmp/honey
@@ -0,0 +1,32 @@
# --- Stage 1: Builder ---
FROM python:3.11-slim AS builder
WORKDIR /app
# Install git (Required to fetch the SDK from GitHub)
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
# Create virtual environment
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Install requirements (This pulls the SDK + your custom libraries)
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir -r requirements.txt
# --- Stage 2: Distroless (Production) ---
FROM gcr.io/distroless/python3-debian12
WORKDIR /app
# Copy the compiled virtual environment from the builder
COPY --from=builder /opt/venv /opt/venv
ENV PYTHONPATH="/opt/venv/lib/python3.11/site-packages"
ENV PATH="/opt/venv/bin:$PATH"
ENV PYTHONUNBUFFERED=1
# Copy the custom sensor script
COPY sensor.py .
# Run the sensor
CMD ["sensor.py"]
+26
View File
@@ -0,0 +1,26 @@
# HoneyWire Python Sensor Template
Welcome to the HoneyWire ecosystem! This template contains everything you need to build a custom, Dockerized security sensor that natively reports to the HoneyWire Hub.
## How to Build Your Sensor
1. **Copy this folder** and rename it to your sensor's name (e.g., `ssh_watcher`).
2. **Write your logic** inside `sensor.py`. The file is heavily commented and shows you exactly where to put your code.
3. **Add dependencies**: If your script needs extra Python libraries (like `scapy` or `paramiko`), add them to `requirements.txt`.
4. **Update `.env.example`**: Add any custom `HW_` variables your sensor needs.
## Testing Your Sensor
You can test your sensor locally by running:
```bash
# Copy the example environment file and fill in your Hub details
cp .env.example .env
# Build and run the Distroless Docker container
docker build -t honeywire-custom-sensor .
docker run --rm --env-file .env honeywire-custom-sensor
```
## CI/CD Requirement
To ensure your sensor works, our GitHub Actions will run it with HW_TEST_MODE=true. The base SDK handles this automatically, so you don't need to write any test logic! Just ensure you don't override the start() method in the base class.
@@ -0,0 +1,8 @@
# This single line installs the official HoneyWire SDK directly from GitHub.
# It automatically fetches required libraries like 'requests' and 'aiohttp'.
git+https://github.com/andreicscs/HoneyWire.git@main#subdirectory=SDKs/python-honeywire
# --- ADD YOUR SENSOR's SPECIFIC LIBRARIES BELOW ---
# Example:
# watchdog==4.0.0
# scapy==2.5.0
+60
View File
@@ -0,0 +1,60 @@
import os
import asyncio
import time
# Import the official HoneyWire SDK
from honeywire import HoneyWireSensor
class MyCustomSensor(HoneyWireSensor):
def __init__(self):
# Initialize the base class.
# Change 'custom' to whatever category fits your sensor (e.g., 'network', 'file', 'auth')
super().__init__(sensor_type="custom")
# Add any custom configuration from your .env file here
# Example: self.target_path = os.getenv("HW_TARGET_PATH", "/tmp/honey")
self.severity = os.getenv("HW_SEVERITY", "medium")
async def monitor(self):
"""
REQUIRED METHOD: This is the main loop of your sensor.
Write your detection logic here. Do NOT block the async loop.
"""
print(f"[*] Starting Custom Sensor | Severity: {self.severity}")
try:
while True:
# --- YOUR SENSOR LOGIC GOES HERE ---
# Example: Wait for an event...
await asyncio.sleep(60)
# Assume an attack happened!
print("[!] Attack detected! Gathering forensics...")
# Format your specific forensic data
metadata = {
"source_ip": "192.168.1.100", # Replace with actual data
"attack_type": "example_probe",
"raw_payload": "GET /etc/passwd HTTP/1.1"
}
# Send the alert to the Hub using the SDK's built-in method.
# Wrap it in asyncio.to_thread to prevent blocking your async monitor loop.
await asyncio.to_thread(
self.report_event,
event_type="custom_anomaly_detected",
severity=self.severity,
metadata=metadata,
action_taken="logged"
)
except asyncio.CancelledError:
print("[*] Monitor loop cancelled. Shutting down gracefully.")
if __name__ == "__main__":
sensor = MyCustomSensor()
try:
# The SDK's start() method handles connecting to the Hub and running your monitor()
asyncio.run(sensor.start())
except KeyboardInterrupt:
print("\n[*] Shutting down custom sensor...")