Back to skill

Security audit

Sr Next Clerk Expert

Security checks for vulnerabilities and agentic risk

Overview

This Clerk/Next.js skill is mostly coherent, but it includes unsafe authentication and billing examples that could lead developers to ship broken route protection or unapproved Stripe checkout behavior.

Review this skill before installing or using it as an authority source. Do not copy the proxy.ts route matcher as written; protect actual URL paths or use a default-deny pattern with tests. For Stripe, map internal plan IDs to approved server-side Price IDs instead of trusting client input. Treat webhook and billing examples as starting points that require privacy disclosure, retention decisions, signature verification, and idempotent processing.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:60
Finding
Ineffective Clerk Route Protection Due to Filesystem Route-Group Matcher<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 60–65 **Vulnerability Type**: Authentication and route-protection bypass **Risk Level**: High ### Vulnerable Code ```typescript const isPrivateRoute = createRouteMatcher(["/(private)(.*)"]); export default clerkMiddleware(async (auth, request) => { if (isPrivateRoute(request)) { await auth.protect(); } }); ``` ### Technical Analysis The matcher attempts to protect `/(private)(.*)`. However, `(private)` is a Next.js filesystem route group, not a segment included in the public URL. For example, `app/(private)/dashboard/page.tsx` is served at `/dashboard`, not `/(private)/dashboard`. Consequently, requests to `/dashboard`, `/settings`, and other pages stored under the route group do not satisfy this matcher. The middleware therefore skips `auth.protect()`. The separate protected-layout example can mitigate this issue for pages only when developers implement it exactly. It does not make the documented proxy matcher effective, and it does not independently protect API handlers. ### Attack Path 1. A developer copies the documented proxy configuration and places sensitive pages under `app/(private)/`. 2. The developer assumes the proxy protects every route in that filesystem group. 3. An unauthenticated attacker sends a direct request to an actual public path such as `/dashboard`. 4. The matcher tests the request path against `/(private)(.*)`. 5. Because the request URL does not contain the route-group name, the matcher returns false. 6. `auth.protect()` is not called. 7. If the requested page or API lacks a separate server-side authorization check, the attacker accesses the protected functionality or data. ### Impact Assessment The flaw can permit unauthenticated access to routes that developers believe are protected. The exact scope depends on whether individual pages and API handlers perform independent authorization checks. Potential consequences include: - Unauthorized a ...[truncated 405 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Match the actual URL paths rather than filesystem route-group names. For example: ```typescript import { clerkMiddleware, createRouteMatcher, } from "@clerk/nextjs/server"; const isPrivateRoute = createRouteMatcher([ "/dashboard(.*)", "/settings(.*)", "/billing(.*)", ]); export default clerkMiddleware(async (auth, request) => { if (isPrivateRoute(request)) { await auth.protect(); } }); ``` For stronger default-deny behavior: 1. Define a narrow allowlist of genuinely public routes. 2. Protect every route not included in that allowlist. 3. Explicitly account for sign-in, sign-up, static assets, and public webhook endpoints. 4. Require authentication and authorization inside every sensitive API handler; do not rely exclusively on middleware. 5. Validate ownership or tenant membership for each accessed object. 6. Add integration tests that request every protected URL as an anonymous user and verify a redirect or `401/403` response. 7. Test actual paths such as `/dashboard`, not filesystem paths containing `(private)`. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
references/stripe.md:72
Finding
Client-Controlled Stripe Price ID Allows Selection of Unapproved Billing Prices<![CDATA[ ## Vulnerability Details **File Location**: `references/stripe.md`, lines 72–87 **Vulnerability Type**: Improper validation of client-controlled billing parameters **Risk Level**: High ### Vulnerable Code ```typescript const user = await currentUser(); const { priceId } = await req.json(); // Get or create Stripe customer // const stripeCustomerId = await getStripeCustomerId(userId); const session = await stripe.checkout.sessions.create({ customer_email: user?.emailAddresses[0]?.emailAddress, mode: "subscription", payment_method_types: ["card"], line_items: [{ price: priceId, quantity: 1 }], success_url: `${process.env.NEXT_PUBLIC_URL}/dashboard?success=true`, cancel_url: `${process.env.NEXT_PUBLIC_URL}/pricing?canceled=true`, metadata: { clerkId: userId }, }); ``` ### Technical Analysis The endpoint accepts `priceId` directly from the request body and forwards it to Stripe without server-side validation or allowlisting. Authentication confirms who is making the request, but it does not establish that the submitted Stripe Price is approved for that user or purchase flow. An authenticated user who knows or discovers another active Price ID associated with the Stripe account may request a Checkout Session for that price. This can include a legacy, discounted, test, alternate-currency, or otherwise unintended price. The risk becomes more severe if application entitlement logic treats any active subscription as equivalent to a specific paid plan instead of validating the resulting Stripe product and price. ### Attack Path 1. The attacker creates or signs into a valid account. 2. The attacker obtains an unintended Stripe Price ID through client assets, API responses, historical links, logs, documentation, or predictable application data. 3. The attacker sends a crafted POST request to the checkout endpoint: ```json { "priceId": "price_unapproved_or_legacy" } ``` 4. The server confirms that the attacker is authenticated but does ...[truncated 942 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Do not accept raw Stripe Price IDs as authoritative client input. Accept a constrained internal plan identifier and map it to a trusted Price ID on the server: ```typescript const APPROVED_PRICES = { basic: process.env.STRIPE_BASIC_PRICE_ID, pro: process.env.STRIPE_PRO_PRICE_ID, } as const; type Plan = keyof typeof APPROVED_PRICES; const body = await req.json(); const plan = body.plan as Plan; const priceId = APPROVED_PRICES[plan]; if (!priceId) { return new Response("Invalid plan", { status: 400 }); } const session = await stripe.checkout.sessions.create({ customer_email: user?.emailAddresses[0]?.emailAddress, mode: "subscription", line_items: [{ price: priceId, quantity: 1 }], success_url: `${process.env.NEXT_PUBLIC_URL}/dashboard?success=true`, cancel_url: `${process.env.NEXT_PUBLIC_URL}/pricing?canceled=true`, metadata: { clerkId: userId, plan, }, }); ``` Additional hardening should include: 1. Store approved Price IDs in server-only configuration. 2. Validate the selected plan against the user's organization, region, and eligibility. 3. Retrieve the Stripe Price server-side when necessary and verify its product, currency, recurrence, amount, and active status. 4. Derive entitlements from an explicit allowlist of approved product/price combinations. 5. Verify Stripe webhook signatures before changing subscription state. 6. Make webhook processing idempotent and correlate subscriptions with the authenticated Clerk user. 7. Add tests proving that arbitrary, legacy, and cross-plan Price IDs are rejected. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:213
Finding
Unpinned Remote Package Execution Through npx<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 213–218 **Vulnerability Type**: Mutable third-party dependency execution **Risk Level**: Medium ### Vulnerable Code ```bash # Option 1: Rename mv middleware.ts proxy.ts # Option 2: Codemod npx @next/codemod@latest middleware-to-proxy ``` ### Technical Analysis The command instructs users to execute `@next/codemod` through `npx` using the mutable `latest` distribution tag. If the package is not already installed locally, `npx` may download and execute the version currently published under that tag. The effective executable code can therefore change after the Skill has been audited. No exact version, lockfile resolution, or integrity verification is required by the instruction. This is a supply-chain risk rather than evidence that the named package is currently malicious. Exploitation requires compromise of the package publication process, registry account, dependency tree, or a future release resolved by `latest`. ### Attack Path 1. An attacker compromises the relevant package, maintainer account, release process, registry delivery path, or dependency. 2. A malicious version becomes associated with the `latest` tag. 3. A developer follows the Skill instruction and runs the `npx` command. 4. `npx` retrieves the mutable package version and executes its command or installation lifecycle behavior. 5. The malicious code runs with the developer's local operating-system privileges. 6. It may access files, source code, environment variables, authentication tokens, or other resources available to that developer account. ### Impact Assessment A successful supply-chain compromise could result in arbitrary local code execution under the invoking developer account. Depending on the local environment, accessible resources may include: - Project source code and configuration. - Environment files and development credentials. - Package-registry or deployment tokens. - Cloud CLI credentials. - Wr ...[truncated 282 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Pin the codemod to an exact reviewed version rather than using `latest`: ```bash npx --yes @next/codemod@<reviewed-exact-version> middleware-to-proxy ``` Prefer installing the reviewed version through the project's package manager and lockfile before running it: ```bash npm install --save-dev --save-exact @next/codemod@<reviewed-exact-version> npx --no-install @next/codemod middleware-to-proxy ``` Additional controls should include: 1. Review the package version and its dependency changes before execution. 2. Commit and enforce the package-manager lockfile. 3. Verify registry provenance and integrity metadata where supported. 4. Use `npx --no-install` after installing the approved dependency locally. 5. Avoid running package tooling with administrator or root privileges. 6. Run codemods in a clean branch or isolated development environment. 7. Review the generated diff before committing or deploying it. 8. Prefer the non-executable manual rename option when the codemod is unnecessary. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (6)

Credential Access

High
Category
Privilege Escalation
Content
## Environment Variables

```bash
# .env.local - COPY FROM CLERK DASHBOARD (do not type manually)
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
This markdown file includes example code that sends the user's email, name, and Clerk identifier to Stripe when a user is created. The document does not include any warning or disclosure that user data will be shared with a third-party billing provider, which is relevant to user privacy and fits the markdown-file warning criterion.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
This markdown file includes example handlers that extract email, name, and image URL from webhook payloads and create, update, or delete corresponding user records, but it does not provide any user-facing warning about privacy or data-handling implications. Under the markdown criteria for missing warnings, descriptions that affect user data should disclose those behaviors.

Natural-Language Policy Violations

Low
Confidence
89% confidence
Finding
The markdown includes `<html lang="en">` as the prescribed root layout pattern, which implicitly forces an English locale. Under the policy, locale-specific behavior should either be optional, user-selectable, or explicitly justified as region-specific.

Natural-Language Policy Violations

Low
Confidence
91% confidence
Finding
The root layout sets `lang="en"`, which enforces an English locale in the example without any visible opt-in, language selection, or justification for a region-specific constraint. Under the policy rule for natural-language locale violations, this is a user-facing language default that should either be configurable or explicitly documented.