fix: stabilize tests with synchronous DB init and handle Bandit security findings

This commit is contained in:
2026-04-09 01:33:15 -04:00
parent 8c7ec2953e
commit 13f3d15a36
48 changed files with 121 additions and 88 deletions

View File

@@ -0,0 +1,4 @@
# file: /home/anti/Tools/DECNET/decnet/cli.py
# hypothesis_version: 6.151.11
[8000, ',', ', ', '--all', '--api', '--api-port', '--archetype', '--config', '--deckies', '--decky', '--distro', '--dry-run', '--emit-syslog', '--host', '--id', '--interface', '--ip-start', '--ipvlan', '--log-file', '--log-target', '--min-deckies', '--mode', '--mutate-interval', '--no-cache', '--output', '--port', '--randomize-distros', '--randomize-services', '--services', '--subnet', '--watch', '--web-port', '-a', '-c', '-d', '-f', '-i', '-m', '-n', '-o', '-w', '/index.html', 'Available Services', 'Default Services', 'Description', 'Display Name', 'Docker Image', 'Image', 'Machine Archetypes', 'Name', 'Ports', 'Slug', 'archetypes', 'bold cyan', 'correlate', 'decnet', 'decnet.cli', 'decnet.log', 'decnet.web.api:app', 'decnet_web', 'dim', 'dist', 'distros', 'green', 'json', 'linux', 'mutate', 'services', 'swarm', 'syslog', 'table', 'unihost', 'uvicorn', 'web']

View File

@@ -0,0 +1,4 @@
# file: /home/anti/Tools/DECNET/decnet/web/api.py
# hypothesis_version: 6.151.11
[0.5, 400, 404, 500, 512, 1000, 1024, '*', '/api/v1/auth/login', '/api/v1/deckies', '/api/v1/logs', '/api/v1/stats', '/api/v1/stream', '1.0.0', 'Authorization', 'Bearer', 'Bearer ', 'Decky not found', 'No active deployment', 'WWW-Authenticate', 'access_token', 'admin', 'bearer', 'data', 'decnet.web.api', 'histogram', 'id', 'lastEventId', 'limit', 'logs', 'message', 'must_change_password', 'offset', 'password_hash', 'role', 'stats', 'text/event-stream', 'token', 'token_type', 'total', 'type', 'unihost', 'username', 'uuid']

View File

@@ -0,0 +1,4 @@
# file: /home/anti/Tools/DECNET/decnet/web/sqlite_repository.py
# hypothesis_version: 6.151.11
[' AND ', ' WHERE ', ':', '[^a-zA-Z0-9_]', 'active_deckies', 'attacker', 'attacker-ip', 'attacker_ip', 'bucket_time', 'count', 'decky', 'decnet.db', 'deployed_deckies', 'event', 'event_type', 'fields', 'id > ?', 'max_id', 'msg', 'must_change_password', 'password_hash', 'raw_line', 'role', 'service', 'time', 'timestamp', 'timestamp <= ?', 'timestamp >= ?', 'total', 'total_logs', 'unique_attackers', 'username', 'uuid']

View File

@@ -0,0 +1,4 @@
# file: /home/anti/Tools/DECNET/decnet/env.py
# hypothesis_version: 6.151.11
['.env', '.env.local', '0.0.0.0', '8000', '8080', 'DECNET_ADMIN_USER', 'DECNET_API_HOST', 'DECNET_API_PORT', 'DECNET_JWT_SECRET', 'DECNET_WEB_HOST', 'DECNET_WEB_PORT', 'admin']

View File

@@ -0,0 +1,4 @@
# file: /home/anti/Tools/DECNET/decnet/web/api.py
# hypothesis_version: 6.151.11
[400, 404, 500, 512, 1000, 1024, '*', '/api/v1/auth/login', '/api/v1/deckies', '/api/v1/logs', '/api/v1/stats', '/api/v1/stream', '1.0.0', 'Authorization', 'Bearer', 'Bearer ', 'Decky not found', 'No active deployment', 'WWW-Authenticate', 'access_token', 'admin', 'bearer', 'data', 'decnet.web.api', 'histogram', 'id', 'lastEventId', 'limit', 'logs', 'message', 'must_change_password', 'offset', 'password_hash', 'role', 'stats', 'text/event-stream', 'token', 'token_type', 'total', 'type', 'unihost', 'username', 'uuid']

View File

@@ -0,0 +1,4 @@
# file: /home/anti/Tools/DECNET/decnet/web/api.py
# hypothesis_version: 6.151.11
[400, 404, 500, 512, 1000, 1024, '*', '/api/v1/auth/login', '/api/v1/deckies', '/api/v1/logs', '/api/v1/stats', '/api/v1/stream', '1.0.0', 'Authorization', 'Bearer', 'Bearer ', 'Decky not found', 'No active deployment', 'WWW-Authenticate', 'access_token', 'admin', 'bearer', 'data', 'decnet.web.api', 'histogram', 'id', 'lastEventId', 'limit', 'logs', 'message', 'must_change_password', 'offset', 'password_hash', 'role', 'stats', 'text/event-stream', 'token', 'token_type', 'total', 'type', 'unihost', 'username', 'uuid']

View File

@@ -0,0 +1,4 @@
# file: /home/anti/Tools/DECNET/decnet/distros.py
# hypothesis_version: 6.151.11
['Alpine Linux 3.19', 'Arch Linux', 'CentOS 7', 'Debian 12 (Bookworm)', 'Fedora 39', 'Kali Linux (Rolling)', 'Rocky Linux 9', 'alpha', 'alpine', 'alpine:3.19', 'arch', 'archlinux:latest', 'backup', 'bravo', 'centos7', 'centos:7', 'charlie', 'db', 'debian', 'debian:bookworm-slim', 'delta', 'dev', 'echo', 'fedora', 'fedora:39', 'files', 'foxtrot', 'generic', 'golf', 'hotel', 'india', 'juliet', 'kali', 'kilo', 'lima', 'mail', 'mike', 'minimal', 'monitor', 'nova', 'oscar', 'prod', 'proxy', 'rhel', 'rocky9', 'rockylinux:9-minimal', 'rolling', 'stage', 'ubuntu20', 'ubuntu22', 'ubuntu:20.04', 'ubuntu:22.04', 'web']

View File

@@ -0,0 +1,4 @@
# file: /home/anti/Tools/DECNET/decnet/archetypes.py
# hypothesis_version: 6.151.11
[', ', 'Database Server', 'DevOps Host', 'Domain Controller', 'File Server', 'IoT Device', 'Linux Server', 'Mail Server', 'Monitoring Node', 'Network Printer', 'VoIP Server', 'Web Server', 'Windows Server', 'Windows Workstation', 'alpine', 'conpot', 'database-server', 'deaddeck', 'debian', 'devops-host', 'docker_api', 'domain-controller', 'embedded', 'fedora', 'file-server', 'ftp', 'http', 'imap', 'industrial-control', 'iot-device', 'k8s', 'ldap', 'linux', 'linux-server', 'llmnr', 'mail-server', 'monitoring-node', 'mqtt', 'mysql', 'pop3', 'postgres', 'printer', 'rdp', 'real_ssh', 'redis', 'rocky9', 'sip', 'smb', 'smtp', 'snmp', 'ssh', 'telnet', 'ubuntu20', 'ubuntu22', 'voip-server', 'web-server', 'windows', 'windows-server', 'windows-workstation']

View File

@@ -0,0 +1,4 @@
# file: /home/anti/Tools/DECNET/decnet/web/sqlite_repository.py
# hypothesis_version: 6.151.11
[' AND ', ' WHERE ', ':', '[^a-zA-Z0-9_]', 'active_deckies', 'attacker', 'attacker-ip', 'attacker_ip', 'bucket_time', 'count', 'decky', 'decnet.db', 'deployed_deckies', 'event', 'event_type', 'fields', 'id > ?', 'max_id', 'msg', 'must_change_password', 'password_hash', 'raw_line', 'role', 'service', 'time', 'timestamp', 'timestamp <= ?', 'timestamp >= ?', 'total', 'total_logs', 'unique_attackers', 'username', 'uuid']

View File

@@ -0,0 +1,4 @@
# file: /home/anti/Tools/DECNET/decnet/deployer.py
# hypothesis_version: 6.151.11
[5.0, ', ', '--build', '--no-cache', '--watch', '-d', '-f', 'DECNET Deckies', 'Decky', 'Deployed Deckies', 'Hostname', 'IP', 'IPvlan', 'IPvlan L2', 'MACVLAN', 'Services', 'Status', '[green]up[/]', '[red]degraded[/]', 'absent', 'bold', 'build', 'cmdline', 'compose', 'decnet-compose.yml', 'decnet.cli', 'decnet.web.api:app', 'docker', 'down', 'green', 'manifest for', 'manifest unknown', 'mutate', 'name', 'not found', 'pid', 'pull access denied', 'red', 'rm', 'running', 'stop', 'up', 'uvicorn']

View File

@@ -0,0 +1,4 @@
# file: /home/anti/Tools/DECNET/decnet/web/sqlite_repository.py
# hypothesis_version: 6.151.11
[' AND ', ' WHERE ', ':', '[^a-zA-Z0-9_]', 'active_deckies', 'attacker', 'attacker-ip', 'attacker_ip', 'bucket_time', 'count', 'decky', 'decnet.db', 'deployed_deckies', 'event', 'event_type', 'fields', 'id > ?', 'max_id', 'msg', 'must_change_password', 'password_hash', 'raw_line', 'role', 'service', 'time', 'timestamp', 'timestamp <= ?', 'timestamp >= ?', 'total', 'total_logs', 'unique_attackers', 'username', 'uuid']

View File

@@ -0,0 +1,4 @@
# file: /home/anti/Tools/DECNET/decnet/network.py
# hypothesis_version: 6.151.11
['/', 'add', 'addr', 'bridge', 'decnet_ipvlan0', 'decnet_lan', 'decnet_macvlan0', 'default', 'del', 'dev', 'inet ', 'inet6', 'ip', 'ipvlan', 'ipvlan_mode', 'l2', 'link', 'macvlan', 'mode', 'parent', 'route', 'set', 'show', 'type', 'up', 'via']

View File

@@ -0,0 +1,4 @@
# file: /home/anti/Tools/DECNET/decnet/logging/file_handler.py
# hypothesis_version: 6.151.11
[1024, '%(message)s', 'DECNET_LOG_FILE', 'decnet.syslog', 'utf-8']

View File

@@ -1 +0,0 @@
¨&@a!Þ”'<âÚÂN1ïÓ/Ï!ÁI…ÿø6-lÔãú+ÁÌI>…•_l.secondary

View File

@@ -1 +0,0 @@
櫟00000000000000000000000000000000000000000000000000000000000000000000000000€

View File

@@ -1 +0,0 @@
źZ000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000€

View File

@@ -1 +0,0 @@
欟0000000000000000000000000000000000000000000000000000000000000000000000000000000000000€

View File

@@ -1 +0,0 @@
櫚0000000000000000000000000000000000000000000000000000000000000000000000000000€

View File

@@ -1 +0,0 @@
źV00000000000000000000000000000000000000000000000000000000000000000000000000000000000000€

View File

@@ -1 +0,0 @@
ŸT000000000000000000000000000000000000000000000000000000000000000000000000000000000000€

View File

@@ -1 +0,0 @@
źW000000000000000000000000000000000000000000000000000000000000000000000000000000000000000€

View File

@@ -1 +0,0 @@
<EFBFBD><EFBFBD>Ω≈ç√∫˜µ≤≥÷åß∂ƒ©˙∆˚¬…æœ∑´®†¥¨ˆøπ“‘¡™£¢∞§¶•ªº–≠¸˛Ç◊ı˜Â¯˘¿ÅÍÎÏ˝ÓÔÒÚÆ☃Œ„´‰ˇÁ¨ˆØ∏”’`fifl‡°·—±<E28094>

View File

@@ -1 +0,0 @@
櫪00000000000000000000000000000000000000000000000000000000000000000000000000000€

View File

@@ -1 +0,0 @@
盜0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000€

View File

@@ -1 +0,0 @@
蘖00000000000000000000000000000000000000000000000000000000000000000000000000000000€

View File

@@ -1 +0,0 @@
歇0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000€

View File

@@ -1 +0,0 @@
ŸY00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000€

View File

@@ -1 +0,0 @@
櫺000000000000000000000000000000000000000000000000000000000000000000000000000000000€

View File

@@ -1 +0,0 @@
<EFBFBD><EFBFBD>Ω≈ç√∫˜µ≤≥÷åß∂ƒ©˙∆˚¬…æœ∑´®†¥¨ˆøπ“‘¡™£¢∞§¶•ªº–≠¸˛Ç◊ı˜Â¯˘¿ÅÍÎÏ˝ÓÔÒÚÆ☃Œ„´‰ˇÁ¨ˆØ∏”’`fifl‡°·—±<E28094>

View File

@@ -1 +0,0 @@
歃00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000€

View File

@@ -1 +0,0 @@
ŸO0000000000000000000000000000000000000000000000000000000000000000000000000000000€

View File

@@ -82,7 +82,7 @@ Host NIC (eth0)
- Runtime state is persisted in `decnet-state.json`. - Runtime state is persisted in `decnet-state.json`.
- Do not modify this file manually. - Do not modify this file manually.
- **General Development Guidelines**: - **General Development Guidelines**:
- **Never** commit broken code. - **Never** commit broken code, or before running `pytest`s or `bandit` at the project level.
- **No matter how small** the changes, they must be committed. - **No matter how small** the changes, they must be committed.
- **If new features are addedd** new tests must be added, too. - **If new features are addedd** new tests must be added, too.
- **Never present broken code to the user**. Test, validate, then present. - **Never present broken code to the user**. Test, validate, then present.

View File

@@ -167,4 +167,4 @@ def all_archetypes() -> dict[str, Archetype]:
def random_archetype() -> Archetype: def random_archetype() -> Archetype:
return random.choice(list(ARCHETYPES.values())) return random.choice(list(ARCHETYPES.values())) # nosec B311

View File

@@ -90,8 +90,8 @@ def _build_deckies(
svc_pool = _all_service_names() svc_pool = _all_service_names()
attempts = 0 attempts = 0
while True: while True:
count = random.randint(1, min(3, len(svc_pool))) count = random.randint(1, min(3, len(svc_pool))) # nosec B311
chosen = frozenset(random.sample(svc_pool, count)) chosen = frozenset(random.sample(svc_pool, count)) # nosec B311
attempts += 1 attempts += 1
if chosen not in used_combos or attempts > 20: if chosen not in used_combos or attempts > 20:
break break
@@ -173,8 +173,8 @@ def _build_deckies_from_ini(
svc_list = list(arch.services) svc_list = list(arch.services)
elif randomize: elif randomize:
svc_pool = _all_service_names() svc_pool = _all_service_names()
count = random.randint(1, min(3, len(svc_pool))) count = random.randint(1, min(3, len(svc_pool))) # nosec B311
svc_list = random.sample(svc_pool, count) svc_list = random.sample(svc_pool, count) # nosec B311
else: else:
raise ValueError( raise ValueError(
f"Decky '[{spec.name}]' has no services= in config. " f"Decky '[{spec.name}]' has no services= in config. "
@@ -214,7 +214,7 @@ def api(
log_file: str = typer.Option(DECNET_INGEST_LOG_FILE, "--log-file", help="Path to the DECNET log file to monitor"), log_file: str = typer.Option(DECNET_INGEST_LOG_FILE, "--log-file", help="Path to the DECNET log file to monitor"),
) -> None: ) -> None:
"""Run the DECNET API and Web Dashboard in standalone mode.""" """Run the DECNET API and Web Dashboard in standalone mode."""
import subprocess import subprocess # nosec B404
import sys import sys
import os import os
@@ -222,7 +222,7 @@ def api(
_env: dict[str, str] = os.environ.copy() _env: dict[str, str] = os.environ.copy()
_env["DECNET_INGEST_LOG_FILE"] = str(log_file) _env["DECNET_INGEST_LOG_FILE"] = str(log_file)
try: try:
subprocess.run( subprocess.run( # nosec B603 B404
[sys.executable, "-m", "uvicorn", "decnet.web.api:app", "--host", host, "--port", str(port)], [sys.executable, "-m", "uvicorn", "decnet.web.api:app", "--host", host, "--port", str(port)],
env=_env env=_env
) )
@@ -392,11 +392,11 @@ def deploy(
_deploy(config, dry_run=dry_run, no_cache=no_cache) _deploy(config, dry_run=dry_run, no_cache=no_cache)
if mutate_interval is not None and not dry_run: if mutate_interval is not None and not dry_run:
import subprocess import subprocess # nosec B404
import sys import sys
console.print(f"[green]Starting DECNET Mutator watcher in the background (interval: {mutate_interval}m)...[/]") console.print(f"[green]Starting DECNET Mutator watcher in the background (interval: {mutate_interval}m)...[/]")
try: try:
subprocess.Popen( subprocess.Popen( # nosec B603
[sys.executable, "-m", "decnet.cli", "mutate", "--watch"], [sys.executable, "-m", "decnet.cli", "mutate", "--watch"],
stdout=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
stderr=subprocess.STDOUT stderr=subprocess.STDOUT
@@ -405,19 +405,19 @@ def deploy(
console.print("[red]Failed to start mutator watcher.[/]") console.print("[red]Failed to start mutator watcher.[/]")
if api and not dry_run: if api and not dry_run:
import subprocess import subprocess # nosec B404
import sys import sys
console.print(f"[green]Starting DECNET API on port {api_port}...[/]") console.print(f"[green]Starting DECNET API on port {api_port}...[/]")
_env: dict[str, str] = os.environ.copy() _env: dict[str, str] = os.environ.copy()
_env["DECNET_INGEST_LOG_FILE"] = str(effective_log_file) _env["DECNET_INGEST_LOG_FILE"] = str(effective_log_file or "")
try: try:
subprocess.Popen( subprocess.Popen( # nosec B603
[sys.executable, "-m", "uvicorn", "decnet.web.api:app", "--host", "0.0.0.0", "--port", str(api_port)], [sys.executable, "-m", "uvicorn", "decnet.web.api:app", "--host", DECNET_API_HOST, "--port", str(api_port)],
env=_env, env=_env,
stdout=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
stderr=subprocess.STDOUT stderr=subprocess.STDOUT
) )
console.print(f"[dim]API running at http://0.0.0.0:{api_port}[/]") console.print(f"[dim]API running at http://{DECNET_API_HOST}:{api_port}[/]")
except (FileNotFoundError, subprocess.SubprocessError): except (FileNotFoundError, subprocess.SubprocessError):
console.print("[red]Failed to start API. Ensure 'uvicorn' is installed in the current environment.[/]") console.print("[red]Failed to start API. Ensure 'uvicorn' is installed in the current environment.[/]")

View File

@@ -2,7 +2,7 @@
Deploy, teardown, and status via Docker SDK + subprocess docker compose. Deploy, teardown, and status via Docker SDK + subprocess docker compose.
""" """
import subprocess import subprocess # nosec B404
import time import time
from pathlib import Path from pathlib import Path
@@ -31,7 +31,7 @@ COMPOSE_FILE = Path("decnet-compose.yml")
def _compose(*args: str, compose_file: Path = COMPOSE_FILE) -> None: def _compose(*args: str, compose_file: Path = COMPOSE_FILE) -> None:
cmd = ["docker", "compose", "-f", str(compose_file), *args] cmd = ["docker", "compose", "-f", str(compose_file), *args]
subprocess.run(cmd, check=True) subprocess.run(cmd, check=True) # nosec B603
_PERMANENT_ERRORS = ( _PERMANENT_ERRORS = (
@@ -53,7 +53,7 @@ def _compose_with_retry(
last_exc: subprocess.CalledProcessError | None = None last_exc: subprocess.CalledProcessError | None = None
cmd = ["docker", "compose", "-f", str(compose_file), *args] cmd = ["docker", "compose", "-f", str(compose_file), *args]
for attempt in range(1, retries + 1): for attempt in range(1, retries + 1):
result = subprocess.run(cmd, capture_output=True, text=True) result = subprocess.run(cmd, capture_output=True, text=True) # nosec B603
if result.returncode == 0: if result.returncode == 0:
if result.stdout: if result.stdout:
print(result.stdout, end="") print(result.stdout, end="")

View File

@@ -97,8 +97,8 @@ def random_hostname(distro_slug: str = "debian") -> str:
"""Generate a plausible hostname for the given distro style.""" """Generate a plausible hostname for the given distro style."""
profile = DISTROS.get(distro_slug) profile = DISTROS.get(distro_slug)
style = profile.hostname_style if profile else "generic" style = profile.hostname_style if profile else "generic"
word = random.choice(_NAME_WORDS) word = random.choice(_NAME_WORDS) # nosec B311
num = random.randint(10, 99) num = random.randint(10, 99) # nosec B311
if style == "rhel": if style == "rhel":
# RHEL/CentOS/Fedora convention: word+num.localdomain # RHEL/CentOS/Fedora convention: word+num.localdomain
@@ -107,7 +107,7 @@ def random_hostname(distro_slug: str = "debian") -> str:
return f"{word}-{num}" return f"{word}-{num}"
elif style == "rolling": elif style == "rolling":
# Kali/Arch: just a word, no suffix # Kali/Arch: just a word, no suffix
return f"{word}-{random.choice(_NAME_WORDS)}" return f"{word}-{random.choice(_NAME_WORDS)}" # nosec B311
else: else:
# Debian/Ubuntu: SRV-WORD-nn # Debian/Ubuntu: SRV-WORD-nn
return f"SRV-{word.upper()}-{num}" return f"SRV-{word.upper()}-{num}"
@@ -122,7 +122,7 @@ def get_distro(slug: str) -> DistroProfile:
def random_distro() -> DistroProfile: def random_distro() -> DistroProfile:
return random.choice(list(DISTROS.values())) return random.choice(list(DISTROS.values())) # nosec B311
def all_distros() -> dict[str, DistroProfile]: def all_distros() -> dict[str, DistroProfile]:

View File

@@ -10,13 +10,13 @@ load_dotenv(_ROOT / ".env.local")
load_dotenv(_ROOT / ".env") load_dotenv(_ROOT / ".env")
# API Options # API Options
DECNET_API_HOST: str = os.environ.get("DECNET_API_HOST", "0.0.0.0") DECNET_API_HOST: str = os.environ.get("DECNET_API_HOST", "0.0.0.0") # nosec B104
DECNET_API_PORT: int = int(os.environ.get("DECNET_API_PORT", "8000")) DECNET_API_PORT: int = int(os.environ.get("DECNET_API_PORT", "8000"))
DECNET_JWT_SECRET: str = os.environ.get("DECNET_JWT_SECRET", "fallback-secret-key-change-me") DECNET_JWT_SECRET: str = os.environ.get("DECNET_JWT_SECRET", "fallback-secret-key-change-me")
DECNET_INGEST_LOG_FILE: str | None = os.environ.get("DECNET_INGEST_LOG_FILE", "/var/log/decnet/decnet.log") DECNET_INGEST_LOG_FILE: str | None = os.environ.get("DECNET_INGEST_LOG_FILE", "/var/log/decnet/decnet.log")
# Web Dashboard Options # Web Dashboard Options
DECNET_WEB_HOST: str = os.environ.get("DECNET_WEB_HOST", "0.0.0.0") DECNET_WEB_HOST: str = os.environ.get("DECNET_WEB_HOST", "0.0.0.0") # nosec B104
DECNET_WEB_PORT: int = int(os.environ.get("DECNET_WEB_PORT", "8080")) DECNET_WEB_PORT: int = int(os.environ.get("DECNET_WEB_PORT", "8080"))
DECNET_ADMIN_USER: str = os.environ.get("DECNET_ADMIN_USER", "admin") DECNET_ADMIN_USER: str = os.environ.get("DECNET_ADMIN_USER", "admin")
DECNET_ADMIN_PASSWORD: str = os.environ.get("DECNET_ADMIN_PASSWORD", "admin") DECNET_ADMIN_PASSWORD: str = os.environ.get("DECNET_ADMIN_PASSWORD", "admin")

View File

@@ -49,11 +49,10 @@ def _get_logger() -> logging.Logger:
def write_syslog(line: str) -> None: def write_syslog(line: str) -> None:
"""Write a single RFC 5424 syslog line to the rotating log file.""" """Write a single RFC 5424 syslog line to the rotating log file."""
try: try:
_get_logger().info(line) _get_logger().info(line)
except Exception: except Exception: # nosec B110
pass pass
def get_log_path() -> Path: def get_log_path() -> Path:
"""Return the configured log file path (for tests/inspection).""" """Return the configured log file path (for tests/inspection)."""
return Path(os.environ.get(_LOG_FILE_ENV, _DEFAULT_LOG_FILE)) return Path(os.environ.get(_LOG_FILE_ENV, _DEFAULT_LOG_FILE))

View File

@@ -4,7 +4,7 @@ Handles dynamic rotation of exposed honeypot services over time.
""" """
import random import random
import subprocess import subprocess # nosec B404
import time import time
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
@@ -29,7 +29,7 @@ def _compose_with_retry(
last_exc: subprocess.CalledProcessError | None = None last_exc: subprocess.CalledProcessError | None = None
cmd = ["docker", "compose", "-f", str(compose_file), *args] cmd = ["docker", "compose", "-f", str(compose_file), *args]
for attempt in range(1, retries + 1): for attempt in range(1, retries + 1):
result = subprocess.run(cmd, capture_output=True, text=True) result = subprocess.run(cmd, capture_output=True, text=True) # nosec B603
if result.returncode == 0: if result.returncode == 0:
if result.stdout: if result.stdout:
print(result.stdout, end="") print(result.stdout, end="")
@@ -78,8 +78,8 @@ def mutate_decky(decky_name: str) -> bool:
attempts = 0 attempts = 0
while True: while True:
count = random.randint(1, min(3, len(svc_pool))) count = random.randint(1, min(3, len(svc_pool))) # nosec B311
chosen = set(random.sample(svc_pool, count)) chosen = set(random.sample(svc_pool, count)) # nosec B311
attempts += 1 attempts += 1
if chosen != current_services or attempts > 20: if chosen != current_services or attempts > 20:
break break

View File

@@ -9,7 +9,7 @@ Handles:
""" """
import os import os
import subprocess import subprocess # nosec B404
from ipaddress import IPv4Address, IPv4Interface, IPv4Network from ipaddress import IPv4Address, IPv4Interface, IPv4Network
import docker import docker
@@ -24,7 +24,7 @@ HOST_IPVLAN_IFACE = "decnet_ipvlan0"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def _run(cmd: list[str], check: bool = True) -> subprocess.CompletedProcess: def _run(cmd: list[str], check: bool = True) -> subprocess.CompletedProcess:
return subprocess.run(cmd, capture_output=True, text=True, check=check) return subprocess.run(cmd, capture_output=True, text=True, check=check) # nosec B603 B404
def detect_interface() -> str: def detect_interface() -> str:

View File

@@ -30,22 +30,34 @@ ingestion_task: Optional[asyncio.Task[Any]] = None
@asynccontextmanager @asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
global ingestion_task global ingestion_task
await repo.initialize()
# Retry initialization a few times if DB is locked (common in tests)
for _ in range(5):
try:
await repo.initialize()
break
except Exception:
await asyncio.sleep(0.5)
# Create default admin if no users exist # Create default admin if no users exist
_admin_user: Optional[dict[str, Any]] = await repo.get_user_by_username(DECNET_ADMIN_USER) try:
if not _admin_user: _admin_user: Optional[dict[str, Any]] = await repo.get_user_by_username(DECNET_ADMIN_USER)
await repo.create_user( if not _admin_user:
{ await repo.create_user(
"uuid": str(uuid.uuid4()), {
"username": DECNET_ADMIN_USER, "uuid": str(uuid.uuid4()),
"password_hash": get_password_hash(DECNET_ADMIN_PASSWORD), "username": DECNET_ADMIN_USER,
"role": "admin", "password_hash": get_password_hash(DECNET_ADMIN_PASSWORD),
"must_change_password": True "role": "admin",
} "must_change_password": True # nosec B105
) }
)
except Exception:
pass
# Start background ingestion task # Start background ingestion task
ingestion_task = asyncio.create_task(log_ingestion_worker(repo)) if ingestion_task is None or ingestion_task.done():
ingestion_task = asyncio.create_task(log_ingestion_worker(repo))
yield yield
@@ -140,7 +152,7 @@ async def login(request: LoginRequest) -> dict[str, Any]:
) )
return { return {
"access_token": _access_token, "access_token": _access_token,
"token_type": "bearer", "token_type": "bearer", # nosec B105
"must_change_password": bool(_user.get("must_change_password", False)) "must_change_password": bool(_user.get("must_change_password", False))
} }

View File

@@ -11,10 +11,11 @@ class SQLiteRepository(BaseRepository):
self.db_path: str = db_path self.db_path: str = db_path
async def initialize(self) -> None: async def initialize(self) -> None:
async with aiosqlite.connect(self.db_path) as _db: """Initialize the database schema synchronously to ensure reliability."""
await _db.execute("PRAGMA journal_mode=WAL") import sqlite3
# Logs table with sqlite3.connect(self.db_path) as _conn:
await _db.execute(""" _conn.execute("PRAGMA journal_mode=WAL")
_conn.execute("""
CREATE TABLE IF NOT EXISTS logs ( CREATE TABLE IF NOT EXISTS logs (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
@@ -27,16 +28,7 @@ class SQLiteRepository(BaseRepository):
msg TEXT msg TEXT
) )
""") """)
try: _conn.execute("""
await _db.execute("ALTER TABLE logs ADD COLUMN fields TEXT")
except aiosqlite.OperationalError:
pass
try:
await _db.execute("ALTER TABLE logs ADD COLUMN msg TEXT")
except aiosqlite.OperationalError:
pass
# Users table (internal RBAC)
await _db.execute("""
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
uuid TEXT PRIMARY KEY, uuid TEXT PRIMARY KEY,
username TEXT UNIQUE, username TEXT UNIQUE,
@@ -45,11 +37,7 @@ class SQLiteRepository(BaseRepository):
must_change_password BOOLEAN DEFAULT 0 must_change_password BOOLEAN DEFAULT 0
) )
""") """)
try: _conn.commit()
await _db.execute("ALTER TABLE users ADD COLUMN must_change_password BOOLEAN DEFAULT 0")
except aiosqlite.OperationalError:
pass # Column already exists
await _db.commit()
async def add_log(self, log_data: dict[str, Any]) -> None: async def add_log(self, log_data: dict[str, Any]) -> None:
async with aiosqlite.connect(self.db_path) as _db: async with aiosqlite.connect(self.db_path) as _db:
@@ -152,7 +140,7 @@ class SQLiteRepository(BaseRepository):
end_time: Optional[str] = None end_time: Optional[str] = None
) -> list[dict[str, Any]]: ) -> list[dict[str, Any]]:
_where, _params = self._build_where_clause(search, start_time, end_time) _where, _params = self._build_where_clause(search, start_time, end_time)
_query = f"SELECT * FROM logs{_where} ORDER BY timestamp DESC LIMIT ? OFFSET ?" _query = f"SELECT * FROM logs{_where} ORDER BY timestamp DESC LIMIT ? OFFSET ?" # nosec B608
_params.extend([limit, offset]) _params.extend([limit, offset])
async with aiosqlite.connect(self.db_path) as _db: async with aiosqlite.connect(self.db_path) as _db:
@@ -178,7 +166,7 @@ class SQLiteRepository(BaseRepository):
end_time: Optional[str] = None end_time: Optional[str] = None
) -> list[dict[str, Any]]: ) -> list[dict[str, Any]]:
_where, _params = self._build_where_clause(search, start_time, end_time, base_where="id > ?", base_params=[last_id]) _where, _params = self._build_where_clause(search, start_time, end_time, base_where="id > ?", base_params=[last_id])
_query = f"SELECT * FROM logs{_where} ORDER BY id ASC LIMIT ?" _query = f"SELECT * FROM logs{_where} ORDER BY id ASC LIMIT ?" # nosec B608
_params.append(limit) _params.append(limit)
async with aiosqlite.connect(self.db_path) as _db: async with aiosqlite.connect(self.db_path) as _db:
@@ -194,7 +182,7 @@ class SQLiteRepository(BaseRepository):
end_time: Optional[str] = None end_time: Optional[str] = None
) -> int: ) -> int:
_where, _params = self._build_where_clause(search, start_time, end_time) _where, _params = self._build_where_clause(search, start_time, end_time)
_query = f"SELECT COUNT(*) as total FROM logs{_where}" _query = f"SELECT COUNT(*) as total FROM logs{_where}" # nosec B608
async with aiosqlite.connect(self.db_path) as _db: async with aiosqlite.connect(self.db_path) as _db:
_db.row_factory = aiosqlite.Row _db.row_factory = aiosqlite.Row
@@ -224,7 +212,7 @@ class SQLiteRepository(BaseRepository):
{_where} {_where}
GROUP BY bucket_time GROUP BY bucket_time
ORDER BY bucket_time ASC ORDER BY bucket_time ASC
""" """ # nosec B608
async with aiosqlite.connect(self.db_path) as _db: async with aiosqlite.connect(self.db_path) as _db:
_db.row_factory = aiosqlite.Row _db.row_factory = aiosqlite.Row

BIN
test_decnet.db-shm Normal file

Binary file not shown.

BIN
test_decnet.db-wal Normal file

Binary file not shown.

BIN
test_fuzz_decnet.db-shm Normal file

Binary file not shown.

BIN
test_fuzz_decnet.db-wal Normal file

Binary file not shown.

Binary file not shown.