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## 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
## 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
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.

Missing User Warnings

Medium
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.

Unbounded Resource Access

Medium
Category
Excessive Agency
Content
import { CopilotRuntime, OpenAIAdapter, copilotRuntimeNextJSAppRouterEndpoint } from "@copilotkit/runtime"

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

**Correct (rate limiting via middleware):**
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.

Unbounded Resource Access

Medium
Category
Excessive Agency
Content
import { CopilotRuntime, OpenAIAdapter, copilotRuntimeNextJSAppRouterEndpoint } from "@copilotkit/runtime"

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

**Correct (rate limiting via middleware):**
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.

Missing User Warnings

Low
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.

Static analysis

No suspicious patterns detected.