Back to skill

Security audit

PRD Visualization Skill

Security checks for vulnerabilities and agentic risk

Overview

The skill is a legitimate hierarchy visualizer, but it under-scopes local file and browser exposure in ways users should review before installing.

Install only if you are comfortable with a skill that writes visualization files into your project and may start a local web server. Prefer running it from a dedicated temporary directory containing only the HTML, D3 file, and JSON, bind the server to 127.0.0.1, avoid serving project roots with secrets, and treat PRD or JSON content from others as untrusted until the HTML escaping issue is fixed.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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 (2)

T09 · Insecure Skill Coding Practices

Error
Location
assets/hierarchy-visualizer.html:1747
Finding

Stored DOM-Based Cross-Site Scripting Through Unescaped Hierarchy Data

Content
View full analysis
{ // Initialize d3.hierarchy rootNodeGlobal = d3.hierarchy(treeData); ``` Hierarchy fields are inserted into a tooltip through D3's `.html()` method: ```javascript d3.select("#tooltip").style("opacity", 1) .html(`${truncate(data.title, 60)}
${data.description || ''}
${(data.type || '').replace(/_/g, ' ')} ${cc ? ` · ${cc} sub-branches` : ' · leaf'} ${hasHidden ? '
Click to expand' : ''}`) .style("left", (event.offsetX + 15) + "px") .style("top", (event.offsetY - 15) + "px"); ``` Additional hierarchy fields are interpolated into `innerHTML`, including an unvalidated URL: ```javascript const goals = (data.goals || []).map(g => `
  • ${g}
  • `).join(''); details.innerHTML = `

    ${data.title}

    Type:${(data.type || '').replace(/_/g, ' ')}
    ${data.domain ? `
    Domain:${data.domain}
    ` : ''} ${data.status ? `
    Status:${data.status}
    ` : ''} ${data.stage ? `
    Stage:${data.stage}
    Remediation
    View remediation

    T05 · Unauthorized Access and Privilege Escalation

    Warning
    Location
    SKILL.md:28
    Finding

    Entire User Project Exposed Through Overbroad HTTP Server Configuration

    Content
    View full analysis
    /path/to/user/project/requirements-hierarchy.json << 'EOF' {paste the JSON here} EOF # Start server from their project folder cd /path/to/user/project && python3 -m http.server 8080 ``` ### Technical Analysis The command starts Python's static HTTP server with the entire user project as its document root. The visualization requires only the HTML file, D3 library, and hierarchy JSON, but every readable file beneath the project directory becomes potentially retrievable over HTTP. The command also does not specify `--bind 127.0.0.1`. Depending on the Python version and runtime environment, the server can listen on all available interfaces rather than being restricted to the local loopback interface. Firewall rules, container networking, virtual-machine networking, or local network configuration may therefore make it reachable by other hosts. This violates least privilege in two dimensions: - **Filesystem scope:** The server exposes substantially more files than the visualization needs. - **Network scope:** The service is not explicitly limited to the local machine. The use of a development HTTP server also provides no authentication, access authorization, or transport security. ### Attack Path 1. The user follows the Skill instructions and runs the HTTP server from the project root. 2. The server recursively exposes files below that directory using the permissions of the invoking process. 3. A local network peer, another process, or browser-injected code connects to port 8080. 4. The attacker requests known or guessed project paths, such as configuration files, ...[truncated 1290 chars]
    Remediation
    View remediation
    Vulnerability Patterns
    • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
    • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
    • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
    • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
    • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
    Findings (13)

    Vague Triggers

    High
    Category
    Not specified by scanner
    Confidence
    97% confidence
    Finding

    The trigger phrases are extremely broad and include common terms like PRD, requirement, feature, and specification, plus an instruction to 'always use this skill' for such requests. That makes accidental invocation likely in many planning conversations, increasing the chance that the agent performs file writes or server startup in contexts where the user only wanted discussion or analysis.

    Content

    No source excerpt is available for this finding.

    Ae1

    High
    Category
    analysis-evasion
    Confidence
    100% confidence
    Finding

    Referenced artifact was not completely inspected

    Content

    Scanner excerpt · SKILL.md (reported line 108)May include surrounding context.

    md
    **Need custom styling?** → Edit CSS variables in `hierarchy-visualizer.html` (~line 78)
    

    Obfuscated Code

    High
    Category
    Supply Chain
    Confidence
    60% confidence
    Finding

    Code contains obfuscation (base64, hex encoding with execution). This is often used to hide malicious functionality.

    Content

    Scanner excerpt · assets/d3.min.js (reported line 2)May include surrounding context.

    js
    // https://d3js.org v7.8.5 Copyright 2010-2023 Mike Bostock
    !function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).d3=t.d3||{})}(this,(function(t){"use strict";function n(t,n){return null==t||null==n?NaN:t<n?-1:t>n?1:t>=n?0:NaN}function e(t,n){return null==t||null==n?NaN:n<t?-1:n>t?1:n>=t?0:NaN}function r(t){let r,o,a;function u(t,n,e=0,i=t.length){if(e<i){if(0!==r(n,n))return i;do{const r=e+i>>>1;o(t[r],n)<0?e=r+1:i=r}while(e<i)}return e}return 2!==t.length?(r=n,o=(e,r)=>n(t(e),r),a=(n,e)=>t(n)-e):(r=t===n||t===e?t:i,o=t,a=t),{left:u,center:function(t,n,e=0,r=t.length){const i=u(t,n,e,r-1);return i>e&&a(t[i-1],n)>-a(t[i],n)?i-1:i},right:function(t,n,e=0,i=t.length){if(e<i){if(0!==r(n,n))return i;do{const r=e+i>>>1;o(t[r],n)<=0?e=r+1:i=r}while(e<i)}return e}}}function i(){return 0}function o(t){return null===t?NaN:+t}const a=r(n),u=a.right,c=a.left,f=r(o).center;var s=u;const l=d(y),h=d((function(t){const n=y(t);return(t,e,r,i,o)=>{n(t,e,(r<<=2)+0,(i<<=2)+0,o<<=2),n(t,e,r+1,i+1,o),n(t,e,r+2,i+2,o),n(t,e,r+3,i+3,o)}}));function d(t){return function(n,e,r=e){if(!((e=+e)>=0))throw new RangeError("invalid rx");if(!((r=+r)>=0))throw new RangeError("invalid ry");let{data:i,width:o,height:a}=n;if(!((o=Math.floor(o))>=0))throw new RangeError("invalid width");if(!((a=Math.floor(void 0!==a?a:i.length/o))>=0))throw new RangeError("invalid height");if(!o||!a||!e&&!r)return n;const u=e&&t(e),c=r&&t(r),f=i.slice();return u&&c?(p(u,f,i,o,a),p(u,i,f,o,a),p(u,f,i,o,a),g(c,i,f,o,a),g(c,f,i,o,a),g(c,i,f,o,a)):u?(p(u,i,f,o,a),p(u,f,i,o,a),p(u,i,f,o,a)):c&&(g(c,i,f,o,a),g(c,f,i,o,a),g(c,i,f,o,a)),n}}function p(t,n,e,r,i){for(let o=0,a=r*i;o<a;)t(n,e,o,o+=r,1)}function g(t,n,e,r,i){for(let o=0,a=r*i;o<r;++o)t(n,e,o,o+a,r)}function y(t){const n=Math.floor(t);if(n===t)return function(t){const n=2*t+1;return(e,r,i,o,a)=>{if(!((o-=a)>=i))return;let u=t*r
    ...[truncated 28 chars]
    

    Obfuscated Code

    High
    Category
    Supply Chain
    Confidence
    60% confidence
    Finding

    Code contains obfuscation (base64, hex encoding with execution). This is often used to hide malicious functionality.

    Content

    Scanner excerpt · assets/d3.min.js (reported line 2)May include surrounding context.

    js
    // https://d3js.org v7.8.5 Copyright 2010-2023 Mike Bostock
    !function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).d3=t.d3||{})}(this,(function(t){"use strict";function n(t,n){return null==t||null==n?NaN:t<n?-1:t>n?1:t>=n?0:NaN}function e(t,n){return null==t||null==n?NaN:n<t?-1:n>t?1:n>=t?0:NaN}function r(t){let r,o,a;function u(t,n,e=0,i=t.length){if(e<i){if(0!==r(n,n))return i;do{const r=e+i>>>1;o(t[r],n)<0?e=r+1:i=r}while(e<i)}return e}return 2!==t.length?(r=n,o=(e,r)=>n(t(e),r),a=(n,e)=>t(n)-e):(r=t===n||t===e?t:i,o=t,a=t),{left:u,center:function(t,n,e=0,r=t.length){const i=u(t,n,e,r-1);return i>e&&a(t[i-1],n)>-a(t[i],n)?i-1:i},right:function(t,n,e=0,i=t.length){if(e<i){if(0!==r(n,n))return i;do{const r=e+i>>>1;o(t[r],n)<=0?e=r+1:i=r}while(e<i)}return e}}}function i(){return 0}function o(t){return null===t?NaN:+t}const a=r(n),u=a.right,c=a.left,f=r(o).center;var s=u;const l=d(y),h=d((function(t){const n=y(t);return(t,e,r,i,o)=>{n(t,e,(r<<=2)+0,(i<<=2)+0,o<<=2),n(t,e,r+1,i+1,o),n(t,e,r+2,i+2,o),n(t,e,r+3,i+3,o)}}));function d(t){return function(n,e,r=e){if(!((e=+e)>=0))throw new RangeError("invalid rx");if(!((r=+r)>=0))throw new RangeError("invalid ry");let{data:i,width:o,height:a}=n;if(!((o=Math.floor(o))>=0))throw new RangeError("invalid width");if(!((a=Math.floor(void 0!==a?a:i.length/o))>=0))throw new RangeError("invalid height");if(!o||!a||!e&&!r)return n;const u=e&&t(e),c=r&&t(r),f=i.slice();return u&&c?(p(u,f,i,o,a),p(u,i,f,o,a),p(u,f,i,o,a),g(c,i,f,o,a),g(c,f,i,o,a),g(c,i,f,o,a)):u?(p(u,i,f,o,a),p(u,f,i,o,a),p(u,i,f,o,a)):c&&(g(c,i,f,o,a),g(c,f,i,o,a),g(c,i,f,o,a)),n}}function p(t,n,e,r,i){for(let o=0,a=r*i;o<a;)t(n,e,o,o+=r,1)}function g(t,n,e,r,i){for(let o=0,a=r*i;o<r;++o)t(n,e,o,o+a,r)}function y(t){const n=Math.floor(t);if(n===t)return function(t){const n=2*t+1;return(e,r,i,o,a)=>{if(!((o-=a)>=i))return;let u=t*r
    ...[truncated 28 chars]
    

    Obfuscated Code

    High
    Category
    Supply Chain
    Confidence
    60% confidence
    Finding

    Code contains obfuscation (base64, hex encoding with execution). This is often used to hide malicious functionality.

    Content

    Scanner excerpt · assets/d3.min.js (reported line 2)May include surrounding context.

    js
    // https://d3js.org v7.8.5 Copyright 2010-2023 Mike Bostock
    !function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).d3=t.d3||{})}(this,(function(t){"use strict";function n(t,n){return null==t||null==n?NaN:t<n?-1:t>n?1:t>=n?0:NaN}function e(t,n){return null==t||null==n?NaN:n<t?-1:n>t?1:n>=t?0:NaN}function r(t){let r,o,a;function u(t,n,e=0,i=t.length){if(e<i){if(0!==r(n,n))return i;do{const r=e+i>>>1;o(t[r],n)<0?e=r+1:i=r}while(e<i)}return e}return 2!==t.length?(r=n,o=(e,r)=>n(t(e),r),a=(n,e)=>t(n)-e):(r=t===n||t===e?t:i,o=t,a=t),{left:u,center:function(t,n,e=0,r=t.length){const i=u(t,n,e,r-1);return i>e&&a(t[i-1],n)>-a(t[i],n)?i-1:i},right:function(t,n,e=0,i=t.length){if(e<i){if(0!==r(n,n))return i;do{const r=e+i>>>1;o(t[r],n)<=0?e=r+1:i=r}while(e<i)}return e}}}function i(){return 0}function o(t){return null===t?NaN:+t}const a=r(n),u=a.right,c=a.left,f=r(o).center;var s=u;const l=d(y),h=d((function(t){const n=y(t);return(t,e,r,i,o)=>{n(t,e,(r<<=2)+0,(i<<=2)+0,o<<=2),n(t,e,r+1,i+1,o),n(t,e,r+2,i+2,o),n(t,e,r+3,i+3,o)}}));function d(t){return function(n,e,r=e){if(!((e=+e)>=0))throw new RangeError("invalid rx");if(!((r=+r)>=0))throw new RangeError("invalid ry");let{data:i,width:o,height:a}=n;if(!((o=Math.floor(o))>=0))throw new RangeError("invalid width");if(!((a=Math.floor(void 0!==a?a:i.length/o))>=0))throw new RangeError("invalid height");if(!o||!a||!e&&!r)return n;const u=e&&t(e),c=r&&t(r),f=i.slice();return u&&c?(p(u,f,i,o,a),p(u,i,f,o,a),p(u,f,i,o,a),g(c,i,f,o,a),g(c,f,i,o,a),g(c,i,f,o,a)):u?(p(u,i,f,o,a),p(u,f,i,o,a),p(u,i,f,o,a)):c&&(g(c,i,f,o,a),g(c,f,i,o,a),g(c,i,f,o,a)),n}}function p(t,n,e,r,i){for(let o=0,a=r*i;o<a;)t(n,e,o,o+=r,1)}function g(t,n,e,r,i){for(let o=0,a=r*i;o<r;++o)t(n,e,o,o+a,r)}function y(t){const n=Math.floor(t);if(n===t)return function(t){const n=2*t+1;return(e,r,i,o,a)=>{if(!((o-=a)>=i))return;let u=t*r
    ...[truncated 28 chars]
    

    Obfuscated Code

    High
    Category
    Supply Chain
    Confidence
    60% confidence
    Finding

    Code contains obfuscation (base64, hex encoding with execution). This is often used to hide malicious functionality.

    Content

    Scanner excerpt · assets/d3.min.js (reported line 2)May include surrounding context.

    js
    // https://d3js.org v7.8.5 Copyright 2010-2023 Mike Bostock
    !function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).d3=t.d3||{})}(this,(function(t){"use strict";function n(t,n){return null==t||null==n?NaN:t<n?-1:t>n?1:t>=n?0:NaN}function e(t,n){return null==t||null==n?NaN:n<t?-1:n>t?1:n>=t?0:NaN}function r(t){let r,o,a;function u(t,n,e=0,i=t.length){if(e<i){if(0!==r(n,n))return i;do{const r=e+i>>>1;o(t[r],n)<0?e=r+1:i=r}while(e<i)}return e}return 2!==t.length?(r=n,o=(e,r)=>n(t(e),r),a=(n,e)=>t(n)-e):(r=t===n||t===e?t:i,o=t,a=t),{left:u,center:function(t,n,e=0,r=t.length){const i=u(t,n,e,r-1);return i>e&&a(t[i-1],n)>-a(t[i],n)?i-1:i},right:function(t,n,e=0,i=t.length){if(e<i){if(0!==r(n,n))return i;do{const r=e+i>>>1;o(t[r],n)<=0?e=r+1:i=r}while(e<i)}return e}}}function i(){return 0}function o(t){return null===t?NaN:+t}const a=r(n),u=a.right,c=a.left,f=r(o).center;var s=u;const l=d(y),h=d((function(t){const n=y(t);return(t,e,r,i,o)=>{n(t,e,(r<<=2)+0,(i<<=2)+0,o<<=2),n(t,e,r+1,i+1,o),n(t,e,r+2,i+2,o),n(t,e,r+3,i+3,o)}}));function d(t){return function(n,e,r=e){if(!((e=+e)>=0))throw new RangeError("invalid rx");if(!((r=+r)>=0))throw new RangeError("invalid ry");let{data:i,width:o,height:a}=n;if(!((o=Math.floor(o))>=0))throw new RangeError("invalid width");if(!((a=Math.floor(void 0!==a?a:i.length/o))>=0))throw new RangeError("invalid height");if(!o||!a||!e&&!r)return n;const u=e&&t(e),c=r&&t(r),f=i.slice();return u&&c?(p(u,f,i,o,a),p(u,i,f,o,a),p(u,f,i,o,a),g(c,i,f,o,a),g(c,f,i,o,a),g(c,i,f,o,a)):u?(p(u,i,f,o,a),p(u,f,i,o,a),p(u,i,f,o,a)):c&&(g(c,i,f,o,a),g(c,f,i,o,a),g(c,i,f,o,a)),n}}function p(t,n,e,r,i){for(let o=0,a=r*i;o<a;)t(n,e,o,o+=r,1)}function g(t,n,e,r,i){for(let o=0,a=r*i;o<r;++o)t(n,e,o,o+a,r)}function y(t){const n=Math.floor(t);if(n===t)return function(t){const n=2*t+1;return(e,r,i,o,a)=>{if(!((o-=a)>=i))return;let u=t*r
    ...[truncated 28 chars]
    

    Undeclared Tool Scope

    Medium
    Category
    MCP Least Privilege
    Confidence
    79% confidence
    Finding

    The skill directs the agent to start a local HTTP server and expose content on localhost:8080, which is a network-capable action, but the manifest declares no explicit tool scope or permissions. This mismatch weakens reviewability and consent boundaries because a seemingly simple visualization skill can perform network-adjacent behavior without clearly declaring it.

    Content

    No source excerpt is available for this finding.

    Vague Triggers

    Medium
    Category
    Not specified by scanner
    Confidence
    92% confidence
    Finding

    The activation guidance mixes a clear visualization intent with any mention of hierarchical-domain content, making invocation ambiguous. In context, that ambiguity is more dangerous because the skill is not read-only; once triggered, it can create files and start a server, so accidental activation has real side effects.

    Content

    No source excerpt is available for this finding.

    Description-Behavior Mismatch

    Medium
    Category
    Not specified by scanner
    Confidence
    94% confidence
    Finding

    The skill advertises visualization, but its workflow also instructs the agent to create structured project artifacts from source PRD content. That expands the skill from rendering into project-data transformation and persistence, increasing the chance of unintended file creation, stale derived artifacts, and silent modification of user materials.

    Content

    No source excerpt is available for this finding.

    Description-Behavior Mismatch

    Medium
    Category
    Not specified by scanner
    Confidence
    92% confidence
    Finding

    The documented workflow copies files into the user's project, writes a JSON file, and starts a local web server, all of which substantially exceed a passive visualization role. These actions modify the project and launch a service without an explicit warning or consent step, creating risk of workspace pollution, accidental exposure, or interference with existing development processes.

    Content

    No source excerpt is available for this finding.

    Missing User Warnings

    Medium
    Category
    Not specified by scanner
    Confidence
    95% confidence
    Finding

    The skill instructs writing multiple files into the user's project directory and copying runtime assets there without any explicit warning that project files will be modified. Hidden or unexpected workspace mutation is dangerous because it can overwrite files, introduce untracked artifacts, and alter the repository state without informed user consent.

    Content

    No source excerpt is available for this finding.

    Description-Behavior Mismatch

    Medium
    Category
    Not specified by scanner
    Confidence
    89% confidence
    Finding

    The instruction to keep the PRD JSON updated throughout the entire development process broadens the skill into continuous project maintenance rather than one-time visualization. Persistent synchronization behavior can cause ongoing unauthorized edits, drift between source and derived files, and repeated writes beyond the user’s original intent.

    Content

    No source excerpt is available for this finding.

    Missing User Warnings

    Low
    Category
    Not specified by scanner
    Confidence
    89% confidence
    Finding

    The page polls requirements-hierarchy.json every 5 seconds via setInterval without meaningful user consent or clear disclosure beyond a small 'Watching...' indicator. In a local skill context this is not a severe exploit by itself, but it can unexpectedly monitor changing data, consume resources, and normalize background data access that users may not realize is happening.

    Content

    No source excerpt is available for this finding.

    Static analysis

    Detected: suspicious.dynamic_code_execution

    Dynamic code execution detected.

    Critical
    Code
    suspicious.dynamic_code_execution
    Location
    assets/d3.min.js:2