Back to skill

Security audit

DeepSeek-Harness-cordis-plugin-builder

Security checks for vulnerabilities and agentic risk

Overview

This plugin-building skill is mostly coherent documentation, but it includes production-style templates that could expose host or session data without enough access-control guidance.

Review this skill before using it to generate production plugins. Prefer pinned package versions, isolated test environments, least-privilege credentials, authenticated HTTP routes with per-session authorization, and explicit review before registering local skill directories or model-visible instructions.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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 (5)

T09 · Insecure Skill Coding Practices

Warning
Location
references/30-capability/client-ui.md:68
Finding
Unauthenticated Host Data Exposure Through Persistent HTTP Routes<![CDATA[ ## Vulnerability Details **File Location**: `references/30-capability/client-ui.md:68-104` **Vulnerability Type**: Unauthenticated data exposure and missing access control **Risk Level**: Medium ### Vulnerable Code ```ts // Host side const webServer = ctx.get('webServer') if (webServer) { ctx.effect(() => webServer.register({ kind: 'exact', path: '/api/my-plugin/stats', handler(_req, res) { res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }) res.end(JSON.stringify({ count: 42 })) }, })) } // Client side const response = await fetch('/api/my-plugin/stats') const data = await response.json() ``` The accompanying guidance states: ```md - Client uses fetch() with same-origin access - trustedHosts restrictions only apply to WebSocket upgrade paths and do not affect HTTP fetch ``` ### Technical Analysis The recommended persistent communication pattern registers an HTTP endpoint without authentication, authorization, session ownership validation, origin validation, or CSRF protection. The handler ignores the request and returns Host-side data to any caller able to reach the web server. The relative `fetch()` URL is same-origin, so this is not evidence of deliberate external exfiltration. However, same-origin routing does not establish that the requester is authorized to access the returned data. If DSH is exposed beyond loopback, reverse-proxied, or shared by multiple users or sessions, an unauthenticated caller may invoke the endpoint directly. The guidance explicitly notes that `trustedHosts` does not protect HTTP routes, but does not prescribe an equivalent HTTP access-control mechanism. ### Attack Path 1. A plugin developer follows the documented `webServer.register()` pattern for statistics, session data, topics, logs, or other Host information. 2. The developer deploys DSH on an interface reachable by another local user, LAN client, reverse-proxy user, or browser context. 3. The attacker ...[truncated 818 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require authentication for every HTTP endpoint. 2. Derive the caller identity and session authorization from trusted server-side request context; do not trust a client-supplied session ID. 3. Verify that the authenticated principal owns or is permitted to access the requested session or resource. 4. Bind the server to loopback by default unless remote access is explicitly required. 5. Apply CSRF protection to state-changing routes, including origin checks and anti-CSRF tokens. 6. Reject unsupported methods and content types, and define strict request and response schemas. 7. Return only the minimum necessary fields; never return credentials, environment variables, raw session logs, or unrestricted service objects. 8. Add rate limits, response-size limits, security logging, and tests for unauthenticated and cross-session requests. 9. Update the documentation so authentication and authorization are mandatory parts of the template rather than optional additions. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
references/30-capability/harness-integration.md:219
Finding
Session-Derived Data Exposed Without Request-Bound Authorization<![CDATA[ ## Vulnerability Details **File Location**: `references/30-capability/harness-integration.md:219-245` **Vulnerability Type**: Cross-session data exposure through an insecure API template **Risk Level**: Medium ### Vulnerable Code ```ts // Source-loaded plugin directly registers an HTTP endpoint const webServer = ctx.get('webServer') if (webServer) { ctx.effect(() => webServer.register({ kind: 'exact', path: '/api/my-plugin/topics', handler(_req, res) { res.writeHead(200, { 'Content-Type': 'application/json' }) res.end(JSON.stringify(engine.getLatestTopics(sessionId))) }, })) } ``` ### Technical Analysis This example exposes session-derived topics through a persistent HTTP endpoint while ignoring the incoming request. The `sessionId` is not derived from an authenticated request, and the example contains no authorization check connecting the caller to that session. Depending on how adopting code obtains or captures `sessionId`, the endpoint may expose one session's data globally, leak stale data from another session, or encourage developers to accept an arbitrary session identifier without ownership validation. The issue is separate from transport confidentiality: even if HTTPS is used, the server still needs to decide whether the requester is authorized to access the selected session. ### Attack Path 1. A developer adapts the example to expose real session topics or related conversation information. 2. The endpoint is reachable by more than one browser, user, or session. 3. An attacker directly requests `/api/my-plugin/topics`. 4. The handler neither authenticates the caller nor verifies session ownership. 5. `engine.getLatestTopics(sessionId)` returns data for the captured or selected session. 6. The response discloses that data to the unauthorized requester. If an implementation accepts `sessionId` from query parameters or JSON without server-side ownership checks, an attacker could enumerate identifiers and ...[truncated 478 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Obtain the caller identity from authenticated server-side request context. 2. Resolve permitted session identifiers on the server instead of using a captured global value or trusting a client-provided identifier. 3. Enforce explicit per-session ownership or role-based authorization before calling `engine.getLatestTopics`. 4. Return `401 Unauthorized` for unauthenticated requests and `403 Forbidden` for unauthorized session access. 5. Prevent identifier enumeration by using access-controlled opaque identifiers and uniform error responses. 6. Keep session state in a session-scoped map with lifecycle cleanup, but do not treat isolation in memory as authorization. 7. Add tests involving two distinct users and sessions to prove that one cannot read the other's data. 8. Document a secure route wrapper that performs method validation, authentication, authorization, rate limiting, and error handling. ]]>

T01 · Skill Instruction Hijacking

Warning
Location
references/20-develop/hook-tool-data-flow.md:43
Finding
Untrusted Hook Output Is Injected Into the Agent Instruction Context<![CDATA[ ## Vulnerability Details **File Location**: `references/20-develop/hook-tool-data-flow.md:43-65` **Vulnerability Type**: Prompt injection through an untrusted process boundary **Risk Level**: Medium ### Vulnerable Code ```ts function contextFrom(merged: MergedHookOutcome): UserMessage | undefined { if (merged.additionalContext.length === 0) return undefined const content: ContentBlock[] = merged.additionalContext.map(text => ({ type: 'text', text })) return createUserMessage({ content, source: PLUGIN_SOURCE }) } ``` The documented flow is: ```text Hook stdout: { additionalContext: "Project rule: all files must use UTF-8" } → mergeHookOutputs() → merged.additionalContext[] → contextFrom() → UserMessage → agent.inject() / prependContext() → inbox → next model request → tool execution context ``` ### Technical Analysis The document identifies the Hook as an untrusted external process, but its free-form `additionalContext` output is converted directly into a model-visible user message. The text can therefore contain arbitrary instructions rather than passive data. The merge behavior accumulates context from all Hooks, and the documentation states that this content can affect subsequent model decisions and tool arguments. No content validation, instruction/data separation, provenance warning, allowlist, user confirmation, or policy check is prescribed before injection. A malicious or compromised Hook can consequently issue instructions that attempt to override the user's goal, induce unsafe tool calls, disclose information available in context, or manipulate later decisions. Existing tool approval may reduce the effect of some actions, but it does not eliminate prompt injection or protect non-interactive capabilities. ### Attack Path 1. An attacker modifies a configured Hook, compromises its executable, or controls data that the Hook copies into `additionalContext`. 2. The Hook writes valid JSON to stdout containing adv ...[truncated 962 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat all Hook stdout fields as untrusted data, including output from locally configured Hooks. 2. Replace unrestricted `additionalContext` with a strict schema of typed facts where possible. 3. Mark injected content clearly as untrusted external data and instruct the model not to follow commands contained within it. 4. Keep Hook-provided data in a dedicated structured context channel rather than converting it into an ordinary user message. 5. Reject or require explicit user approval for Hook output that requests tool use, policy changes, credential access, data transmission, or goal changes. 6. Enforce length, nesting, character, and rate limits before storing or injecting Hook output. 7. Apply provenance metadata identifying the Hook executable and handler that produced each field. 8. Preserve the system and user instruction hierarchy so Hook content cannot supersede higher-priority instructions. 9. Add adversarial tests where `additionalContext` contains instruction-override text, secret-exfiltration requests, and unsafe tool requests. ]]>

T01 · Skill Instruction Hijacking

Warning
Location
references/30-capability/harness-integration.md:104
Finding
Configurable Skill Directory Allows Unverified Instructions to Become Model-Invocable<![CDATA[ ## Vulnerability Details **File Location**: `references/30-capability/harness-integration.md:104-139` **Vulnerability Type**: Local instruction injection through an unrestricted skill path **Risk Level**: Medium ### Vulnerable Code ```ts import { readFile } from 'node:fs/promises' import { join } from 'node:path' export async function apply(ctx: Context, config: { skillRoot?: string }) { const skills = ctx.get('skills') const skillRoot = config.skillRoot if (!skills || !skillRoot) return try { const content = await readFile(join(skillRoot, 'SKILL.md'), 'utf-8') await skills.register({ name: 'agent-memory', description: 'Trigger when long-term memory is needed', whenToUse: 'Use for memory or cross-session context; read guidance before tool use', content, source: 'custom', provider: 'dsh-memory', resourceBase: { kind: 'directory', path: skillRoot }, invocation: { modelInvocable: true, userInvocable: true }, }) } catch { // Skill registration failure is silently ignored } } ``` ### Technical Analysis The template accepts an arbitrary configurable filesystem path, reads the complete `SKILL.md` file from that path, and registers the text as model-invocable guidance. It does not canonicalize the path, constrain it to an approved directory, verify ownership or permissions, check provenance or integrity, or review the content before registration. If an attacker can influence configuration, replace files in the selected directory, or exploit a writable or symlinked path, attacker-authored instructions can be loaded into the Agent's skill catalog. The `resourceBase` also makes references beneath the selected directory available to the skill. The silent catch block further reduces visibility: registration or validation failures are hidden rather than producing an auditable warning. ### Attack Path 1. An attacker gains write access to the configured `skillRoot`, replaces a syml ...[truncated 996 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Canonicalize `skillRoot` with `realpath()` before use. 2. Require the canonical path to remain beneath an administrator-approved skill directory. 3. Reject symlinks or revalidate the canonical target immediately before reading. 4. Verify directory and file ownership and reject group- or world-writable skill sources where applicable. 5. Use packaged, read-only skill assets by default instead of arbitrary runtime paths. 6. Record and verify a content hash or signed manifest for externally supplied skills. 7. Require explicit administrator or user approval before enabling `modelInvocable` for custom content. 8. Validate front matter, resource references, file size, and content policy before registration. 9. Log registration failures and provenance information instead of silently suppressing every exception. 10. Add tests for path traversal, symlink substitution, untrusted writable directories, oversized files, and malicious instruction content. ]]>

T08 · Insecure Dependencies

Warning
Location
references/10-setup/environment.md:54
Finding
Unpinned Registry Packages Are Downloaded and Executed<![CDATA[ ## Vulnerability Details **File Location**: `references/10-setup/environment.md:54-67` **Vulnerability Type**: Unpinned remote dependency execution **Risk Level**: Medium ### Vulnerable Code ```bash # Start Web UI npx -y @deepseek-ai/dsh web # Install plugin npx @deepseek-ai/dsh plugin --profile web add <package> # Remove plugin npx @deepseek-ai/dsh plugin --profile web remove <package> ``` The document explains that `-y` automatically confirms the download. Elsewhere in the same setup guidance, an unpinned community plugin is installed: ```bash pnpm exec dsh plugin --profile web add @kiwifruit/dsh-context-pro ``` ### Technical Analysis `npx` resolves and downloads packages from the configured registry when the requested package is not already available locally. No exact version is specified in the examples, and `-y` suppresses the interactive confirmation. Therefore, the code executed at invocation time may differ from the code previously reviewed. The plugin installation mechanism also supports package names and broader external source specifications. Installed plugins run with the user's privileges and are not isolated by DSH tool approval. The project does include later warnings recommending source review, isolated testing, and exact versions, which reduces but does not eliminate the risk created by the primary quick-start commands. This is a supply-chain weakness rather than evidence that the named packages are malicious. ### Attack Path 1. A user copies the documented unpinned `npx` or plugin-install command. 2. The package manager resolves the current registry version at execution time. 3. An attacker compromises the publisher account, registry package, transitive dependency, or an ambiguously sourced package name. 4. The malicious package or lifecycle script is downloaded. 5. `npx` executes the package, or DSH loads the installed plugin. 6. The package runs with the user's filesystem, credential, process, and network permissions. ...[truncated 480 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin all executable examples to exact reviewed versions, such as `@deepseek-ai/dsh@x.y.z`. 2. Avoid `npx -y` in security-sensitive guidance because it suppresses an important trust decision. 3. Prefer project-local installation with a committed lockfile and frozen-lockfile enforcement. 4. Verify registry provenance, package signatures or attestations, integrity hashes, publisher identity, and repository ownership. 5. Pin community plugins to exact versions and review their source and package contents before installation. 6. Disable lifecycle scripts during initial inspection where practical, then explicitly authorize only required build scripts. 7. Test plugins in an isolated user account, container, or virtual machine before granting access to real workspaces or credentials. 8. Restrict Git and local-path package specifications in production installation workflows unless they are explicitly approved. 9. Keep the existing warning that plugins run with user privileges, but move it adjacent to every installation example. 10. Add automated dependency scanning and reproducible package-content verification to the release and installation workflow. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (1)

Missing User Warnings

Low
Confidence
91% confidence
Finding
The document explicitly promotes a 'high-trust development mode' that executes model-generated code and only gives a brief note to use it in a trusted workspace. For a plugin-building skill, this can normalize unsafe behavior and understate risks such as filesystem modification, secret exposure, or arbitrary command execution during development and debugging.

Static analysis

No suspicious patterns detected.