← All Research

The vulnerability classes that don't come back

Six classes of vulnerability slipped past traditional tooling for years. A study of 28 high-confidence findings from advanced security review shows why cyber-capable models now catch them at review time - before they ship.

securityappseccyber-capable-models

When you point a cyber-capable model at a real production codebase, it doesn't return a tidy list of typos. It returns the kinds of findings a determined attacker would chain: an authorization gate that fails open, a path that escapes its sandbox, a token that travels to the wrong host. Across advanced security reviews of modern backend services, this generation of models surfaced a corpus of 28 high-confidence findings - every one at the maximum confidence tier.

This is not a vulnerability disclosure. The targets are anonymized, each issue was caught and fixed before it could ship, and the specifics that would help an adversary - exact routes, identifiers, encoded payloads, integration hosts, configuration keys - are deliberately masked throughout (a ▒▒ marks a redacted value). What we keep is the part that generalizes: the classes of defect these findings represent, why traditional tooling missed them for so long, and why this entire category is now closing.

A corpus of 28 findings

Twenty-eight findings, every one rated at maximum confidence, drawn from a representative service architecture: a request/API tier and a set of worker/processing services. The request tier - the outermost surface - held the majority; the worker tier held fewer but, on balance, more severe issues: filesystem traversal, process panics, and cross-host token leakage.

DimensionBreakdown
Total findings28
ConfidenceMaximum on every finding
Vulnerability classes6
Request / API tier20 findings
Worker / processing tier8 findings
Trace to one fail-open root cause13 findings

Six classes of defect

Every finding maps to one of six classes - the bug a developer would actually fix. Authorization failures dominate: broken authentication and broken object-level authorization together account for half the corpus.

Findings by vulnerability class28 advanced-security findings · confidence 5/5
28 total
All 28 findings grouped by the primary class of defect each represents. Authorization failures - broken authentication plus object-level (IDOR) - account for half the corpus. Hover any bar for the exact count.

The shape of that distribution is the headline. This is not a codebase riddled with exotic memory bugs; it is a modern service mesh where the recurring risk is who is allowed to do what - and where a handful of access-control assumptions, once wrong, repeat across many handlers.

Why traditional tooling missed them

For years, these classes were exactly the ones static analyzers and pattern matchers were worst at. A scanner reasons about syntax; it does not understand authorization *intent* or cross-tenant context. An IDOR is invisible to a linter because the code is "correct" - it simply authorizes the wrong scope. A fail-open prefix gate looks like ordinary routing. Traversal-in-context, a token sent to the wrong host, and resource exhaustion all require following data and intent across functions, not matching a signature.

That gap is the whole story of why these bugs survived. Cyber-capable models close it because they reason about the program the way a reviewer does - see Model vs. SAST and Can cyber-capable models fix AppSec? for how that reasoning differs from rule-based scanning.

The force multiplier: one fail-open decision

The single most valuable thing this review surfaced was not a bug. It was an architectural pattern.

A request gate authorized callers by URL prefix and returned early for an entire internal namespace, skipping token validation for everything behind it. Any handler mounted under that namespace inherited the bypass - no matter how privileged its work. A caller able to reach the service directly (a neighboring workload in the cluster, or an SSRF pivot to the internal address) could invoke any of them, supplying whatever tenant identifier it liked.

Behind that one gate sat: cross-tenant identity enumeration, integration-secret exfiltration, integration signing-key disclosure, forged privileged approvals, injected automated jobs, resource-metadata and storage-URL leakage, billing mutation, budget mutation, alert spoofing, duplicate-job replay, and unbounded analytic scans. The lesson is blunt: authorizing by the shape of a path instead of the identity of a caller fails open.

Class 1 - Broken authentication

*11 findings · CWE-306 Missing Authentication for Critical Function · CWE-862 Missing Authorization*

Handlers that performed privileged work with no caller-identity check, because a shared gate waved their whole namespace through. The blast radius is cross-tenant: the attacker names any tenant and acts on it.

  • Disclosure. Integration credentials, signing keys, and shared secrets returned to unauthenticated callers - enough to impersonate the integration for any tenant.
  • Integrity. Forge a privileged approval using the integration's write identity; inject automated remediation jobs into private projects.
  • Abuse. Mutate paid-seat counts and budget credits; post spoofed messages into an internal alert channel.
text
# Representative - masked
GET  /internal/▒▒▒▒/▒▒/credentials       → decrypted integration secrets
GET  /internal/▒▒▒▒/integrations/▒▒      → integration signing key + shared secret
POST /internal/▒▒▒▒/{tenantId}/seats     → billing mutation
fix: require signed service identity / mTLS · fail closed when credential absent

Class 2 - Broken object-level authorization (IDOR)

*3 findings · CWE-639 Authorization Bypass Through User-Controlled Key*

Here the caller *is* authenticated, but the handler trusts an object identifier from the request without confirming the object belongs to the caller's scope. Legitimate access to one object becomes access to a sibling.

  • A scope-limited user reads the contents of a record in a different project of the same tenant - the query authorized the tenant but never the project.
  • A notification's read-state flips on another user's record by guessing an identifier - the update matched on object id alone, never on owner id.
  • A processing job launches by pairing an authorized project id with a record id from a different project - the two were never checked for consistency.
text
# Representative - masked
GET /resources/▒▒▒▒/content
   authorized:  tenant == activeTenant       ✗ object-level ACL never applied
   missing:     allowedObjects(READ)         ← the fix

Class 3 - Path traversal

*2 findings · CWE-22 Improper Limitation of a Pathname to a Restricted Directory*

A caller-controlled identifier joined straight into a filesystem path with no parent-directory or base-directory check. An encoded traversal sequence escaped the intended per-tenant directory - once to read across every tenant's data, once to delete it. The deletion path sat behind the fail-open gate, so the worst case escalates to unauthenticated destruction of broad stored data.

text
# Representative - masked (encoded parent sequence redacted)
DELETE /v1/▒▒▒▒/{tenant}/▒▒▒▒▒▒
   target = {DATA}/{scope}/▒▒    → resolves up to {scope}
   remove_dir_all(target)        → deletes far beyond one tenant
fix: reject parent/absolute components · canonicalize + verify under base dir

Class 4 - SSRF and credential leakage

*3 findings · CWE-918 Server-Side Request Forgery · CWE-522 Insufficiently Protected Credentials*

The server made an outbound request to a destination it did not validate, and attached a secret to it. The destination might be an internal address (classic SSRF) or simply an attacker-controlled host that receives a token meant for someone else.

  • An integration accepted an arbitrary domain, then sent a private access token to it during validation and later operations - internal, link-local, and localhost targets were not rejected.
  • A forged queue job set the target host; a worker fetched the tenant's storage token and attached it to a request aimed at the attacker's origin - no same-origin check against the configured integration.
  • An access-token cache keyed by id alone returned a token minted for one host when called for another - colliding ids across the managed host and self-hosted instances caused token confusion and cache poisoning.
text
# Representative - masked
integration.host = https://▒▒▒▒▒▒    → token sent to attacker / internal host
fix: allowlist origins · reject private/link-local/localhost · pin token to origin

Class 5 - Resource exhaustion and crashes

*8 findings · CWE-400 Uncontrolled Resource Consumption · CWE-770 · CWE-248 Uncaught Exception*

Untrusted input drove unbounded work or an unhandled crash. Two flavors appeared: amplification - a small request that costs the server dearly - and panics, where malformed data aborts a worker and triggers repeat delivery until the message is force-archived.

  • Memory. Downloading and base64-buffering every remote attachment at once; serializing an arbitrarily large input before any size cap is applied.
  • Amplification. A tenant-controlled manifest referencing thousands of files, turning a single scan into thousands of downstream model and storage calls.
  • Panics. An unwrap() on a non-JSON parser line, and a byte-index slice on an externally supplied string that ignored length and character boundaries.
  • Rate-limit bypass. A limiter keyed on a proxy address behind an untrusted-proxy configuration, so every external client shared one bucket - one client could exhaust it for all.
  • Replay and scans. A non-atomic job claim let duplicate triggers re-run expensive work; unbounded query parameters allowed multi-year analytic table scans.
text
# Representative - masked (process panic)
let short = &value[..7];                // panics on short / non-ASCII input
serde_json::from_str(line).unwrap();    // panics on malformed parser line
fix: validate length/encoding · return an error to the existing fallback · bound size

Class 6 - Cost and budget abuse

*1 finding (plus 2 cross-tagged from Class 1) · CWE-770 Allocation Without Limits · economic denial of service*

A code path spent the vendor's own metered model budget on behalf of a caller who supplied no key and hit no quota. Distinct from the authentication findings above - which let an attacker *mutate* billing - this one drains spend directly: an authenticated user repeatedly submits large inputs in a mode that silently falls back to house credentials.

text
# Representative - masked
mode = "local"; header x-▒▒▒▒-model-token = <absent>
   → falls back to house model provider · no per-request quota
fix: require a caller-supplied model token for local mode · apply quota / rate controls

Impact and attack surface

Confidence is uniform across the corpus, so the more useful overlay is impact tier - how damaging exploitation would be. Secret and key disclosure, data destruction, and forged privileged actions land Critical; resource-exhaustion and crash bugs land Medium.

Impact tierHow damaging exploitation would be
11 critical
Analyst-assigned impact tier (separate from the model's uniform 5/5 confidence). Secret disclosure, data destruction, and forged privileged actions rank Critical; resource-exhaustion and crash bugs rank Medium.

Grouping instead by the plane an attacker would operate from shows where defensive investment compounds. The internal service plane - reachable from a neighboring workload or an SSRF pivot - is by far the richest target, which is exactly why fixing the gate in front of it mattered so much.

Attacker planeFindingsWhat it takes to reach
Internal service plane12In-cluster workload or an SSRF pivot; no external auth
Authenticated tenant user4A valid session, abusing object scope or house compute
Unauthenticated external4A misconfigured deployment and a shared-bucket limiter
Malicious tenant content3A crafted input or manifest drives unbounded work
Outbound integration3An attacker-chosen host receives a server-held token
Forgeable queue message2A crafted job panics a worker or redirects a token

The full register

All 28 findings, masked. Component is reduced to a generic service role; no exploitable route, identifier, or payload appears.

#ClassDefect (masked)ComponentTier
01Broken authenticationCross-tenant identity enumeration via auth-skipped namespaceControl plane · authCritical
02Broken authenticationFail-open when a required gate key is unsetStorage serviceCritical
03Broken authenticationIntegration credential exfiltrationIntegration serviceCritical
04Broken authenticationIntegration signing key + shared-secret disclosureIntegration serviceCritical
05Broken authenticationIntegration secret lookup by host / tenantIntegration serviceCritical
06Broken authenticationForged privileged approval via integration identityAutomation serviceCritical
07Broken authenticationInjected automated remediation jobsRemediation serviceCritical
08Broken authenticationResource metadata + signed storage-URL exposureCatalog serviceHigh
09Broken authenticationPaid-seat count mutationBilling serviceHigh
10Broken authenticationBudget-credit mutationMetering serviceHigh
11Broken authenticationSpoofed messages into alert channelNotification serviceHigh
12Broken object authCross-scope record read (object ACL skipped)Content serviceHigh
13Broken object authNotification read-state IDORNotification serviceHigh
14Broken object authCross-scope job via object mismatchProcessing serviceHigh
15Path traversalTenant-data deletion path traversalStorage serviceCritical
16Path traversalCross-tenant search escape via traversalSearch serviceHigh
17SSRF / cred leakArbitrary integration domain → SSRF + token leakIntegration serviceCritical
18SSRF / cred leakForged-job storage-token exfiltrationIngestion workerCritical
19SSRF / cred leakAccess-token cache confusion across hostsIntegration token cacheCritical
20Resource exhaustionUnbounded attachment buffering → memory exhaustionIntegration serviceMedium
21Resource exhaustionLarge-input processing memory exhaustionProcessing workerMedium
22Resource exhaustionWorker panic via malformed queue messageQueue workerMedium
23Resource exhaustionWorker panic via malformed parser outputProcessing workerMedium
24Resource exhaustionManifest-driven unbounded fan-out (amplification)Extension loaderMedium
25Resource exhaustionRate-limit bypass via shared proxy IPPublic endpointMedium
26Resource exhaustionDuplicate-job replay (non-atomic claim)Batch jobMedium
27Resource exhaustionUnbounded analytic DB scansAnalytics jobMedium
28Cost / budget abuseUnmetered house-model spend (local mode)Inference gatewayMedium

Why this class is closing

Every finding here has two things in common. Each is the kind of defect that earlier tooling could not see - logic and authorization bugs that don't match a signature. And each is now the kind a cyber-capable model catches at review time, before merge.

That second fact is what makes this a closing chapter rather than an open backlog. As cyber-capable review becomes the default gate on every change, this whole category stops reaching production - not because engineers stop making the mistakes, but because the mistakes are caught at the door, in the same pass that already reads the diff. The fail-open gate, the cross-tenant IDOR, the token sent to the wrong host: a reviewer that reasons about intent flags them as a matter of course.

For where this is heading, see Advanced Security Reviewer: security review for the cyber-capable model era, Getting ready for cyber-capable models like Mythos, and cyber.md: AI-native posture that speaks agent.

Method and masking

Source. Twenty-eight findings produced by advanced security review using cyber-capable models, each rated at maximum confidence. The class assignments and impact tiers in this report are an analyst overlay on that data; the raw model confidence is uniform across every finding.

Masking. No actionable entry point is disclosed. Targets are anonymized; exact routes, identifiers, configuration keys, encoded traversal payloads, and integration hosts are redacted or generalized, and components are reduced to generic service roles. Every pattern, count, and consequence is grounded in a real finding - only the values an adversary would need are removed.

The takeaway. The corpus is dominated by authorization failures, and most of those collapse to a single fail-open architectural choice. The value was less the individual catches than naming the *class* - and the larger point is that naming these classes is now routine for the models doing the reviewing.