[Security Testing & Hardening]

Comprehensive security testing infrastructure, auth hardening, and CI/CD security scanning for the Hoox trading platform.

Overview

Security work spans three layers:

  1. Code Hardening — Fixing auth vulnerabilities (fail-closed middleware, timing-safe comparisons)
  2. Security Testing — Auth bypass tests, fuzz tests, header verification
  3. CI/CD Scanning — Dependency auditing, secret detection, CodeQL analysis

1. Auth Middleware Hardening

requireInternalAuth — Fail-Closed

File: packages/shared/src/middleware/auth.ts

Previously, requireInternalAuth() returned null (allowed the request through) when INTERNAL_KEY_BINDING was not configured. This meant workers with unconfigured auth keys were wide open.

Fix: The middleware now returns 401 Unauthorized when the key is missing:

// Before: auth was optional when key not set
if (!expectedKey) return null;

// After: fails closed
if (!expectedKey) {
  return new Response(
    JSON.stringify({
      success: false,
      error: `Internal auth key ${keyName} not configured`,
    }),
    { status: 401, headers: { "Content-Type": "application/json" } }
  );
}

timingSafeEqual — Exported for Cross-Worker Use

File: packages/shared/src/middleware/auth.ts

The timingSafeEqual() function was a private helper used only internally by requireAuth() and requireInternalAuth(). It's now exported so all workers can use it for constant-time string comparisons.

Webhook API Key — Timing-Safe Comparison

File: workers/hoox/src/index.ts

The hoox gateway's webhook API key validation was changed from === to timingSafeEqual() to prevent timing side-channel attacks:

// Before (vulnerable to timing attack):
const isValid = apiKey === expectedKey;

// After (constant-time comparison):
const isValid = timingSafeEqual(apiKey, expectedKey);

2. Auth Coverage by Worker

All workers now have authentication on their internal endpoints:

WorkerAuth MethodProtected EndpointsUnprotected
hooxAPI key in body (timing-safe) + IP allowlistPOST /webhookGET /health
d1-workerX-Internal-Auth-KeyPOST /query, POST /batch, GET /api/*GET /health
trade-workerX-Internal-Auth-KeyPOST /webhook, POST /processGET /health, GET /api/signals
agent-workerX-Internal-Auth-KeyAll POST /agent/*, GET /agent/status|config|models|healthGET /
analytics-workerX-Internal-Auth-KeyAll POST /track/* (5 endpoints)GET /health
telegram-workerX-Internal-Auth-KeyPOST /processGET /health, POST /webhook (Telegram secret)
email-workerX-Internal-Auth-KeyPOST /email-signalGET /health, POST /webhook (Mailgun HMAC)
report-workerX-Internal-Auth-KeyGET /reportGET /health
web3-wallet-workerX-Internal-Auth-KeyGET / (wallet address)GET /health
dashboardSession cookieAll routes/login, /api/auth, /_next/*

Note

Health check endpoints (GET /health) are intentionally left unauthenticated for monitoring systems.


3. Shared Security Headers Middleware

File: packages/shared/src/middleware/security-headers.ts

A reusable middleware that provides 7 standard security headers following the same pattern as the existing cors.ts middleware.

API

// Get headers as a plain object
const headers = secureHeaders({ contentSecurityPolicy: "default-src 'self'" });

// Wrap an existing Response with security headers
const secure = wrapWithSecurityHeaders(response);

// Customize individual headers
const custom = secureHeaders({
  xFrameOptions: "SAMEORIGIN",
  contentSecurityPolicy: "", // Disable CSP
});

Default Headers

HeaderDefault Value
X-Content-Type-Optionsnosniff
X-Frame-OptionsDENY
X-XSS-Protection1; mode=block
Referrer-Policystrict-origin-when-cross-origin
Permissions-PolicyAll features disabled
Strict-Transport-Securitymax-age=31536000; includeSubDomains
Content-Security-Policydefault-src 'self'

Dashboard

File: workers/dashboard/src/middleware.ts

The dashboard was upgraded from 2 headers (X-Content-Type-Options, X-Frame-Options) to the full 7-header set via the shared middleware. The withSecurityHeaders() wrapper ensures headers are applied on all return paths (including early returns for static files, login pages, CSRF errors, and auth failures).


4. Security Test Suites

Auth Bypass Tests

File: tests/security/auth-bypass.test.ts Tests: 12

Validates:

  • requireInternalAuth returns 401 when key is not configured (fail-closed)
  • requireInternalAuth returns 401 with missing/wrong auth header
  • requireInternalAuth passes with valid key
  • timingSafeEqual correctness: identical strings, different lengths, different same-length strings, empty strings, special characters, long keys

Security Headers Tests

File: tests/security/security-headers.test.ts Tests: 9

Validates:

  • All 7 default headers are present
  • Individual header overrides work
  • CSP can be disabled (set to empty string)
  • wrapWithSecurityHeaders preserves body, status, and existing headers
  • Custom options pass through

Fuzz Tests

File: tests/security/fuzz.test.ts Tests: 19

Validates:

  • Auth enforcement on d1-worker /query (valid, missing, wrong key)
  • SQL injection attempts via auth headers (' OR '1'='1, UNION injection)
  • timingSafeEqual edge cases: Unicode homoglyphs, null bytes, 10k-char strings, regex special chars, emoji
  • Request edge cases: missing Content-Type, GET to POST, OPTIONS preflight, extremely long headers
  • Webhook payload edge cases: non-JSON body, empty body, unconfigured key (fail-closed)

Running Security Tests

# Run all security tests
bun test tests/security/

# Run specific test files
bun test tests/security/auth-bypass.test.ts
bun test tests/security/security-headers.test.ts
bun test tests/security/fuzz.test.ts

5. CI/CD Security Scanning

Dependency Audit

File: .github/workflows/ci.yml

A bun audit step runs in CI after unit tests to detect known vulnerabilities in dependencies. It's configured with continue-on-error: true so it doesn't block development, but findings are visible in workflow output.

Secret Scanning

File: .github/workflows/secret-scan.yml Config: .gitleaks.toml

Uses gitleaks/gitleaks-action@v2 to detect hardcoded secrets (API keys, tokens, passwords) across the full git history. Runs on:

  • Push/PR to main and develop
  • Weekly (Sunday)
  • Manual trigger via workflow_dispatch

Information only (continue-on-error: true) — findings don't block CI.

Secret Allowlist

The .gitleaks.toml allowlists:

  • Test files (.test., .spec., tests/)
  • Example/template files (.example, .env.example)
  • Documentation and changelogs
  • Generated files (dist/, .next/, coverage/)
  • Lock files (bun.lock, package-lock.json)
  • Placeholder patterns (__SECRET__, YOUR_, <USE_WRANGLER_SECRET_PUT>)

CodeQL

File: .github/workflows/codeql.yml

Weekly security analysis with security-and-quality query suite for JavaScript/TypeScript.

Dependabot

File: .github/dependabot.yml

Daily npm and GitHub Actions dependency checks with automatic PR creation.


6. CI Pipeline Security Flow


7. Performance & Load Testing

File: tests/load/ Tool: k6 (separate from bun test)

See the tests/load/ directory for full documentation.

ScriptTargetDescription
webhook-flow.jshoox POST /webhookTradingView alert simulation
d1-query-load.jsd1-worker POST /query+batchConcurrent D1 query patterns
agent-cron-sim.jsagent-worker5-minute cron cycle simulation
system-mixed.jsAll combined70/20/10 traffic distribution

CI: Nightly via .github/workflows/load-test.yml (informational thresholds).


8. Environment Setup

Required GitHub Secrets

For CI security scanning to work, add these to your repository:

SecretUsed ByPurpose
GITHUB_TOKENsecret-scan.ymlgitleaks PR annotations (auto-provided)
INTERNAL_AUTH_KEYload-test.ymlInternal worker auth for load tests
HOOX_API_KEYload-test.ymlWebhook API key for load tests
LOAD_TEST_BASE_URLload-test.ymlTarget URL for load tests

9. Related Documentation