Skip to main content
Security9 min read

AI-Generated Code Is Growing Your Attack Surface—Retrofit DAST + API Discovery Gates Without Slowing CI/CD

AI-assisted coding is accelerating merges faster than most teams can validate them—and the result is a quietly expanding attack surface. This post outlines a practical way to add DAST and agent/API discovery gates to an existing CI/CD pipeline so modernization velocity doesn’t become long-lived security debt.

AI-assisted development has changed the shape of risk. Code arrives faster, in larger batches, and often with fewer humans deeply understanding the edges. If your CI/CD pipeline was built for “human-authored diffs,” you’re likely shipping new routes, handlers, and third-party calls that never hit meaningful security validation.

Snyk recently summarized the dynamic well: delivery speed has outpaced validation, and 62% of LLM-generated code tested as insecure. Add AI agents that call undocumented APIs, and you end up with gaps that legacy security tools—and even mature test suites—can miss. The good news: you don’t need to halt delivery to catch up. You can retrofit testing gates that focus on what AI changes tend to break: exposure, integration drift, and unobserved surface area.

Context: why AI-generated changes expand risk in maintenance and modernization work

AI-Generated Code Is Growing Your Attack Surface—Retrofit DAST + API Discovery Gates Without Slowing CI/CD
AI-Generated Code Is Growing Your Attack Surface—Retrofit DAST + API Discovery Gates Without Slowing CI/CD

Maintenance and modernization teams are uniquely exposed. You’re often:

  • Updating frameworks and runtime versions
  • Refactoring monoliths into services
  • Wrapping legacy systems with new APIs
  • Merging AI-authored patches into older code that lacks comprehensive tests

That combination makes it easy to ship functionality that “works” in happy paths but quietly introduces:

  • New endpoints (sometimes without auth parity)
  • Weak input validation or inconsistent encoding
  • New dependency chains
  • Calls to internal or legacy admin APIs that were never meant for general use

Snyk’s piece, “AI Is Building Your Attack Surface. Are You Testing It?”, frames the core problem: AI increases throughput, but it also increases the volume of changes that can create exploitable behaviors. Worse, AI agents can interact with systems through “tribal knowledge” endpoints—things nobody documented because they were never intended to last.

At the same time, the broader threat landscape isn’t slowing down. Botnets, supply chain attacks, and rapidly weaponized public vulnerabilities (as covered across security reporting from outlets like KrebsOnSecurity and BleepingComputer) thrive on exactly these gaps: exposed services, stale dependencies, and misconfigured interfaces.

Main analysis: where legacy gates fall short with LLMs and agents

Most pipelines already have some combination of unit tests, SAST, dependency scanning, and maybe container scanning. Those are necessary—but AI introduces patterns that can slip through.

1) “It compiles” is not validation

LLM-generated code is often syntactically correct and plausibly structured. That makes it easy to accept changes based on green builds alone. But security failures tend to be semantic:

  • Missing authorization checks on a newly added route
  • SSRF via a “convenience” URL fetch helper
  • IDOR from overly broad resource lookups
  • Deserialization hazards in “quick” integrations

These issues frequently don’t show up in unit tests unless you already had security-focused test cases.

2) SAST and dependency scanning don’t see runtime exposure

Static tooling is great at known classes of mistakes and vulnerable packages, but it doesn’t reliably answer:

  • What endpoints are now reachable from the internet?
  • What parameters can be influenced by untrusted callers?
  • What auth flows can be bypassed due to proxy/header behavior?

That’s why Snyk emphasizes that speed has outpaced validation and highlights the insecure rate observed in testing LLM-generated code.

3) AI agents can create “shadow integrations” via undocumented APIs

Modern dev teams increasingly run agents that:

  • Open PRs based on issue text
  • Call internal services to retrieve context
  • Stitch together workflows across SaaS and internal APIs

Agents often “discover” APIs by reading code, logs, or examples—and may call endpoints that aren’t in your OpenAPI specs, Postman collections, or official docs. Those calls can become production dependencies. If your security gates only validate documented surfaces, you’re blind to what the agent actually used.

The retrofit strategy: DAST + agent/API discovery as CI/CD gates

You don’t need a massive re-platform to fix this. The practical goal is:

  1. Detect new or changed runtime surface area (API discovery)
  2. Exercise it like an attacker would (DAST)
  3. Gate releases based on risk and change scope, not by making every build painfully slow

Below is a pattern that works well for maintenance and modernization teams because it’s additive: you can layer it onto existing CI/CD.

Step 1: Add an ephemeral “scan environment” stage

Why it matters

DAST needs something running. The fastest way to do this without slowing delivery is to standardize a short-lived environment per build or per release candidate.

Implementation pattern

  • In CI, build an artifact (container image or deployable package).
  • Deploy it to an ephemeral environment (Kubernetes namespace, ephemeral VM, preview environment).
  • Seed minimal test data.
  • Run smoke tests first to confirm the environment is stable.

This is modernization-friendly: you can do it even if the app is legacy, as long as you can stand up a runnable instance (often via containers, even if production isn’t fully containerized yet).

Step 2: Perform API discovery focused on “what changed”

What discovery should do

API discovery is not just inventory for compliance—it’s a change detector for attack surface.

At minimum, you want to answer:

  • Did this PR introduce new routes?
  • Did it change HTTP methods, auth requirements, or parameter shapes?
  • Did it add new outbound calls to internal services?

Practical approaches

  • Traffic-based discovery: run your integration tests (or basic scripted flows) while capturing API calls and endpoints reached.
  • Spec diffing: if you have OpenAPI, diff generated specs vs baseline. If you don’t, consider generating a “best-effort” spec from routing metadata.
  • Agent-aware discovery: if AI agents run workflows that hit internal APIs, capture and track those calls as first-class interfaces.

The key is to treat “undocumented but used” APIs as real. Snyk’s warning about agents using undocumented APIs is exactly the scenario where teams later discover they’ve been depending on—and exposing—interfaces nobody reviews.

Gate recommendation

Fail (or require approval) when:

  • A new public-facing endpoint appears without an associated auth policy
  • A new endpoint has no tests and no documented owner
  • Undocumented endpoints are called from automation/agent workflows without being registered

This is a lightweight governance layer that doesn’t require a full documentation overhaul on day one.

Step 3: Run DAST in two tiers (fast PR checks + deep nightly)

DAST has a reputation for being slow. The trick is to scope it.

Tier A: “PR DAST” (fast, scoped)

Run on pull requests that:

  • Add routes/controllers n- Touch auth/session code
  • Change input validation/serialization
  • Modify API gateway/proxy config

Configuration:

  • Scan only the endpoints discovered in Step 2 (delta scan)
  • Time-box to a small budget (e.g., 5–10 minutes)
  • Fail only on high-confidence, high-severity issues (auth bypass, injection, exposed admin endpoints)

Tier B: “Release/ nightly DAST” (deep, broad)

Run daily or on release candidates:

  • Full crawl of the application
  • More aggressive tests and longer runtime
  • Broader rule set (including medium severity)

This preserves delivery speed while still giving you comprehensive coverage.

Step 4: Add “risk-based gating” rather than “one gate for everything”

To avoid slowing delivery, gates should be proportional to risk.

A practical policy:

  • Low-risk changes (docs, UI text, refactors with no route changes): existing unit tests + SAST/deps only
  • Medium-risk changes (business logic, data models): add API discovery + PR DAST time-boxed
  • High-risk changes (auth, routing, file upload, deserialization, gateway rules): require PR DAST + manual security review and deeper scans

This is especially important in maintenance programs where many PRs are routine (dependency bumps, small compatibility edits) and shouldn’t be treated like brand-new product work.

Step 5: Make results actionable (and hard to ignore)

Security gates fail when they become noisy. Improve signal with:

  • Deduplication and baselining: don’t re-alert on known accepted risks every build; track them explicitly.
  • Ownership mapping: every endpoint/service needs an owner (team or system).
  • Fix guidance: link findings to specific code lines, commits, or endpoint diffs.

This reduces the “tool fatigue” that causes teams to bypass gates.

Practical implications for engineering teams and CTOs

For developers: shift left without shifting pain

Developers don’t want more process—they want faster feedback.

  • Keep PR DAST short and scoped.
  • Trigger DAST intelligently (only when surface changes).
  • Provide a clear “what changed” API diff in the PR.

This turns security into a tight feedback loop rather than a release-blocking surprise.

For platform/DevOps teams: treat discovery as a first-class artifact

Make “API inventory + diff” a build artifact alongside SBOMs and test reports. Over time, this becomes an operational map of your modernization journey:

  • Which legacy modules still expose endpoints?
  • Which services are growing in complexity?
  • Where do AI agents interact with internal systems?

For CTOs: avoid compounding security debt during modernization

Modernization already carries risk (framework upgrades, architectural changes). If AI-generated code increases throughput without equivalent validation, you effectively finance speed with future incident cost.

Recent real-world reporting on botnets, malicious packages, and quickly exploited vulnerabilities underscores the practical stakes: attackers capitalize on exposure and inconsistency. The Snyk statistic—62% of LLM-generated code testing as insecure—isn’t a reason to ban AI; it’s a reason to ensure your pipeline catches the kinds of mistakes AI commonly introduces.

Conclusion: modernize your gates the same way you modernize your stack

AI will keep accelerating delivery. The question is whether your validation system evolves with it.

Retrofitting DAST plus agent/API discovery into CI/CD is a pragmatic upgrade strategy: you don’t need to rewrite your app or slow teams down, but you do need to start measuring what’s actually being exposed and exercised at runtime. As maintenance and modernization teams merge more AI-authored changes into legacy systems, these gates become the difference between sustainable velocity and security debt that lingers for years.

Next step: start with one service, stand up an ephemeral scan environment, generate an API diff per PR, and add a time-boxed PR DAST scan for surface-changing changes. Within a sprint or two, you’ll have meaningfully reduced your unknown exposure—without giving up speed.

Vibgrate CLI

See a real scan run

A replay of the actual CLI running against our test repositories — live progress, real findings, a genuine DriftScore. Nothing executes in your browser.

Replay
demo@vibgrate — bash
npx @vibgrate/cli scan
 
╭──────────────────────────────────────────╮
Vibgrate Drift Report
╰──────────────────────────────────────────╯
 
── node-turborepo (node) .
Runtime: >=18.0.0 (6 majors behind)
Frameworks:
Turbo: 1.13.4 → 2.10.13 (1 behind)
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Dependencies:
1 current 1 1-behind 3 2+ behind 1 unknown
 
── @repo/admin (node) apps/admin
Frameworks:
TanStack Query: 5.103.1 → 5.103.1 (current)
React: 18.3.1 → 19.3.0 (1 behind)
React DOM: 18.3.1 → 19.3.0 (1 behind)
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Vite: 5.4.21 → 8.3.0 (3 behind)
Dependencies:
3 current 9 1-behind 3 2+ behind 4 unknown
 
── @repo/api (node) apps/api
Frameworks:
Express: 4.22.3 → 5.2.1 (1 behind)
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Vitest: 1.6.1 → 5.0.1 (4 behind)
Dependencies:
7 current 5 1-behind 3 2+ behind 4 unknown
 
── @repo/web (node) apps/web
Frameworks:
Next.js: 14.2.35 → 16.3.5 (2 behind)
React: 18.3.1 → 19.3.0 (1 behind)
React DOM: 18.3.1 → 19.3.0 (1 behind)
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Dependencies:
2 current 6 1-behind 3 2+ behind 5 unknown
 
── @repo/config (node) packages/config
Frameworks:
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Dependencies:
2 current 2 1-behind 5 2+ behind 0 unknown
 
── @repo/database (node) packages/database
Frameworks:
Prisma: 5.22.0 → 7.10.0 (2 behind)
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Dependencies:
1 current 0 1-behind 3 2+ behind 1 unknown
 
── @repo/types (node) packages/types
Frameworks:
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Dependencies:
0 current 0 1-behind 1 2+ behind 1 unknown
 
── @repo/ui (node) packages/ui
Frameworks:
React: 18.3.1 → 19.3.0 (1 behind)
TypeScript: 5.9.3 → 7.0.2 (2 behind)
React: 18.3.1 → 19.3.0 (1 behind)
Dependencies:
1 current 4 1-behind 1 2+ behind 1 unknown
 
── @repo/utils (node) packages/utils
Frameworks:
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Vitest: 1.6.1 → 5.0.1 (4 behind)
Dependencies:
0 current 1 1-behind 2 2+ behind 1 unknown
 
Tech Stack
Frontend: React, React DOM
Meta-frameworks: Next.js
Bundlers: tsx, Turbo, Vite
CSS / UI: Autoprefixer, PostCSS, Tailwind CSS
Backend: Express
ORM / Database: Prisma, Prisma Client
Testing: Vitest
Lint & Format: ESLint, ESLint Prettier, ESLint React, Prettier, typescript-eslint
 
Services & Integrations
Auth: JWT 9.0.3
Databases: Prisma 5.22.0
 
TypeScript
v5.3.3 · strict ✔ · MIXED · target: ES2022
 
Build & Deploy
Package Managers: pnpm
Monorepo: npm-workspaces, pnpm-workspaces, turbo
 
Product Purpose Signals
Frameworks: react, nextjs
Evidence: 177
Top Signals:
- [heading] Dashboard (apps/admin/src/pages/Dashboard.tsx)
- [title] Revenue Overview (apps/admin/src/pages/Dashboard.tsx)
- [copy] workspace:* (packages/ui/package.json)
- [copy] ./dist (packages/ui/tsconfig.json)
- [copy] ./src/index.ts (packages/ui/package.json)
- [copy] @repo/config/tsconfig-base.json (packages/ui/tsconfig.json)
- [copy] @repo/ui (packages/ui/package.json)
- [copy] #3b82f6 (apps/admin/src/pages/Dashboard.tsx)
Unknowns:
- No pricing or billing evidence found.
- No integrations/connectors evidence found.
- No route structure evidence found.
 
Security Posture
Lockfile ✖ · .env ✔ · node_modules ✔
 
Platform
Native modules: turbo
 
Code Quality
Files: 36 · Functions: 183 · Avg complexity: 2.62 · Avg length: 21.13 lines
Max nesting: 2 · Circular deps: 0 · Dead code: 0%
God files: apps/admin/src/pages/Products (448 lines)
 
Database Schema
postgresql · 8 models · 1 enum
Models: Address, CartItem, Category, Order, OrderItem (+3 more)
 
Findings (16 errors, 11 warnings)
Node.js runtime ">=18.0.0" reached end-of-life on 2025-04-30 (latest: 24.0.0).
vibgrate/runtime-eol in .
TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in .
60% of dependencies are 2+ major versions behind in node-turborepo.
vibgrate/dependency-rot in .
@types/node is 6 major versions behind (spec: ^20.11.0, latest: 26.6.1).
vibgrate/dependency-major-lag in .
TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in apps/admin
Vite is 3 major versions behind (current: 5.4.21, latest: 8.3.0).
vibgrate/framework-major-lag in apps/admin
vite is 3 major versions behind (spec: ^5.0.12, latest: 8.3.0).
vibgrate/dependency-major-lag in apps/admin
TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in apps/api
Vitest is 4 major versions behind (current: 1.6.1, latest: 5.0.1).
vibgrate/framework-major-lag in apps/api
@types/node is 6 major versions behind (spec: ^20.11.0, latest: 26.6.1).
vibgrate/dependency-major-lag in apps/api
vitest is 4 major versions behind (spec: ^1.2.1, latest: 5.0.1).
vibgrate/dependency-major-lag in apps/api
Next.js is 2 major versions behind (current: 14.2.35, latest: 16.3.5).
vibgrate/framework-major-lag in apps/web
TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in apps/web
@types/node is 6 major versions behind (spec: ^20.11.0, latest: 26.6.1).
vibgrate/dependency-major-lag in apps/web
TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in packages/config
56% of dependencies are 2+ major versions behind in @repo/config.
vibgrate/dependency-rot in packages/config
eslint-plugin-react-hooks is 3 major versions behind (spec: ^4.6.0, latest: 7.1.1).
vibgrate/dependency-major-lag in packages/config
Prisma is 2 major versions behind (current: 5.22.0, latest: 7.10.0).
vibgrate/framework-major-lag in packages/database
TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in packages/database
75% of dependencies are 2+ major versions behind in @repo/database.
vibgrate/dependency-rot in packages/database
TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in packages/types
100% of dependencies are 2+ major versions behind in @repo/types.
vibgrate/dependency-rot in packages/types
TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in packages/ui
TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in packages/utils
Vitest is 4 major versions behind (current: 1.6.1, latest: 5.0.1).
vibgrate/framework-major-lag in packages/utils
67% of dependencies are 2+ major versions behind in @repo/utils.
vibgrate/dependency-rot in packages/utils
vitest is 4 major versions behind (spec: ^1.2.1, latest: 5.0.1).
vibgrate/dependency-major-lag in packages/utils
 
╭──────────────────────────────────────────╮
Top Priority Actions
╰──────────────────────────────────────────╯
 
1. Upgrade EOL runtime in node-turborepo
End-of-life runtimes no longer receive security patches and block ecosystem upgrades.
./.
>=18.0.0 → 24.0.0 (6 majors behind)
Impact: −10 drift points (runtime & EOL)
 
2. Fix security posture: no lockfile found
Without a lockfile, installs are non-deterministic. Run the install command to generate one and commit it.
./
Missing: package-lock.json, pnpm-lock.yaml, or yarn.lock
 
3. Upgrade Vitest 1.6.1 → 5.0.1 in @repo/api (+2 more)
4 major versions behind. Major framework drift increases breaking change risk and blocks access to security fixes and performance improvements.
./apps/api
Vitest: 1.6.1 → 5.0.1 (4 majors behind)
./packages/utils
Vitest: 1.6.1 → 5.0.1 (4 majors behind)
./apps/admin
Vite: 5.4.21 → 8.3.0 (3 majors behind)
Impact: −5–15 drift points
 
4. Reduce dependency rot in @repo/types (100% severely outdated)
1 of 1 dependencies are 2+ majors behind. Run `npm outdated` and prioritise packages with known CVEs or breaking API changes.
./packages/types
typescript: 5.9.3 → 7.0.2 (2 majors behind)
Impact: −5–10 drift points
 
5. Reduce dependency rot in @repo/database (75% severely outdated)
3 of 4 dependencies are 2+ majors behind. Run `npm outdated` and prioritise packages with known CVEs or breaking API changes.
./packages/database
@prisma/client: 5.22.0 → 7.10.0 (2 majors behind)
prisma: 5.22.0 → 7.10.0 (2 majors behind)
typescript: 5.9.3 → 7.0.2 (2 majors behind)
Impact: −5–10 drift points
 
╭──────────────────────────────────────────╮
Architecture Layers
╰──────────────────────────────────────────╯
 
Archetype: nextjs (80% confidence)
Files classified: 24 (11 unclassified)
Folders classified: 8
apps/admin/src presentation 100% 4 files
apps/admin/src/pages presentation 100% 2 files
apps/api/src/middleware middleware 100% 2 files
apps/api/src/routes routing 100% 2 files
apps/web/src/app presentation 100% 4 files
apps/web/src/app/products presentation 100% 2 files
apps/web/src/app/products/[id] presentation 100% 1 file
packages/ui/src presentation 100% 6 files
Unclassified source (sample): 11
 
presentation 15 files drift ████████████████████ 100 risk high
routing 4 files drift ████████████████████ 100 risk high
middleware 2 files drift ███████▍░░░░░░░░░░░░ 37 risk moderate
config 2 files drift ░░░░░░░░░░░░░░░░░░░░ 0 risk none
shared 1 file drift ████████████████████ 100 risk high
 
╭──────────────────────────────────────────╮
DriftScore Summary
╰──────────────────────────────────────────╯
 
DriftScore: 70/100
Risk Level: HIGH
Projects: 9
Classified: 8 nano · 1 micro · 0 small · 0 standard
Billable: 0.42 · 9 detected → 0.42 billable projects (micro-project pricing)
0.1 micro · 0.32 nano
These fractions add up across repositories, then round down to whole billable projects.
 
Score Breakdown
Runtime: ████████████████████ 100
Frameworks: ███████████▊░░░░░░░░ 59
Dependencies: ██████▌░░░░░░░░░░░░░ 33
EOL Risk: ████████████████████ 100
 
Scanned at 2026-09-17T13:19:05.436Z · 6.0s · 286 files scanned · 56 workspace files · 27 dirs
Press Run to start.