Back to skill

Security audit

fulcra-dashboard

Security checks for vulnerabilities and agentic risk

Overview

This skill appears intended to build a Fulcra dashboard, but it has real review-worthy risks around private data, public publishing, remote dependencies, and unsafe HTML rendering.

Install only if you are comfortable reviewing exactly which Fulcra records are copied into public/ before sharing. Prefer local-only use, vendor or pin browser libraries, avoid publishing sensitive timelines, and fix the x-html/formatValue rendering issue before deploying a dashboard publicly.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
template-dashboard/public/app.js:114
Finding
Stored Cross-Site Scripting Through Unsanitized Fulcra Record Values<![CDATA[ ## Vulnerability Details **File Location**: `template-dashboard/public/app.js:114-132`; `template-dashboard/public/index.html:76` **Vulnerability Type**: Stored cross-site scripting caused by rendering untrusted record data as HTML **Risk Level**: High ### Vulnerable Code ```javascript formatValue(d) { if (d.value === undefined || d.value === null) return ''; // Special formatting for ScaleAnnotations if (d.metadata && d.metadata.measurement_spec && d.metadata.measurement_spec.measurement_type === 'scale') { const maxVal = d.metadata.measurement_spec.scale.max_allowed || 5; let textStr = `${d.value}/${maxVal}`; // Look for a custom label mapping if (d.metadata.spec && d.metadata.spec.scale && d.metadata.spec.scale.label_mapping && d.metadata.spec.scale.label_mapping.string && d.metadata.spec.scale.label_mapping.string.mapping) { const label = d.metadata.spec.scale.label_mapping.string.mapping[String(d.value)]; if (label) { textStr += ` &mdash; <em>${label}</em>`; } } return textStr; } // Fallback for primitive values return d.value; } ``` The returned value is rendered through an HTML interpretation sink: ```html <span class="entry-value" x-html="formatValue(detail.item)"></span> ``` ### Technical Analysis Timeline records are read from JSONL files and passed to `formatValue()`. Both `d.value` and the metadata-derived `label` can originate from downloaded Fulcra records. The function concatenates these values into an HTML string without escaping or sanitization. The dashboard then uses Alpine.js `x-html`, which assigns interpreted HTML rather than text. Consequently, a malicious record value such as an element with an event handler can introduce executable browser content. The fallback path is also vulnerable because it returns `d.value` directly to the same HTML sink. This is a stored XSS condition because ...[truncated 1485 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the HTML binding with a text binding: ```html <span class="entry-value" x-text="formatValue(detail.item)"></span> ``` 2. Do not use HTML entities or tags in `formatValue()`. Return plain text instead: ```javascript formatValue(d) { if (d.value === undefined || d.value === null) return ''; if ( d.metadata?.measurement_spec?.measurement_type === 'scale' ) { const maxVal = d.metadata.measurement_spec.scale?.max_allowed || 5; let result = `${String(d.value)}/${String(maxVal)}`; const mapping = d.metadata?.spec?.scale?.label_mapping?.string?.mapping; const label = mapping?.[String(d.value)]; if (label) { result += ` — ${String(label)}`; } return result; } return String(d.value); } ``` 3. If emphasized formatting is required, render the value and label in separate elements, each using `x-text`, instead of assembling HTML. 4. If arbitrary HTML is an unavoidable product requirement, sanitize it with a pinned, audited sanitizer and a strict element-and-attribute allowlist before passing it to `x-html`. 5. Add a restrictive Content Security Policy as defense in depth. Avoid permitting inline scripts or event handlers. 6. Add tests using payloads containing elements, event handlers, malformed tags, SVG content, and encoded markup to verify that all record fields are rendered only as text. ]]>

T08 · Insecure Dependencies

Warning
Location
template-dashboard/public/index.html:9
Finding
Mutable and Unverified Third-Party Dependencies<![CDATA[ ## Vulnerability Details **File Location**: `template-dashboard/public/index.html:9-10`; `SKILL.md:147-158`; `template-dashboard/generate_wordcloud.py:7,17` **Vulnerability Type**: Unpinned remote scripts and package installations without integrity verification **Risk Level**: Medium ### Vulnerable Code The browser loads remotely hosted executable JavaScript using mutable version selectors and without Subresource Integrity: ```html <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script> <script src="https://d3js.org/d3.v7.min.js"></script> ``` The Skill also recommends unpinned global package installations: ```bash npm install -g surge ``` ```bash npm i -g vercel ``` The Python visualization script recommends unpinned dependencies: ```text Requirements: pip install wordcloud matplotlib ``` ```python except ImportError: print("Error: Missing required packages. Run: pip install wordcloud matplotlib") sys.exit(1) ``` ### Technical Analysis The Alpine.js URL uses the mutable `3.x.x` selector, while the D3 URL uses a mutable major-version alias. Neither script tag specifies an SRI hash. The effective browser code can therefore change after the Skill has been audited without any corresponding change to the project. The npm and pip installation commands similarly resolve whatever package versions are current at installation time. The npm tools are installed globally, increasing their execution scope and making them available outside this project. This creates a supply-chain trust boundary in which code from external CDNs and package registries executes without a reproducible version or cryptographic integrity check. ### Attack Path 1. A CDN, package registry, maintainer account, or upstream release channel is compromised, or a mutable dependency receives a malicious or incompatible release. 2. The dashboard loads the changed Alpine.js or D3 asset, or the user executes one of the unpinned package-ins ...[truncated 1014 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin browser libraries to exact, reviewed versions rather than major-version or wildcard aliases. 2. Add Subresource Integrity hashes and the corresponding `crossorigin` attribute: ```html <script defer src="https://cdn.example.invalid/alpinejs/exact-version/cdn.min.js" integrity="sha384-REVIEWED_HASH" crossorigin="anonymous"></script> ``` 3. Prefer self-hosting reviewed Alpine.js and D3 assets under `public/assets/` so the deployed dashboard does not execute mutable third-party code. 4. Pin exact npm and Python package versions and maintain lockfiles or hash-checked requirements. 5. Avoid global npm installations. Use project-local, version-pinned execution where possible. 6. Use Python hash verification, such as a requirements file with exact versions and `--require-hashes`. 7. Audit deployment and visualization dependencies before upgrades, and use automated dependency scanning. 8. Apply a restrictive Content Security Policy that limits `script-src` and network destinations to explicitly approved origins. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (17)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
A substantial description-behavior mismatch makes it harder for users and enforcement systems to understand what the skill actually does, which is dangerous for a skill handling private data and publication workflows. When declared behavior understates or misstates actual actions, reviewers may approve a capability set that is broader or riskier than intended.

Ae1

High
Category
analysis-evasion
Content
- `app.js` (Alpine `Alpine.data()` and D3 functions)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill directs network access, local file operations, and environment-dependent execution but does not declare any explicit tool scope or permission boundaries. In an agent setting, this increases the chance of overbroad capability use, especially because the skill also handles private Fulcra data and later discusses public deployment workflows.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The skill introduces public publishing very early without an equally prominent upfront privacy warning, despite working with private Fulcra data. Even though later sections add confirmation steps, deferring the warning increases the risk that users normalize the idea of publishing before fully appreciating that sensitive personal records may be involved.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
The skill explicitly authorizes fallback to an external rapid-UI service and other external tools, which can transfer private user data outside the local dashboard environment described in the manifest. In context, this is risky because the skill's core dataset is the user's private Fulcra store, so externalizing even summaries or structured records expands the trust boundary significantly.

Vague Triggers

Medium
Confidence
88% confidence
Finding
The trigger guidance includes broad natural-language phrases like "set up the web app" and "create a dashboard for the Fulcra skills," which could overlap with ordinary requests outside this specific skill. The file does not provide negative examples or tighter scope constraints to distinguish when this skill should activate versus other web-app or dashboard-related skills.

Rp1

Medium
Category
MCP Rug Pull
Confidence
93% confidence
Finding
The unpinned invocation `uv tool run fulcra-api` allows the resolved tool version to change over time, which can alter behavior or introduce a compromised dependency into a workflow that accesses private user data. Because the skill uses this tool for data discovery and ingestion, supply-chain drift directly affects confidentiality and integrity.

Rp1

Medium
Category
MCP Rug Pull
Confidence
93% confidence
Finding
This use of `uv tool run fulcra-api` is unpinned in a step that catalogs user data, so a changed or malicious upstream version could enumerate or process data differently than reviewed. Since the command influences which private records are selected, the risk is not only reliability but also unintended data exposure.

Rp1

Medium
Category
MCP Rug Pull
Confidence
94% confidence
Finding
The unpinned `fulcra-api get-records` command fetches sensitive records and writes them to local files, so any upstream change in the fetched tool could exfiltrate data, over-collect data, or produce unsafe output. The danger is elevated by the skill's later guidance to copy approved files into a publishable directory.

Rp1

Medium
Category
MCP Rug Pull
Confidence
94% confidence
Finding
The unpinned `data-updates` invocation is part of a required pipeline that moves output into `public/`, creating a path from external tool execution to publishable artifacts. If the tool version changes unexpectedly, it could generate misleading or privacy-sensitive output that is later exposed to the user or the public.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
Requiring an external image generation tool introduces unnecessary data-sharing and dependency risk that is not essential to building a local dashboard. Because the prompt generation may incorporate user theme, activity summaries, or other contextual details, it can leak sensitive information to a third-party service without a strong functional need.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The manifest frames sharing as export of a previewable directory, but the instructions go further and direct direct deployment to public hosting providers. That expands the operational risk from local file preparation to internet publication of user data, and users may not realize the skill includes full deployment behavior when invoking it.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
The manifest frames this skill as building a local dashboard for private Fulcra data and exporting a previewable directory for sharing. This file adds a 3D Plotly visualization helper whose inline documentation explicitly instructs injecting a remote CDN script, which is not an obvious requirement for a local private dashboard and would introduce third-party network dependency if used.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The page loads Alpine.js and D3 from third-party CDNs, which means a dashboard intended to display private local Fulcra data executes remote JavaScript in the same origin and context as that sensitive data. If the CDN content is compromised, swapped, or observed through network controls, an attacker could run arbitrary code in the dashboard and exfiltrate private records from the locally viewed/exported dataset.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The stylesheet imports Google Fonts from a third-party domain, which causes browsers viewing the dashboard to make outbound requests and disclose client metadata such as IP address, user agent, and access timing. In a skill explicitly described as operating on private Fulcra data locally and producing shareable static exports, this undermines the local-only privacy expectation and can create avoidable external network dependencies.

Scope Creep

Low
Category
Excessive Agency
Content
- **High-Fidelity Goal:** If the user is specifically trying to build a high-quality dashboard, you should spend more time troubleshooting and trying to make the static triad, localhost server, or public deployment work.
- **Fast Visibility (e.g., Onboarding):** If the user is just trying to view things for the first time (such as during the `fulcra-get-started` flow), give faster delivery more weight.

If the primary static triad and deployment routes are truly not viable in the current environment, you must gracefully fall back to alternative delivery mechanisms. Alternative options include (but are not limited to):
- **Prefab (`https://gofastmcp.com/apps/prefab`):** Using an external rapid-UI generator if configured.
- **Custom HTML/Image Generation:** Generating a simpler bespoke HTML file or using Python (e.g., `matplotlib`) to render a static image chart summarizing the data.
- **ASCII Charts & Markdown:** As a last resort, or for extremely fast inline updates, render the data directly in the chat using Markdown tables and ASCII visualizations.
Confidence
70% confidence
Finding
Skill's behavior or capabilities extend beyond its stated purpose. Scope creep allows an agent to perform actions unrelated to its documented functionality, increasing the attack surface.

Missing User Warnings

Low
Confidence
91% confidence
Finding
Loading fonts from fonts.googleapis.com leaks viewer metadata to Google without an in-product warning or consent mechanism. While this is not direct code execution, it is still a privacy issue, especially because the dashboard is meant to present private data locally and may be assumed to avoid third-party calls.

Static analysis

No suspicious patterns detected.