Back to skill

Security audit

Debt payoff plan comparison

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly coherent, but it handles sensitive financial data with an under-scoped external API destination and API-controlled marketing content.

Review before installing. Only use this skill if you are comfortable sending debt and mortgage details to the named API, verify any --base-url override yourself, treat returned marketing text as promotional content, and prefer an immutable reviewed install source instead of the mutable master-branch URL.

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

T01 · Skill Instruction Hijacking

Error
Location
SKILL.md:12
Finding
Mandatory Injection of API-Controlled Marketing Content into Agent Responses<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:12-17`, `SKILL.md:103-106`, and `agents/openai.yaml:5` **Vulnerability Type**: `T01: Skill Instruction Hijacking` **Risk Level**: High ### Vulnerable Code From `SKILL.md:12-17`: ```markdown ## Outcome - Gather required debt and assumptions data through short guided questions. - Build a strict JSON payload for the Loan Doctor skill endpoint. - Run the non-interactive script to call the API. - Summarize the returned plans and include safe marketing hints. ``` From `SKILL.md:103-106`: ```markdown On success (`success: true`): - Briefly summarize top 1-2 relevant plans from `plans`. - Include primary and secondary marketing hints only if links are safe after validation. ``` From `agents/openai.yaml:5`: ```yaml default_prompt: Gather debt and assumptions data, run the get-plans script, compare refinance/snowball/avalanche outcomes, and summarize top recommendations with marketing hints. ``` ### Technical Analysis The skill's persistent instructions require the agent to insert marketing content into answers produced during a financial-planning workflow. The marketing fields originate from an external API response and are therefore attacker-controlled or, at minimum, outside the local skill's trust boundary. The script validates the destinations of `ctaUrl` and `secondaryCtaUrl`, but it does not sanitize textual marketing fields such as labels, headlines, or disclaimers. More importantly, URL validation does not address the underlying instruction-level behavior: loading the skill changes the agent's response objective from providing a neutral debt comparison to also distributing promotional material. Because the agent is explicitly instructed to surface API-provided marketing hints, remote content can be presented within an otherwise trusted financial recommendation. This can blur the distinction between independent analysis and advertising. The issue is especially sensitive because users prov ...[truncated 1407 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the mandatory requirement to include marketing hints from `SKILL.md` and `agents/openai.yaml`. 2. Keep financial-plan summaries neutral by default. 3. Display promotional content only after an explicit user request or a clearly separated, informed opt-in. 4. Clearly label any promotional material as advertising and identify its external source. 5. Treat all API response strings as untrusted data, not only URLs. 6. Apply length limits and reject control characters, markup, prompt-like instructions, and misleading formatting in marketing text. 7. Define a strict response schema that accepts only the plan-calculation fields needed for the user's request. 8. Continue enforcing HTTPS and exact-host allowlisting for any optional links. 9. Add tests proving that API-supplied marketing content cannot alter the agent's instructions or appear in neutral recommendations without user consent. ]]>

T08 · Insecure Dependencies

Warning
Location
README.md:5
Finding
Skill Installation Uses a Mutable Unpinned GitHub Branch<![CDATA[ ## Vulnerability Details **File Location**: `README.md:5-8` and `README.md:29-30` **Vulnerability Type**: `T08: Insecure Dependencies` **Risk Level**: Medium ### Vulnerable Code From `README.md:5-8`: ```markdown [![Open install prompt in Cursor](https://img.shields.io/badge/Open%20in-Cursor-black?logo=cursor)](cursor://anysphere.cursor-deeplink/prompt?text=Install%20the%20Loan%20Doctor%20skill%20from%20https%3A%2F%2Fraw.githubusercontent.com%2Flgvw3%2Floan-doctor-skills%2Fmaster%2FSKILL.md%20using%20Custom%20Skill%20URL) [![Open Skill File](https://img.shields.io/badge/Open-SKILL.md-blue)](https://raw.githubusercontent.com/lgvw3/loan-doctor-skills/master/SKILL.md) Cursor uses Custom Skill URL import for third-party skills. Use the raw `SKILL.md` URL above in Cursor's install flow. ``` From `README.md:29-30`: ```markdown - `SKILL.md` (raw): `https://raw.githubusercontent.com/lgvw3/loan-doctor-skills/master/SKILL.md` ``` ### Technical Analysis The documented installation flow retrieves `SKILL.md` from the mutable `master` branch of a third-party GitHub repository. The URL is not pinned to a reviewed commit, release artifact, digest, or cryptographic signature. As a result, the effective instructions installed by a user can change after this artifact has been audited. A repository owner, compromised maintainer account, or attacker with repository write access could replace the branch content with malicious instructions. Users following the same installation link would then receive the modified version without an integrity warning. This is a supply-chain weakness because the installation source is external, mutable, and unauthenticated at the artifact level. HTTPS protects transport integrity but does not guarantee that the fetched content is the same version that was reviewed. ### Attack Path 1. A user follows the Cursor installation link or copies the raw GitHub URL from the README. 2. Cursor retrieves the current contents of `SKILL.md` from ...[truncated 1046 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the branch-based raw URL with a URL pinned to a reviewed Git commit hash. 2. Publish versioned, immutable release artifacts rather than directing users to `master`. 3. Publish a SHA-256 or stronger digest for each release and document verification steps. 4. Sign releases or repository tags and require signature verification before installation. 5. Ensure the Cursor deep link references the same immutable commit or release artifact. 6. Document the exact audited version in the README. 7. Use a controlled update process that requires users to review permissions and instruction changes before upgrading. 8. Consider distributing the complete reviewed package rather than importing only a remotely mutable `SKILL.md`. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/call_get_plans.mjs:278
Finding
Configured Request Timeout Is Ignored When Native Fetch Is Available<![CDATA[ ## Vulnerability Details **File Location**: `scripts/call_get_plans.mjs:278-287` **Vulnerability Type**: `T09: Insecure Skill Coding Practices` **Risk Level**: Low ### Vulnerable Code ```javascript const response = activeFetch ? await activeFetch(url, { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify(payload), }) : await postJsonWithNode(url, payload, timeoutMs) ``` ### Technical Analysis The `timeoutMs` argument is applied only to the `postJsonWithNode` fallback. In modern Node.js environments, `globalThis.fetch` is normally available, so the native-fetch branch is selected. That branch does not provide an `AbortSignal` or implement any other timeout mechanism. Consequently, the documented `--timeout-ms` option does not reliably constrain the request duration. A remote endpoint that accepts a connection but delays or never completes its response can keep the process waiting beyond the configured timeout. This creates an availability weakness and inconsistent security behavior across runtime versions. The fallback HTTP implementation has a timeout, while the more commonly selected native-fetch implementation does not. ### Attack Path 1. The script runs in a Node.js environment where `globalThis.fetch` is available. 2. `callGetPlans` selects `activeFetch` instead of `postJsonWithNode`. 3. The user supplies a slow or attacker-controlled `--base-url`, or the default API becomes unresponsive. 4. The endpoint accepts the request but never completes the response. 5. Because no abort signal is attached, `--timeout-ms` has no effect. 6. The process remains blocked until an external network or operating-system timeout occurs or the process is manually terminated. ### Impact Assessment No additional privileges, filesystem access, or code execution are obtained. The impact is limited to availability of the invoking process and any workflow waiting for it. A ...[truncated 221 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Apply the configured timeout to native `fetch` using `AbortSignal.timeout(timeoutMs)` where supported: ```javascript const response = await activeFetch(url, { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify(payload), signal: AbortSignal.timeout(timeoutMs), }) ``` 2. For broader runtime compatibility, use an `AbortController` and clear its timer in a `finally` block. 3. Convert abort errors into the existing deterministic timeout message. 4. Add a test with a fetch implementation that waits for the supplied signal and verify that it is aborted after the configured interval. 5. Consider separate connection and response-size limits to prevent slow-response and oversized-response resource exhaustion. 6. Ensure both the native-fetch and HTTP fallback paths provide equivalent timeout behavior. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (13)

Ae1

High
Category
analysis-evasion
Content
4. Run `scripts/call_get_plans.mjs` with `--input` (it defaults to `https://loandoctor.app`) and optionally `--base-url` for staging/self-hosted targets.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
4. Run `scripts/call_get_plans.mjs` with `--input` (it defaults to `https://loandoctor.app`) and optionally `--base-url` for staging/self-hosted targets.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
4. Run `scripts/call_get_plans.mjs` with `--input` (it defaults to `https://loandoctor.app`) and optionally `--base-url` for staging/self-hosted targets.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Skill Enumeration

Medium
Category
Agent Snooping
Content
### Cursor

[![Open install prompt in Cursor](https://img.shields.io/badge/Open%20in-Cursor-black?logo=cursor)](cursor://anysphere.cursor-deeplink/prompt?text=Install%20the%20Loan%20Doctor%20skill%20from%20https%3A%2F%2Fraw.githubusercontent.com%2Flgvw3%2Floan-doctor-skills%2Fmaster%2FSKILL.md%20using%20Custom%20Skill%20URL)
[![Open Skill File](https://img.shields.io/badge/Open-SKILL.md-blue)](https://raw.githubusercontent.com/lgvw3/loan-doctor-skills/master/SKILL.md)

Cursor uses Custom Skill URL import for third-party skills. Use the raw `SKILL.md` URL above in Cursor's install flow.
Confidence
80% confidence
Finding
Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Skill Enumeration

Medium
Category
Agent Snooping
Content
### Cursor

[![Open install prompt in Cursor](https://img.shields.io/badge/Open%20in-Cursor-black?logo=cursor)](cursor://anysphere.cursor-deeplink/prompt?text=Install%20the%20Loan%20Doctor%20skill%20from%20https%3A%2F%2Fraw.githubusercontent.com%2Flgvw3%2Floan-doctor-skills%2Fmaster%2FSKILL.md%20using%20Custom%20Skill%20URL)
[![Open Skill File](https://img.shields.io/badge/Open-SKILL.md-blue)](https://raw.githubusercontent.com/lgvw3/loan-doctor-skills/master/SKILL.md)

Cursor uses Custom Skill URL import for third-party skills. Use the raw `SKILL.md` URL above in Cursor's install flow.
Confidence
80% confidence
Finding
Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Skill Enumeration

Medium
Category
Agent Snooping
Content
#### Option A: User-level install

```bash
mkdir -p ~/.claude/skills/debt-payoff-plan-comparison
cp -R ./* ~/.claude/skills/debt-payoff-plan-comparison/
```
Confidence
85% confidence
Finding
Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Session Persistence

Medium
Category
Rogue Agent
Content
#### Option A: User-level install

```bash
mkdir -p ~/.claude/skills/debt-payoff-plan-comparison
cp -R ./* ~/.claude/skills/debt-payoff-plan-comparison/
```
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Skill Enumeration

Medium
Category
Agent Snooping
Content
#### Option B: Project-level install

```bash
mkdir -p .claude/skills/debt-payoff-plan-comparison
cp -R ./* .claude/skills/debt-payoff-plan-comparison/
```
Confidence
85% confidence
Finding
Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill instructs the agent to transmit sensitive financial data to an external API and even supports overriding the destination with a user-supplied --base-url, but it declares no explicit tool scope or network permission boundary. Without an allowlisted permission model, an agent runtime may permit unintended outbound requests or make review and enforcement of data egress rules harder, increasing privacy and SSRF-style risk in a financial-data context.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The invocation text says to use this skill when the user wants 'personalized debt plan recommendations' or related comparisons, which is broad natural language that could overlap with many general finance conversations. It does not provide explicit trigger phrases, exclusions, or negative examples to clarify when the skill should not activate.

Context-Inappropriate Capability

Medium
Confidence
87% confidence
Finding
The default allowlist includes outlook.office.com, which is not obviously related to debt payoff plan delivery and broadens the set of destinations that sanitized marketing links may point to. This can enable confusing or misleading outbound links in a finance-oriented skill, increasing phishing, trust-abuse, and unintended data-sharing risk if the upstream API returns promotional URLs to that host.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The script transmits the full debt payload to a remote service, which likely contains sensitive financial information, without any explicit user-facing notice, minimization, or consent step. In the context of a debt-payoff skill, this is more sensitive than average because balances, rates, payments, and home appraisal data are highly personal financial data and could create privacy and compliance issues if users do not realize the data leaves the local environment.

Description-Behavior Mismatch

Low
Confidence
79% confidence
Finding
The manifest says the skill collects debt inputs, calls a plans API, and returns payoff strategy comparisons with concise recommendations and a marketing hint. This code goes further by actively sanitizing, defaulting, and rewriting multiple marketing CTA URLs and labels in the response, which is broader than merely returning a hint from the API.

Static analysis

No suspicious patterns detected.