Back to skill

Security audit

Ebm Calculator

Security checks for vulnerabilities and agentic risk

Overview

This skill is a coherent medical-statistics calculator, but its optional file output can overwrite arbitrary writable paths and its clinical input validation is weak.

Review this skill before installing or running it in a workspace with important files. Use --output only with a safe, intended relative path, avoid running it with elevated filesystem permissions, and treat results cautiously unless inputs are validated externally.

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

Warning
Location
scripts/main.py:169
Finding

Unrestricted Output Path Allows Arbitrary File Overwrite

Content
View full analysis

Vulnerability Details

File Location: scripts/main.py:169-172
Vulnerability Type: Unrestricted file write and path traversal
Risk Level: Medium

python
if args.output:
    with open(args.output, 'w', encoding='utf-8') as f:
        f.write(output)
    print(f"Results saved to: {args.output}")

Technical Analysis

The application accepts a caller-controlled output path and opens it in write mode without path normalization, workspace-boundary enforcement, traversal checks, symlink validation, or overwrite protection. Python's open(..., 'w') truncates an existing destination and follows symbolic links.

Although SKILL.md states that output should be restricted to the workspace and that ../ traversal should be prevented, the implementation does not enforce either control.

Attack Path

  1. An attacker or untrusted caller supplies an output argument containing an absolute path or traversal sequence, such as:
    bash
    python scripts/main.py --tp 90 --fn 10 --tn 80 --fp 20 \
      --output ../../writable-target
    
  2. The calculator produces valid JSON output.
  3. The program passes the attacker-controlled path directly to open() with mode w.
  4. If the process can write to the destination, the existing file is truncated and replaced with calculator output.
  5. If the selected path is a symbolic link, the linked target may be overwritten as well.

Impact Assessment

The attacker can overwrite or corrupt any file writable by the operating-system account running the Skill. The vulnerability does not independently elevate privileges; its scope is bounded by the process's existing filesystem permissions. Potential consequences include project corruption, configuration replacement, denial of service, and overwriting files outside the intended workspace.

Remediation
View remediation

Remediation Suggestions

  • Define a dedicated output directory and resolve both it and the requested destination with pathlib.Path.resolve().
  • Reject destinations that are not descendants of the approved output directory.
  • Reject absolute paths when only workspace-relative paths are expected.
  • Prevent symlink-based escapes by rejecting symlink components and safely validating the final destination.
  • Avoid silently truncating existing files. Use exclusive creation mode (x) where overwriting is unnecessary, or require explicit overwrite authorization.
  • Run the Skill with minimal filesystem permissions.
  • Catch filesystem exceptions and return concise, sanitized errors.

Example boundary check:

python
from pathlib import Path

output_root = (Path.cwd() / "output").resolve()
output_root.mkdir(parents=True, exist_ok=True)
destination = (output_root / args.output).resolve()

if output_root not in destination.parents:
    raise ValueError("Output path must remain inside the output directory")
if destination.exists() or destination.is_symlink():
    raise ValueError("Refusing to overwrite an existing destination")

with destination.open("x", encoding="utf-8") as f:
    f.write(output)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/main.py:89
Finding

Missing Numeric Domain Validation Causes Crashes and Invalid Clinical Results

Content
View full analysis

Vulnerability Details

File Location: scripts/main.py:89-99
Vulnerability Type: Improper input validation and unhandled arithmetic edge cases
Risk Level: Medium

python
def pretest_to_posttest(self, pretest_prob: float, lr: float) -> Dict:
    """Convert pre-test to post-test probability using likelihood ratio."""
    pretest_odds = pretest_prob / (1 - pretest_prob)
    posttest_odds = pretest_odds * lr
    posttest_prob = posttest_odds / (1 + posttest_odds)
    
    return {
        "pretest_probability": round(pretest_prob, 4),
        "likelihood_ratio": round(lr, 4),
        "posttest_probability": round(posttest_prob, 4),
        "probability_change": round(posttest_prob - pretest_prob, 4)
    }

Related CLI arguments are parsed as unrestricted numeric values:

python
parser.add_argument("--tp", "--true-pos", type=int, help="True positives")
parser.add_argument("--fn", "--false-neg", type=int, help="False negatives")
parser.add_argument("--tn", "--true-neg", type=int, help="True negatives")
parser.add_argument("--fp", "--false-pos", type=int, help="False positives")
parser.add_argument("--prevalence", "-p", type=float, help="Disease prevalence (0-1)")
parser.add_argument("--control-rate", type=float, help="Control event rate (0-1)")
parser.add_argument("--experimental-rate", type=float, help="Experimental event rate (0-1)")
parser.add_argument("--pretest", type=float, help="Pre-test probability (0-1)")
parser.add_argument("--lr", type=float, help="Likelihood ratio")

Technical Analysis

The argument descriptions specify expected probability ranges, but the implementation does not enforce them. In probability mode, a pre-test probability of exactly 1 makes the denominator in pretest_prob / (1 - pretest_prob) equal to zero, raising an unhandled ZeroDivisionError.

Other modes have the same validation weakness. Negative confusion-matrix counts, rates outside ...[truncated 1429 chars]

Remediation
View remediation

Remediation Suggestions

  • Reject negative confusion-matrix counts.
  • Enforce prevalence, control rate, and experimental rate within [0,1].
  • Define and enforce the supported pre-test probability domain. If 1 is supported, handle it explicitly; otherwise require 0 <= pretest < 1.
  • Require likelihood ratios to be nonnegative and finite.
  • Reject NaN and positive or negative infinity with math.isfinite().
  • Check every denominator before division and define explicit behavior for undefined metrics.
  • Catch expected validation and arithmetic errors in main() and return sanitized messages with a nonzero exit status instead of a traceback.
  • Add boundary tests for 0, 1, negative values, values above 1, zero denominators, NaN, and infinity.

Example validation:

python
import math

if not math.isfinite(pretest_prob) or not 0 <= pretest_prob < 1:
    raise ValueError("Pre-test probability must be finite and in [0, 1)")
if not math.isfinite(lr) or lr < 0:
    raise ValueError("Likelihood ratio must be finite and nonnegative")
Vulnerability Patterns
  • 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
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (3)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill advertises file-writing capability via the --output parameter and risk table, but it does not declare any explicit tool scope such as permissions or allowed-tools. This creates a governance gap: an execution environment may permit broader write behavior than users or reviewers expect, increasing the chance of unintended file modification or abuse if the implementation writes arbitrary paths.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The main documentation exposes an --output file path parameter but does not prominently warn that the skill writes to disk or define clear constraints on where files may be created. That omission can mislead users about side effects and makes unsafe usage more likely, especially if the underlying tool accepts arbitrary paths.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The manifest describes an Evidence-Based Medicine calculator for computing clinical statistics, which implies local computation and result presentation. The code also supports writing JSON output to a user-specified file path via --output, which goes beyond pure calculation behavior described in the manifest.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.