C-2: JWT moved from localStorage to httpOnly cookie (eliminates XSS token theft) C-3: WebSocket auth via short-lived single-use tickets (JWT no longer in URLs) H-1: JWT expiry reduced from 7 days to 4 hours H-3: TOTP secrets encrypted at rest with vault EncryptionService (auto-migrates plaintext) H-6: Rate limiting via @nestjs/throttler (60 req/min global, tighten on auth) H-8: Constant-time login — Argon2id verify runs against dummy hash for non-existent users H-9: Password hashing upgraded from bcrypt(10) to Argon2id (auto-upgrades on login) H-10: Credential list API no longer returns encrypted blobs H-16: Admin pages use Nuxt route middleware instead of client-side guard Plus: auth bootstrap plugin, cookie-parser middleware, all frontend Authorization headers removed Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
370 lines
20 KiB
Markdown
370 lines
20 KiB
Markdown
# Wraith Remote — Security Audit Report
|
|
|
|
**Date:** 2026-03-14
|
|
**Auditor:** Claude (Opus 4.6) — secure-code-guardian + security-reviewer + ISO 27001 frameworks
|
|
**Scope:** Full-stack — Auth, Vault, WebSocket/SSH/SFTP/RDP, Frontend, Infrastructure, ISO 27001 gap assessment
|
|
**Codebase:** RDP-SSH-Client (Nuxt 3 + NestJS + guacd)
|
|
|
|
---
|
|
|
|
## Executive Summary
|
|
|
|
**54 unique findings** across 4 audit domains after deduplication.
|
|
|
|
| Severity | Count |
|
|
|----------|-------|
|
|
| CRITICAL | 8 |
|
|
| HIGH | 16 |
|
|
| MEDIUM | 18 |
|
|
| LOW | 12 |
|
|
|
|
The platform has a solid encryption foundation (Argon2id vault is well-implemented) but has significant gaps in transport security, session management, infrastructure hardening, and real-time channel authorization. The most urgent issues are **unauthenticated guacd exposure**, **JWT in localStorage/URLs**, and **missing session ownership checks on WebSocket channels**.
|
|
|
|
---
|
|
|
|
## CRITICAL Findings (8)
|
|
|
|
### C-1. guacd exposed on all interfaces via `network_mode: host`
|
|
**Location:** `docker-compose.yml:23`
|
|
**Domain:** Infrastructure
|
|
|
|
guacd runs with `network_mode: host` and binds to `0.0.0.0:4822`. guacd is an **unauthenticated** service — anyone who can reach port 4822 can initiate RDP/VNC connections to any host reachable from the Docker host. This completely bypasses all application-level authentication.
|
|
|
|
**Impact:** Full unauthenticated RDP/VNC access to every target host in the environment.
|
|
|
|
**Fix:** Remove `network_mode: host`. Place guacd on the internal Docker network. Bind to `127.0.0.1`. The app container connects via the Docker service name `guacd` over the internal network.
|
|
|
|
---
|
|
|
|
### C-2. JWT stored in localStorage (XSS → full account takeover)
|
|
**Location:** `frontend/stores/auth.store.ts:19,42`
|
|
**Domain:** Auth / Frontend
|
|
|
|
`wraith_token` JWT stored in `localStorage`. Any XSS payload, browser extension, or injected script can read it. The token has a 7-day lifetime with no revocation mechanism — a stolen token is valid for up to a week with no way to invalidate it.
|
|
|
|
**Impact:** Single XSS vulnerability → 7-day persistent access to the victim's account, including all SSH/RDP sessions and stored credentials.
|
|
|
|
**Fix:** Issue JWT via `Set-Cookie: httpOnly; Secure; SameSite=Strict`. Remove all `localStorage` token operations. Browser automatically attaches the cookie to every request.
|
|
|
|
---
|
|
|
|
### C-3. JWT passed in WebSocket URL query parameters
|
|
**Location:** `backend/src/auth/ws-auth.guard.ts:11-13`, all three WS gateways
|
|
**Domain:** Auth / WebSocket
|
|
|
|
All WebSocket connections (`/api/ws/terminal`, `/api/ws/sftp`, `/api/ws/rdp`) accept JWT via `?token=<jwt>` in the URL. Query parameters are logged by: web server access logs, browser history, Referrer headers, network proxies, and the application itself (`main.ts:75` logs `req.url`).
|
|
|
|
**Impact:** JWT exposure in every log and monitoring system in the path. Combined with C-2, this creates multiple extraction vectors for a 7-day-lived credential.
|
|
|
|
**Fix:** Issue short-lived (30-second) single-use WebSocket tickets via an authenticated REST endpoint. Frontend exchanges JWT for a ticket, connects WS with `?ticket=<nonce>`. Server validates and destroys the ticket on use.
|
|
|
|
---
|
|
|
|
### C-4. No HTTPS/TLS anywhere in the stack
|
|
**Location:** `docker-compose.yml`, `backend/src/main.ts`
|
|
**Domain:** Infrastructure
|
|
|
|
No TLS termination configured. No nginx reverse proxy. No `helmet()` middleware. The application serves over plain HTTP. JWT tokens, SSH passwords, and TOTP codes all transit in cleartext.
|
|
|
|
**Impact:** Any network observer (same Wi-Fi, ISP, network tap) can intercept credentials, tokens, and terminal data.
|
|
|
|
**Fix:** Add nginx with TLS termination in front of the app. Install `helmet()` in NestJS for security headers (HSTS, X-Frame-Options, X-Content-Type-Options). Enforce HTTPS-only.
|
|
|
|
---
|
|
|
|
### C-5. SSH host key verification auto-accepts all keys (MITM blind spot)
|
|
**Location:** `terminal.gateway.ts:61`, `ssh-connection.service.ts:98-119`
|
|
**Domain:** SSH
|
|
|
|
`hostVerifier` callback returns `true` unconditionally. New fingerprints are silently accepted. **Changed** fingerprints (active MITM) are also silently accepted and overwrite the stored fingerprint.
|
|
|
|
**Impact:** Man-in-the-middle attacker between the Wraith server and SSH target is completely invisible. Attacker gets the decrypted credentials and a live shell.
|
|
|
|
**Fix:** Block connections to hosts with changed fingerprints. Require explicit user acceptance via a WS round-trip for new hosts. Never auto-accept changed fingerprints.
|
|
|
|
---
|
|
|
|
### C-6. SFTP gateway has no session ownership check (horizontal privilege escalation)
|
|
**Location:** `sftp.gateway.ts:36-215`
|
|
**Domain:** SFTP / Authorization
|
|
|
|
`SftpGateway.handleMessage()` looks up sessions by caller-supplied `sessionId` without verifying the requesting WebSocket client owns that session. User B can supply User A's `sessionId` and get full filesystem access on User A's server.
|
|
|
|
**Impact:** Any authenticated user can read/write/delete files on any other user's active SSH session.
|
|
|
|
**Fix:** Maintain a `clientSessions` map in `SftpGateway` (same pattern as `TerminalGateway`). Verify session ownership before every SFTP operation.
|
|
|
|
---
|
|
|
|
### C-7. Raw Guacamole instructions forwarded to guacd without validation
|
|
**Location:** `rdp.gateway.ts:43-47`
|
|
**Domain:** RDP
|
|
|
|
When `msg.type === 'guac'`, raw `msg.instruction` is written directly to the guacd TCP socket. Zero parsing, validation, or opcode whitelisting. The Guacamole protocol supports `file`, `put`, `pipe`, `disconnect`, and other instructions.
|
|
|
|
**Impact:** Authenticated user can inject arbitrary Guacamole protocol instructions — write files via guacd file transfer, crash guacd via malformed instructions, or cause protocol desync.
|
|
|
|
**Fix:** Parse incoming instructions via `guacamole.service.ts` `decode()`. Whitelist permitted opcodes (`input`, `mouse`, `key`, `size`, `sync`, `disconnect`). Enforce per-message size limit. Reject anything that doesn't parse.
|
|
|
|
---
|
|
|
|
### C-8. PostgreSQL port exposed to host network
|
|
**Location:** `docker-compose.yml:27`
|
|
**Domain:** Infrastructure
|
|
|
|
`ports: ["4211:5432"]` maps PostgreSQL to the host. Without a host-level firewall rule, the database is network-accessible. Contains encrypted credentials, SSH private keys, TOTP secrets, password hashes.
|
|
|
|
**Impact:** Direct database access from the network. Even with password auth, the attack surface is unnecessary.
|
|
|
|
**Fix:** Remove the `ports` mapping. Only the app container needs DB access via the internal Docker network. Use `docker exec` for admin access.
|
|
|
|
---
|
|
|
|
## HIGH Findings (16)
|
|
|
|
### H-1. 7-day JWT with no revocation mechanism
|
|
**Location:** `backend/src/auth/auth.module.ts:14`
|
|
**Domain:** Auth
|
|
|
|
JWTs signed with 7-day expiry. No token blocklist, no session table, no refresh token pattern. Admin password reset, TOTP reset, and role changes do not invalidate existing tokens.
|
|
|
|
**Fix:** Short-lived access token (15min) + refresh token in httpOnly cookie. Or: Redis-backed blocklist checked on every request.
|
|
|
|
### H-2. RDP certificate verification hardcoded to disabled
|
|
**Location:** `rdp.gateway.ts:90`, `guacamole.service.ts:142`
|
|
**Domain:** RDP
|
|
|
|
`ignoreCert: true` hardcoded unconditionally. Every RDP connection accepts any certificate — MITM attacks are invisible.
|
|
|
|
**Fix:** Store `ignoreCert` as a per-host setting (default `false`). Surface a UI warning when enabled.
|
|
|
|
### H-3. TOTP secret stored as plaintext in database
|
|
**Location:** `users` table, `totp_secret` column
|
|
**Domain:** Auth / Vault
|
|
|
|
TOTP secrets stored unencrypted. If the database is compromised (C-8 makes this plausible), attacker can generate valid TOTP codes for every user with 2FA enabled, completely defeating the second factor.
|
|
|
|
**Fix:** Encrypt TOTP secrets using the vault's `EncryptionService` (Argon2id v2) before storage. Decrypt only when validating a TOTP code.
|
|
|
|
### H-4. SSH private key material logged in cleartext
|
|
**Location:** `ssh-connection.service.ts:126-129`
|
|
**Domain:** SSH / Logging
|
|
|
|
First 40 characters of decrypted private key, key length, and passphrase existence boolean logged to stdout. Docker routes stdout to `docker logs`, which may be shipped to external log aggregation.
|
|
|
|
**Fix:** Remove lines 126-129 entirely. Log only key type and fingerprint.
|
|
|
|
### H-5. Terminal keystroke data logged (passwords in sudo prompts)
|
|
**Location:** `terminal.gateway.ts:31`
|
|
**Domain:** WebSocket / Logging
|
|
|
|
`JSON.stringify(msg).substring(0, 200)` logs raw terminal keystrokes including passwords typed at `sudo` prompts. 200-char truncation still captures most passwords.
|
|
|
|
**Fix:** For `msg.type === 'data'`, log only `{ type: 'data', sessionId, bytes: msg.data?.length }`.
|
|
|
|
### H-6. No rate limiting on authentication endpoints
|
|
**Location:** Entire backend — no throttler installed
|
|
**Domain:** Auth / Infrastructure
|
|
|
|
No `@nestjs/throttler`, no `express-rate-limit`. Login endpoint accepts unlimited attempts. Combined with 6-character minimum password = viable online brute-force.
|
|
|
|
**Fix:** Install `@nestjs/throttler`. Apply tight limit on auth endpoints (10 req/min/IP). Add account lockout after repeated failures.
|
|
|
|
### H-7. Container runs as root
|
|
**Location:** `Dockerfile:19-28`
|
|
**Domain:** Infrastructure
|
|
|
|
Final Docker stage runs as `root`. Any code execution vulnerability (path traversal, injection) gives root access inside the container.
|
|
|
|
**Fix:** Add `RUN addgroup -S wraith && adduser -S wraith -G wraith` and `USER wraith` before `CMD`.
|
|
|
|
### H-8. Timing attack on login (bcrypt comparison)
|
|
**Location:** `auth.service.ts` login handler
|
|
**Domain:** Auth
|
|
|
|
Failed login for non-existent user returns faster than for existing user (skips bcrypt comparison). Enables username enumeration via timing analysis.
|
|
|
|
**Fix:** Always run `bcrypt.compare()` against a dummy hash when user not found, ensuring constant-time response.
|
|
|
|
### H-9. bcrypt cost factor is 10 (below modern recommendations)
|
|
**Location:** `auth.service.ts`
|
|
**Domain:** Auth
|
|
|
|
bcrypt cost 10 = ~100ms on modern hardware. OWASP recommends 12+ for password hashing.
|
|
|
|
**Fix:** Increase to `bcrypt.hash(password, 12)`. Existing hashes auto-upgrade on next login.
|
|
|
|
### H-10. `findAll` credentials endpoint leaks encrypted blobs
|
|
**Location:** `credentials.service.ts` / `credentials.controller.ts`
|
|
**Domain:** Vault
|
|
|
|
The `GET /api/credentials` response includes `encryptedValue` fields. While encrypted, exposing ciphertext over the API gives attackers material for offline analysis and is unnecessary — the frontend never needs the encrypted blob.
|
|
|
|
**Fix:** Add `select` clause to exclude `encryptedValue` from list responses.
|
|
|
|
### H-11. No upload size limit on SFTP
|
|
**Location:** `sftp.gateway.ts:130-138`
|
|
**Domain:** SFTP
|
|
|
|
`upload` handler does `Buffer.from(msg.data, 'base64')` with no size check. An authenticated user can send multi-gigabyte payloads, exhausting server memory.
|
|
|
|
**Fix:** Check `msg.data.length` before `Buffer.from()`. Enforce max (e.g., 50MB base64 = ~37MB file). Set `maxPayload` on WebSocket server config.
|
|
|
|
### H-12. No write size limit on SFTP file editor
|
|
**Location:** `sftp.gateway.ts:122-128`
|
|
**Domain:** SFTP
|
|
|
|
`write` handler (save from Monaco editor) has no size check. `MAX_EDIT_SIZE` exists but is only applied to `read`.
|
|
|
|
**Fix:** Apply `MAX_EDIT_SIZE` check on the write path.
|
|
|
|
### H-13. Shell integration injected into remote sessions without consent
|
|
**Location:** `ssh-connection.service.ts:59-65`
|
|
**Domain:** SSH
|
|
|
|
`PROMPT_COMMAND` / `precmd_functions` modification injected into every SSH shell for CWD tracking. Users are not informed. If this injection were modified (supply chain, code change), it would execute on every connected host.
|
|
|
|
**Fix:** Make opt-in. Document the behavior. Scope injection to minimum needed.
|
|
|
|
### H-14. Password auth credentials logged with username and host
|
|
**Location:** `ssh-connection.service.ts:146`
|
|
**Domain:** SSH / Logging
|
|
|
|
Logs `username@host:port` for every password-authenticated connection. Creates a persistent record correlating users to targets.
|
|
|
|
**Fix:** Log at DEBUG only. Use `hostId` instead of hostname.
|
|
|
|
### H-15. guacd routing via `host.docker.internal` bypasses container isolation
|
|
**Location:** `docker-compose.yml:9`
|
|
**Domain:** Infrastructure
|
|
|
|
App-to-guacd traffic routes out of the container network, through the host, and back. Unnecessary external routing path.
|
|
|
|
**Fix:** After fixing C-1, both services on the same Docker network. Use service name `guacd` as hostname.
|
|
|
|
### H-16. Client-side-only admin guard
|
|
**Location:** `frontend/pages/admin/users.vue:4-6`
|
|
**Domain:** Frontend
|
|
|
|
`if (!auth.isAdmin) navigateTo('/')` is a UI redirect, not access control. Can be bypassed during hydration gaps.
|
|
|
|
**Fix:** Backend `AdminGuard` handles the real enforcement. Add proper route middleware (`definePageMeta({ middleware: 'admin' })`) for consistent frontend behavior.
|
|
|
|
---
|
|
|
|
## MEDIUM Findings (18)
|
|
|
|
| # | Finding | Location | Domain |
|
|
|---|---------|----------|--------|
|
|
| M-1 | Terminal gateway no session ownership check on `data`/`resize`/`disconnect` | `terminal.gateway.ts:76-79` | WebSocket |
|
|
| M-2 | TOTP replay possible (no used-code tracking) | `auth.service.ts` | Auth |
|
|
| M-3 | Email change has no verification step | `users.controller.ts` | Auth |
|
|
| M-4 | Email uniqueness not enforced at DB level | `users` table | Auth |
|
|
| M-5 | Password minimum length is 6 chars (NIST says 8+, OWASP says 12+) | Frontend + backend DTOs | Auth |
|
|
| M-6 | JWT_SECRET has no startup validation | `auth.module.ts` | Auth |
|
|
| M-7 | TOTP secret returned in setup response (exposure window) | `auth.controller.ts` | Auth |
|
|
| M-8 | Mass assignment via object spread in update endpoints | Multiple controllers | API |
|
|
| M-9 | CORS config may not behave as expected in production | `main.ts:24-27` | Infrastructure |
|
|
| M-10 | Weak `.env.example` defaults (`DB_PASSWORD=changeme`) | `.env.example` | Infrastructure |
|
|
| M-11 | Seed script runs on every container start | `Dockerfile:28` | Infrastructure |
|
|
| M-12 | File paths logged for every SFTP operation | `sftp.gateway.ts:27` | Logging |
|
|
| M-13 | SFTP `delete` falls through from `unlink` to `rmdir` silently | `sftp.gateway.ts:154-165` | SFTP |
|
|
| M-14 | Unbounded TCP buffer for guacd stream (no max size) | `rdp.gateway.ts:100-101` | RDP |
|
|
| M-15 | Connection log `updateMany` closes sibling sessions | `ssh-connection.service.ts:178-181` | SSH |
|
|
| M-16 | RDP `security`/`width`/`height`/`dpi` params not validated | `rdp.gateway.ts:85-89` | RDP |
|
|
| M-17 | Frontend file upload has no client-side size validation | `SftpSidebar.vue:64-73` | Frontend |
|
|
| M-18 | Error messages from server reflected to UI verbatim | `login.vue:64` | Frontend |
|
|
|
|
---
|
|
|
|
## LOW Findings (12)
|
|
|
|
| # | Finding | Location | Domain |
|
|
|---|---------|----------|--------|
|
|
| L-1 | No Content Security Policy header | `main.ts` | Frontend |
|
|
| L-2 | No WebSocket connection limit per user | `terminal.gateway.ts:8` | WebSocket |
|
|
| L-3 | Internal error messages forwarded to WS clients | `terminal.gateway.ts:34-35`, `rdp.gateway.ts:51` | WebSocket |
|
|
| L-4 | Server timezone leaked in Guacamole CONNECT | `guacamole.service.ts:81-85` | RDP |
|
|
| L-5 | SFTP event listeners re-registered on every message | `sftp.gateway.ts:53-58` | SFTP |
|
|
| L-6 | Default SSH username falls back to `root` | `ssh-connection.service.ts:92` | SSH |
|
|
| L-7 | Weak seed password for default admin | `seed.js` | Infrastructure |
|
|
| L-8 | SSH fingerprint derived from private key (should use public) | `ssh-keys.service.ts` | Vault |
|
|
| L-9 | `console.error` used instead of structured logger | Multiple files | Logging |
|
|
| L-10 | `confirm()` used for SFTP delete | `SftpSidebar.vue:210` | Frontend |
|
|
| L-11 | Settings mirrored to localStorage unnecessarily | `default.vue:25-27` | Frontend |
|
|
| L-12 | No DTO validation on admin password reset | `auth.controller.ts` | Auth |
|
|
|
|
---
|
|
|
|
## ISO 27001:2022 Gap Assessment
|
|
|
|
| Control | Status | Gap |
|
|
|---------|--------|-----|
|
|
| **A.5 — Security Policies** | MISSING | No security policies, incident response plan, or vulnerability disclosure process |
|
|
| **A.6 — Security Roles** | MISSING | No defined security responsibilities or RACI for incidents |
|
|
| **A.8.1 — Asset Management** | MISSING | No data classification scheme (SSH keys, TOTP secrets, credentials treated uniformly) |
|
|
| **A.8.5 — Access Control** | PARTIAL | Auth exists but: no brute-force protection, no account lockout, no session revocation, only 2 roles (admin/user) with no least-privilege granularity |
|
|
| **A.8.9 — Configuration Mgmt** | FAIL | guacd on host network, DB port exposed, no security headers |
|
|
| **A.8.15 — Logging** | FAIL | No structured audit log. Sensitive data IN logs. No failed login tracking |
|
|
| **A.8.16 — Monitoring** | MISSING | No anomaly detection, no alerting on repeated auth failures |
|
|
| **A.8.24 — Cryptography** | PARTIAL | Vault encryption is excellent (Argon2id). But: no TLS, tokens in URLs, TOTP unencrypted, keys in logs |
|
|
| **A.8.25 — Secure Development** | MISSING | No SAST, no dependency scanning, no security testing |
|
|
| **A.8.28 — Secure Coding** | MISSING | No documented coding standard, no input validation framework |
|
|
|
|
---
|
|
|
|
## Prioritized Remediation Roadmap
|
|
|
|
### Phase 1 — Stop the Bleeding (do this week)
|
|
|
|
| Priority | Finding | Effort | Impact |
|
|
|----------|---------|--------|--------|
|
|
| 1 | **C-1:** Fix guacd `network_mode: host` | 30 min | Closes unauthenticated backdoor to entire infrastructure |
|
|
| 2 | **C-8:** Remove PostgreSQL port exposure | 5 min | Closes direct DB access from network |
|
|
| 3 | **C-6:** Add session ownership to SFTP gateway | 1 hr | Blocks cross-user file access |
|
|
| 4 | **H-4:** Remove private key logging | 15 min | Stop bleeding secrets to logs |
|
|
| 5 | **H-5:** Stop logging terminal keystroke data | 15 min | Stop logging passwords |
|
|
| 6 | **H-11:** Add upload size limit | 15 min | Block memory exhaustion DoS |
|
|
|
|
### Phase 2 — Auth Hardening (next sprint)
|
|
|
|
| Priority | Finding | Effort | Impact |
|
|
|----------|---------|--------|--------|
|
|
| 7 | **C-2 + C-3:** Move JWT to httpOnly cookie + WS ticket auth | 4 hr | Eliminates primary token theft vectors |
|
|
| 8 | **C-4:** Add TLS termination (nginx + Let's Encrypt) | 2 hr | Encrypts all traffic |
|
|
| 9 | **H-1:** Short-lived access + refresh token | 3 hr | Limits exposure window of stolen tokens |
|
|
| 10 | **H-6:** Rate limiting on auth endpoints | 1 hr | Blocks brute-force |
|
|
| 11 | **H-3:** Encrypt TOTP secrets in DB | 1 hr | Protects 2FA if DB compromised |
|
|
| 12 | **M-5:** Increase password minimum to 12 chars | 15 min | NIST/OWASP compliance |
|
|
|
|
### Phase 3 — Channel Hardening (following sprint)
|
|
|
|
| Priority | Finding | Effort | Impact |
|
|
|----------|---------|--------|--------|
|
|
| 13 | **C-5:** SSH host key verification (block changed fingerprints) | 3 hr | Blocks MITM on SSH |
|
|
| 14 | **C-7:** Guacamole instruction validation + opcode whitelist | 2 hr | Blocks protocol injection |
|
|
| 15 | **H-2:** RDP cert validation (per-host configurable) | 2 hr | Blocks MITM on RDP |
|
|
| 16 | **M-1:** Terminal gateway session ownership check | 30 min | Blocks cross-user terminal access |
|
|
| 17 | **H-7:** Run container as non-root | 30 min | Limits blast radius of any RCE |
|
|
|
|
### Phase 4 — Hardening & Compliance (ongoing)
|
|
|
|
Everything in MEDIUM and LOW, plus ISO 27001 documentation gaps. Most are incremental improvements that can be addressed as part of normal development.
|
|
|
|
---
|
|
|
|
## What's Actually Good
|
|
|
|
Credit where due — these areas are solid:
|
|
|
|
- **Vault encryption (Argon2id v2)** — OWASP-recommended parameters, per-record salts, backwards-compatible versioning, migration endpoint. This is production-grade.
|
|
- **Credential isolation** — `decryptForConnection()` is internal-only, never exposed over API. Correct pattern.
|
|
- **Per-user data isolation** — Users can only see their own credentials and SSH keys (ownership checks in vault services).
|
|
- **TOTP 2FA implementation** — Correct TOTP flow with QR code generation (aside from the plaintext storage issue).
|
|
- **Password hashing** — bcrypt is correct choice (cost factor should increase, but the algorithm is right).
|
|
- **Admin guards on backend** — `AdminGuard` properly enforces server-side. Not just frontend checks.
|
|
|
|
---
|
|
|
|
*Report generated by 4 parallel audit agents covering Auth/JWT/Session, Vault/Encryption/DB, WebSocket/SSH/SFTP/RDP, and Frontend/Infrastructure/ISO 27001. Deduplicated from 70+ raw findings to 54 unique issues.*
|