Compare commits

...

12 Commits

Author SHA1 Message Date
50a20734ff feat: split honeypot and dashboard into separate services on dedicated ports
Some checks failed
Build and Push Docker Image / build-and-push (push) Has been cancelled
- Honeypot listens on port 5010
- Dashboard listens on port 5123 at URL /
- Add app_dashboard.py as standalone FastAPI app
- Add start.sh to launch both uvicorn processes
- Fix dashboard_path computation to return "" at root (avoid double-slash URLs)
- Update Dockerfile, docker-compose.yaml, config.yaml, config.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 20:16:35 +01:00
Patrick Di Fazio
a0700d1960 Merge pull request #128 from BlessedRebuS/dev
Feat/release 1.2
2026-03-10 14:09:46 +01:00
Patrick Di Fazio
1b8dc53952 Merge pull request #127 from BlessedRebuS/feat/release-1.2
Feat/release 1.2
2026-03-10 11:31:18 +01:00
Lorenzo Venerandi
da3ffd64c9 feat: update installation section to quickstart in README.md 2026-03-10 11:27:04 +01:00
Lorenzo Venerandi
f1c89cc8e3 fix: correct link formatting for contributing section in README.md 2026-03-10 11:22:16 +01:00
Lorenzo Venerandi
2aab758e4b feat: update dashboard tab descriptions for consistency and clarity 2026-03-10 11:18:52 +01:00
Lorenzo Venerandi
e09d5436ee linted code 2026-03-10 11:07:55 +01:00
Lorenzo Venerandi
7d14e98860 feat: update Helm chart version to 1.2.0 in Chart.yaml, README.md, and values.yaml 2026-03-10 11:02:40 +01:00
Lorenzo Venerandi
da9170f7a0 feat: update dashboard documentation to reflect new tab organization and features 2026-03-10 11:01:32 +01:00
Lorenzo Venerandi
65b12d16bd feat: enhance dashboard documentation and add new images for improved visualization 2026-03-10 11:00:47 +01:00
Patrick Di Fazio
1faa891fde Merge pull request #119 from BlessedRebuS/dev
Feat/release 1.1.3
2026-03-04 15:31:58 +01:00
Lorenzo Venerandi
90a65dff6b Merge pull request #111 from BlessedRebuS/dev
Feat/release 1.1.0
2026-03-01 22:09:10 +01:00
23 changed files with 353 additions and 81 deletions

View File

@@ -15,16 +15,18 @@ RUN pip install --no-cache-dir --upgrade pip && \
COPY src/ /app/src/ COPY src/ /app/src/
COPY wordlists.json /app/ COPY wordlists.json /app/
COPY entrypoint.sh /app/ COPY entrypoint.sh /app/
COPY start.sh /app/
COPY config.yaml /app/ COPY config.yaml /app/
RUN useradd -m -u 1000 krawl && \ RUN useradd -m -u 1000 krawl && \
mkdir -p /app/logs /app/data /app/exports && \ mkdir -p /app/logs /app/data /app/exports && \
chown -R krawl:krawl /app && \ chown -R krawl:krawl /app && \
chmod +x /app/entrypoint.sh chmod +x /app/entrypoint.sh /app/start.sh
EXPOSE 5000 EXPOSE 5010
EXPOSE 5123
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1
ENTRYPOINT ["/app/entrypoint.sh"] ENTRYPOINT ["/app/entrypoint.sh"]
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "5000", "--app-dir", "src"] CMD ["/app/start.sh"]

View File

@@ -39,7 +39,7 @@
- [Demo](#demo) - [Demo](#demo)
- [What is Krawl?](#what-is-krawl) - [What is Krawl?](#what-is-krawl)
- [Krawl Dashboard](#krawl-dashboard) - [Krawl Dashboard](#krawl-dashboard)
- [Installation](#-installation) - [Quickstart](#quickstart)
- [Docker Run](#docker-run) - [Docker Run](#docker-run)
- [Docker Compose](#docker-compose) - [Docker Compose](#docker-compose)
- [Kubernetes](#kubernetes) - [Kubernetes](#kubernetes)
@@ -51,7 +51,7 @@
- [IP Reputation](#ip-reputation) - [IP Reputation](#ip-reputation)
- [Forward Server Header](#forward-server-header) - [Forward Server Header](#forward-server-header)
- [Additional Documentation](#additional-documentation) - [Additional Documentation](#additional-documentation)
- [Contributing](#-contributing) - [Contributing](#contributing)
## Demo ## Demo
Tip: crawl the `robots.txt` paths for additional fun Tip: crawl the `robots.txt` paths for additional fun
@@ -88,24 +88,29 @@ You can easily expose Krawl alongside your other services to shield them from we
Krawl provides a comprehensive dashboard, accessible at a **random secret path** generated at startup or at a **custom path** configured via `KRAWL_DASHBOARD_SECRET_PATH`. This keeps the dashboard hidden from attackers scanning your honeypot. Krawl provides a comprehensive dashboard, accessible at a **random secret path** generated at startup or at a **custom path** configured via `KRAWL_DASHBOARD_SECRET_PATH`. This keeps the dashboard hidden from attackers scanning your honeypot.
The dashboard is organized in three main tabs: The dashboard is organized in five tabs:
- **Overview** — High-level view of attack activity: an interactive map of IP origins, recent suspicious requests, and top IPs, User-Agents, and paths. - **Overview**: high-level view of attack activity: an interactive map of IP origins, recent suspicious requests, and top IPs, User-Agents, and paths.
![geoip](img/geoip_dashboard.png) ![geoip](img/geoip_dashboard.png)
- **Attacks** — Detailed breakdown of captured credentials, honeypot triggers, and detected attack types (SQLi, XSS, path traversal, etc.) with charts and tables. - **Attacks**: detailed breakdown of captured credentials, honeypot triggers, and detected attack types (SQLi, XSS, path traversal, etc.) with charts and tables.
![attack_types](img/attack_types.png) ![attack_types](img/attack_types.png)
- **IP Insight** — In-depth forensic view of a selected IP: geolocation, ISP/ASN info, reputation flags, behavioral timeline, attack type distribution, and full access history. - **IP Insight**: in-depth forensic view of a selected IP: geolocation, ISP/ASN info, reputation flags, behavioral timeline, attack type distribution, and full access history.
![ipinsight](img/ip_insight_dashboard.png) ![ipinsight](img/ip_insight_dashboard.png)
Additionally, after authenticating with the dashboard password, two protected tabs become available:
- **Tracked IPs**: maintain a watchlist of IP addresses you want to monitor over time.
- **IP Banlist**: manage IP bans, view detected attackers, and export the banlist in raw or IPTables format.
For more details, see the [Dashboard documentation](docs/dashboard.md). For more details, see the [Dashboard documentation](docs/dashboard.md).
## 🚀 Installation ## Quickstart
### Docker Run ### Docker Run
@@ -308,7 +313,7 @@ location / {
| [Wordlist](docs/wordlist.md) | Customize fake usernames, passwords, and directory listings | | [Wordlist](docs/wordlist.md) | Customize fake usernames, passwords, and directory listings |
| [Dashboard](docs/dashboard.md) | Access and explore the real-time monitoring dashboard | | [Dashboard](docs/dashboard.md) | Access and explore the real-time monitoring dashboard |
## 🤝 Contributing ## Contributing
Contributions welcome! Please: Contributions welcome! Please:
1. Fork the repository 1. Fork the repository

View File

@@ -1,7 +1,7 @@
# Krawl Honeypot Configuration # Krawl Honeypot Configuration
server: server:
port: 5000 port: 5010
delay: 100 # Response delay in milliseconds delay: 100 # Response delay in milliseconds
# manually set the server header, if null a random one will be used. # manually set the server header, if null a random one will be used.
@@ -20,10 +20,9 @@ canary:
token_tries: 10 token_tries: 10
dashboard: dashboard:
# if set to "null" this will Auto-generates random path if not set port: 5123
# can be set to "/dashboard" or similar <-- note this MUST include a forward slash # Set to empty string "" to serve dashboard at root "/" on its own dedicated port
# secret_path: super-secret-dashboard-path secret_path: ""
secret_path: null
# Password for accessing protected dashboard panels. # Password for accessing protected dashboard panels.
# If null, a random password will be generated and printed in the logs. # If null, a random password will be generated and printed in the logs.

View File

@@ -1,5 +1,4 @@
--- ---
# THIS IS FOR DEVELOPMENT PURPOSES
services: services:
krawl: krawl:
build: build:
@@ -7,12 +6,15 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
container_name: krawl-server container_name: krawl-server
ports: ports:
- "5000:5000" - "5010:5010" # Honeypot (crawler trap)
- "5123:5123" # Dashboard (public UI)
environment: environment:
- CONFIG_LOCATION=config.yaml - CONFIG_LOCATION=config.yaml
# Uncomment to set a custom dashboard password (auto-generated if not set) # Uncomment to set a custom dashboard password (auto-generated if not set)
# - KRAWL_DASHBOARD_PASSWORD=your-secret-password # - KRAWL_DASHBOARD_PASSWORD=your-secret-password
# set this to change timezone, alternatively mount /etc/timezone or /etc/localtime based on the time system management of the host environment # Override ports if needed
# - HONEYPOT_PORT=5010
# - DASHBOARD_PORT=5123
# - TZ=${TZ} # - TZ=${TZ}
volumes: volumes:
- ./wordlists.json:/app/wordlists.json:ro - ./wordlists.json:/app/wordlists.json:ro

View File

@@ -2,20 +2,182 @@
Access the dashboard at `http://<server-ip>:<port>/<dashboard-path>` Access the dashboard at `http://<server-ip>:<port>/<dashboard-path>`
The dashboard shows: The Krawl dashboard is a single-page application with **5 tabs**: Overview, Attacks, IP Insight, Tracked IPs, and IP Banlist. The last two tabs are only visible after authenticating with the dashboard password.
- Total and unique accesses
- Suspicious activity and attack detection
- Top IPs, paths, user-agents and GeoIP localization
- Real-time monitoring
The attackers' access to the honeypot endpoint and related suspicious activities (such as failed login attempts) are logged. ---
Krawl also implements a scoring system designed to distinguish between malicious and legitimate behavior on the website. ## Overview
![dashboard-1](../img/dashboard-1.png) The default landing page provides a high-level summary of all traffic and suspicious activity detected by Krawl.
The top IP Addresses is shown along with top paths and User Agents ### Stats Cards
![dashboard-2](../img/dashboard-2.png) Seven metric cards are displayed at the top:
![dashboard-3](../img/dashboard-3.png) - **Total Accesses** — total number of requests received
- **Unique IPs** — distinct IP addresses observed
- **Unique Paths** — distinct request paths
- **Suspicious Accesses** — requests flagged as suspicious
- **Honeypot Caught** — requests that hit honeypot endpoints
- **Credentials Captured** — login attempts captured by the honeypot
- **Unique Attackers** — distinct IPs classified as attackers
### Search
A real-time search bar lets you search across attacks, IPs, patterns, and locations. Results are loaded dynamically as you type.
### IP Origins Map
An interactive world map (powered by Leaflet) displays the geolocation of top IP addresses. You can filter by category:
- Attackers
- Bad Crawlers
- Good Crawlers
- Regular Users
- Unknown
The number of displayed IPs is configurable (top 10, 100, 1,000, or all).
![Overview — Stats and Map](../img/geoip_dashboard.png)
### Recent Suspicious Activity
A table showing the last 10 suspicious requests with IP address, path, user-agent, and timestamp. Each entry provides actions to view the raw HTTP request or inspect the IP in detail.
### Top IP Addresses
A paginated, sortable table ranking IPs by access count. Each IP shows its category badge and can be clicked to expand inline details or open the IP Insight tab.
### Top Paths
A paginated table of the most accessed HTTP paths and their request counts.
### Top User-Agents
A paginated table of the most common user-agent strings with their frequency.
![Overview — Tables](../img/overview_tables_dashboard.png)
---
## Attacks
The Attacks tab focuses on detected malicious activity, attack patterns, and captured credentials.
### Attackers by Total Requests
A paginated table listing all detected attackers ranked by total requests. Columns include IP, total requests, first seen, last seen, and location. Sortable by multiple fields.
![Attacks — Attackers and Credentials](../img/top_attackers_dashboard.png)
### Captured Credentials
A table of usernames and passwords captured from honeypot login forms, with timestamps. Useful for analyzing common credential stuffing patterns.
### Honeypot Triggers by IP
Shows which IPs accessed honeypot endpoints and how many times, sorted by trigger count.
### Detected Attack Types
A detailed table of individual attack detections showing IP, path, attack type classifications, user-agent, and timestamp. Each entry can be expanded to view the raw HTTP request.
### Most Recurring Attack Types
A Chart.js visualization showing the frequency distribution of detected attack categories (e.g., SQL injection, path traversal, XSS).
### Most Recurring Attack Patterns
A paginated table of specific attack patterns and their occurrence counts across all traffic.
![Attacks — Attack Types and Patterns](../img/attack_types_dashboard.png)
---
## IP Insight
The IP Insight tab provides a deep-dive view for a single IP address. It is activated by clicking "Inspect IP" from any table in the dashboard.
### IP Information Card
Displays comprehensive details about the selected IP:
- **Activity** — total requests, first seen, last seen, last analysis timestamp
- **Geo & Network** — location, region, timezone, ISP, ASN, reverse DNS
- **Category** — classification badge (Attacker, Good Crawler, Bad Crawler, Regular User, Unknown)
### Ban & Track Actions
When authenticated, admin actions are available:
- **Ban/Unban** — immediately add or remove the IP from the banlist
- **Track/Untrack** — add the IP to your watchlist for ongoing monitoring
### Blocklist Memberships
Shows which threat intelligence blocklists the IP appears on, providing external reputation context.
### Access Logs
A filtered view of all requests made by this specific IP, with full request details.
![IP Insight — Detail View](../img/ip_insight_dashboard.png)
---
## Tracked IPs
> Requires authentication with the dashboard password.
The Tracked IPs tab lets you maintain a watchlist of IP addresses you want to monitor over time.
### Track New IP
A form to add any IP address to your tracking list for ongoing observation.
### Currently Tracked IPs
A paginated table of all manually tracked IPs, with the option to untrack each one.
![Tracked IPs](../img/tracked_ips_dashboard.png)
---
## IP Banlist
> Requires authentication with the dashboard password.
The IP Banlist tab provides tools for managing IP bans. Bans are exported every 5 minutes.
### Force Ban IP
A form to immediately ban any IP address by entering it manually.
### Detected Attackers
A paginated list of all IPs detected as attackers, with quick-ban actions for each entry.
![IP Banlist — Detected](../img/banlist_attackers_dashboard.png)
### Active Ban Overrides
A table of currently active manual ban overrides, with options to unban or reset the override status for each IP.
![IP Banlist — Overrides](../img/banlist_overrides_dashboard.png)
### Export Banlist
A dropdown menu to download the current banlist in two formats:
- **Raw IPs List** — plain text, one IP per line
- **IPTables Rules** — ready-to-use firewall rules
---
## Authentication
The dashboard uses session-based authentication with secure HTTP-only cookies. Protected features (Tracked IPs, IP Banlist, ban/track actions) require entering the dashboard password. The login includes brute-force protection with IP-based rate limiting and exponential backoff.
Click the lock icon in the top-right corner of the navigation bar to authenticate or log out.
![Authentication Modal](../img/auth_prompt.png)

View File

@@ -2,8 +2,8 @@ apiVersion: v2
name: krawl-chart name: krawl-chart
description: A Helm chart for Krawl honeypot server description: A Helm chart for Krawl honeypot server
type: application type: application
version: 1.1.7 version: 1.2.0
appVersion: 1.1.7 appVersion: 1.2.0
keywords: keywords:
- honeypot - honeypot
- security - security

View File

@@ -14,7 +14,7 @@ A Helm chart for deploying the Krawl honeypot application on Kubernetes.
```bash ```bash
helm install krawl oci://ghcr.io/blessedrebus/krawl-chart \ helm install krawl oci://ghcr.io/blessedrebus/krawl-chart \
--version 1.1.3 \ --version 1.2.0 \
--namespace krawl-system \ --namespace krawl-system \
--create-namespace \ --create-namespace \
-f values.yaml # optional -f values.yaml # optional
@@ -170,7 +170,7 @@ kubectl get secret krawl-server -n krawl-system \
You can override individual values with `--set` without a values file: You can override individual values with `--set` without a values file:
```bash ```bash
helm install krawl oci://ghcr.io/blessedrebus/krawl-chart --version 1.1.3 \ helm install krawl oci://ghcr.io/blessedrebus/krawl-chart --version 1.2.0 \
--set ingress.hosts[0].host=honeypot.example.com \ --set ingress.hosts[0].host=honeypot.example.com \
--set config.canary.token_url=https://canarytokens.com/your-token --set config.canary.token_url=https://canarytokens.com/your-token
``` ```
@@ -178,7 +178,7 @@ helm install krawl oci://ghcr.io/blessedrebus/krawl-chart --version 1.1.3 \
## Upgrading ## Upgrading
```bash ```bash
helm upgrade krawl oci://ghcr.io/blessedrebus/krawl-chart --version 1.1.3 -f values.yaml helm upgrade krawl oci://ghcr.io/blessedrebus/krawl-chart --version 1.2.0 -f values.yaml
``` ```
## Uninstalling ## Uninstalling

View File

@@ -3,7 +3,7 @@ replicaCount: 1
image: image:
repository: ghcr.io/blessedrebus/krawl repository: ghcr.io/blessedrebus/krawl
pullPolicy: Always pullPolicy: Always
tag: "1.1.3" tag: "1.2.0"
imagePullSecrets: [] imagePullSecrets: []
nameOverride: "krawl" nameOverride: "krawl"

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

BIN
img/auth_prompt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 343 KiB

After

Width:  |  Height:  |  Size: 808 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

View File

@@ -10,7 +10,6 @@ import os
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from fastapi import FastAPI, Request, Response from fastapi import FastAPI, Request, Response
from fastapi.staticfiles import StaticFiles
from config import get_config from config import get_config
from tracker import AccessTracker, set_tracker from tracker import AccessTracker, set_tracker
@@ -72,21 +71,7 @@ async def lifespan(app: FastAPI):
tasks_master = get_tasksmaster() tasks_master = get_tasksmaster()
tasks_master.run_scheduled_tasks() tasks_master.run_scheduled_tasks()
password_line = "" app_logger.info(f"Starting honeypot deception server on port {config.port}...")
if config.dashboard_password_generated:
password_line = (
f"\n\nDASHBOARD PASSWORD (auto-generated)\n{config.dashboard_password}"
)
banner = f"""
============================================================
DASHBOARD AVAILABLE AT
{config.dashboard_secret_path}{password_line}
============================================================
"""
app_logger.info(banner)
app_logger.info(f"Starting deception server on port {config.port}...")
if config.canary_token_url: if config.canary_token_url:
app_logger.info( app_logger.info(
f"Canary token will appear after {config.canary_token_tries} tries" f"Canary token will appear after {config.canary_token_tries} tries"
@@ -155,29 +140,10 @@ def create_app() -> FastAPI:
access_logger.info(f"[{method}] {client_ip} - {path} - {status}") access_logger.info(f"[{method}] {client_ip} - {path} - {status}")
return response return response
# Mount static files for the dashboard
config = get_config()
secret = config.dashboard_secret_path.lstrip("/")
static_dir = os.path.join(os.path.dirname(__file__), "templates", "static")
application.mount(
f"/{secret}/static",
StaticFiles(directory=static_dir),
name="dashboard-static",
)
# Import and include routers # Import and include routers
from routes.honeypot import router as honeypot_router from routes.honeypot import router as honeypot_router
from routes.api import router as api_router
from routes.dashboard import router as dashboard_router
from routes.htmx import router as htmx_router
# Dashboard/API/HTMX routes (prefixed with secret path, before honeypot catch-all) # Honeypot routes (catch-all)
dashboard_prefix = f"/{secret}"
application.include_router(dashboard_router, prefix=dashboard_prefix)
application.include_router(api_router, prefix=dashboard_prefix)
application.include_router(htmx_router, prefix=dashboard_prefix)
# Honeypot routes (catch-all must be last)
application.include_router(honeypot_router) application.include_router(honeypot_router)
return application return application

94
src/app_dashboard.py Normal file
View File

@@ -0,0 +1,94 @@
#!/usr/bin/env python3
"""
FastAPI application for the Krawl dashboard.
Runs on a dedicated port (default 5123) and serves the dashboard at /.
"""
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request, Response
from fastapi.staticfiles import StaticFiles
from config import get_config
from database import initialize_database
from tasks_master import get_tasksmaster
from logger import initialize_logging, get_app_logger
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Dashboard application startup and shutdown lifecycle."""
config = get_config()
# Force dashboard path to empty (root) for this dedicated app
config.dashboard_secret_path = ""
initialize_logging(log_level=config.log_level)
app_logger = get_app_logger()
try:
app_logger.info(f"Initializing database at: {config.database_path}")
initialize_database(config.database_path)
app_logger.info("Database ready")
except Exception as e:
app_logger.warning(
f"Database initialization failed: {e}. Continuing with in-memory only."
)
app.state.config = config
# Start scheduled tasks (handles cache warming, backups, etc.)
tasks_master = get_tasksmaster()
tasks_master.run_scheduled_tasks()
password_line = ""
if config.dashboard_password_generated:
password_line = (
f"\n\nDASHBOARD PASSWORD (auto-generated)\n{config.dashboard_password}"
)
banner = f"""
============================================================
DASHBOARD AVAILABLE AT http://0.0.0.0:{config.dashboard_port}/{password_line}
============================================================
"""
app_logger.info(banner)
app_logger.info(f"Starting dashboard server on port {config.dashboard_port}...")
yield
app_logger.info("Dashboard server shutting down...")
def create_dashboard_app() -> FastAPI:
"""Create and configure the dashboard FastAPI application."""
application = FastAPI(
docs_url=None,
redoc_url=None,
openapi_url=None,
lifespan=lifespan,
)
# Mount static files at /static
static_dir = os.path.join(os.path.dirname(__file__), "templates", "static")
application.mount(
"/static",
StaticFiles(directory=static_dir),
name="dashboard-static",
)
from routes.api import router as api_router
from routes.dashboard import router as dashboard_router
from routes.htmx import router as htmx_router
# All dashboard routes at root prefix
application.include_router(dashboard_router, prefix="")
application.include_router(api_router, prefix="")
application.include_router(htmx_router, prefix="")
return application
app = create_dashboard_app()

View File

@@ -18,7 +18,8 @@ import yaml
class Config: class Config:
"""Configuration class for the deception server""" """Configuration class for the deception server"""
port: int = 5000 port: int = 5010
dashboard_port: int = 5123
delay: int = 100 # milliseconds delay: int = 100 # milliseconds
server_header: str = "" server_header: str = ""
links_length_range: Tuple[int, int] = (5, 15) links_length_range: Tuple[int, int] = (5, 15)
@@ -173,6 +174,8 @@ class Config:
dashboard_path = dashboard.get("secret_path") dashboard_path = dashboard.get("secret_path")
if dashboard_path is None: if dashboard_path is None:
dashboard_path = f"/{os.urandom(16).hex()}" dashboard_path = f"/{os.urandom(16).hex()}"
elif dashboard_path == "":
dashboard_path = ""
else: else:
# ensure the dashboard path starts with a / # ensure the dashboard path starts with a /
if dashboard_path[:1] != "/": if dashboard_path[:1] != "/":
@@ -186,7 +189,8 @@ class Config:
dashboard_password_generated = True dashboard_password_generated = True
return cls( return cls(
port=server.get("port", 5000), port=server.get("port", 5010),
dashboard_port=dashboard.get("port", 5123),
delay=server.get("delay", 100), delay=server.get("delay", 100),
server_header=server.get("server_header", ""), server_header=server.get("server_header", ""),
links_length_range=( links_length_range=(

View File

@@ -254,7 +254,13 @@ async def all_ips(
page_size = min(max(1, page_size), 10000) page_size = min(max(1, page_size), 10000)
# Serve from cache on default map request (top 100 IPs) # Serve from cache on default map request (top 100 IPs)
if page == 1 and page_size == 100 and sort_by == "total_requests" and sort_order == "desc" and is_warm(): if (
page == 1
and page_size == 100
and sort_by == "total_requests"
and sort_order == "desc"
and is_warm()
):
cached = get_cached("map_ips") cached = get_cached("map_ips")
if cached: if cached:
return JSONResponse(content=cached, headers=_no_cache_headers()) return JSONResponse(content=cached, headers=_no_cache_headers())

View File

@@ -19,7 +19,8 @@ router = APIRouter()
@router.get("/") @router.get("/")
async def dashboard_page(request: Request): async def dashboard_page(request: Request):
config = request.app.state.config config = request.app.state.config
dashboard_path = "/" + config.dashboard_secret_path.lstrip("/") _path = config.dashboard_secret_path.strip("/")
dashboard_path = f"/{_path}" if _path else ""
# Serve from pre-computed cache when available, fall back to live queries # Serve from pre-computed cache when available, fall back to live queries
if is_warm(): if is_warm():
@@ -50,7 +51,8 @@ async def ip_page(ip_address: str, request: Request):
try: try:
stats = db.get_ip_stats_by_ip(ip_address) stats = db.get_ip_stats_by_ip(ip_address)
config = request.app.state.config config = request.app.state.config
dashboard_path = "/" + config.dashboard_secret_path.lstrip("/") _path = config.dashboard_secret_path.strip("/")
dashboard_path = f"/{_path}" if _path else ""
if stats: if stats:
# Transform fields for template compatibility # Transform fields for template compatibility

View File

@@ -17,7 +17,8 @@ router = APIRouter()
def _dashboard_path(request: Request) -> str: def _dashboard_path(request: Request) -> str:
config = request.app.state.config config = request.app.state.config
return "/" + config.dashboard_secret_path.lstrip("/") path = config.dashboard_secret_path.strip("/")
return f"/{path}" if path else ""
# ── Honeypot Triggers ──────────────────────────────────────────────── # ── Honeypot Triggers ────────────────────────────────────────────────
@@ -60,7 +61,11 @@ async def htmx_top_ips(
sort_order: str = Query("desc"), sort_order: str = Query("desc"),
): ):
# Serve from cache on default first-page request # Serve from cache on default first-page request
cached = get_cached("top_ips") if (page == 1 and sort_by == "count" and sort_order == "desc" and is_warm()) else None cached = (
get_cached("top_ips")
if (page == 1 and sort_by == "count" and sort_order == "desc" and is_warm())
else None
)
if cached: if cached:
result = cached result = cached
else: else:
@@ -93,7 +98,11 @@ async def htmx_top_paths(
sort_by: str = Query("count"), sort_by: str = Query("count"),
sort_order: str = Query("desc"), sort_order: str = Query("desc"),
): ):
cached = get_cached("top_paths") if (page == 1 and sort_by == "count" and sort_order == "desc" and is_warm()) else None cached = (
get_cached("top_paths")
if (page == 1 and sort_by == "count" and sort_order == "desc" and is_warm())
else None
)
if cached: if cached:
result = cached result = cached
else: else:
@@ -126,7 +135,11 @@ async def htmx_top_ua(
sort_by: str = Query("count"), sort_by: str = Query("count"),
sort_order: str = Query("desc"), sort_order: str = Query("desc"),
): ):
cached = get_cached("top_ua") if (page == 1 and sort_by == "count" and sort_order == "desc" and is_warm()) else None cached = (
get_cached("top_ua")
if (page == 1 and sort_by == "count" and sort_order == "desc" and is_warm())
else None
)
if cached: if cached:
result = cached result = cached
else: else:

17
start.sh Executable file
View File

@@ -0,0 +1,17 @@
#!/bin/sh
set -e
HONEYPOT_PORT=${HONEYPOT_PORT:-5010}
DASHBOARD_PORT=${DASHBOARD_PORT:-5123}
echo "Starting Krawl honeypot on port $HONEYPOT_PORT..."
uvicorn app:app --host 0.0.0.0 --port "$HONEYPOT_PORT" --app-dir src &
HONEYPOT_PID=$!
echo "Starting Krawl dashboard on port $DASHBOARD_PORT..."
uvicorn app_dashboard:app --host 0.0.0.0 --port "$DASHBOARD_PORT" --app-dir src &
DASHBOARD_PID=$!
# Wait for either process to exit; if one dies, kill the other
wait -n 2>/dev/null || wait
kill $HONEYPOT_PID $DASHBOARD_PID 2>/dev/null || true