Back to skill

Security audit

Adaptive Trial Simulator

Security checks for vulnerabilities and agentic risk

Overview

This draft clinical-trial simulation skill is not malicious, but it needs Review because its advertised capabilities and safety claims do not match the implementation and it can overwrite user-selected files.

Install only in an isolated, nonprivileged environment, choose an output path in a disposable directory, and do not rely on its clinical-trial design conclusions without expert statistical validation. Treat the advertised drop-the-loser and optimization/visualization features as unvalidated until the implementation and tests are corrected, and pin dependencies before use.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/main.py:704
Finding

Caller-Controlled Output Path Allows Arbitrary File Overwrite

Content
View full analysis

Vulnerability Details

File Location: scripts/main.py:638-642, scripts/main.py:704-705, and scripts/main.py:782-783
Vulnerability Type: Unrestricted file write and overwrite
Risk Level: Medium

Vulnerable Code

python
parser.add_argument(
    "--output",
    type=str,
    default="results.json",
    help="输出文件路径"
)
python
# 保存结果
with open(args.output, 'w', encoding='utf-8') as f:
    json.dump(output, f, indent=2, ensure_ascii=False)
python
viz_file = args.output.replace('.json', '_visualization.png')
plt.savefig(viz_file, dpi=150, bbox_inches='tight')

Technical Analysis

The --output argument accepts an arbitrary filesystem path. The program does not canonicalize the path, constrain it to a designated output directory, check whether it references a symbolic link, or require confirmation before replacing an existing file.

The call to open(args.output, 'w', ...) creates the selected file or truncates an existing writable file. Consequently, anyone who controls the command-line arguments can overwrite any file writable by the process.

There is an additional filename-collision issue in visualization mode. If the output path does not contain the literal substring .json, the following expression returns the original path unchanged:

python
args.output.replace('.json', '_visualization.png')

In that case, plt.savefig() writes image data to the same path that previously received the JSON output, corrupting or replacing the simulation result.

Attack Path

  1. An attacker, untrusted automation layer, or manipulated Agent controls the value passed to --output.

  2. The attacker selects an existing file writable by the current process, for example:

    bash
    python scripts/main.py --n-simulations 1 --output /home/user/important-file
    
  3. The program reaches open(args.output, 'w', ...).

  4. The selected file is truncated and replaced with simulation JSON.

  5. If --visualize is also enable ...[truncated 934 chars]

Remediation
View remediation

Remediation Suggestions

  1. Use pathlib.Path to resolve and validate the output path.
  2. Restrict generated files to a dedicated output directory.
  3. Verify after resolution that the path remains inside the permitted directory.
  4. Refuse to replace existing files by default. Require an explicit --overwrite option where replacement is intended.
  5. Reject symbolic-link destinations or use platform-specific safe file-creation controls where hostile local users are in scope.
  6. Generate the visualization path independently so it can never equal the JSON path.

For example:

python
from pathlib import Path

output_root = Path("outputs").resolve()
output_root.mkdir(parents=True, exist_ok=True)

requested_name = Path(args.output).name
json_path = (output_root / requested_name).resolve()

if output_root not in json_path.parents:
    raise ValueError("Output path must be inside the output directory")

if json_path.exists() and not args.overwrite:
    raise FileExistsError(f"Output already exists: {json_path}")

with json_path.open("x" if not args.overwrite else "w", encoding="utf-8") as f:
    json.dump(output, f, indent=2, ensure_ascii=False)

viz_path = json_path.with_name(f"{json_path.stem}_visualization.png")
if viz_path == json_path:
    raise ValueError("Visualization and JSON paths must be different")

The application should also document that it must run with the minimum filesystem privileges required.

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding

Unpinned Third-Party Dependencies Create Supply-Chain and Reproducibility Risk

Content
View full analysis

Vulnerability Details

File Location: requirements.txt:1-4 and SKILL.md:130,163
Vulnerability Type: Unpinned dependencies without artifact integrity verification
Risk Level: Medium

Vulnerable Code

text
dataclasses
matplotlib
numpy
scipy

The documented installation command is:

bash
pip install -r requirements.txt

Technical Analysis

Every dependency is specified without an exact version or package hash. As a result, installation behavior depends on the packages and versions available from the configured package index at installation time.

Python package installation can execute package build and installation logic. If an upstream package, release account, package index, dependency chain, or local package-index configuration is compromised, following the documented installation command can introduce attacker-controlled code.

The absence of version pinning also means that different users may install materially different dependency versions. This reduces reproducibility and could change statistical results or introduce incompatible behavior without any change to the audited repository.

The dataclasses package is unnecessary for the documented Python 3.8-or-later environment because dataclasses is part of the standard library in supported versions. Keeping an unnecessary external dependency expands the supply-chain attack surface.

No evidence was found that the named packages are intentionally malicious or typographically deceptive. The finding concerns unsafe dependency management rather than a confirmed malicious package.

Attack Path

  1. A user follows the instructions in SKILL.md:

    bash
    pip install -r requirements.txt
    
  2. Pip resolves mutable, unpinned package versions from the configured index.

  3. A compromised release, dependency, index, or package-index configuration supplies malicious or unexpected installation content.

  4. Pip downloads and installs that content.

  5. Package build or i ...[truncated 979 chars]

Remediation
View remediation

Remediation Suggestions

  1. Remove dataclasses when Python 3.8 or later is required.

  2. Pin every direct and transitive dependency to reviewed versions.

  3. Generate and retain a lock file using a dependency-management tool such as pip-tools, Poetry, or an equivalent controlled workflow.

  4. Record cryptographic hashes and install with hash verification:

    bash
    python -m pip install --require-hashes -r requirements.txt
    
  5. Use a trusted package index and explicitly control index configuration in build environments.

  6. Run dependency vulnerability and license scanning in continuous integration.

  7. Review and deliberately update dependencies rather than allowing installation-time resolution to select arbitrary future releases.

  8. Install packages in an isolated virtual environment under a nonprivileged account.

A hash-locked requirements file should resemble:

text
numpy==<reviewed-version> \
    --hash=sha256:<reviewed-artifact-hash>
scipy==<reviewed-version> \
    --hash=sha256:<reviewed-artifact-hash>
matplotlib==<reviewed-version> \
    --hash=sha256:<reviewed-artifact-hash>

All transitive dependencies must also be pinned and hashed for --require-hashes protection to be effective.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/main.py:588
Finding

Unbounded Numeric Arguments Permit CPU and Memory Exhaustion

Content
View full analysis

Vulnerability Details

File Location: scripts/main.py:588-626, with resource allocation at scripts/main.py:253-254
Vulnerability Type: Missing input bounds and resource controls
Risk Level: Low

Vulnerable Code

python
parser.add_argument(
    "--n-simulations",
    type=int,
    default=10000,
    help="模拟次数"
)
parser.add_argument(
    "--sample-size",
    type=int,
    default=200,
    help="每臂初始样本量"
)
python
parser.add_argument(
    "--interim-looks",
    type=int,
    default=1,
    help="期中分析次数"
)

The unchecked values influence allocations and loops, including:

python
full_control = np.random.normal(0, 1, max_n)
full_treatment = np.random.normal(actual_effect, 1, max_n)

The simulation count controls repeated execution:

python
for i in range(self.config.n_simulations):
    if i % 1000 == 0 and i > 0:
        print(f"  Alternative scenario: {i}/{self.config.n_simulations}")
    result = self.simulate_single_trial(
        self.config.effect_size, is_null=False
    )
    alt_results.append(result)
python
for i in range(self.config.n_simulations):
    if i % 1000 == 0 and i > 0:
        print(f"  Null scenario: {i}/{self.config.n_simulations}")
    result = self.simulate_single_trial(
        self.config.effect_size, is_null=True
    )
    null_results.append(result)

Technical Analysis

argparse converts the relevant arguments to integers but imposes no acceptable minimum or maximum. Extremely large values can therefore cause:

  • Large NumPy allocations based on sample_size.
  • Very long alternative and null simulation loops based on n_simulations.
  • Excessive interim-processing work based on interim_looks.
  • Growth of in-memory result lists because every simulation result is retained.
  • Additional multiplication of work in optimization or visualization modes.

Malformed values such as zero or negative sample sizes and simulation counts can also lead to invalid calculations, empty lists ...[truncated 1523 chars]

Remediation
View remediation

Remediation Suggestions

  1. Define explicit minimum and maximum values for all numeric parameters.
  2. Reject zero or negative simulation counts, sample sizes, and interim-look counts.
  3. Validate that floating-point inputs such as alpha, power, and effect size are finite and within scientifically meaningful ranges.
  4. Estimate anticipated memory and computational cost before starting a run.
  5. Require explicit confirmation or a separate unsafe override for unusually expensive jobs.
  6. Stream or incrementally aggregate results instead of retaining every TrialResult in memory.
  7. Apply operating-system or container-level CPU, memory, and execution-time limits when processing untrusted requests.
  8. Disable or separately constrain optimization and visualization sweeps because they multiply the simulation workload.

A bounded parser helper can be used:

python
def bounded_int(minimum: int, maximum: int):
    def parse(value: str) -> int:
        parsed = int(value)
        if not minimum <= parsed <= maximum:
            raise argparse.ArgumentTypeError(
                f"value must be between {minimum} and {maximum}"
            )
        return parsed
    return parse

parser.add_argument(
    "--n-simulations",
    type=bounded_int(1, 1_000_000),
    default=10000
)
parser.add_argument(
    "--sample-size",
    type=bounded_int(2, 100_000),
    default=200
)
parser.add_argument(
    "--interim-looks",
    type=bounded_int(0, 100),
    default=1
)

The exact upper limits should be selected from measured resource usage and the deployment environment rather than relying solely on the illustrative limits above.

Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (12)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The documented capabilities materially overstate or misrepresent the implemented behavior, including unsupported trial designs, broken visualization, and output fields that can fail at runtime. In a clinical-trial design context, misleading functionality can cause users to rely on invalid simulations or incomplete adaptive-design support, potentially affecting scientific, regulatory, or patient-safety decisions.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill advertises executable Python usage and output file writing but does not declare any explicit tool scope such as permissions or allowed-tools. This creates an authorization gap where an agent may infer broader file-write capability than intended, increasing the chance of unsafe or unintended filesystem modification during execution.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

This Python file includes the top-level description and many CLI/help strings in Chinese, which imposes a specific language/locale on users. Under the stated policy, forcing a specific language without user opt-in is a natural-language policy violation unless the locale restriction is explicitly documented and justified, which is not present here.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The enum and module-level documentation present DROP_THE_LOSER as a supported adaptive design option, which implies multi-arm selection and dropping inferior arms. However, the simulator only generates two arms (control and treatment) and contains no branch anywhere that implements loser-dropping logic for this design type.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
97% confidence
Finding

Line L120 contains a user-facing status message entirely in Chinese ("AI自主验收状态: 需人工检查") while the rest of the document is in English. This imposes a language-specific instruction without opt-in or explanation, which fits the language/locale policy violation category.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
93% confidence
Finding

The dependency list is unpinned, so builds may resolve to different package versions over time, reducing reproducibility and increasing supply-chain risk if a newly published or compromised release is installed. This is a real but low-severity issue in a requirements manifest because it makes it impossible to verify which code will actually run.

Content

Scanner excerpt · requirements.txt (reported line 1)May include surrounding context.

text
dataclasses
matplotlib
numpy
scipy

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
93% confidence
Finding

matplotlib is specified without a version, allowing installation of any current release that satisfies the resolver. That creates reproducibility and supply-chain exposure, since future installs may pull a vulnerable, incompatible, or maliciously substituted version.

Content

Scanner excerpt · requirements.txt (reported line 2)May include surrounding context.

text
dataclasses
matplotlib
numpy
scipy

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
97% confidence
Finding

numpy is unpinned, so the environment may silently install different versions over time, including releases with known defects or advisories. In a Monte Carlo simulation skill, this also affects numerical reproducibility and makes it harder to determine whether an installed version is safe.

Content

Scanner excerpt · requirements.txt (reported line 3)May include surrounding context.

text
dataclasses
matplotlib
numpy
scipy

Unverifiable Dependency: numpy has 16 known advisory(ies) (CVE-2014-1859 (Numpy arbitrary file write via symlink attack); CVE-2021-41495 (NumPy NULL Pointer Dereference); CVE-2021-33430 (NumPy Buffer Overflow (Disputed)) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
89% confidence
Finding

numpy has known advisories, and because no version is pinned, there is no way to determine from this manifest whether the deployed environment is using a patched or affected release. This is dangerous primarily as an uncertainty and supply-chain hygiene issue rather than proof of an active vulnerable package in the file itself.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
97% confidence
Finding

scipy is also unpinned, which means package resolution is non-deterministic and may select versions with security or stability issues. Because this skill performs statistical simulation, unpinned scientific packages additionally risk inconsistent analytical results across environments.

Content

Scanner excerpt · requirements.txt (reported line 4)May include surrounding context.

text
dataclasses
matplotlib
numpy
scipy

Unverifiable Dependency: scipy has 4 known advisory(ies) (CVE-2013-4251 (SciPy creates insecure temporary directories); CVE-2013-4251 (The scipy.weave component in SciPy before 0.12.1 creates insecure temporary dire); CVE-2023-25399 (A refcounting issue which leads to potential memory leak was discovered in scipy) +1 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
88% confidence
Finding

scipy is listed without a version even though some releases have known advisories, so the manifest cannot demonstrate that installations will avoid affected versions. The main risk is unverifiable exposure and inconsistent environments, not evidence that this exact file already installs a vulnerable version.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

The optimal_design output is documented as reporting max_sample_size, but it references config.max_sample_multiplier, which is not defined on TrialConfig; the actual field is max_sample_size_multiplier. This means the documented output intent does not match the implemented data model and will fail rather than produce the advertised result.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.