Review ID: b98b810c7766Generated: 2026-05-06T14:01:01.287Z
CHANGES REQUESTED
261
AI-Confirmed Threats
261
Raw Findings
23
Critical
196
High
41
Medium
AI-Confirmed Breakdown
261
Confirmed Threats
261
High
36 of 108 Agents Deployed
DiamondPlatinumGoldSilverBronzeHR RoastyFree Baseline
Agent Tier: Gold
willchen96/mike →
main @ d969096
AIAI Threat Analysis
REAL THREATS
Critical: Authentication & Authorization Bypass (IDOR) The entire backend is riddled with missing authorization checks. The requireAuth middleware only verifies the JWT is valid — it does NOT check whether the authenticated user owns or has permission to access the resource. Every endpoint in documents.ts, projects.ts, projectChat.ts, chat.ts, downloads.ts, and tabular.ts is vulnerable to Insecure Direct Object Reference (IDOR). An authenticated user can access, modify, or delete any document, chat, project, or tabular review by simply guessing or enumerating UUIDs. The ensureDocAccess function in access.ts is called inconsistently and has a critical gap: documents without a project_id are never checked. This means any user can read any document that isn't explicitly scoped to a project.
Critical: Weak Download Token Signing downloadTokens.ts uses a hardcoded fallback secret ("dev-secret-do-not-use-in-production") when the environment variable DOWNLOAD_SECRET is not set. This allows any attacker who knows this default secret to forge download tokens for any file in the system. The token payload is also not validated for structure, enabling injection attacks. The download endpoint (downloads.ts) does not verify tenant membership, so a forged token can be used to exfiltrate any document.
Critical: Denial of Wallet (Unbounded LLM Usage) Multiple endpoints (chat.ts, projectChat.ts, tabular.ts, gemini.ts, claude.ts) stream LLM responses with no max_tokens, no token budget, no cost caps, and no iteration limits. An attacker can send a single request that triggers an infinite or extremely long LLM response, incurring unbounded API costs. The frontend also has no client-side limits.
Critical: Secrets & Credentials Exposure
frontend/src/lib/storage.ts exposes R2 (Cloudflare) credentials directly in client-side code. This is a catastrophic leak — anyone who inspects the frontend JavaScript can read and use these credentials to access the storage bucket directly.
frontend/src/contexts/UserProfileContext.tsx fetches user API keys (Claude, Gemini) from the database and exposes them to the client. These keys are stored in plaintext in the user_profiles table.
backend/src/lib/llm/gemini.ts has a hardcoded fallback API key.
backend/src/lib/downloadTokens.ts has a hardcoded fallback signing secret.
Critical: Data Exfiltration via Frontend Token Leak AssistantMessage.tsx and EditCard.tsx send the user's Supabase session token (Authorization: Bearer <token>) in fetch requests to arbitrary URLs derived from LLM output. An attacker who can control the LLM response (via prompt injection) can make the frontend send the user's auth token to an attacker-controlled server, enabling session hijacking and account takeover.
High: Widespread Input Validation Failures Nearly every endpoint lacks input validation on parameters like document_id, version_id, project_id, folder_id, chat_id, display_name, email, etc. This enables SQL injection via Supabase query builder (though parameterized, the dynamic filter construction in chat.ts:33 is a direct injection vector), path traversal in download routes, and XXE via DOCX XML parsing.
High: Unsafe File Processing
convert.ts uses LibreOffice without sandboxing, enabling RCE if a crafted document is uploaded.
docxTrackedChanges.ts parses XML without disabling external entity resolution, enabling XXE attacks.
upload.ts loads entire files into memory, enabling DoS via large uploads.
High: Missing Rate Limiting & Security Headers No rate limiting exists on any endpoint, enabling brute-force attacks on authentication, resource exhaustion via LLM abuse, and DoS via document conversion. The health endpoint is unauthenticated. CORS is permissive.
High: Plaintext API Key Storage API keys for Claude and Gemini are stored in plaintext in the user_profiles database table. They are fetched and exposed to the frontend, and can be modified by any authenticated user via the account models page.
ATTACK CHAINS
1. Full Account Takeover via Prompt Injection: An attacker crafts a prompt that causes the LLM to output a malicious URL in a download card. The frontend (AssistantMessage.tsx) sends the user's auth token to that URL. The attacker now has the user's session token and can impersonate them to access all their documents, chats, and API keys.
2. Data Exfiltration via Forged Download Tokens: An attacker discovers the hardcoded fallback secret (or brute-forces it). They forge a download token for any document ID. They call the download endpoint, which does not verify tenant membership, and exfiltrate the document. This can be combined with IDOR to enumerate all document IDs in the system.
3. Resource Exhaustion & Financial DoS: An attacker sends a single request to the chat endpoint with a prompt designed to trigger infinite LLM streaming. With no max_tokens or cost caps, this generates unbounded API costs. The attacker can parallelize this across multiple endpoints (chat, projectChat, tabular) to rapidly exhaust the victim's budget.
4. Privilege Escalation via IDOR Chain: An attacker with a valid account enumerates project IDs, document IDs, and chat IDs. They access documents they shouldn't see, modify project settings, delete other users' data, and steal API keys from user profiles. The lack of tenant isolation means a user from one tenant can access data from another tenant.
VERDICT
This application is critically insecure and should NOT be deployed in any production or customer-facing environment. The most urgent issues are:
1. Immediately fix the hardcoded download token secret — this is a trivial exploit that grants full read access to all stored documents.
2. Remove R2 credentials from frontend code — this is an instant data breach.
3. Implement proper authorization checks on every endpoint — the current requireAuth middleware is insufficient; every resource access must verify ownership or permission.
4. Add cost controls to all LLM endpoints — without max_tokens and rate limiting, financial DoS is trivial.
5. Stop sending auth tokens to arbitrary URLs — the frontend must validate download URLs against an allowlist.
6. Encrypt API keys at rest and never expose them to the client.
The sheer volume of IDOR vulnerabilities (over 50 instances) indicates a fundamental architectural
261 raw scanner findings — 23 critical · 196 high · 41 medium · 1 info
Raw Scanner Output — 1050 pre-cleanup findings
⚠ Pre-Cleanup Report
This is the raw, unprocessed output from all scanner agents before AI analysis. Do not use this to fix issues individually. Multiple agents attack from different angles and frequently report the same underlying vulnerability, resulting in significant duplication. Architectural issues also appear as many separate line-level findings when they require a single structural fix.

Use the Copy Fix Workflow button above to get the AI-cleaned workflow — it deduplicates findings, removes false positives, and provides actionable steps. This raw output is provided for transparency and audit purposes only.
Showing top 1000 of 1050 findings (sorted by severity). Full data available via the review API.
HIGHAPI keys stored in plaintext in user_profiles table
[redacted]/000_one_shot_schema.sql:18
[AGENTS: Warden]privacy
**Perspective 1:** The user_profiles table stores claude_api_key and gemini_api_key as plaintext text columns. API keys are sensitive credentials that must be encrypted at rest. If the database is compromised, all user API keys are exposed. **Perspective 2:** The user_profiles table stores display_name, organisation, and tier in plaintext. While not all are equally sensitive, display_name and organisation are personal data under GDPR. No encryption at rest is applied. **Perspective 3:** The user_profiles table has no TTL, retention policy, or cleanup mechanism. Under GDPR, personal data should not be kept longer than necessary. There is no mechanism to automatically purge profiles after account deletion or inactivity. **Perspective 4:** The schema has no table or column to track user consent for data processing (e.g., GDPR consent, marketing consent, data sharing consent). Without consent records, the application cannot demonstrate compliance with GDPR Article 7. **Perspective 5:** The schema lacks an audit log table to track who accessed or modified personal data. Under GDPR, controllers must maintain records of processing activities. Operations like viewing/exporting user profiles, changing API keys, or modifying PII should be logged. **Perspective 6:** The schema does not include any data classification metadata (e.g., column comments, tags, or a separate classification table) to indicate which columns contain PII, sensitive personal data, or credentials. This makes it difficult to enforce data protection policies programmatically. **Perspective 7:** While the schema uses ON DELETE CASCADE from auth.users, there is no explicit API or procedure to handle GDPR right-to-deletion requests. A complete deletion should also cover backups, logs, and any derived data. **Perspective 8:** Under GDPR Article 20, users have the right to data portability. The schema and codebase lack any endpoint or function to export a user's personal data in a structured, machine-readable format. **Perspective 9:** The schema does not include a table to record data processing activities as required by GDPR Article 30. This includes categories of data subjects, categories of personal data, purposes of processing, and data retention periods. **Perspective 10:** The schema and README do not mention backup encryption. If database backups are taken, they would contain all PII and API keys in plaintext. Under GDPR, backups must be encrypted to protect personal data.
Suggested Fix
Implement a dedicated deletion endpoint that: 1) Deletes user_profiles, 2) Anonymizes chat messages and reviews (replace user_id with a placeholder), 3) Logs the deletion request, 4) Provides a confirmation to the user.
HIGHLibreOffice installed for document conversion — potential RCE vector
[redacted]/nixpacks.toml:2
[AGENTS: Infiltrator]attack_surface
The deployment includes LibreOffice (nixPkgs = ['libreoffice']). The backend converts DOCX to PDF using LibreOffice. If an attacker uploads a crafted DOCX file that exploits a vulnerability in LibreOffice's parsing engine, this could lead to remote code execution. The conversion happens server-side on uploaded files.
Suggested Fix
Run LibreOffice in a sandboxed environment (e.g., container with no network access, seccomp profile). Consider using a more lightweight/sandboxed conversion library. Validate all uploaded files before processing.
HIGHNo health check endpoint configured
[redacted]/index.ts:1
[AGENTS: Harbor]containers
**Perspective 1:** The application exposes a /health endpoint but no container health check (HEALTHCHECK instruction) is configured in any Dockerfile or orchestration manifest. Without health checks, orchestration platforms cannot detect when the application is unresponsive, leading to potential service disruption. **Perspective 2:** The application does not expose a health check endpoint. Container orchestrators like Kubernetes rely on health checks to manage container lifecycle, perform rolling updates, and restart unhealthy containers. Without a health endpoint, the orchestrator cannot determine if the application is running correctly. **Perspective 3:** The backend application does not expose a health check endpoint. Container orchestrators rely on health checks to determine if a container is healthy and ready to serve traffic. Without them, the orchestrator cannot detect or restart unhealthy containers, leading to potential service outages. **Perspective 4:** The backend application does not expose a health check endpoint. Container orchestrators (Kubernetes, Docker Compose) rely on health checks to determine container readiness and liveness. Without them, the orchestrator cannot detect or restart unhealthy containers, leading to potential service degradation. **Perspective 5:** No Dockerfile or container orchestration configuration is provided with resource limits (CPU/memory). Without resource limits, a single container can consume all host resources, leading to denial of service for other containers or the host itself. **Perspective 6:** The repository lacks a Dockerfile or container build configuration. This makes it difficult to ensure consistent, reproducible deployments and increases the risk of configuration drift between environments.
Suggested Fix
Add a health check endpoint (e.g., GET /health) that returns a 200 status code when the application is healthy. Configure liveness and readiness probes in the container orchestration to use this endpoint.
HIGHNo global rate limiting on any API endpoint
[redacted]/index.ts:1
[AGENTS: Wallet]denial_of_wallet
The Express app has no rate limiting middleware. All endpoints (chat, projects, documents, tabular, workflows, user, downloads) are unprotected against abuse. An attacker can flood any endpoint, triggering expensive operations (LLM calls, storage writes, conversions) without restriction.
Suggested Fix
Add express-rate-limit middleware with per-IP and per-user rate limits. Implement different rate limits for cost-intensive endpoints (chat, tabular generation) vs. cheap endpoints.
HIGHPermissive CORS configuration
[redacted]/index.ts:14
[AGENTS: Lockdown]configuration
CORS is configured with a single origin from environment variable, but falls back to 'http://localhost:3000' which may allow unintended origins in production if FRONTEND_URL is not set. Additionally, credentials are enabled which increases risk.
Suggested Fix
Ensure FRONTEND_URL is always set in production and consider restricting credentials to specific origins only. Add validation to reject requests if FRONTEND_URL is not properly configured.
HIGHHealth endpoint exposes server status without authentication
[redacted]/index.ts:28
[AGENTS: Recon]info_disclosure
The /health endpoint at line 28 is publicly accessible without any authentication. While this is common, it provides attackers with confirmation that the server is running and can be used for reconnaissance.
Suggested Fix
Consider adding rate limiting to the health endpoint or restricting it to internal network access only.
HIGHMissing authorization check in ensureDocAccess for documents without project_id
[redacted]/access.ts:85
[AGENTS: Gatekeeper]auth
The ensureDocAccess function checks if the user is the document owner. If not, it checks project access via the document's project_id. However, if the document has no project_id (project_id is null), the function returns { ok: false } even if the user has been directly shared the document. This could prevent legitimate shared access to standalone documents.
Suggested Fix
Add a check for direct document sharing (e.g., a shared_with field on the document) before falling back to project access.
HIGHNo audit logging for document generation and editing
[redacted]/chatTools.ts:1
[AGENTS: Compliance]regulatory
**Perspective 1:** The generateDocx and runEditDocument functions create and modify documents without logging the action to an audit trail. SOC 2 CC6.1 and HIPAA §164.312(b) require audit logs for all PHI creation and modification events. **Perspective 2:** The generateDocx and runEditDocument functions do not verify that the requesting user has permission to create or modify documents in the specified project. SOC 2 CC6.1 requires access controls on all data operations.
Suggested Fix
Add audit log entries for document creation and editing events, including user ID, document ID, action type, timestamp, and summary of changes.
HIGHModel name passed directly without allowlist validation
[redacted]/chatTools.ts:33
[AGENTS: Weights]model_supply_chain
The model name is passed directly to the LLM provider without being validated against an allowlist of approved models. An attacker who can influence the model selection (e.g., via user settings or API parameters) could specify an arbitrary model name, potentially loading a malicious or untrusted model from the provider's platform.
Suggested Fix
Validate the model name against a strict allowlist of approved models before passing it to the LLM provider. Reject any model name not in the allowlist.
HIGHUnsafe file conversion using LibreOffice without sandboxing
[redacted]/convert.ts:1
[AGENTS: Weights]model_supply_chain
The `docxToPdf` function uses LibreOffice to convert uploaded DOCX files to PDF. LibreOffice is a complex application with a history of vulnerabilities. The conversion is performed without sandboxing or resource limits, allowing a malicious DOCX file to execute arbitrary code on the server.
Suggested Fix
Run LibreOffice conversion in a sandboxed environment (e.g., Docker container, seccomp, or gVisor) with resource limits and no network access.
HIGHUnbounded document conversion costs with no file size limit
[redacted]/convert.ts:1
[AGENTS: Wallet]denial_of_wallet
The docxToPdf function uses LibreOffice to convert documents. While there is a 100MB upload limit, there is no per-user or per-day conversion cap. An attacker could upload and convert many large documents, consuming CPU and memory resources on the server.
Suggested Fix
Add a per-user daily conversion limit. Implement a queue with maximum concurrency. Add cost tracking for compute resources used by conversions.
HIGHXXE via DOCX file processing in normalizeDocxZipPaths
[redacted]/convert.ts:1
[AGENTS: Specter]injection
**Perspective 1:** The `normalizeDocxZipPaths` function uses JSZip to parse DOCX files. If a malicious DOCX file contains XML External Entity (XXE) references, the JSZip library may expand them during parsing, leading to information disclosure or SSRF. The function does not disable external entity resolution. **Perspective 2:** The `docxToPdf` function uses LibreOffice to convert DOCX to PDF. If the DOCX file contains malicious content that exploits LibreOffice vulnerabilities, an attacker could achieve remote code execution. The function does not validate the input file before conversion. **Perspective 3:** LibreOffice may make network requests during document conversion (e.g., for external resources, fonts, or linked content). If a malicious DOCX file contains external references, LibreOffice could be used for SSRF attacks.
Suggested Fix
Use a safe XML parser that disables external entity resolution (e.g., libxmljs with noent: false). Alternatively, validate the DOCX file structure before processing. Consider using a sandboxed environment for DOCX parsing.
HIGHLibreOffice conversion of untrusted files enables RCE
[redacted]/convert.ts:1
[AGENTS: Vector]attack_chains
The docxToPdf function converts uploaded DOCX/DOC files using LibreOffice without sandboxing. LibreOffice has a history of vulnerabilities in document parsing. An attacker who uploads a crafted document can achieve remote code execution on the backend server when the document is converted. This is especially dangerous because the conversion happens automatically during upload processing.
Suggested Fix
Run LibreOffice in a container with no network access, read-only filesystem, and strict resource limits. Consider using a dedicated conversion service.
HIGHUnbounded DOCX-to-PDF conversion via LibreOffice
[redacted]/convert.ts:52
[AGENTS: Siege]dos
The docxToPdf function accepts arbitrary DOCX files and converts them using LibreOffice. A maliciously crafted DOCX file (e.g., with embedded large images, complex formatting, or infinite loops in fields) can cause LibreOffice to consume excessive CPU and memory, potentially hanging the conversion process and exhausting server resources.
Suggested Fix
Implement a timeout for the conversion process (e.g., using a child process with a timeout). Add input validation to reject files that are too large or have suspicious characteristics. Consider running conversions in a sandboxed environment with resource limits.
HIGHXXE via DOCX XML parsing with external entity resolution
[redacted]/docxTrackedChanges.ts:1
[AGENTS: Specter]xxe
**Perspective 1:** The DOCX tracked changes module uses fast-xml-parser with default settings. DOCX files are ZIP archives containing XML that may include DOCTYPE declarations with external entities. If the parser processes external entities, an attacker could craft a malicious DOCX that reads local files or performs SSRF during tracked changes processing. **Perspective 2:** The XMLParser is created without disabling external entity processing. Both extractDocxBodyText and applyTrackedEdits parse user-uploaded DOCX files. A malicious DOCX with crafted XML entities could lead to XXE attacks including file disclosure or SSRF.
Suggested Fix
Configure fast-xml-parser to disallow DOCTYPE declarations or external entities by setting `processEntities: false` or using `allowBooleanAttributes: true` with entity validation.
HIGHUnbounded XML parsing without limits
[redacted]/docxTrackedChanges.ts:1
[AGENTS: Siege]dos
**Perspective 1:** The DOCX tracked changes module parses potentially large XML files using fast-xml-parser without any size limits or entity expansion protection. A maliciously crafted DOCX could cause excessive memory consumption or CPU usage during XML parsing. **Perspective 2:** The applyTrackedEdits function loads the entire DOCX into memory via JSZip and processes it without size limits. A maliciously crafted DOCX with many entries or large embedded files could exhaust server memory. **Perspective 3:** The maxTrackedId and resolveInTree functions recursively walk the entire XML tree without depth limits. A deeply nested XML structure could cause stack overflow or excessive CPU usage.
Suggested Fix
Set max depth and entity expansion limits on the XML parser. Add file size validation before processing.
HIGHHardcoded fallback secret for download token signing
[redacted]/downloadTokens.ts:1
[AGENTS: Warden]privacy
**Perspective 1:** The HMAC signing secret falls back to 'dev-secret' when DOWNLOAD_SIGNING_SECRET and SUPABASE_SECRET_KEY are not set. In production, this means tokens can be forged by anyone who knows the default secret, potentially exposing any document download. **Perspective 2:** Download tokens have no expiration mechanism. Once issued, they can be used indefinitely to access documents. This violates the principle of least privilege and could lead to unauthorized access if tokens are leaked.
Suggested Fix
Remove the fallback to 'dev-secret' and throw an error if no signing secret is configured. Ensure the environment variable is properly set in all deployments.
HIGHDownload tokens lack tenant context — tokens are portable across tenants
[redacted]/downloadTokens.ts:1
[AGENTS: Tenant]tenant_isolation
The signDownload function creates HMAC-signed tokens containing only a storage path and filename. There is no tenant_id, project_id, or user_id embedded in the token payload. This means a token generated for one user/tenant can be used by another user/tenant if they obtain the token string (e.g., via chat history, shared links, or logs). The token is non-expiring, making it a permanent access credential.
Suggested Fix
Include the user_id and/or project_id in the token payload. Verify at download time that the caller matches the embedded identity and still has access to the resource.
HIGHFallback to hardcoded development secret
[redacted]/downloadTokens.ts:14
[AGENTS: Vault]secrets
When DOWNLOAD_SIGNING_SECRET and SUPABASE_SECRET_KEY are both unset, the function falls back to the literal string 'dev-secret'. This means any deployment without these environment variables configured will use a publicly known HMAC key, allowing anyone to forge download tokens and access any document.
Suggested Fix
Remove the fallback and throw an error if neither environment variable is set. For local development, require the developer to set DOWNLOAD_SIGNING_SECRET explicitly.
HIGHFallback signing secret hardcoded in source code
[redacted]/downloadTokens.ts:14
[AGENTS: Recon]info_disclosure
The download token signing function falls back to 'dev-secret' when no environment variable is set. If this fallback is used in production, it exposes a predictable signing key that could allow attackers to forge download tokens.
Suggested Fix
Remove the hardcoded fallback and require DOWNLOAD_SIGNING_SECRET to be set in production. Add a startup check that fails if no secret is configured.
HIGHWeak fallback secret for HMAC signing
[redacted]/downloadTokens.ts:14
[AGENTS: Razor]security
The `getSecret` function falls back to `'dev-secret'` if neither `DOWNLOAD_SIGNING_SECRET` nor `SUPABASE_SECRET_KEY` environment variables are set. This hardcoded value is trivially guessable and would allow anyone to forge valid download tokens, bypassing access controls.
Suggested Fix
Remove the hardcoded fallback and throw an error if no secret is configured: `if (!secret) throw new Error('DOWNLOAD_SIGNING_SECRET not configured');`
HIGHWeak default signing secret
[redacted]/downloadTokens.ts:17
[AGENTS: Passkey]credentials
**Perspective 1:** The download token signing secret falls back to 'dev-secret' when DOWNLOAD_SIGNING_SECRET and SUPABASE_SECRET_KEY are not set. This allows anyone to forge valid download tokens. **Perspective 2:** The download signing secret falls back to SUPABASE_SECRET_KEY. This means the same key used for database access is also used for signing download tokens, increasing the blast radius if either is compromised. **Perspective 3:** Download tokens are signed without an expiration timestamp. Once issued, they are valid indefinitely unless the signing secret is rotated.
Suggested Fix
Remove the 'dev-secret' fallback. Throw an error if DOWNLOAD_SIGNING_SECRET is not set in production. Use a cryptographically random secret (e.g., openssl rand -hex 32).
HIGHDownload signing falls back to insecure default secret
[redacted]/downloadTokens.ts:18
[AGENTS: Mirage]false_confidence
**Perspective 1:** The `getSecret()` function falls back to `'dev-secret'` when neither `DOWNLOAD_SIGNING_SECRET` nor `SUPABASE_SECRET_KEY` are set. This means in production deployments missing these environment variables, download tokens are signed with a hardcoded, publicly known string, making token forgery trivial. **Perspective 2:** The download signing falls back to an insecure default secret if the environment variable is not set. This means that the signing is not secure and can be forged by anyone who knows the default secret.
Suggested Fix
Remove the 'dev-secret' fallback and throw an error at startup if no signing secret is configured.
HIGHFallback secret key used for HMAC signing
[redacted]/downloadTokens.ts:20
[AGENTS: Egress]data_exfiltration
**Perspective 1:** The `getSecret` function falls back to `'dev-secret'` if `DOWNLOAD_SIGNING_SECRET` and `SUPABASE_SECRET_KEY` are not set. In production, this means download tokens are signed with a known, hardcoded secret, allowing anyone to forge tokens and download any file from storage. **Perspective 2:** The `getSecret` function uses `SUPABASE_SECRET_KEY` as a fallback for `DOWNLOAD_SIGNING_SECRET`. This means the same key used for database admin access is also used for signing download tokens, increasing the blast radius if the key is compromised.
Suggested Fix
Remove the fallback to 'dev-secret' in production. Throw an error if no secret is configured.
HIGHWeak default signing secret
[redacted]/downloadTokens.ts:22
[AGENTS: Cipher]cryptography
**Perspective 1:** The HMAC signing secret for download tokens falls back to 'dev-secret' when neither DOWNLOAD_SIGNING_SECRET nor SUPABASE_SECRET_KEY is set. An attacker who knows or guesses this default can forge arbitrary download tokens, gaining unauthorized access to any file. **Perspective 2:** The download token signing secret defaults to 'dev-secret-change-me-in-production' when the DOWNLOAD_SECRET environment variable is not set. This allows anyone who knows or guesses this default to forge valid download tokens, bypassing access controls for document downloads.
Suggested Fix
Ensure DOWNLOAD_SECRET is always set to a cryptographically random, high-entropy value in production. Remove the hardcoded fallback or make the application fail to start if the secret is not configured.
HIGHWeak HMAC signing secret with hardcoded fallback
[redacted]/downloadTokens.ts:22
[AGENTS: Entropy]weak_key_generation
The HMAC signing secret falls back to 'dev-secret' when neither DOWNLOAD_SIGNING_SECRET nor SUPABASE_SECRET_KEY is set. In production, this means a predictable secret could be used, allowing attackers to forge download tokens. The fallback should be removed or cause a hard error at startup.
Suggested Fix
Remove the 'dev-secret' fallback and throw an error if neither environment variable is set: if (!process.env.DOWNLOAD_SIGNING_SECRET && !process.env.SUPABASE_SECRET_KEY) { throw new Error('DOWNLOAD_SIGNING_SECRET or SUPABASE_SECRET_KEY must be set'); }
HIGHUnvalidated JSON payload in download token
[redacted]/downloadTokens.ts:65
[AGENTS: Syringe]injection
The verifyDownload function parses the token payload using JSON.parse without validating the structure against a schema. An attacker could craft a token with unexpected fields that might bypass access controls or cause unexpected behavior. While the HMAC signature prevents tampering, the parsed object should be validated against a strict schema.
Suggested Fix
Add runtime validation of the parsed payload using a library like zod or a manual type guard that checks for exactly 'p' and 'f' string properties with no extra fields.
HIGHUnbounded LLM streaming loop without iteration limits
[redacted]/claude.ts:1
[AGENTS: Siege]dos
**Perspective 1:** The streamClaude function has a maxIterations parameter defaulting to 10, but the actual value is passed from the caller. If a caller sets a high value or the default is not enforced, the LLM could be called repeatedly in a tool-use loop, causing excessive API costs and latency. **Perspective 2:** The streamClaude function logs every stream event to a file (claude-raw-stream.log) without any size limits or rotation. Over time, this file can grow unboundedly, filling disk space.
Suggested Fix
Implement log rotation, size limits, or remove verbose logging in production.
HIGHAnthropic API key read from environment variable without runtime fetch
[redacted]/claude.ts:44
[AGENTS: Vault]secrets
The `client` function reads `ANTHROPIC_API_KEY` from environment variables. This API key is loaded at module initialization and could be exposed through error messages, logs, or process inspection. It should be fetched from a secure vault at runtime.
Suggested Fix
Fetch the Anthropic API key from a secure secrets manager at runtime instead of reading from environment variables.
HIGHRaw LLM stream events logged to local file
[redacted]/claude.ts:62
[AGENTS: Egress]data_exfiltration
Every streaming event from Claude (including full user messages, tool calls, and responses) is serialized to JSON and appended to 'claude-raw-stream.log' via console.log and fs.appendFile. This log file may contain sensitive legal document content, user queries, and API responses with PII. The log is written to a file in the current working directory with no rotation, access control, or retention policy.
Suggested Fix
Remove the raw stream logging in production. If debugging is needed, use a structured logger with configurable levels, redact sensitive fields, and ensure logs are rotated and access-controlled.
HIGHRaw LLM stream events logged to console
[redacted]/claude.ts:63
[AGENTS: Egress]data_exfiltration
Every streaming event from Claude is logged to stdout via console.log, including full user messages, tool calls, and responses. In production environments, stdout may be captured by logging infrastructure (e.g., CloudWatch, Datadog, Papertrail) that persists indefinitely. This exposes sensitive legal document content and user queries to the logging pipeline.
Suggested Fix
Remove the console.log of raw stream events in production. Use a structured logger that can be configured to omit sensitive data or disable verbose logging entirely.
HIGHRaw Claude API stream events logged to file and console
[redacted]/claude.ts:82
[AGENTS: Recon]info_disclosure
Every streaming event from the Claude API is logged both to console and to a file at 'claude-raw-stream.log'. This includes the full content of all messages, tool calls, and responses, potentially exposing sensitive legal document content, user queries, and internal system prompts. The file is written to the current working directory which may be accessible if the server is misconfigured.
Suggested Fix
Remove the console.log and fs.appendFile calls. If debugging is needed, use a configurable debug flag that defaults to off and never writes to a file in the working directory.
HIGHRaw stream events logged to file and console
[redacted]/claude.ts:90
[AGENTS: Vault]secrets
The `streamClaude` function logs every stream event to both the console and a file (`claude-raw-stream.log`). These events may contain sensitive information including API responses, tool calls, and potentially user data. The log file is stored in the current working directory without any access controls.
Suggested Fix
Remove the console.log and file logging of raw stream events. If logging is necessary for debugging, ensure it is behind a feature flag and the log file is properly secured with restricted permissions.
HIGHSensitive data written to file without access controls
[redacted]/claude.ts:91
[AGENTS: Vault]secrets
The `RAW_STREAM_LOG_PATH` is set to `claude-raw-stream.log` in the current working directory. Stream events containing API responses, tool calls, and potentially user data are appended to this file without any access control or encryption. The file is world-readable by default on most systems.
Suggested Fix
Remove the file logging of raw stream events. If logging is absolutely necessary, use a secure logging service with access controls and encryption at rest.
HIGHNo input sanitization before LLM prompt construction
[redacted]/gemini.ts:1
[AGENTS: Prompt]llm_security
**Perspective 1:** User-provided message content is directly passed to the LLM via `toNativeContents` without any sanitization, structural separation, or delimiter-based isolation. This allows prompt injection attacks where user input can override system instructions or manipulate tool selection. **Perspective 2:** The `runTools` callback receives tool calls from the LLM and executes them. The arguments (`part.functionCall.args`) are accepted directly without validation against the declared tool schema. An attacker could craft a prompt that causes the LLM to generate malicious tool arguments (e.g., arbitrary file paths, SQL queries). **Perspective 3:** The `maxIterations` parameter defaults to 10 but can be set arbitrarily high via `params.maxIterations`. An attacker could craft prompts that force the LLM to repeatedly call tools, leading to excessive token consumption, high costs, and potential denial of service. **Perspective 4:** There is no check on the total input token count before sending to the LLM. An attacker could submit extremely long messages to maximize cost per request (context window stuffing). **Perspective 5:** The `fullText` result from the LLM is returned directly to the caller without any output filtering. This could expose sensitive information (PII, internal system details, credentials) if the LLM is manipulated via prompt injection to leak such data. **Perspective 6:** The `systemInstruction` is passed as a separate config field, but user messages are still concatenated into the same `contents` array. If the system prompt is not properly isolated, user input could override it via prompt injection.
Suggested Fix
Apply input sanitization to strip or escape known prompt injection patterns. Use structural delimiters (e.g., XML tags, markdown blocks) to separate user input from system instructions. Consider using a dedicated prompt template library.
HIGHPrompt injection via user-controlled messages in Gemini streaming
[redacted]/gemini.ts:1
[AGENTS: Specter]injection
**Perspective 1:** The `streamGemini` function passes user-controlled messages directly to the Gemini API without sanitization. While this is expected for LLM interactions, the function also passes tool definitions and system prompts that could be manipulated if an attacker can control the `params.messages` content. The function does not validate or sanitize user input before sending to the LLM, which could lead to prompt injection attacks. **Perspective 2:** The `streamGemini` function processes tool calls from the LLM and executes them via `runTools`. If an attacker can manipulate the LLM's output (via prompt injection), they could inject malicious tool calls that execute arbitrary functions. The function does not validate tool call names or arguments before execution. **Perspective 3:** The `systemPrompt` parameter is passed directly to the Gemini API without sanitization. If the system prompt contains user-controlled content, an attacker could inject malicious instructions that override the intended behavior.
Suggested Fix
Implement input sanitization for user messages before sending to the LLM. Use a separate system prompt that instructs the model to ignore injection attempts. Consider using a content filter or moderation layer.
HIGHAPI key loaded from environment variable without validation
[redacted]/gemini.ts:15
[AGENTS: Weights]model_supply_chain
The Gemini API key is loaded from process.env.GEMINI_API_KEY or a user-provided override without any validation or integrity check. An attacker who can modify the environment variable could substitute a malicious model endpoint or exfiltrate the key.
Suggested Fix
Validate the API key format and origin before use. Consider using a secrets manager and verifying the key against a known pattern or checksum.
HIGHHardcoded fallback API key in Gemini client
[redacted]/gemini.ts:18
[AGENTS: Razor]security
The `client` function falls back to `process.env.GEMINI_API_KEY || ''` when no override is provided. If the environment variable is missing, the API key becomes an empty string, which could lead to unauthenticated requests or silent failures. Additionally, the fallback to an empty string may cause confusing behavior.
Suggested Fix
Throw an explicit error if no API key is configured: `if (!apiKey) throw new Error('Gemini API key not configured');`
HIGHAPI key fallback to environment variable may leak in error messages
[redacted]/gemini.ts:27
[AGENTS: Recon]info_disclosure
The Gemini API client falls back to process.env.GEMINI_API_KEY. If the API key is invalid or missing, error messages from the Google GenAI SDK may include the key or reveal configuration details in stack traces returned to the client.
Suggested Fix
Ensure all API errors are caught and sanitized before returning to the client. Never expose raw API error messages.
HIGHUnbounded iteration in Gemini streaming loop
[redacted]/gemini.ts:51
[AGENTS: Siege]dos
The streamGemini function has a maxIterations of 10, but each iteration can involve multiple tool calls and responses. If the model keeps making tool calls (e.g., due to a malicious prompt designed to cause infinite tool loops), the function could run for an extended period, consuming CPU and API resources. The loop does not have a total time limit.
Suggested Fix
Add a total execution timeout (e.g., 60 seconds) in addition to the iteration limit. Consider also limiting the total number of tool calls across all iterations.
HIGHUnvalidated tool call inputs passed to runTools
[redacted]/gemini.ts:56
[AGENTS: Razor]security
The `streamGemini` function passes `part.functionCall.args` directly to `runTools` without any validation or sanitization. If the LLM produces malicious or unexpected arguments, they will be forwarded to the tool execution handler, potentially leading to SSRF, command injection, or data exfiltration.
Suggested Fix
Validate and sanitize all tool call arguments against a schema before passing them to runTools. Implement strict type checking and whitelist allowed values.
HIGHRaw API response logged to console exposing internal data
[redacted]/gemini.ts:76
[AGENTS: Recon]info_disclosure
The line `console.log('[gemini stream chunk]', JSON.stringify(chunk, null, 2))` logs the full Gemini API response to the console, which may include sensitive data, API internals, or model configuration details. In production, these logs could be exposed through log aggregation systems or error monitoring.
Suggested Fix
Remove or sanitize this console.log statement in production. Use a structured logging approach that filters sensitive fields.
HIGHSensitive data in console.log
[redacted]/gemini.ts:82
[AGENTS: Trace]logging
The full Gemini API response chunk is logged via console.log, which may contain user messages, model responses, and function call arguments. This is a server-side log that could expose sensitive data in production logs.
Suggested Fix
Remove the console.log statement entirely or sanitize the output to exclude sensitive fields before logging.
HIGHR2 storage keys constructed from user-provided filenames without sanitization
[redacted]/storage.ts:1
[AGENTS: Infiltrator]attack_surface
The storageKey, versionStorageKey, and generatedDocKey functions construct R2 storage paths by concatenating userId, docId, and filename directly. While the extension is validated, the filename itself is not sanitized for path traversal characters (../). If an attacker can control the filename parameter, they could write or read files outside the intended directory structure.
Suggested Fix
Sanitize the filename parameter to remove path traversal sequences (../, ..\). Consider using a hash or UUID-based filename instead of the user-provided filename for storage paths.
HIGHHardcoded AWS credentials in client initialization
[redacted]/storage.ts:22
[AGENTS: Cipher]cryptography
**Perspective 1:** The S3 client is initialized with R2_ACCESS_KEY_ID and R2_SECRET_ACCESS_KEY from environment variables, but these are passed directly into the client constructor without any encryption or secure storage mechanism. If an attacker gains access to the environment, they can extract these credentials. Additionally, the credentials are used directly without any key rotation mechanism or temporary credential generation. **Perspective 2:** AWS S3 credentials (access key ID and secret access key) are hardcoded directly in the client initialization code. This exposes cryptographic credentials in the source code, making them vulnerable to extraction from version control, build artifacts, or decompiled code. **Perspective 3:** The S3Client is created without explicit TLS configuration. While the AWS SDK defaults to HTTPS, there is no certificate pinning or explicit TLS version enforcement. An attacker with network access could potentially downgrade the connection or perform a MITM attack if the endpoint is compromised.
Suggested Fix
Configure the S3 client with explicit TLS settings, including minimum TLS version (1.2 or higher) and optionally implement certificate pinning for the R2 endpoint. Consider adding request-level encryption for sensitive document content.
HIGHLarge file upload without streaming
[redacted]/upload.ts:6
[AGENTS: Siege]dos
Multer is configured with memoryStorage, which buffers the entire file in RAM before processing. With a 100 MB limit, a single request can consume 100 MB of memory. Multiple concurrent requests can exhaust server memory, leading to denial of service.
Suggested Fix
Use diskStorage or stream the file directly to storage (e.g., R2/S3) instead of buffering in memory. Alternatively, reduce the file size limit significantly or implement a streaming upload handler.
HIGHUser API keys fetched from database and exposed to server memory
[redacted]/userSettings.ts:1
[AGENTS: Vault]secrets
**Perspective 1:** The getUserModelSettings and getUserApiKeys functions fetch user API keys (claude_api_key, gemini_api_key) from the database and return them in plaintext. These keys are stored unencrypted in the database and are accessible to any code that calls these functions. **Perspective 2:** The user_profiles table stores claude_api_key and gemini_api_key in plaintext columns. This means any database compromise or SQL injection would expose all user API keys. **Perspective 3:** The user_profiles table has an RLS policy that allows users to view their own profile. However, the schema migration shows the table has RLS enabled but the policy only covers SELECT and UPDATE - not INSERT. The handle_new_user trigger inserts with security definer, but the API keys could be exposed through the SELECT policy.
Suggested Fix
Encrypt API keys at rest in the database using application-level encryption (e.g., AES-256-GCM with a key encryption key stored in a vault). Decrypt only when needed and never log or expose the plaintext keys.
HIGHAPI keys stored in plaintext in database
[redacted]/userSettings.ts:1
[AGENTS: Compliance]regulatory
**Perspective 1:** User API keys (claude_api_key, gemini_api_key) are stored in plaintext in the user_profiles table. PCI-DSS 3.4 and HIPAA §164.312(a)(2)(iv) require encryption of sensitive data at rest. **Perspective 2:** The getUserApiKeys function retrieves sensitive API keys without logging the access event. SOC 2 CC6.1 and HIPAA §164.312(b) require audit logging for access to sensitive data.
Suggested Fix
Encrypt API keys using a strong encryption algorithm (e.g., AES-256-GCM) before storing in the database, and decrypt only when needed for API calls.
HIGHAPI keys fetched from database and passed in memory without encryption
[redacted]/userSettings.ts:32
[AGENTS: Warden]privacy
**Perspective 1:** The getUserModelSettings and getUserApiKeys functions retrieve claude_api_key and gemini_api_key from the database and return them as plaintext strings in memory. These keys are then passed to the LLM module. If memory is dumped or a logging framework captures these values, API keys are exposed. **Perspective 2:** The getUserModelSettings and getUserApiKeys functions do not log when API keys are accessed. Under GDPR, access to sensitive credentials should be audited to detect unauthorized access or data breaches.
Suggested Fix
Encrypt API keys at rest in the database and decrypt only at the point of use. Consider using a secrets manager (e.g., AWS Secrets Manager, HashiCorp Vault) instead of storing keys in the database. Ensure keys are not logged or serialized.
HIGHMissing JWT signature verification
[redacted]/auth.ts:1
[AGENTS: Gatekeeper]auth
The requireAuth middleware uses the Supabase admin client's getUser() method which verifies the JWT signature. However, the token is extracted from the Authorization header and passed to getUser() without any additional validation. If the Supabase admin client is misconfigured or if there's a fallback path, the token could be accepted without proper verification. Additionally, the middleware does not check for token expiration or revocation.
Suggested Fix
Add explicit token validation including expiration check, and ensure the Supabase admin client is properly configured with the correct JWT secret.
HIGHService role key used for token verification without runtime fetch
[redacted]/auth.ts:14
[AGENTS: Vault]secrets
**Perspective 1:** The auth middleware reads SUPABASE_SECRET_KEY from environment variables at module load time and uses it to create a Supabase admin client for token verification. This service role key has full database access and should be fetched at runtime from a secure vault or KMS. **Perspective 2:** The auth middleware uses the Supabase service role key to verify user tokens via admin.auth.getUser(). This is an anti-pattern - the service role key should only be used for administrative operations. Token verification should use the Supabase JWT secret directly. **Perspective 3:** The auth middleware creates a Supabase admin client with the service role key without checking if the key is valid or has been rotated. If the key is compromised, all authentication is compromised.
Suggested Fix
Fetch the service role key at runtime from a secrets manager (e.g., AWS Secrets Manager, HashiCorp Vault). Cache it with a short TTL. Never load it from environment variables at module initialization.
HIGHMissing error handling for auth header parsing
[redacted]/auth.ts:15
[AGENTS: Fuse]error_security
If the Authorization header is malformed (e.g., 'Bearer ' with no token), the code will proceed with an empty token. The subsequent getUser call will fail, but the error is not caught, potentially leading to an unhandled rejection or a 500 error that leaks internal state.
Suggested Fix
Add a check for empty token after trimming: if (!token) { res.status(401).json({ detail: 'Missing token' }); return; }
HIGHService role key used for token verification
[redacted]/auth.ts:20
[AGENTS: Gatekeeper]auth
The middleware uses the SUPABASE_SECRET_KEY (service role key) to create an admin client for token verification. If this key is compromised, an attacker could forge tokens or bypass authentication entirely. The service role key should never be used in client-side code or exposed to users.
Suggested Fix
Use the anon key with proper JWT verification instead of the service role key for token validation. Reserve the service role key for server-side operations that require admin privileges.
HIGHMissing rate limiting on chat creation endpoint
[redacted]/chat.ts:1
[AGENTS: Phantom]api_security
**Perspective 1:** The POST /chat/create endpoint has no rate limiting, allowing an attacker to create unlimited chats and potentially exhaust database resources or storage. **Perspective 2:** The POST /chat endpoint that handles streaming LLM responses has no rate limiting, allowing an attacker to make unlimited expensive LLM calls, potentially causing financial abuse. **Perspective 3:** The POST /chat/:chatId/generate-title endpoint has no rate limiting, allowing an attacker to generate unlimited titles and consume LLM API credits. **Perspective 4:** The DELETE /chat/:chatId endpoint has no rate limiting, allowing an attacker to rapidly delete chats and potentially cause data loss. **Perspective 5:** The API routes do not include versioning (e.g., /v1/chat). This makes it difficult to introduce breaking changes without affecting existing clients.
Suggested Fix
Implement rate limiting middleware on the chat creation endpoint, e.g., using express-rate-limit with a reasonable limit per user per time window.
HIGHMissing authorization check on chat listing
[redacted]/chat.ts:1
[AGENTS: Gatekeeper]auth
The GET /chat endpoint lists all chats where the user is the owner or the chat's project is owned by the user. However, it does not verify that the user has access to the project's chats via the shared_with mechanism. A user who is a shared member of a project (not the owner) will not see chats in that project's listing, but this is a design choice. The real issue is that the endpoint does not check if the user is authenticated before returning results, though requireAuth middleware is applied. The filter logic uses an 'or' clause that could potentially expose chats from projects the user does not own if the project_id is manipulated.
Suggested Fix
Ensure that the chat listing endpoint only returns chats from projects where the user has explicit access (owner or shared member). Use the listAccessibleProjectIds helper from access.ts to get the full set of accessible project IDs.
HIGHDynamic filter string concatenation in Supabase query
[redacted]/chat.ts:33
[AGENTS: Syringe]db_injection
The `filter` variable is constructed by concatenating user-controlled `userId` and `ownProjectIds` directly into a string that is passed to the `.or()` method. While Supabase's `.or()` method may provide some escaping, the direct string interpolation of user IDs into the filter expression creates a potential injection vector if the IDs contain special characters.
Suggested Fix
Use Supabase's `.in()` method with parameterized arrays instead of string concatenation in `.or()`. For example: `.in('user_id', [userId]).or('project_id.in.(' + ownProjectIds.map(id => `'${id}'`).join(',') + ')')` or use separate `.eq()` and `.in()` calls combined with `.or()` properly.
HIGHIDOR in chat creation
[redacted]/chat.ts:42
[AGENTS: Gatekeeper]auth
The POST /chat/create endpoint accepts a project_id from the request body but does not verify that the user has access to the specified project. An attacker could create a chat associated with any project ID, potentially gaining visibility into project-related data or bypassing access controls.
Suggested Fix
Add a call to checkProjectAccess before creating the chat to verify the user has access to the specified project_id.
HIGHIDOR in chat message retrieval
[redacted]/chat.ts:85
[AGENTS: Gatekeeper]auth
The GET /chat/:chatId endpoint retrieves chat messages. While it checks if the user is the chat owner or a project member, it does not verify that the chat actually belongs to the project the user has access to. An attacker could enumerate chat IDs to access messages from chats they should not have access to.
Suggested Fix
Add a check to ensure the chat's project_id (if set) matches a project the user has access to, using checkProjectAccess.
HIGHIDOR in chat title generation
[redacted]/chat.ts:175
[AGENTS: Gatekeeper]auth
The POST /chat/:chatId/generate-title endpoint checks if the user can access the chat but does not verify that the user is the chat owner before updating the title. A shared project member could generate and set a new title for a chat they do not own.
Suggested Fix
Restrict title generation to the chat owner only, or ensure that shared members can only generate titles for chats they own.
HIGHIDOR in chat deletion
[redacted]/chat.ts:280
[AGENTS: Gatekeeper]auth
The DELETE /chat/:chatId endpoint only allows the chat owner to delete the chat. However, it does not verify that the user has access to the chat's project. An attacker could attempt to delete chats from projects they do not have access to, though the owner check would prevent this. The error message 'Chat not found' is returned for both non-existent chats and unauthorized access, which is good for preventing enumeration.
Suggested Fix
No fix needed for the deletion logic itself, but ensure consistent error messages across all endpoints.
HIGHUser message content directly interpolated into LLM prompt for title generation
[redacted]/chat.ts:297
[AGENTS: Prompt]llm_security
The user's message content is directly concatenated into the LLM prompt for generating chat titles via template literal: `Message: ${message.slice(0, 500)}`. This is a classic prompt injection vector — a user can craft a message that overrides the system instruction and causes the LLM to generate an arbitrary title or leak information. The 500-character truncation reduces but does not eliminate the risk.
Suggested Fix
Use a structured prompt with clear delimiters and role separation. For example, pass the user message as a separate user-role message in the LLM call rather than embedding it in the system prompt. Alternatively, use a dedicated, non-instruction-following model call for title generation that treats the input as data, not instructions.
HIGHUser message content stored and later included in LLM context without sanitization
[redacted]/chat.ts:379
[AGENTS: Prompt]llm_security
The last user message content is stored directly into the database and later retrieved by `enrichWithPriorEvents` and `buildMessages` to construct the LLM prompt. While this is expected chat functionality, there is no input sanitization or structural separation between user input and system instructions in the prompt construction pipeline. The `buildMessages` function in chatTools likely concatenates user messages with system prompts without adversarial delimiters.
Suggested Fix
Ensure `buildMessages` uses strict role separation (system vs user vs assistant messages) and never concatenates user input into system-level instructions. Add input length limits and content filtering for obviously malicious patterns (e.g., 'ignore previous instructions').
HIGHInconsistent error messages enable account enumeration
[redacted]/chat.ts:430
[AGENTS: Gatekeeper]auth
The GET /chat/:chatId endpoint returns 'Chat not found' for both non-existent chats and unauthorized access. However, the POST /chat/:chatId/generate-title endpoint also returns 'Chat not found' for unauthorized access. This consistency is good, but the POST /chat streaming endpoint returns different error messages ('Project not found' vs 'Failed to create chat') which could be used to enumerate valid project IDs.
Suggested Fix
Use consistent error messages across all endpoints to prevent enumeration attacks.
HIGHMissing authorization check on chat message insertion
[redacted]/chat.ts:500
[AGENTS: Gatekeeper]auth
The POST /chat streaming endpoint inserts user messages into the database before verifying that the user has access to the chat. If the chat_id is valid but the user does not have access, the message is still stored. This could lead to data pollution or information leakage.
Suggested Fix
Move the authorization check before the message insertion to ensure only authorized users can add messages.
HIGHIDOR in chat title update
[redacted]/chat.ts:650
[AGENTS: Gatekeeper]auth
The PATCH /chat/:chatId endpoint updates the chat title but only checks if the user is the chat owner. It does not verify that the user has access to the chat's project. An attacker could potentially update titles of chats they do not own if they can guess the chat ID.
Suggested Fix
Add a check to verify the user has access to the chat's project before allowing the update.
HIGHIDOR in chat deletion by non-owner
[redacted]/chat.ts:700
[AGENTS: Gatekeeper]auth
The DELETE /chat/:chatId endpoint only allows the chat owner to delete the chat. However, it does not verify that the user has access to the chat's project. An attacker could attempt to delete chats from projects they do not have access to, though the owner check would prevent this.
Suggested Fix
Add a check to verify the user has access to the chat's project before allowing deletion.
HIGHIDOR in chat title generation by non-owner
[redacted]/chat.ts:750
[AGENTS: Gatekeeper]auth
The POST /chat/:chatId/generate-title endpoint allows shared project members to generate titles for chats they do not own. This could lead to unauthorized modification of chat metadata.
Suggested Fix
Restrict title generation to the chat owner only.
HIGHMissing authorization check on chat streaming for existing chats
[redacted]/chat.ts:800
[AGENTS: Gatekeeper]auth
The POST /chat streaming endpoint checks if the user can access the chat when a chat_id is provided. However, if the chat_id is invalid, it creates a new chat without verifying the user's intent. An attacker could potentially create chats under any project they have access to by providing a valid project_id.
Suggested Fix
Ensure that when creating a new chat, the user must explicitly confirm their intent, or restrict chat creation to specific endpoints.
HIGHMissing input sanitization on file upload
[redacted]/documents.ts:1
[AGENTS: Sanitizer]sanitization
**Perspective 1:** The file upload handler uses `file.originalname` directly without sanitizing the filename. An attacker could upload a file with a malicious filename (e.g., containing path traversal characters like '../' or null bytes) that could lead to directory traversal or storage injection. The filename is used in storage key generation and database insertion without validation. **Perspective 2:** The code validates file type based on the file extension from `file.originalname` but does not validate the actual file content (magic bytes). An attacker could upload a file with a .pdf extension but containing executable code, bypassing the extension-based filter. The content type is also derived from the extension rather than being validated against the actual content. **Perspective 3:** The code constructs storage paths using user-provided filenames without canonicalizing or normalizing the path. While the storage layer may prevent path traversal, the lack of canonicalization could allow encoding-based bypasses (e.g., URL-encoded path separators) if the storage layer interprets them differently.
Suggested Fix
Validate file content using magic bytes or a content-type detection library (e.g., file-type) in addition to extension checking. Reject files where the declared extension doesn't match the actual content.
HIGHMissing authorization check on document access
[redacted]/documents.ts:1
[AGENTS: Gatekeeper]auth
The `ensureDocAccess` function is called inconsistently across endpoints. Some endpoints (like GET /single-documents/:documentId/display) check access, but others (like POST /single-documents/:documentId/edits/:editId/accept) only check access after fetching the document. The `handleEditResolution` function fetches the edit row before checking document access, which could leak information about edit existence. Additionally, the `handleDocumentUpload` function does not check if the user has access to the project they're uploading to.
Suggested Fix
Ensure all endpoints check document access before processing any data. Move the access check to the beginning of each handler, before any database queries that could leak information.
HIGHUnbounded ZIP generation in memory
[redacted]/documents.ts:1
[AGENTS: Siege]dos
**Perspective 1:** The /single-documents/download-zip endpoint loads all requested documents into memory simultaneously, then generates a ZIP archive entirely in memory using JSZip. An attacker could request many large documents, causing the server to exhaust available memory. The endpoint also lacks rate limiting. **Perspective 2:** The POST /single-documents endpoint accepts file uploads without any rate limiting. An attacker could upload many large files in rapid succession, exhausting disk space and storage bandwidth. **Perspective 3:** The POST /single-documents/:documentId/versions endpoint accepts file uploads without any rate limiting. An attacker could upload many versions of a document, exhausting storage and database resources. **Perspective 4:** The version upload endpoint performs DOCX-to-PDF conversion using LibreOffice (via docxToPdf) without any timeout or resource limits. An attacker could upload a malformed DOCX file that causes the conversion to hang or consume excessive CPU/memory. **Perspective 5:** The countPdfPages function loads the entire PDF into memory and parses it with pdfjs-dist. A crafted PDF with many pages or complex structure could cause excessive memory consumption or CPU usage. **Perspective 6:** The extractStructureTree function loads the entire PDF into memory and extracts the outline. A PDF with a deeply nested or maliciously crafted outline could cause excessive memory consumption or a stack overflow. **Perspective 7:** The extractStructureTree function uses mammoth.extractRawText on DOCX files without size limits. A large or maliciously crafted DOCX could cause excessive memory consumption during text extraction. **Perspective 8:** The GET /single-documents/:documentId/display endpoint serves file content without rate limiting. An attacker could repeatedly request large documents, consuming bandwidth and I/O resources. **Perspective 9:** The GET /single-documents/:documentId/url endpoint generates signed URLs without rate limiting. An attacker could generate many URLs, consuming storage service quota and CPU. **Perspective 10:** The GET /single-documents/:documentId/docx endpoint streams file content without rate limiting. An attacker could repeatedly request large documents, consuming bandwidth and I/O resources. **Perspective 11:** The GET /single-documents/:documentId/tracked-change-ids endpoint downloads and parses DOCX files without rate limiting. An attacker could repeatedly request this for large documents, consuming CPU and bandwidth. **Perspective 12:** The POST /single-documents/:documentId/edits/:editId/accept and /reject endpoints download, parse, and re-upload DOCX files without rate limiting. An attacker could rapidly accept/reject edits, causing excessive CPU and I/O usage.
Suggested Fix
Stream the ZIP generation using a streaming ZIP library (e.g., archiver) and enforce rate limiting. Add a maximum total size limit for the requested documents.
HIGHDocument listing query scoped to user_id but not tenant_id
[redacted]/documents.ts:28
[AGENTS: Tenant]tenant_isolation
The GET /single-documents endpoint filters documents by user_id but does not verify tenant membership. In a multi-tenant system where a user may belong to multiple tenants, this query could return documents from other tenants the user has access to, or fail to properly isolate tenant data. The query uses .eq('user_id', userId) without any tenant context.
Suggested Fix
Add a tenant_id filter to the query: .eq('tenant_id', tenantId) where tenantId is extracted from the authenticated user's current tenant context.
HIGHNo input validation on document_id parameter
[redacted]/documents.ts:30
[AGENTS: Razor]security
The document_id parameter from the URL is used directly in database queries without validation. While parameterized queries via Supabase ORM provide some protection, the lack of format validation could allow unexpected behavior or be used in combination with other vulnerabilities.
Suggested Fix
Add validation that document_id matches expected UUID format before using in queries. Consider using a library like uuid-validate or regex check.
HIGHIDOR in document deletion endpoint
[redacted]/documents.ts:42
[AGENTS: Gatekeeper]auth
The DELETE /single-documents/:documentId endpoint checks that the document belongs to the user, but it does not check if the document is shared via a project. A user who has access to a document through a shared project could potentially delete it, even though they should only have read access.
Suggested Fix
Use `ensureDocAccess` with a write permission check before allowing deletion. Consider adding a permission level parameter to `ensureDocAccess`.
HIGHNo input validation on version_id query parameter
[redacted]/documents.ts:42
[AGENTS: Razor]security
The version_id query parameter is used directly in database queries without validation. An attacker could potentially inject malicious values through this parameter.
Suggested Fix
Validate that version_id matches expected UUID format before using in queries.
HIGHDocument deletion query lacks tenant scope verification
[redacted]/documents.ts:52
[AGENTS: Tenant]tenant_isolation
The DELETE /single-documents/:documentId endpoint checks that the document belongs to the user via .eq('user_id', userId) but does not verify tenant context. A user with access to multiple tenants could delete documents from any tenant they belong to, or the query may not properly isolate tenant data.
Suggested Fix
Add tenant_id filter: .eq('tenant_id', tenantId) to ensure the document belongs to the correct tenant before deletion.
HIGHNo input validation on document_ids in request body
[redacted]/documents.ts:56
[AGENTS: Razor]security
The document_ids array from the request body is used directly in database queries without validation. An attacker could inject arbitrary values.
Suggested Fix
Validate each document_id in the array matches expected UUID format before using in queries.
HIGHDocument display endpoint lacks tenant scope in initial query
[redacted]/documents.ts:94
[AGENTS: Tenant]tenant_isolation
The GET /single-documents/:documentId/display endpoint fetches the document by ID without tenant filtering. While ensureDocAccess is called later, the initial database query to fetch the document does not include tenant context, potentially leaking document existence across tenants.
Suggested Fix
Add tenant_id filter to the initial document query: .eq('tenant_id', tenantId) to prevent information disclosure about document existence.
HIGHZip download endpoint processes arbitrary document IDs without ownership verification
[redacted]/documents.ts:100
[AGENTS: Infiltrator]attack_surface
POST /single-documents/download-zip accepts an array of document_ids and creates a ZIP file. While it checks access for each document, it loads all documents into memory simultaneously (Promise.all with downloadFile). An attacker could request hundreds of large documents to cause OOM. The ZIP generation uses DEFLATE compression which could be exploited for zip bombs.
Suggested Fix
Limit the number of documents per request (e.g., max 50). Stream ZIP generation instead of building in memory. Add decompression bomb protection. Consider adding rate limiting.
HIGHNo input validation on display_name parameter
[redacted]/documents.ts:120
[AGENTS: Razor]security
The display_name parameter from the request body is used directly in filename generation without proper sanitization. An attacker could inject path traversal characters or other malicious content.
Suggested Fix
Sanitize display_name to remove path traversal characters and limit length. Consider using a whitelist of allowed characters.
HIGHNo input validation on editId parameter
[redacted]/documents.ts:150
[AGENTS: Razor]security
The editId parameter from the URL is used directly in database queries without validation. An attacker could potentially access or modify edits belonging to other users.
Suggested Fix
Validate that editId matches expected UUID format and verify ownership before processing.
HIGHIDOR in document version upload
[redacted]/documents.ts:175
[AGENTS: Gatekeeper]auth
The POST /single-documents/:documentId/versions endpoint checks document access, but does not verify that the user has write permission. A user with read-only access to a shared document could upload a new version, potentially overwriting or corrupting the document.
Suggested Fix
Add a write permission check before allowing version uploads. Use `ensureDocAccess` with a write flag or implement a separate permission check.
HIGHDocument download-zip endpoint may include cross-tenant documents
[redacted]/documents.ts:175
[AGENTS: Tenant]tenant_isolation
The POST /single-documents/download-zip endpoint fetches documents by IDs without tenant filtering. While access checks are performed per document, the initial query .in('id', document_ids) could return documents from any tenant, and the access check relies on ensureDocAccess which may not properly validate tenant context.
Suggested Fix
Add tenant_id filter to the initial query: .eq('tenant_id', tenantId) to limit the document set to the current tenant before performing access checks.
HIGHDocument URL endpoint lacks tenant scope in initial query
[redacted]/documents.ts:218
[AGENTS: Tenant]tenant_isolation
The GET /single-documents/:documentId/url endpoint fetches the document by ID without tenant filtering. The initial query .eq('id', documentId) could return a document from any tenant, leaking document existence information.
Suggested Fix
Add tenant_id filter to the initial document query: .eq('tenant_id', tenantId).
HIGHNo input validation on file upload content type
[redacted]/documents.ts:250
[AGENTS: Razor]security
The file upload endpoint accepts files without validating the actual content type. An attacker could upload a file with a .pdf extension but containing malicious content.
Suggested Fix
Validate the actual content type of uploaded files using magic bytes or a library like file-type, not just the file extension.
HIGHDocument edit resolution endpoint mutates storage in-place without versioning
[redacted]/documents.ts:250
[AGENTS: Infiltrator]attack_surface
POST /single-documents/:documentId/edits/:editId/accept and /reject overwrite the current version's storage path bytes in-place. This means accept/reject operations are destructive — there's no way to revert a rejected change. The overwrite happens before the database transaction completes, creating a window where storage is inconsistent with DB state.
Suggested Fix
Create new version rows for each accept/reject instead of mutating in-place. Use atomic operations: write new bytes to a new path, then update the DB pointer atomically.
HIGHIDOR in document version rename
[redacted]/documents.ts:280
[AGENTS: Gatekeeper]auth
The PATCH /single-documents/:documentId/versions/:versionId endpoint checks document access, but does not verify write permission. A user with read-only access could rename document versions.
Suggested Fix
Add a write permission check before allowing version renaming.
HIGHDocument docx endpoint lacks tenant scope in initial query
[redacted]/documents.ts:280
[AGENTS: Tenant]tenant_isolation
The GET /single-documents/:documentId/docx endpoint fetches the document by ID without tenant filtering. The initial query .eq('id', documentId) could return a document from any tenant.
Suggested Fix
Add tenant_id filter to the initial document query: .eq('tenant_id', tenantId).
HIGHNo input validation on version_number parameter
[redacted]/documents.ts:300
[AGENTS: Razor]security
The version_number parameter is used directly in database queries without validation. An attacker could potentially access version history they shouldn't have access to.
Suggested Fix
Validate that version_number is a positive integer and verify user has access to the document.
HIGHIDOR in tracked change IDs endpoint
[redacted]/documents.ts:320
[AGENTS: Gatekeeper]auth
The GET /single-documents/:documentId/tracked-change-ids endpoint checks document access, but does not verify that the user should have access to the tracked changes. This could leak information about document edits.
Suggested Fix
Ensure the access check is appropriate for the sensitivity of tracked changes. Consider adding a permission level check.
HIGHDocument versions endpoint lacks tenant scope in initial query
[redacted]/documents.ts:345
[AGENTS: Tenant]tenant_isolation
The GET /single-documents/:documentId/versions endpoint fetches the document by ID without tenant filtering. The initial query .eq('id', documentId) could return a document from any tenant.
Suggested Fix
Add tenant_id filter to the initial document query: .eq('tenant_id', tenantId).
HIGHDocument version upload endpoint lacks tenant scope in initial query
[redacted]/documents.ts:388
[AGENTS: Tenant]tenant_isolation
The POST /single-documents/:documentId/versions endpoint fetches the document by ID without tenant filtering. The initial query .eq('id', documentId) could return a document from any tenant.
Suggested Fix
Add tenant_id filter to the initial document query: .eq('tenant_id', tenantId).
HIGHNo input validation on folder_id parameter
[redacted]/documents.ts:400
[AGENTS: Razor]security
The folder_id parameter from the request body is used directly in database queries without validation. An attacker could potentially access or modify folders belonging to other users.
Suggested Fix
Validate that folder_id matches expected UUID format and verify user has access to the folder.
HIGHInconsistent error messages enable account enumeration
[redacted]/documents.ts:430
[AGENTS: Gatekeeper]auth
The edit resolution endpoint returns different error messages for 'edit not found' (404) vs 'document not found' (404) vs 'document bytes not available' (404). While all return 404, the different messages could be used to enumerate valid document and edit IDs.
Suggested Fix
Return a generic 'not found' message for all failure cases to prevent enumeration.
HIGHNo input validation on parent_folder_id parameter
[redacted]/documents.ts:450
[AGENTS: Razor]security
The parent_folder_id parameter from the request body is used directly in database queries without validation. An attacker could potentially move folders to unauthorized locations.
Suggested Fix
Validate that parent_folder_id matches expected UUID format and verify user has access to both source and destination folders.
HIGHMissing authorization check on document upload
[redacted]/documents.ts:500
[AGENTS: Gatekeeper]auth
The `handleDocumentUpload` function does not check if the user has permission to upload to the specified project. If a project_id is provided, the function should verify that the user is a member of that project.
Suggested Fix
Add a project membership check before allowing document uploads to a project.
HIGHNo input validation on document_id in folder operations
[redacted]/documents.ts:500
[AGENTS: Razor]security
The document_id parameter from the URL is used directly in database queries without validation. An attacker could potentially move documents between folders they shouldn't have access to.
Suggested Fix
Validate that document_id matches expected UUID format and verify user has access to the document.
HIGHDocument version rename endpoint lacks tenant scope in initial query
[redacted]/documents.ts:521
[AGENTS: Tenant]tenant_isolation
The PATCH /single-documents/:documentId/versions/:versionId endpoint fetches the document by ID without tenant filtering. The initial query .eq('id', documentId) could return a document from any tenant.
Suggested Fix
Add tenant_id filter to the initial document query: .eq('tenant_id', tenantId).
HIGHNo input validation on project_id parameter
[redacted]/documents.ts:550
[AGENTS: Razor]security
The project_id parameter from the URL is used directly in database queries without validation. An attacker could potentially access projects they shouldn't have access to.
Suggested Fix
Validate that project_id matches expected UUID format and verify user has access to the project.
HIGHTracked change IDs endpoint lacks tenant scope in initial query
[redacted]/documents.ts:572
[AGENTS: Tenant]tenant_isolation
The GET /single-documents/:documentId/tracked-change-ids endpoint fetches the document by ID without tenant filtering. The initial query .eq('id', documentId) could return a document from any tenant.
Suggested Fix
Add tenant_id filter to the initial document query: .eq('tenant_id', tenantId).

Summary

Consensus from 180 reviewer(s): Syringe, Cipher, Harbor, Deadbolt, Entropy, Mirage, Gateway, Vault, Recon, Egress, Supply, Lockdown, Blacklist, Fuse, Passkey, Warden, Weights, Razor, Siege, Tripwire, Trace, Wallet, Tenant, Prompt, Specter, Provenance, Chaos, Pedant, Vector, Sentinel, Gatekeeper, Sanitizer, Phantom, Compliance, Exploit, Infiltrator, Cipher, Harbor, Vault, Deadbolt, Supply, Weights, Fuse, Passkey, Gateway, Sanitizer, Gatekeeper, Blacklist, Siege, Razor, Entropy, Infiltrator, Exploit, Compliance, Trace, Egress, Sentinel, Syringe, Tripwire, Phantom, Lockdown, Pedant, Chaos, Tenant, Provenance, Specter, Prompt, Recon, Vector, Wallet, Mirage, Warden, Passkey, Syringe, Harbor, Cipher, Specter, Sanitizer, Deadbolt, Supply, Recon, Egress, Exploit, Tripwire, Lockdown, Blacklist, Warden, Wallet, Prompt, Gateway, Entropy, Vault, Siege, Compliance, Weights, Gatekeeper, Chaos, Phantom, Fuse, Pedant, Mirage, Trace, Provenance, Sentinel, Razor, Infiltrator, Tenant, Vector, Syringe, Harbor, Passkey, Deadbolt, Weights, Specter, Gateway, Vault, Exploit, Cipher, Phantom, Supply, Trace, Mirage, Sanitizer, Wallet, Warden, Siege, Egress, Fuse, Prompt, Gatekeeper, Blacklist, Entropy, Compliance, Infiltrator, Chaos, Provenance, Razor, Sentinel, Pedant, Vector, Lockdown, Tripwire, Recon, Tenant, Harbor, Entropy, Gatekeeper, Deadbolt, Passkey, Egress, Gateway, Sanitizer, Mirage, Recon, Prompt, Weights, Trace, Vault, Cipher, Fuse, Supply, Warden, Compliance, Provenance, Tripwire, Chaos, Siege, Blacklist, Sentinel, Pedant, Specter, Phantom, Syringe, Wallet, Lockdown, Infiltrator, Tenant, Exploit, Razor, Vector Total findings: 1060 Severity breakdown: 23 critical, 361 high, 369 medium, 206 low, 101 info

Note: Fixing issues can create a domino effect — resolving one finding often surfaces new ones that were previously hidden. Multiple scan-and-fix cycles may be needed until you’re satisfied no further issues remain. How deep you go is your call.