Merge pull request #300 from Surya-0823/feat/xxe-detection-rule

Fix XXE detection consistency and harden development infrastructure
This commit is contained in:
Carter Perez 2026-07-06 10:07:33 -04:00 committed by GitHub
commit 757f9d6782
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 146 additions and 122 deletions

View File

@ -5,11 +5,11 @@ rules.py
Cold-start rule-based detection engine inspired by Cold-start rule-based detection engine inspired by
ModSecurity Core Rule Set ModSecurity Core Rule Set
RuleEngine.score_request evaluates requests against 9 RuleEngine.score_request evaluates requests against 10
regex-based _PatternRules (LOG4SHELL 0.95, COMMAND_ regex-based _PatternRules (LOG4SHELL 0.95, COMMAND_
INJECTION 0.90, SQL_INJECTION 0.85, XSS 0.80, FILE_ INJECTION 0.90, SQL_INJECTION 0.85, XXE_INJECTION 0.82,
INCLUSION 0.75, SSRF 0.70, CRLF_INJECTION 0.65, XSS 0.80, FILE_INCLUSION 0.75, SSRF 0.70, CRLF_INJECTION
PATH_TRAVERSAL 0.60, OPEN_REDIRECT 0.55), 0.65, PATH_TRAVERSAL 0.60, OPEN_REDIRECT 0.55),
double-encoding detection (0.40), scanner user-agent double-encoding detection (0.40), scanner user-agent
signature matching (0.35), and 2 _ThresholdRules signature matching (0.35), and 2 _ThresholdRules
(RATE_ANOMALY >100 req/min 0.30, HIGH_ERROR_RATE >50% (RATE_ANOMALY >100 req/min 0.30, HIGH_ERROR_RATE >50%
@ -46,6 +46,7 @@ from app.core.features.patterns import (
SQLI, SQLI,
SSRF, SSRF,
XSS, XSS,
XXE_INJECTION,
) )
from app.core.features.signatures import SCANNER_USER_AGENTS from app.core.features.signatures import SCANNER_USER_AGENTS
from app.core.detection.ensemble import classify_severity as _classify_severity from app.core.detection.ensemble import classify_severity as _classify_severity
@ -77,11 +78,12 @@ _PATTERN_RULES: list[_PatternRule] = [
_PatternRule("LOG4SHELL", LOG4SHELL, 0.95), _PatternRule("LOG4SHELL", LOG4SHELL, 0.95),
_PatternRule("COMMAND_INJECTION", COMMAND_INJECTION, 0.90), _PatternRule("COMMAND_INJECTION", COMMAND_INJECTION, 0.90),
_PatternRule("SQL_INJECTION", SQLI, 0.85), _PatternRule("SQL_INJECTION", SQLI, 0.85),
_PatternRule("XXE_INJECTION", XXE_INJECTION, 0.82),
_PatternRule("XSS", XSS, 0.80), _PatternRule("XSS", XSS, 0.80),
_PatternRule("FILE_INCLUSION", FILE_INCLUSION, 0.75), _PatternRule("FILE_INCLUSION", FILE_INCLUSION, 0.75),
_PatternRule("SSRF", SSRF, 0.70), _PatternRule("SSRF", SSRF, 0.70),
_PatternRule("PATH_TRAVERSAL", PATH_TRAVERSAL, 0.60),
_PatternRule("CRLF_INJECTION", CRLF_INJECTION, 0.65), _PatternRule("CRLF_INJECTION", CRLF_INJECTION, 0.65),
_PatternRule("PATH_TRAVERSAL", PATH_TRAVERSAL, 0.60),
_PatternRule("OPEN_REDIRECT", OPEN_REDIRECT, 0.55), _PatternRule("OPEN_REDIRECT", OPEN_REDIRECT, 0.55),
] ]

View File

@ -3,7 +3,7 @@
patterns.py patterns.py
Compiled regex patterns for web attack detection covering Compiled regex patterns for web attack detection covering
9 OWASP categories plus encoding anomalies 10 OWASP categories plus encoding anomalies
Defines case-insensitive compiled patterns for: SQLI Defines case-insensitive compiled patterns for: SQLI
(union select, sleep, benchmark, information_schema, hex (union select, sleep, benchmark, information_schema, hex
@ -14,13 +14,16 @@ document.cookie/write, eval/alert/prompt), PATH_TRAVERSAL
etc/passwd, .git/config, .env), COMMAND_INJECTION etc/passwd, .git/config, .env), COMMAND_INJECTION
(semicolon/pipe chaining to shell commands, $() and (semicolon/pipe chaining to shell commands, $() and
backtick substitution, ${} expansion), FILE_INCLUSION backtick substitution, ${} expansion), FILE_INCLUSION
(php://, file://, data://, phar:// wrapper schemes), SSRF (php://, file://, data://, phar:// wrapper schemes),
(cloud metadata IPs 169.254.169.254, localhost with paths, XXE_INJECTION (<!ENTITY / <!DOCTYPE declarations, SYSTEM
dict:// and gopher://), LOG4SHELL (${jndi, ${lower, ${:- "file:///" references, xmlns:xi includes, and their
patterns), CRLF_INJECTION (%0d%0a sequences, bare CR/LF), URL-encoded variants), SSRF (cloud metadata IPs
169.254.169.254, localhost with paths, dict:// and
gopher://), LOG4SHELL (${jndi, ${lower, ${:- patterns),
CRLF_INJECTION (%0d%0a sequences, bare CR/LF),
OPEN_REDIRECT (redirect params pointing to external URLs). OPEN_REDIRECT (redirect params pointing to external URLs).
Also provides ENCODED_CHARS, DOUBLE_ENCODED for evasion Also provides ENCODED_CHARS, DOUBLE_ENCODED for evasion
detection, and ATTACK_COMBINED unioning all 9 patterns detection, and ATTACK_COMBINED unioning all 10 patterns
Connects to: Connects to:
core/features/ core/features/
@ -103,6 +106,14 @@ _FILE_INCLUSION = (r"(?:php://|"
r"phar://|" r"phar://|"
r"glob://)") r"glob://)")
_XXE_INJECTION = (
r"(?:<!\s*(?:ENTITY|DOCTYPE)\b|"
r"SYSTEM\s+[\"']\s*(?:file|http|https|ftp|php|data)://|"
r"xmlns\s*:\s*xi\s*=|"
r"%(?:25)?3[Cc]!(?:ENTITY|DOCTYPE)|"
r"&(?:xxe|xml|ext|file);|"
r"%(?:25)?(?:53|73)%(?:25)?(?:59|79)%(?:25)?(?:53|73)%(?:25)?(?:54|74)%(?:25)?(?:45|65)%(?:25)?(?:4[Dd]))")
_SSRF = ( _SSRF = (
r"(?:169\.254\.169\.254|" r"(?:169\.254\.169\.254|"
r"metadata\.google\.internal|" r"metadata\.google\.internal|"
@ -129,6 +140,7 @@ XSS = re.compile(_XSS, re.IGNORECASE)
PATH_TRAVERSAL = re.compile(_PATH_TRAVERSAL, re.IGNORECASE) PATH_TRAVERSAL = re.compile(_PATH_TRAVERSAL, re.IGNORECASE)
COMMAND_INJECTION = re.compile(_COMMAND_INJECTION, re.IGNORECASE) COMMAND_INJECTION = re.compile(_COMMAND_INJECTION, re.IGNORECASE)
FILE_INCLUSION = re.compile(_FILE_INCLUSION, re.IGNORECASE) FILE_INCLUSION = re.compile(_FILE_INCLUSION, re.IGNORECASE)
XXE_INJECTION = re.compile(_XXE_INJECTION, re.IGNORECASE)
SSRF = re.compile(_SSRF, re.IGNORECASE) SSRF = re.compile(_SSRF, re.IGNORECASE)
LOG4SHELL = re.compile(_LOG4SHELL, re.IGNORECASE) LOG4SHELL = re.compile(_LOG4SHELL, re.IGNORECASE)
CRLF_INJECTION = re.compile(_CRLF_INJECTION, re.IGNORECASE) CRLF_INJECTION = re.compile(_CRLF_INJECTION, re.IGNORECASE)
@ -137,7 +149,7 @@ OPEN_REDIRECT = re.compile(_OPEN_REDIRECT, re.IGNORECASE)
ATTACK_COMBINED = re.compile( ATTACK_COMBINED = re.compile(
r"|".join(( r"|".join((
_SQLI, _XSS, _PATH_TRAVERSAL, _COMMAND_INJECTION, _SQLI, _XSS, _PATH_TRAVERSAL, _COMMAND_INJECTION,
_FILE_INCLUSION, _SSRF, _LOG4SHELL, _FILE_INCLUSION, _XXE_INJECTION, _SSRF, _LOG4SHELL,
_CRLF_INJECTION, _OPEN_REDIRECT, _CRLF_INJECTION, _OPEN_REDIRECT,
)), )),
re.IGNORECASE, re.IGNORECASE,

View File

@ -26,7 +26,7 @@ services:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD required} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD required}
POSTGRES_INITDB_ARGS: "-E UTF8 --locale=C" POSTGRES_INITDB_ARGS: "-E UTF8 --locale=C"
volumes: volumes:
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql
networks: networks:
- vigil_network - vigil_network
healthcheck: healthcheck:

View File

@ -6,16 +6,17 @@ simulate.py
HTTP traffic simulator for generating realistic attack and HTTP traffic simulator for generating realistic attack and
normal log patterns against the dev-log target application normal log patterns against the dev-log target application
Provides 10 traffic modes via argparse: normal (benign Provides 11 traffic modes via argparse: normal (benign
browsing with GET/POST mix), sqli (12 SQL injection browsing with GET/POST mix), sqli (12 SQL injection
payloads), xss (10 script/event handler vectors), payloads), xss (10 script/event handler vectors),
traversal (10 dot-dot-slash and encoding variants), cmdi traversal (10 dot-dot-slash and encoding variants), cmdi
(7 shell command injection payloads), log4shell (4 JNDI (7 shell command injection payloads), log4shell (4 JNDI
lookup variants), ssrf (5 cloud metadata and internal lookup variants), ssrf (5 cloud metadata and internal
service targets), scanner (20 recon paths with 11 scanner service targets), xxe (5 XML external entity payloads),
user-agents), flood (rapid-fire requests), and mixed scanner (20 recon paths with 11 scanner user-agents),
(50/10/40 normal/scanner/attack split). Uses urllib for flood (rapid-fire requests), and mixed (50/10/40
HTTP requests with configurable count, delay, and target normal/scanner/attack split). Uses urllib for HTTP
requests with configurable count, delay, and target
URL. Checks /health reachability before starting URL. Checks /health reachability before starting
Connects to: Connects to:
@ -29,6 +30,7 @@ import random
import sys import sys
import time import time
import urllib.error import urllib.error
import urllib.parse
import urllib.request import urllib.request
DEFAULT_TARGET = "http://localhost:58319" DEFAULT_TARGET = "http://localhost:58319"
@ -117,6 +119,14 @@ SSRF_PAYLOADS = [
"/api/search?url=http://metadata.google.internal/computeMetadata/v1/", "/api/search?url=http://metadata.google.internal/computeMetadata/v1/",
] ]
XXE_PAYLOADS = [
"/api/test?data=%3C!DOCTYPE+foo+%5B%3C!ENTITY+xxe+SYSTEM+%22file%3A%2F%2F%2Fetc%2Fpasswd%22%3E%5D%3E",
"/api/test?xml=<!DOCTYPE+root+[<!ENTITY+ext+SYSTEM+'file:///etc/hosts'>]>",
"/api/test?body=<root+xmlns:xi='http://www.w3.org/2001/XInclude'><xi:include+href='file:///etc/shadow'/></root>",
"/api/parse?input=%3C%21ENTITY+evil+SYSTEM+%22file%3A%2F%2F%2Fproc%2Fself%2Fenviron%22%3E",
"/api/test?content=<!DOCTYPE+x+[<!ENTITY+test+SYSTEM+'http://evil.com/xxe'>]>&q=&xxe;",
]
SCANNER_USER_AGENTS = [ SCANNER_USER_AGENTS = [
"Nikto/2.1.6", "Nikto/2.1.6",
"sqlmap/1.7.2#stable (https://sqlmap.org)", "sqlmap/1.7.2#stable (https://sqlmap.org)",
@ -149,11 +159,18 @@ ATTACK_POOLS = {
"cmdi": COMMAND_INJECTION_PAYLOADS, "cmdi": COMMAND_INJECTION_PAYLOADS,
"log4shell": LOG4SHELL_PAYLOADS, "log4shell": LOG4SHELL_PAYLOADS,
"ssrf": SSRF_PAYLOADS, "ssrf": SSRF_PAYLOADS,
"xxe": XXE_PAYLOADS,
} }
def send(target, path, user_agent=None, method="GET"): def send(target, path, user_agent=None, method="GET"):
url = f"{target}{path}" if "?" in path:
_raw_path, _raw_query = path.split("?", 1)
_encoded_path = urllib.parse.quote(_raw_path, safe="/%")
_url_path = f"{_encoded_path}?{_raw_query}"
else:
_url_path = urllib.parse.quote(path, safe="/%\\")
url = f"{target}{_url_path}"
ua = user_agent or random.choice(NORMAL_USER_AGENTS) ua = user_agent or random.choice(NORMAL_USER_AGENTS)
req = urllib.request.Request(url, headers={"User-Agent": ua}) req = urllib.request.Request(url, headers={"User-Agent": ua})
if method == "POST": if method == "POST":
@ -270,6 +287,7 @@ MODES = {
"cmdi": lambda t, c, d: run_attack(t, c, d, "cmdi"), "cmdi": lambda t, c, d: run_attack(t, c, d, "cmdi"),
"log4shell": lambda t, c, d: run_attack(t, c, d, "log4shell"), "log4shell": lambda t, c, d: run_attack(t, c, d, "log4shell"),
"ssrf": lambda t, c, d: run_attack(t, c, d, "ssrf"), "ssrf": lambda t, c, d: run_attack(t, c, d, "ssrf"),
"xxe": lambda t, c, d: run_attack(t, c, d, "xxe"),
"scanner": lambda t, c, d: run_scanner(t, c, d), "scanner": lambda t, c, d: run_scanner(t, c, d),
"flood": lambda t, c, d: run_flood(t, c, d), "flood": lambda t, c, d: run_flood(t, c, d),
"mixed": lambda t, c, d: run_mixed(t, c, d), "mixed": lambda t, c, d: run_mixed(t, c, d),

View File

@ -25,7 +25,7 @@ services:
ports: ports:
- "${POSTGRES_HOST_PORT:-16969}:5432" - "${POSTGRES_HOST_PORT:-16969}:5432"
volumes: volumes:
- postgres_dev:/var/lib/postgresql/data - postgres_dev:/var/lib/postgresql
networks: networks:
- vigil_dev - vigil_dev
healthcheck: healthcheck:
@ -72,6 +72,7 @@ services:
ports: ports:
- "${BACKEND_HOST_PORT:-36969}:8000" - "${BACKEND_HOST_PORT:-36969}:8000"
volumes: volumes:
- ./backend:/app
- model_data_dev:/app/data/models - model_data_dev:/app/data/models
- nginx_logs_dev:/var/log/nginx - nginx_logs_dev:/var/log/nginx
depends_on: depends_on:
@ -95,6 +96,7 @@ services:
container_name: vigil-frontend-dev container_name: vigil-frontend-dev
environment: environment:
VITE_API_TARGET: http://backend:8000 VITE_API_TARGET: http://backend:8000
CI: "true"
ports: ports:
- "${FRONTEND_HOST_PORT:-46969}:5173" - "${FRONTEND_HOST_PORT:-46969}:5173"
volumes: volumes:

View File

@ -42,9 +42,12 @@
"vite": "npm:rolldown-vite@7.3.1", "vite": "npm:rolldown-vite@7.3.1",
"vite-tsconfig-paths": "^6.0.5" "vite-tsconfig-paths": "^6.0.5"
}, },
"overrides": {
"vite": "npm:rolldown-vite@7.3.1"
},
"pnpm": { "pnpm": {
"overrides": { "onlyBuiltDependencies": [
"vite": "npm:rolldown-vite@7.2.5" "@parcel/watcher"
} ]
} }
} }

View File

@ -4,9 +4,6 @@ settings:
autoInstallPeers: true autoInstallPeers: true
excludeLinksFromLockfile: false excludeLinksFromLockfile: false
overrides:
vite: npm:rolldown-vite@7.2.5
importers: importers:
.: .:
@ -62,7 +59,7 @@ importers:
version: 19.2.3(@types/react@19.2.14) version: 19.2.3(@types/react@19.2.14)
'@vitejs/plugin-react': '@vitejs/plugin-react':
specifier: ^5.1.2 specifier: ^5.1.2
version: 5.1.4(rolldown-vite@7.2.5(@types/node@25.3.2)(sass@1.97.3)) version: 5.1.4(rolldown-vite@7.3.1(@types/node@25.3.2)(sass@1.97.3))
sass: sass:
specifier: ^1.97.3 specifier: ^1.97.3
version: 1.97.3 version: 1.97.3
@ -79,11 +76,11 @@ importers:
specifier: ~5.9.3 specifier: ~5.9.3
version: 5.9.3 version: 5.9.3
vite: vite:
specifier: npm:rolldown-vite@7.2.5 specifier: npm:rolldown-vite@7.3.1
version: rolldown-vite@7.2.5(@types/node@25.3.2)(sass@1.97.3) version: rolldown-vite@7.3.1(@types/node@25.3.2)(sass@1.97.3)
vite-tsconfig-paths: vite-tsconfig-paths:
specifier: ^6.0.5 specifier: ^6.0.5
version: 6.1.1(rolldown-vite@7.2.5(@types/node@25.3.2)(sass@1.97.3))(typescript@5.9.3) version: 6.1.1(rolldown-vite@7.3.1(@types/node@25.3.2)(sass@1.97.3))(typescript@5.9.3)
packages: packages:
@ -321,12 +318,12 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
'@oxc-project/runtime@0.97.0': '@oxc-project/runtime@0.101.0':
resolution: {integrity: sha512-yH0zw7z+jEws4dZ4IUKoix5Lh3yhqIJWF9Dc8PWvhpo7U7O+lJrv7ZZL4BeRO0la8LBQFwcCewtLBnVV7hPe/w==} resolution: {integrity: sha512-t3qpfVZIqSiLQ5Kqt/MC4Ge/WCOGrrcagAdzTcDaggupjiGxUx4nJF2v6wUCXWSzWHn5Ns7XLv13fCJEwCOERQ==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
'@oxc-project/types@0.97.0': '@oxc-project/types@0.101.0':
resolution: {integrity: sha512-lxmZK4xFrdvU0yZiDwgVQTCvh2gHWBJCBk5ALsrtsBWhs0uDIi+FTOnXRQeQfs304imdvTdaakT/lqwQ8hkOXQ==} resolution: {integrity: sha512-nuFhqlUzJX+gVIPPfuE6xurd4lST3mdcWOhyK/rZO0B9XWMKm79SuszIQEnSMmmDhq1DC8WWVYGVd+6F93o1gQ==}
'@parcel/watcher-android-arm64@2.5.6': '@parcel/watcher-android-arm64@2.5.6':
resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==} resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==}
@ -416,95 +413,89 @@ packages:
resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
'@rolldown/binding-android-arm64@1.0.0-beta.50': '@rolldown/binding-android-arm64@1.0.0-beta.53':
resolution: {integrity: sha512-XlEkrOIHLyGT3avOgzfTFSjG+f+dZMw+/qd+Y3HLN86wlndrB/gSimrJCk4gOhr1XtRtEKfszpadI3Md4Z4/Ag==} resolution: {integrity: sha512-Ok9V8o7o6YfSdTTYA/uHH30r3YtOxLD6G3wih/U9DO0ucBBFq8WPt/DslU53OgfteLRHITZny9N/qCUxMf9kjQ==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64] cpu: [arm64]
os: [android] os: [android]
'@rolldown/binding-darwin-arm64@1.0.0-beta.50': '@rolldown/binding-darwin-arm64@1.0.0-beta.53':
resolution: {integrity: sha512-+JRqKJhoFlt5r9q+DecAGPLZ5PxeLva+wCMtAuoFMWPoZzgcYrr599KQ+Ix0jwll4B4HGP43avu9My8KtSOR+w==} resolution: {integrity: sha512-yIsKqMz0CtRnVa6x3Pa+mzTihr4Ty+Z6HfPbZ7RVbk1Uxnco4+CUn7Qbm/5SBol1JD/7nvY8rphAgyAi7Lj6Vg==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
'@rolldown/binding-darwin-x64@1.0.0-beta.50': '@rolldown/binding-darwin-x64@1.0.0-beta.53':
resolution: {integrity: sha512-fFXDjXnuX7/gQZQm/1FoivVtRcyAzdjSik7Eo+9iwPQ9EgtA5/nB2+jmbzaKtMGG3q+BnZbdKHCtOacmNrkIDA==} resolution: {integrity: sha512-GTXe+mxsCGUnJOFMhfGWmefP7Q9TpYUseHvhAhr21nCTgdS8jPsvirb0tJwM3lN0/u/cg7bpFNa16fQrjKrCjQ==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
'@rolldown/binding-freebsd-x64@1.0.0-beta.50': '@rolldown/binding-freebsd-x64@1.0.0-beta.53':
resolution: {integrity: sha512-F1b6vARy49tjmT/hbloplzgJS7GIvwWZqt+tAHEstCh0JIh9sa8FAMVqEmYxDviqKBaAI8iVvUREm/Kh/PD26Q==} resolution: {integrity: sha512-9Tmp7bBvKqyDkMcL4e089pH3RsjD3SUungjmqWtyhNOxoQMh0fSmINTyYV8KXtE+JkxYMPWvnEt+/mfpVCkk8w==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64] cpu: [x64]
os: [freebsd] os: [freebsd]
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.50': '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.53':
resolution: {integrity: sha512-U6cR76N8T8M6lHj7EZrQ3xunLPxSvYYxA8vJsBKZiFZkT8YV4kjgCO3KwMJL0NOjQCPGKyiXO07U+KmJzdPGRw==} resolution: {integrity: sha512-a1y5fiB0iovuzdbjUxa7+Zcvgv+mTmlGGC4XydVIsyl48eoxgaYkA3l9079hyTyhECsPq+mbr0gVQsFU11OJAQ==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
'@rolldown/binding-linux-arm64-gnu@1.0.0-beta.50': '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.53':
resolution: {integrity: sha512-ONgyjofCrrE3bnh5GZb8EINSFyR/hmwTzZ7oVuyUB170lboza1VMCnb8jgE6MsyyRgHYmN8Lb59i3NKGrxrYjw==} resolution: {integrity: sha512-bpIGX+ov9PhJYV+wHNXl9rzq4F0QvILiURn0y0oepbQx+7stmQsKA0DhPGwmhfvF856wq+gbM8L92SAa/CBcLg==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
'@rolldown/binding-linux-arm64-musl@1.0.0-beta.50': '@rolldown/binding-linux-arm64-musl@1.0.0-beta.53':
resolution: {integrity: sha512-L0zRdH2oDPkmB+wvuTl+dJbXCsx62SkqcEqdM+79LOcB+PxbAxxjzHU14BuZIQdXcAVDzfpMfaHWzZuwhhBTcw==} resolution: {integrity: sha512-bGe5EBB8FVjHBR1mOLOPEFg1Lp3//7geqWkU5NIhxe+yH0W8FVrQ6WRYOap4SUTKdklD/dC4qPLREkMMQ855FA==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl] libc: [musl]
'@rolldown/binding-linux-x64-gnu@1.0.0-beta.50': '@rolldown/binding-linux-x64-gnu@1.0.0-beta.53':
resolution: {integrity: sha512-gyoI8o/TGpQd3OzkJnh1M2kxy1Bisg8qJ5Gci0sXm9yLFzEXIFdtc4EAzepxGvrT2ri99ar5rdsmNG0zP0SbIg==} resolution: {integrity: sha512-qL+63WKVQs1CMvFedlPt0U9PiEKJOAL/bsHMKUDS6Vp2Q+YAv/QLPu8rcvkfIMvQ0FPU2WL0aX4eWwF6e/GAnA==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
'@rolldown/binding-linux-x64-musl@1.0.0-beta.50': '@rolldown/binding-linux-x64-musl@1.0.0-beta.53':
resolution: {integrity: sha512-zti8A7M+xFDpKlghpcCAzyOi+e5nfUl3QhU023ce5NCgUxRG5zGP2GR9LTydQ1rnIPwZUVBWd4o7NjZDaQxaXA==} resolution: {integrity: sha512-VGl9JIGjoJh3H8Mb+7xnVqODajBmrdOOb9lxWXdcmxyI+zjB2sux69br0hZJDTyLJfvBoYm439zPACYbCjGRmw==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl] libc: [musl]
'@rolldown/binding-openharmony-arm64@1.0.0-beta.50': '@rolldown/binding-openharmony-arm64@1.0.0-beta.53':
resolution: {integrity: sha512-eZUssog7qljrrRU9Mi0eqYEPm3Ch0UwB+qlWPMKSUXHNqhm3TvDZarJQdTevGEfu3EHAXJvBIe0YFYr0TPVaMA==} resolution: {integrity: sha512-B4iIserJXuSnNzA5xBLFUIjTfhNy7d9sq4FUMQY3GhQWGVhS2RWWzzDnkSU6MUt7/aHUrep0CdQfXUJI9D3W7A==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64] cpu: [arm64]
os: [openharmony] os: [openharmony]
'@rolldown/binding-wasm32-wasi@1.0.0-beta.50': '@rolldown/binding-wasm32-wasi@1.0.0-beta.53':
resolution: {integrity: sha512-nmCN0nIdeUnmgeDXiQ+2HU6FT162o+rxnF7WMkBm4M5Ds8qTU7Dzv2Wrf22bo4ftnlrb2hKK6FSwAJSAe2FWLg==} resolution: {integrity: sha512-BUjAEgpABEJXilGq/BPh7jeU3WAJ5o15c1ZEgHaDWSz3LB881LQZnbNJHmUiM4d1JQWMYYyR1Y490IBHi2FPJg==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
cpu: [wasm32] cpu: [wasm32]
'@rolldown/binding-win32-arm64-msvc@1.0.0-beta.50': '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.53':
resolution: {integrity: sha512-7kcNLi7Ua59JTTLvbe1dYb028QEPaJPJQHqkmSZ5q3tJueUeb6yjRtx8mw4uIqgWZcnQHAR3PrLN4XRJxvgIkA==} resolution: {integrity: sha512-s27uU7tpCWSjHBnxyVXHt3rMrQdJq5MHNv3BzsewCIroIw3DJFjMH1dzCPPMUFxnh1r52Nf9IJ/eWp6LDoyGcw==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
'@rolldown/binding-win32-ia32-msvc@1.0.0-beta.50': '@rolldown/binding-win32-x64-msvc@1.0.0-beta.53':
resolution: {integrity: sha512-lL70VTNvSCdSZkDPPVMwWn/M2yQiYvSoXw9hTLgdIWdUfC3g72UaruezusR6ceRuwHCY1Ayu2LtKqXkBO5LIwg==} resolution: {integrity: sha512-cjWL/USPJ1g0en2htb4ssMjIycc36RvdQAx1WlXnS6DpULswiUTVXPDesTifSKYSyvx24E0YqQkEm0K/M2Z/AA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ia32]
os: [win32]
'@rolldown/binding-win32-x64-msvc@1.0.0-beta.50':
resolution: {integrity: sha512-4qU4x5DXWB4JPjyTne/wBNPqkbQU8J45bl21geERBKtEittleonioACBL1R0PsBu0Aq21SwMK5a9zdBkWSlQtQ==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@rolldown/pluginutils@1.0.0-beta.50': '@rolldown/pluginutils@1.0.0-beta.53':
resolution: {integrity: sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==} resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==}
'@rolldown/pluginutils@1.0.0-rc.3': '@rolldown/pluginutils@1.0.0-rc.3':
resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==}
@ -1197,14 +1188,14 @@ packages:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'} engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
rolldown-vite@7.2.5: rolldown-vite@7.3.1:
resolution: {integrity: sha512-u09tdk/huMiN8xwoiBbig197jKdCamQTtOruSalOzbqGje3jdHiV0njQlAW0YvzoahkirFePNQ4RYlfnRQpXZA==} resolution: {integrity: sha512-LYzdNAjRHhF2yA4JUQm/QyARyi216N2rpJ0lJZb8E9FU2y5v6Vk+xq/U4XBOxMefpWixT5H3TslmAHm1rqIq2w==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
deprecated: Use 7.3.1 for migration purposes. For the most recent updates, migrate to Vite 8 once you're ready. deprecated: Use this package to migrate from Vite 7 to Vite 8. For the most recent updates, migrate to Vite 8 once you're ready.
hasBin: true hasBin: true
peerDependencies: peerDependencies:
'@types/node': ^20.19.0 || >=22.12.0 '@types/node': ^20.19.0 || >=22.12.0
esbuild: ^0.25.0 esbuild: ^0.27.0
jiti: '>=1.21.0' jiti: '>=1.21.0'
less: ^4.0.0 less: ^4.0.0
sass: ^1.70.0 sass: ^1.70.0
@ -1238,8 +1229,8 @@ packages:
yaml: yaml:
optional: true optional: true
rolldown@1.0.0-beta.50: rolldown@1.0.0-beta.53:
resolution: {integrity: sha512-JFULvCNl/anKn99eKjOSEubi0lLmNqQDAjyEMME2T4CwezUDL0i6t1O9xZsu2OMehPnV2caNefWpGF+8TnzB6A==} resolution: {integrity: sha512-Qd9c2p0XKZdgT5AYd+KgAMggJ8ZmCs3JnS9PTMWkyUfteKlfmKtxJbWTHkVakxwXs1Ub7jrRYVeFeF7N0sQxyw==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true hasBin: true
@ -1692,9 +1683,9 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5 '@nodelib/fs.scandir': 2.1.5
fastq: 1.20.1 fastq: 1.20.1
'@oxc-project/runtime@0.97.0': {} '@oxc-project/runtime@0.101.0': {}
'@oxc-project/types@0.97.0': {} '@oxc-project/types@0.101.0': {}
'@parcel/watcher-android-arm64@2.5.6': '@parcel/watcher-android-arm64@2.5.6':
optional: true optional: true
@ -1757,51 +1748,48 @@ snapshots:
'@parcel/watcher-win32-x64': 2.5.6 '@parcel/watcher-win32-x64': 2.5.6
optional: true optional: true
'@rolldown/binding-android-arm64@1.0.0-beta.50': '@rolldown/binding-android-arm64@1.0.0-beta.53':
optional: true optional: true
'@rolldown/binding-darwin-arm64@1.0.0-beta.50': '@rolldown/binding-darwin-arm64@1.0.0-beta.53':
optional: true optional: true
'@rolldown/binding-darwin-x64@1.0.0-beta.50': '@rolldown/binding-darwin-x64@1.0.0-beta.53':
optional: true optional: true
'@rolldown/binding-freebsd-x64@1.0.0-beta.50': '@rolldown/binding-freebsd-x64@1.0.0-beta.53':
optional: true optional: true
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.50': '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.53':
optional: true optional: true
'@rolldown/binding-linux-arm64-gnu@1.0.0-beta.50': '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.53':
optional: true optional: true
'@rolldown/binding-linux-arm64-musl@1.0.0-beta.50': '@rolldown/binding-linux-arm64-musl@1.0.0-beta.53':
optional: true optional: true
'@rolldown/binding-linux-x64-gnu@1.0.0-beta.50': '@rolldown/binding-linux-x64-gnu@1.0.0-beta.53':
optional: true optional: true
'@rolldown/binding-linux-x64-musl@1.0.0-beta.50': '@rolldown/binding-linux-x64-musl@1.0.0-beta.53':
optional: true optional: true
'@rolldown/binding-openharmony-arm64@1.0.0-beta.50': '@rolldown/binding-openharmony-arm64@1.0.0-beta.53':
optional: true optional: true
'@rolldown/binding-wasm32-wasi@1.0.0-beta.50': '@rolldown/binding-wasm32-wasi@1.0.0-beta.53':
dependencies: dependencies:
'@napi-rs/wasm-runtime': 1.1.1 '@napi-rs/wasm-runtime': 1.1.1
optional: true optional: true
'@rolldown/binding-win32-arm64-msvc@1.0.0-beta.50': '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.53':
optional: true optional: true
'@rolldown/binding-win32-ia32-msvc@1.0.0-beta.50': '@rolldown/binding-win32-x64-msvc@1.0.0-beta.53':
optional: true optional: true
'@rolldown/binding-win32-x64-msvc@1.0.0-beta.50': '@rolldown/pluginutils@1.0.0-beta.53': {}
optional: true
'@rolldown/pluginutils@1.0.0-beta.50': {}
'@rolldown/pluginutils@1.0.0-rc.3': {} '@rolldown/pluginutils@1.0.0-rc.3': {}
@ -1860,7 +1848,7 @@ snapshots:
dependencies: dependencies:
csstype: 3.2.3 csstype: 3.2.3
'@vitejs/plugin-react@5.1.4(rolldown-vite@7.2.5(@types/node@25.3.2)(sass@1.97.3))': '@vitejs/plugin-react@5.1.4(rolldown-vite@7.3.1(@types/node@25.3.2)(sass@1.97.3))':
dependencies: dependencies:
'@babel/core': 7.29.0 '@babel/core': 7.29.0
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0)
@ -1868,7 +1856,7 @@ snapshots:
'@rolldown/pluginutils': 1.0.0-rc.3 '@rolldown/pluginutils': 1.0.0-rc.3
'@types/babel__core': 7.20.5 '@types/babel__core': 7.20.5
react-refresh: 0.18.0 react-refresh: 0.18.0
vite: rolldown-vite@7.2.5(@types/node@25.3.2)(sass@1.97.3) vite: rolldown-vite@7.3.1(@types/node@25.3.2)(sass@1.97.3)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -2383,39 +2371,38 @@ snapshots:
reusify@1.1.0: {} reusify@1.1.0: {}
rolldown-vite@7.2.5(@types/node@25.3.2)(sass@1.97.3): rolldown-vite@7.3.1(@types/node@25.3.2)(sass@1.97.3):
dependencies: dependencies:
'@oxc-project/runtime': 0.97.0 '@oxc-project/runtime': 0.101.0
fdir: 6.5.0(picomatch@4.0.3) fdir: 6.5.0(picomatch@4.0.3)
lightningcss: 1.31.1 lightningcss: 1.31.1
picomatch: 4.0.3 picomatch: 4.0.3
postcss: 8.5.6 postcss: 8.5.6
rolldown: 1.0.0-beta.50 rolldown: 1.0.0-beta.53
tinyglobby: 0.2.15 tinyglobby: 0.2.15
optionalDependencies: optionalDependencies:
'@types/node': 25.3.2 '@types/node': 25.3.2
fsevents: 2.3.3 fsevents: 2.3.3
sass: 1.97.3 sass: 1.97.3
rolldown@1.0.0-beta.50: rolldown@1.0.0-beta.53:
dependencies: dependencies:
'@oxc-project/types': 0.97.0 '@oxc-project/types': 0.101.0
'@rolldown/pluginutils': 1.0.0-beta.50 '@rolldown/pluginutils': 1.0.0-beta.53
optionalDependencies: optionalDependencies:
'@rolldown/binding-android-arm64': 1.0.0-beta.50 '@rolldown/binding-android-arm64': 1.0.0-beta.53
'@rolldown/binding-darwin-arm64': 1.0.0-beta.50 '@rolldown/binding-darwin-arm64': 1.0.0-beta.53
'@rolldown/binding-darwin-x64': 1.0.0-beta.50 '@rolldown/binding-darwin-x64': 1.0.0-beta.53
'@rolldown/binding-freebsd-x64': 1.0.0-beta.50 '@rolldown/binding-freebsd-x64': 1.0.0-beta.53
'@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.50 '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.53
'@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.50 '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.53
'@rolldown/binding-linux-arm64-musl': 1.0.0-beta.50 '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.53
'@rolldown/binding-linux-x64-gnu': 1.0.0-beta.50 '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.53
'@rolldown/binding-linux-x64-musl': 1.0.0-beta.50 '@rolldown/binding-linux-x64-musl': 1.0.0-beta.53
'@rolldown/binding-openharmony-arm64': 1.0.0-beta.50 '@rolldown/binding-openharmony-arm64': 1.0.0-beta.53
'@rolldown/binding-wasm32-wasi': 1.0.0-beta.50 '@rolldown/binding-wasm32-wasi': 1.0.0-beta.53
'@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.50 '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.53
'@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.50 '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.53
'@rolldown/binding-win32-x64-msvc': 1.0.0-beta.50
run-parallel@1.2.0: run-parallel@1.2.0:
dependencies: dependencies:
@ -2603,12 +2590,12 @@ snapshots:
util-deprecate@1.0.2: {} util-deprecate@1.0.2: {}
vite-tsconfig-paths@6.1.1(rolldown-vite@7.2.5(@types/node@25.3.2)(sass@1.97.3))(typescript@5.9.3): vite-tsconfig-paths@6.1.1(rolldown-vite@7.3.1(@types/node@25.3.2)(sass@1.97.3))(typescript@5.9.3):
dependencies: dependencies:
debug: 4.4.3 debug: 4.4.3
globrex: 0.1.2 globrex: 0.1.2
tsconfck: 3.1.6(typescript@5.9.3) tsconfck: 3.1.6(typescript@5.9.3)
vite: rolldown-vite@7.2.5(@types/node@25.3.2)(sass@1.97.3) vite: rolldown-vite@7.3.1(@types/node@25.3.2)(sass@1.97.3)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
- typescript - typescript

View File

@ -31,11 +31,11 @@ COPY backend/pyproject.toml ./
RUN uv pip install --system -e ".[dev,ml]" RUN uv pip install --system -e ".[dev,ml]"
COPY backend/ . COPY backend/ .
COPY infra/docker/entrypoint.sh /app/entrypoint.sh COPY infra/docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /app/entrypoint.sh && \ RUN chmod +x /entrypoint.sh && \
mkdir -p /app/data/models mkdir -p /app/data/models
EXPOSE 8000 EXPOSE 8000
ENTRYPOINT ["/app/entrypoint.sh"] ENTRYPOINT ["/entrypoint.sh"]
CMD ["uvicorn", "app.factory:create_app", "--factory", "--host", "0.0.0.0", "--port", "8000", "--reload"] CMD ["uvicorn", "app.factory:create_app", "--factory", "--host", "0.0.0.0", "--port", "8000", "--reload"]

View File

@ -16,7 +16,7 @@ RUN corepack enable && corepack prepare pnpm@latest --activate
WORKDIR /app WORKDIR /app
COPY frontend/package.json ./ COPY frontend/package.json frontend/pnpm-lock.yaml* frontend/pnpm-workspace.yaml* ./
RUN pnpm install RUN pnpm install
COPY frontend/ . COPY frontend/ .

View File

@ -16,7 +16,7 @@ RUN corepack enable && corepack prepare pnpm@latest --activate
WORKDIR /app WORKDIR /app
COPY frontend/package.json ./ COPY frontend/package.json frontend/pnpm-lock.yaml* frontend/pnpm-workspace.yaml* ./
RUN pnpm install --frozen-lockfile RUN pnpm install --frozen-lockfile
COPY frontend/ . COPY frontend/ .