Swazz Security Review & Threat Model 🛡️
This document provides a comprehensive security review and threat model of the Swazz Smart API Fuzzer. It details the trust boundaries, cryptographic protocols, mitigations against Server-Side Request Forgery (SSRF), agent authentication flows, and schema resolution safety measures.
1. System Architecture & Trust Boundaries
Swazz operates as a hybrid architecture divided into three main components:
- Web Dashboard (
packages/web): The React-based frontend browser client. - Edge Coordinator (
packages/edge): A Cloudflare Hono Worker and Durable Object proxying commands, orchestrating runners, caching Swagger documents in Cloudflare R2, and storing metadata in SQLite D1. - Go Runner Agent (
packages/container): A high-performance execution engine running locally (CLI) or as a background service connected to the Edge Coordinator via WebSockets.
High-Level System Architecture & Component Interaction
Trust Levels & Boundaries
- User Dashboard to Edge API (HTTP/TLS): Users authenticate via username/password or token. Once authenticated, users can manage projects and trigger scans. Data access is restricted using row-level ownership checks (e.g. project/scan membership).
- Go Runner Agent to Edge Coordinator (WSS/TLS): The agent executes in a potentially untrusted environment (e.g., local developer machines or shared runner networks). It must authenticate before being permitted to receive jobs or upload spec parses.
- Go Runner Agent to Target API (HTTP/S): The fuzzer issues massive quantities of highly mutated and potentially malicious payloads. This outbound execution line is heavily guarded to prevent exploitation of the runner's own network.
2. Server-Side Request Forgery (SSRF) Protection
Because Swazz fetches OpenAPI specifications from user-supplied URLs and issues HTTP fuzzing requests to target systems, it is a high-value target for SSRF attacks. An attacker could register a target or supply a Swagger specification URL pointing to internal hosts (e.g., http://localhost:8080/admin or cloud metadata services like http://169.254.169.254/latest/meta-data/).
Swazz implements a dual-layer SSRF mitigation engine with DNS pinning to block these attacks.
SSRF Protection Layers
- Cloud Agent Mode (
packages/container/internal/safenet/safenet.go): When running in cloud/agent mode, outbound connections to private, loopback, link-local, and unspecified addresses are blocked by default.- Blocked CIDRs include:
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16(RFC 1918),127.0.0.0/8(Loopback),169.254.0.0/16(Link-local/Cloud Metadata),::1/128(IPv6 Loopback),fe80::/10(IPv6 Link-local), and0.0.0.0/8/::/128(Unspecified). - This check is bypassable for local debug setups only if
AllowLocalNetworkis explicitly configured.
- Blocked CIDRs include:
- Local CLI Mode (
packages/container/internal/security/ssrf.go): Provides custom SSRF protection wrapping the standard Go transport. By default, it restricts access to private IPs unless--allow-private-ipsis passed (defaulting tofalsein production environments, and configured via theSWAZZ_ALLOW_PRIVATE_IPSenvironment variable).
DNS Rebinding & Pinning Flow
To prevent Time-of-Check to Time-of-Use (TOCTOU) DNS rebinding attacks (where a DNS entry resolves to a public IP during verification but returns a private IP during connection execution), Swazz handles resolution and dial routing explicitly:
3. Agent-to-Coordinator Security & Authentication
Shared agents connect to the Cloudflare Durable Object coordinator to accept scan jobs. To prevent rogue runners from hijacking work, stealing user configurations, or injecting false reports, Swazz supports Ed25519 Cryptographic Challenge-Response Authentication.
Challenge-Response Flow
Safety Features
- Key Generation: Agents generate keypairs via cryptographically secure randomness (
ed25519.GenerateKey(nil)utilizingcrypto/rand). - Auth Timeout: The coordinator enforces a strict 5-second timeout on pending handshakes. If challenge verification is not completed within 5 seconds, the WebSocket is forcefully closed to mitigate resource exhaustion attacks.
4. User Session & Identity Management
Authentication and session controls on the Web platform enforce the following security parameters:
- Password Hashing: Done via PBKDF2 with SHA-256 using 100,000 iterations and a unique salt. The hashing and verification are performed on the server side using the Cloudflare WebCrypto API.
- JWT Authentication: Users receive a JSON Web Token (JWT) upon successful login, signed with a secure asymmetric key or environmental secret.
- Failed Login Rate Limiting:
- Keeps track of failed login attempts by username and IP address.
- Automatically locks out login capability for a user/IP after 5 failed attempts for a duration of 15 minutes.
- Turnstile Verification: The login and register pages validate a Cloudflare Turnstile token to deter bots and automated brute-force attacks.
- Data Isolation: Database queries validating project updates, scan retrievals, and deletions verify project membership using
checkProjectMembershipor scan membership usingcheckScanMembership.
JWT/Key Authentication Sequences
5. D1 Database & R2 Storage Caching Data Flow
To optimize performance and minimize remote runner overhead, the Edge Coordinator utilizes Cloudflare D1 for metadata indexing and Cloudflare R2 for storing schema documents.
Cache Lookup and Write Sequences
6. WebSocket / SSE Streaming Sequence
Real-time scanning feedback and vulnerability discoveries are reported immediately to the front-end dashboard using persistent streaming channels.
7. OpenAPI Schema Processing Safety (OOM & ReDoS Mitigation)
Large, complex, or circular OpenAPI specifications can crash parsing tools via memory exhaustion (OOM) or stack overflows. Swazz protects its engine with the following limits:
- DAG-based Resolution & Memoization: References (
$ref) are resolved and cached, transforming potential exponential-tree expansion into a Directed Acyclic Graph (DAG) with linear memory complexity. - Cycle Detection: Active resolution paths are tracked on a recursion stack. Recursive loops are broken immediately, returning a fallback representation.
- Node Budget: Enforces a strict budget of 50,000 SchemaProperty nodes per schema. Traversal is truncated if the budget is exceeded, saving the scanner from memory exhaustion.
- Recursion Depth Limit: A strict depth ceiling of 64 is enforced during schema expansion.
8. Supply Chain Security
To protect the Swazz project against supply chain compromises, the build and pipeline actions conform to strict configurations:
- Pinned Actions & Base Images: All GitHub Actions configurations and Dockerfiles pin dependencies and base images to specific, immutable SHA-256 commit hashes (rather than mutable version tags like
latestorv1). - Dependency Audit: The build process runs automated static code analysis (
gosecfor Go backend, npm security audit for frontend) to discover code and dependency vulnerabilities. - Excluded Rules: The
.gosec.confexcludesG404(use of weak random numbers). Since Swazz is a fuzzer, utilizing fast pseudo-random values (viamath/rand) is required for payload variation speed. Cryptographically sensitive operations (challenge signatures, cryptographic nonces, etc.) bypassmath/randin favor of standard secure libraries (crypto/rand).
9. Lifetime Username Lock via Secure Hashing
To prevent username recycling or hijacking (where an account is deleted and its username is subsequently registered by another user to impersonate or hijack the identity), Swazz implements a lifetime username lock mechanism:
- Salted SHA-256 Hashing: Upon registration, a secure SHA-256 hash of the normalized, lower-cased username combined with a pepper/salt constant is generated.
- Persistent Username Registry: This hash is stored in a dedicated
username_registrytable. - GDPR-Compliant Deletion: When an account is deleted or purged (Right to be Forgotten), all records from the
users,projects,scans, andrunnerstables are cascaded and fully purged. However, the record inusername_registryis never deleted. - Hijack Prevention: The registration endpoint verifies the requested username's hash against
username_registryand rejects the request if a match is found, ensuring once a username is registered, it remains forever locked and reserved.