Nebula 500
Back to Insights
Case Study10 min read

9 vulnerabilities in one AI-built app

A real-world security assessment of a small business CRM built with AI assistance uncovered a chain of exploitable vulnerabilities — including a path to complete account takeover.

Note on anonymisation: All identifying information has been removed or altered. The business name, industry, application URL, employee names, and any third-party service names specific enough to identify the company have been replaced with generic descriptions. The vulnerabilities, severity ratings, and technical details are reported accurately.

Background

The subject of this assessment is a custom-built web application: an internal CRM and quoting tool developed for a small service-industry business. The application was built by a technically capable non-specialist using an AI coding assistant, deployed to a cloud hosting platform, and used daily by the business owner and their team to manage customer quotes, job records, and crew assignments.

The application handled sensitive operational data including proprietary pricing models, customer information, and employee details. It had been in production for several months before any security review was conducted.

The assessment was scoped to the web application layer: authentication, authorisation, API security, HTTP headers, and third-party integrations. No social engineering, network-layer, or physical access testing was performed.

What was tested

Test areaResult
Outdated library with known CVEVulnerable
Login endpoint brute-force / rate limitingVulnerable
Client-side authentication bypass (JS disabled)Vulnerable
OAuth CSRF (missing state parameter)Vulnerable
Direct API access without authenticationProtected
SQL injection (login fields)Not vulnerable
HTTP request smugglingNot vulnerable
JWT algorithm confusionNot vulnerable
Username enumeration via error messagesNot vulnerable
Username enumeration via timingNot vulnerable
OAuth redirect_uri manipulationNot vulnerable
Oversized payload denial-of-serviceNot vulnerable
PII in public JavaScript sourceVulnerable
Security response headersSeveral missing
CORS policyOverly permissive
Session token flagsHttpOnly missing

Executive summary

Critical finding: complete account takeover chain

The combination of an outdated third-party library (Finding 1) and an improperly flagged session token (Finding 6) created an end-to-end account takeover path: an attacker could upload a crafted file through a standard application feature, have an authenticated user open it, silently steal their session token, and gain full access to the CRM as that user — with no alerts, no lockouts, and no trace.

The application demonstrated solid fundamentals in several areas: all data API endpoints correctly returned 401 errors to unauthenticated requests, no SQL injection vulnerabilities were found, JWT signature verification was properly implemented, and no username enumeration was possible via timing or error message analysis.

However, 9 distinct vulnerabilities were identified across authentication, session management, third-party dependencies, business logic exposure, and deployment configuration — patterns entirely consistent with AI-assisted development that prioritises functional correctness over security posture.

Findings

2 High
2 Medium
5 Low
HighFinding 1

Outdated library with known remote code execution vulnerability

A third-party JavaScript library loaded from a CDN was three major versions behind the current release. The installed version had a publicly documented, actively exploited vulnerability (CVSS 8.8 — High) allowing arbitrary JavaScript execution when processing user-uploaded files.

The application allowed users to upload files that were then rendered in-browser using this library. An attacker could craft a malicious file that, when opened by any authenticated user, would silently execute attacker-controlled JavaScript in the context of the application's domain.

Impact: Complete account takeover chain when combined with Finding 6.

HighFinding 2

No rate limiting on the login endpoint

The authentication endpoint accepted unlimited login attempts with no throttling, lockout, or CAPTCHA. Twenty sequential requests with invalid credentials all returned standard error messages with no blocking.

This allows automated brute-force and credential stuffing attacks against any known or guessed username, indefinitely. An attacker with a list of common passwords or a leaked credential database could systematically attempt access with no friction.

Impact: Unlimited automated password guessing against any user account.

MediumFinding 3

Client-side only authentication guard

The application served its full HTML shell (including all business logic) to all HTTP requests regardless of authentication status. The redirect to the login page was performed exclusively by client-side JavaScript.

With JavaScript disabled — or via a direct HTTP request — the complete application interface loaded without any credentials. While API calls correctly returned 401 errors, the full HTML source, including pricing formulas, labour rates, margin calculations, overhead percentages, and application structure, was publicly readable.

Impact: Unauthenticated access to sensitive business logic embedded in HTML source.

MediumFinding 4

Missing OAuth state parameter (CSRF in third-party integration)

A third-party OAuth integration flow did not include a state parameter, a basic CSRF protection required by the OAuth 2.0 specification.

Without a cryptographically random state parameter verified on callback, an attacker could trick an authenticated user into completing an OAuth authorisation flow that links the victim's account to the attacker's session — granting the attacker access to the victim's third-party data.

Impact: Attacker can gain access to victim's connected third-party account data.

LowFinding 5

Sensitive business logic exposed in publicly accessible HTML

As a direct consequence of the client-side authentication bypass (Finding 3), proprietary business configuration data was readable by anyone with a browser.

The exposed data included detailed labour rates, sales tax rates, overhead percentages, material pricing formulas, waste factors, and the complete application feature set — information a competitor, supplier, or customer could use to significant disadvantage.

Impact: Competitive intelligence and operational data exposure.

LowFinding 6

Session token accessible to JavaScript

The application's authentication cookie lacked the HttpOnly flag, meaning it could be read by any JavaScript executing on the page.

On its own this is a low-severity finding. Combined with Finding 1 (the RCE-capable file rendering library), it creates a complete account takeover chain: a malicious file triggers JavaScript execution, which reads the session token and transmits it to an attacker, who then uses it to impersonate the victim.

Impact: Enables complete account takeover chain when combined with Finding 1.

LowFinding 7

Missing security response headers

Several standard security headers were absent from HTTP responses, leaving the application without basic protections against cross-site scripting, clickjacking, and MIME-type sniffing.

The missing headers — Content-Security-Policy, X-Frame-Options, and X-Content-Type-Options — are considered baseline security hygiene and are trivially easy to add to deployment configuration. Their absence suggests they were never considered during development.

Impact: Reduced defence-in-depth against common web attack vectors.

LowFinding 8

Employee personally identifiable information in public JavaScript

Real employee full names were hardcoded in a client-side JavaScript file that was accessible without authentication.

The names were embedded in configuration objects as part of operational logic. Any visitor to the application — authenticated or not — could harvest these names. They represent a social engineering and phishing risk: an attacker who knows real employee names can craft far more convincing targeted attacks.

Impact: Employee PII exposure; social engineering attack surface.

LowFinding 9

Overly permissive CORS policy

The application set a wildcard Access-Control-Allow-Origin header on static assets, permitting any external website to make cross-origin requests.

Combined with the absent rate limiting on the login endpoint, any third-party website could script automated authentication requests from a victim's browser without their knowledge — bypassing IP-based restrictions and making attack attribution significantly harder.

Impact: Enables cross-origin scripted attacks against the login endpoint.

Recommended remediation order

Not all vulnerabilities carry equal urgency. The following priority order was provided based on exploitability, potential impact, and remediation effort:

  1. 1

    Update the outdated library immediately

    This is the most critical finding. An active, known exploit exists. Update to the patched version and set the eval mitigation flag as a temporary measure.

  2. 2

    Add rate limiting to the login endpoint

    High risk, straightforward to implement. Most cloud platforms and frameworks offer built-in rate limiting middleware.

  3. 3

    Move the authentication guard server-side

    Eliminates findings 3 and 5 in a single change. No content should be served to unauthenticated requests.

  4. 4

    Add HttpOnly flag to the session token cookie

    Breaks the account takeover chain by preventing JavaScript from reading the session token.

  5. 5

    Move employee data to an authenticated API

    Removes PII from public JavaScript and is architecturally sound regardless of the security benefit.

  6. 6

    Add security response headers

    Low effort, high return. Add to deployment configuration — one change covers every route.

  7. 7

    Add the OAuth state parameter

    Required by the OAuth specification. Prevents CSRF in third-party account linking flows.

  8. 8

    Restrict the CORS policy

    Tighten Access-Control-Allow-Origin to the application's own domain.

Key takeaways

This application was built by someone with genuine technical ability. The codebase was well-structured, the database queries were parameterised, JWT verification was correct, and the API authentication logic was sound. These are not the mistakes of a careless developer.

They are the mistakes of an AI coding assistant that was never asked the right security questions — and a development process that had no step for independent verification before going live. The vulnerabilities found are precisely the ones that AI-generated code is most likely to produce: missing headers, missing flags, missing rate limits, and outdated library recommendations drawn from stale training data.

The good news: every finding on this list has a straightforward fix. None required architectural changes. The total remediation effort, for a developer familiar with the codebase, was estimated at under two working days.

The cost of not finding these issues first? Potentially far greater — in data loss, regulatory exposure, customer trust, and business continuity.

Why this keeps happening

This is not an isolated case. Read our broader analysis of why AI-assisted development introduces predictable, systematic security gaps — and what responsible AI adoption looks like.

The hidden cost of AI-built solutions