Back to skill

Security audit

Node.js Production Engineering

Security checks for vulnerabilities and agentic risk

Overview

This is a documentation-only Node.js production skill, but some production/security examples could lead an agent to generate vulnerable API code.

Review this skill before installing if you expect agents to generate production API code from it. It appears non-executable and not malicious, but the security-sensitive examples should be corrected or treated as illustrative only, especially user role assignment and client IP handling for rate limits.

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 (2)

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:316
Finding
Public User Registration Allows Self-Assignment of Administrator Role<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:316-336` **Vulnerability Type**: User-controlled privilege assignment **Risk Level**: High ```typescript const CreateUserSchema = z.object({ email: z.string().email().max(255).toLowerCase(), name: z.string().min(1).max(100).trim(), role: z.enum(['user', 'admin']).default('user'), }); // Usage with Hono app.post('/users', zValidator('json', CreateUserSchema), async (c) => { const body = c.req.valid('json'); // Fully typed! const user = await userService.create(body); return c.json({ data: user }, 201); }); ``` ### Technical Analysis The public user-creation schema accepts both `user` and `admin` as valid role values. The route then forwards the entire validated request body to `userService.create` without an authentication or authorization check and without replacing the submitted role with a server-controlled value. Schema validation only confirms that `admin` is an allowed string; it does not establish that the requester is authorized to grant that role. If this illustrative template is copied into a production application, it creates a mass-assignment vulnerability at an authorization boundary. The Skill itself is documentation and does not directly execute this code. The vulnerability arises in applications generated from or modeled on this example. ### Attack Path 1. An attacker locates the unauthenticated `POST /users` registration endpoint. 2. The attacker submits a request such as: ```http POST /users HTTP/1.1 Content-Type: application/json { "email": "attacker@example.com", "name": "Attacker", "role": "admin" } ``` 3. `CreateUserSchema` accepts `admin` because it is explicitly included in the role enumeration. 4. The validated body, including the attacker-selected role, is passed directly to `userService.create`. 5. If the service and repository preserve the supplied role as shown by the surrounding pattern, the account is created with administrator privileges. ...[truncated 606 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove `role` from all public registration and profile-update request schemas. - Assign the initial role exclusively on the server: ```typescript const PublicRegistrationSchema = z.object({ email: z.string().email().max(255).toLowerCase(), name: z.string().min(1).max(100).trim(), }); app.post('/users', zValidator('json', PublicRegistrationSchema), async (c) => { const body = c.req.valid('json'); const user = await userService.create({ ...body, role: 'user' }); return c.json({ data: user }, 201); }); ``` - Implement role changes through a separate endpoint requiring authentication and an explicit administrator permission. - Enforce authorization again in the service layer so route omissions do not permit privilege escalation. - Use allowlisted data-transfer objects rather than passing complete request bodies into persistence methods. - Add negative security tests confirming that public registration cannot assign or modify privileged roles. - Record privileged role changes in an immutable audit log. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:932
Finding
Rate Limiting Trusts a Spoofable Forwarded Client Address<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:932-936` **Vulnerability Type**: Rate-limit bypass through untrusted proxy headers **Risk Level**: Medium ```typescript export const apiRateLimit = rateLimiter({ windowMs: 60_000, limit: 100, keyGenerator: (c) => c.get('jwtPayload')?.sub || c.req.header('x-forwarded-for') || 'anonymous', message: { error: { code: 'RATE_LIMITED', message: 'Too many requests' } }, }); ``` ### Technical Analysis For unauthenticated requests, the rate-limit key is derived directly from the `X-Forwarded-For` request header. Clients can generally supply this header themselves unless a trusted reverse proxy removes incoming values and constructs a verified forwarding chain. The example does not define trusted proxy hops, validate the forwarding chain, or verify that the immediate peer is an authorized proxy. Consequently, an attacker can select arbitrary rate-limit identities by changing the header between requests. If no header is supplied, every request falls into the shared `anonymous` bucket, which can also let one client exhaust the quota for unrelated users. The Skill is documentation-only, but applications copying this pattern may inherit the flaw. ### Attack Path 1. An attacker targets an unauthenticated endpoint protected by `apiRateLimit`, such as a login, registration, password-recovery, or public API route. 2. The attacker sends requests until the bucket associated with one address approaches its limit. 3. For subsequent requests, the attacker changes the forwarded address: ```http X-Forwarded-For: 198.51.100.1 X-Forwarded-For: 198.51.100.2 X-Forwarded-For: 198.51.100.3 ``` 4. The key generator treats each attacker-selected value as a distinct client identity. 5. Each forged value receives a separate quota, allowing the attacker to continue sending requests beyond the intended limit. ### Impact Assessment The flaw can weaken protections against password guessing, credential stuffing, accoun ...[truncated 369 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not consume `X-Forwarded-For` directly from arbitrary requests. - Configure an explicit trusted-proxy policy at the framework or deployment layer. - Use the hosting platform or framework's verified client-address API. - Ensure the edge proxy removes client-supplied forwarding headers and constructs a canonical forwarding chain. - Accept a forwarded address only when the immediate network peer is a configured trusted proxy. - Parse the correct trusted hop rather than using the complete raw header value. - Prefer authenticated user identifiers where available, while retaining a verified per-source limit to prevent abuse across multiple accounts. - Use layered limits, such as per-account, verified source IP, endpoint, and global quotas. - Add tests showing that arbitrary client-supplied `X-Forwarded-For` values do not create new rate-limit buckets. ]]>
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (2)

Vague Triggers

Medium
Confidence
89% confidence
Finding
The skill defines very broad natural-language triggers such as 'Audit my API security' and 'Set up testing' that overlap with common user requests, which can cause the skill to activate unexpectedly and steer agent behavior without clear user intent. In an agent ecosystem, overly generic invocation phrases increase the chance of prompt-routing abuse, accidental activation, or inappropriate context injection into unrelated tasks.

Intent-Code Divergence

Low
Confidence
91% confidence
Finding
The documentation repeatedly states that production code should use structured logging and explicitly avoid console logging, including in the health check and commandments sections. However, the example environment validation code uses console.error when validation fails, which contradicts that stated guidance rather than merely omitting detail.