Back to skill

Security audit

CopilotKit-Runtime-Patterns

Security checks for vulnerabilities and agentic risk

Overview

This documentation-only skill is not malicious, but its security guidance includes under-scoped auth and rate-limit examples that users should review before relying on it.

Install only if you treat it as draft guidance, not copy-paste production security code. Review and harden the auth and rate-limit examples before use: derive identity from verified server-side session/JWT state, avoid exposing long-lived tokens to the browser, and minimize/redact request logs.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T09 · Insecure Skill Coding Practices

Warning
Location
rules/security-rate-limiting.md:30
Finding

Client-Controlled Identity Allows Rate-Limit Bypass

Content
View full analysis

Vulnerability Details

File Location: rules/security-rate-limiting.md:30-38
Vulnerability Type: Rate-limit bypass through an untrusted identity key
Risk Level: Medium

Vulnerable Code

typescript
const runtime = new CopilotRuntime({
  middleware: {
    onBeforeRequest: async (options) => {
      const userId = options.properties?.userId
      if (!userId) throw new Error("Unauthorized")

      try {
        await limiter.consume(userId)
      } catch {
        throw new Error("Rate limit exceeded")
      }
    },
  },
})

The same insecure pattern is duplicated in the compiled documentation at AGENTS.md:566-578.

Technical Analysis

The example presented as the correct implementation uses options.properties?.userId as the rate-limiter key. CopilotKit request properties are supplied by the client unless the application explicitly replaces or validates them using trusted server-side authentication state.

The code only verifies that userId is nonempty. It does not authenticate the request, verify that the claimed identifier belongs to the caller, or derive the identifier from a validated JWT or server-managed session. Consequently, possession of any arbitrary string is treated as sufficient authorization and as a distinct rate-limit identity.

Rate limiting and authentication are separate controls. A client-provided identifier cannot securely provide either control without cryptographic or server-side verification.

Attack Path

  1. An attacker sends a request to the exposed CopilotKit runtime endpoint.
  2. The attacker supplies an arbitrary nonempty value in properties.userId.
  3. The middleware accepts that value and creates or selects its corresponding rate-limit bucket.
  4. After approaching the configured limit, the attacker changes properties.userId.
  5. The limiter treats the new value as a different user with a fresh quota.
  6. The attacker repeats this process to make effectively unbounded agent or LLM requests.

...[truncated 594 chars]

Remediation
View remediation

Remediation Suggestions

  • Authenticate every request before applying a user-scoped rate limit.
  • Derive the limiter key exclusively from trusted server-side identity data, such as the sub claim of a cryptographically verified JWT or a server-managed session.
  • Do not trust properties.userId, headers, query parameters, or request bodies as identity sources without verification.
  • Reject requests when a client-provided identity conflicts with the authenticated identity.
  • Clearly document that rate limiting does not replace authentication.
  • Consider layered limits, including per-account, global, and trusted-proxy-aware IP limits, to reduce distributed abuse.
  • Use a shared persistent limiter such as Redis in horizontally scaled production deployments rather than an in-memory limiter local to each instance.

A hardened pattern should resemble:

typescript
const runtime = new CopilotRuntime({
  middleware: {
    onBeforeRequest: async (options) => {
      const token = options.properties?.authToken
      if (!token) throw new Error("Unauthorized")

      const payload = await verifyJwt(token)
      const authenticatedUserId = payload?.sub
      if (!authenticatedUserId) throw new Error("Unauthorized")

      try {
        await limiter.consume(authenticatedUserId)
      } catch {
        throw new Error("Rate limit exceeded")
      }
    },
  },
})

Apply the same correction to the duplicated example in AGENTS.md:566-578.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (5)

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

Section 2.3 claims the runtime should be configured so conversation history survives restarts, yet the shown 'correct' code does not set up storage or persistence in the runtime at all. The surrounding explanation then shifts responsibility to LangGraph checkpointers, which contradicts the direct claim that this snippet demonstrates persistent storage for production threads.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The guidance explicitly recommends sending an authentication token from the frontend in a generic properties object, but it does not warn about token sensitivity, storage, transport guarantees, or safer session-based alternatives. In a runtime-patterns skill used to scaffold backend integrations, this can normalize insecure credential handling and lead developers to expose bearer tokens to client-side code, logs, browser tooling, or unintended propagation paths.

Content

No source excerpt is available for this finding.

Unbounded Resource Access

Medium
Category
Excessive Agency
Confidence
80% confidence
Finding

Skill allows unbounded resource consumption (API calls, storage, compute). Without rate limits or quotas, a compromised or misbehaving agent can cause denial-of-service or cost overruns.

Content

Scanner excerpt · AGENTS.md (reported line 562)May include surrounding context.

import { CopilotRuntime, OpenAIAdapter, copilotRuntimeNextJSAppRouterEndpoint } from "@copilotkit/runtime"

const runtime = new CopilotRuntime() // Anyone can make unlimited requests

text

**Correct (rate limiting via middleware):**

Unbounded Resource Access

Medium
Category
Excessive Agency
Confidence
80% confidence
Finding

Skill allows unbounded resource consumption (API calls, storage, compute). Without rate limits or quotas, a compromised or misbehaving agent can cause denial-of-service or cost overruns.

Content

Scanner excerpt · rules/security-rate-limiting.md (reported line 18)May include surrounding context.

import { CopilotRuntime, OpenAIAdapter, copilotRuntimeNextJSAppRouterEndpoint } from "@copilotkit/runtime"

const runtime = new CopilotRuntime() // Anyone can make unlimited requests

text

**Correct (rate limiting via middleware):**

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

This markdown file includes sample code that captures and logs request metadata including threadId, runId, message counts, and properties?.userId. The surrounding description promotes response logging and usage tracking but does not warn that these actions may record user-associated data, which is the kind of privacy-affecting behavior SQP-2 covers for markdown files.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.