Back to skill

Security audit

Swmm Climate

Security checks for vulnerabilities and agentic risk

Overview

This is a deterministic SWMM rainfall utility with no hidden network, credential, or persistence behavior, but users should validate names before using generated SWMM snippets from untrusted input.

Install only if you are comfortable using it as a local SWMM rainfall formatter/generator. Avoid feeding it untrusted CSV, DAT, station IDs, gage IDs, or series-name templates unless you validate them to simple SWMM-safe identifiers, because malformed names can corrupt or alter generated SWMM configuration fragments.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/build_raingage_section.py:29
Finding

SWMM Configuration Injection Through Unsanitized Raingage Identifiers

Content
View full analysis
str: hhmm = interval_hhmm(interval_min) lines = [ "[RAINGAGES]", ";;Name Format Interval SCF Source", f"{gage_id:<18} {rain_format:<10} {hhmm:<10} {scf:<8g} TIMESERIES {series_name}", ] return "\n".join(lines) + "\n" ``` The values originate directly from command-line parameters: ```python ap.add_argument("--gage-id", default="RG1") ap.add_argument("--series-name", default=None) ``` ### Technical Analysis `gage_id` and `series_name` are inserted verbatim into a generated SWMM configuration fragment. The code does not enforce SWMM token syntax and does not reject carriage returns, line feeds, whitespace, brackets, semicolons, or other configuration control characters. An attacker who can influence these CLI parameters, including through a wrapper exposing them as agent tool parameters, can terminate the intended raingage row and inject additional SWMM directives or sections. Python field-width formatting such as `<18` only pads short strings; it does not truncate, escape, or sanitize malicious input. This is configuration-language injection rather than operating-system command injection. The reviewed code does not execute the resulting text as shell commands, but downstream SWMM tooling may interpret injected content as trusted model configuration. ### Attack Path 1. An attacker obtains control over `--gage-id` or `--series-name`, directly or through an MCP/agent wrapper. 2. The attacker provides a value containing a newline and additional SWMM syntax, such as a new section header and attacker-selected model options. 3. `bu ...[truncated 892 chars]
Remediation
View remediation
str: if not IDENTIFIER_RE.fullmatch(value): raise ValueError(f"{field} contains unsupported characters") return value ``` - Explicitly reject all control characters, including `\r`, `\n`, and `\t`. - Reject brackets, semicolons, and whitespace even if future changes loosen the identifier expression. - Apply validation after loading `series_name` from rainfall JSON, because that JSON may itself contain untrusted values. - Add negative tests for newline injection, section headers, comments, spaces, tabs, and empty identifiers. - If arbitrary display names must be supported, maintain a separate sanitized internal identifier rather than embedding display text into SWMM syntax. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/format_rainfall.py:331
Finding

SWMM Timeseries Injection Through Raw Station and Series Names

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/design_storm.py:647
Finding

SWMM Timeseries Injection Through Unvalidated Design-Storm Series Name

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • 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
Findings (9)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description says this skill is for deterministic rainfall/climate formatting: converting timestamped rainfall CSV files into SWMM-ready [TIMESERIES] lines and [RAINGAGES] helper snippets. The supplied code instead implements a design-storm generator. Its main functions compute synthetic hyetographs using Chicago/Keifer-Chu formulas or the alternating-block method, based on coefficients, return period, duration, timestep, and optional IDF tables. It outputs TIMESERIES text and metadata JSON in a format compatible with another tool, but there is no functionality for ingesting timestamped rainfall CSV files as the primary input, no climate/rainfall formatting pipeline, and no RAINGAGES helper snippet generation. This is a material purpose mismatch, not just an implementation detail.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest description limits the skill to deterministic conversion of timestamped rainfall CSV files into SWMM-ready outputs. However, the same skill documentation later introduces design_storm.py and the MCP tool generate_design_storm, which generate synthetic hyetographs when no measured rainfall data exists rather than formatting existing CSV rainfall input.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

Lines describing MVP limitations state that the MVP focuses on rainfall intensity and raingage helper only, implying a narrower scope. Earlier documentation in the same file describes design_storm.py and an MCP generate_design_storm tool that synthesize rainfall hyetographs, which contradicts the claimed limitation.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The manifest describes a deterministic formatter for existing timestamped rainfall CSV input, focused on producing SWMM [TIMESERIES] and [RAINGAGES] helper snippets. This script instead generates new synthetic rainfall hyetographs from IDF formulas or tables and writes output files, which is materially broader than format conversion.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
98% confidence
Finding

The docstring first states the value is 'depth per timestep in mm' and mentions VOLUME format, then immediately says the script emits 'mm/hr' to match format_rainfall.py, and the code indeed converts depth to intensity at line L307. This is an active contradiction in the inline documentation about the meaning of emitted data.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/design_storm.py (reported line 483)May include surrounding context.

python
def _build_cn_coefficients(args: argparse.Namespace) -> dict[str, float]:
    required = ["A1", "C", "b", "n"]
    missing = [k for k in required if getattr(args, k.lower(), None) is None]
    if missing:
        raise ValueError(f"CN form requires --A1, --C, --b, --n. Missing: {missing}")
    return {

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/design_storm.py (reported line 498)May include surrounding context.

python
required_attrs = [("a_coeff", "--a"), ("b", "--b"), ("c", "--c")]
    missing = []
    for attr, flag in required_attrs:
        if getattr(args, attr, None) is None:
            missing.append(flag)
    if missing:
        raise ValueError(f"generic form requires --a, --b, --c. Missing: {missing}")

Description-Behavior Mismatch

Low
Category
Not specified by scanner
Confidence
92% confidence
Finding

The manifest describes a skill for converting timestamped rainfall CSV files into SWMM-ready output, suggesting a CSV-focused formatter. However, the code explicitly supports a separate --input-dat mode and additional unit semantics such as mm_per_day/in_per_day, which materially expands behavior beyond the stated CSV conversion scope.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.