From bee0591ea114d875fede08c5a1a2f8dc26461314 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Sun, 22 Feb 2026 08:40:50 +0100 Subject: [PATCH] safety: enforce JWT secret min length, validate order symbol input - Require JWT_SECRET >= 32 characters at startup (prevents weak secrets) - Add SECURITY comment documenting auth.rs login as dev-only stub - Add symbol input validation in OrderForm: uppercase, alphanumeric with dots/hyphens/underscores, max 20 chars, visual error state Co-Authored-By: Claude Opus 4.6 --- web-dashboard/src/components/trading/OrderForm.tsx | 13 +++++++++++-- web-gateway/src/main.rs | 5 ++++- web-gateway/src/routes/auth.rs | 5 +++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/web-dashboard/src/components/trading/OrderForm.tsx b/web-dashboard/src/components/trading/OrderForm.tsx index 57e301566..91b910d71 100644 --- a/web-dashboard/src/components/trading/OrderForm.tsx +++ b/web-dashboard/src/components/trading/OrderForm.tsx @@ -18,8 +18,11 @@ export function OrderForm() { const submitOrder = useApiPost('/trading/orders'); + const symbolValid = /^[A-Z0-9._-]{1,20}$/.test(symbol); + function handleSubmit(e: FormEvent) { e.preventDefault(); + if (!symbolValid) return; const qty = parseFloat(quantity); if (isNaN(qty) || qty <= 0) return; @@ -52,8 +55,14 @@ export function OrderForm() { setSymbol(e.target.value)} - className="w-full px-2 py-1.5 text-sm rounded border border-[var(--color-border)] bg-[var(--color-bg-primary)] text-[var(--color-text-primary)] focus:border-[var(--color-accent)] outline-none" + onChange={(e) => setSymbol(e.target.value.toUpperCase())} + maxLength={20} + pattern="[A-Z0-9._-]+" + className={`w-full px-2 py-1.5 text-sm rounded border bg-[var(--color-bg-primary)] text-[var(--color-text-primary)] outline-none ${ + symbolValid + ? 'border-[var(--color-border)] focus:border-[var(--color-accent)]' + : 'border-[var(--color-red)]' + }`} /> diff --git a/web-gateway/src/main.rs b/web-gateway/src/main.rs index a99edca59..d704de499 100644 --- a/web-gateway/src/main.rs +++ b/web-gateway/src/main.rs @@ -16,10 +16,13 @@ async fn main() -> Result<()> { let config = GatewayConfig::from_env(); - // Fail fast if JWT secret is not configured + // Fail fast if JWT secret is not configured or too short if config.jwt_secret.is_empty() { bail!("JWT_SECRET environment variable must be set (non-empty) for token validation"); } + if config.jwt_secret.len() < 32 { + bail!("JWT_SECRET must be at least 32 characters for adequate security"); + } let listen_addr = config.listen_addr.clone(); diff --git a/web-gateway/src/routes/auth.rs b/web-gateway/src/routes/auth.rs index 8b8c9c53f..3372fdde9 100644 --- a/web-gateway/src/routes/auth.rs +++ b/web-gateway/src/routes/auth.rs @@ -21,12 +21,13 @@ pub fn router() -> Router { Router::new().route("/login", routing::post(login)) } +// SECURITY: This is a development-only login stub. Before production deployment, +// replace with real authentication (LDAP, OAuth2, bcrypt-verified credentials, etc.). +// Currently accepts any non-empty username/password combination. async fn login( State(state): State, Json(body): Json, ) -> Result, AppError> { - // Validate credentials against the configured secret - // In production, this would check against a user database or auth service if body.username.is_empty() || body.password.is_empty() { return Err(AppError::BadRequest("Username and password required".into())); }