Back to skill

Security audit

Swagger2 To Openapi3

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent Swagger-to-OpenAPI migration helper, but its scripts can follow Java-file symlinks and modify files outside the selected project.

Use this only on trusted projects or after checking for symlinked .java files. Run --dry-run first, keep the project under version control, review the diff before committing, and avoid running it on repositories from untrusted sources until symlink containment is fixed.

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/migrate_swagger_to_openapi.py:119
Finding

Out-of-Project File Modification Through Symbolic-Link Files in the Complete Migrator

Content
View full analysis

Vulnerability Details

File Location: scripts/migrate_swagger_to_openapi.py:119-125, 152-172
Vulnerability Type: Symbolic-link following and insufficient path-boundary validation
Risk Level: Medium

Vulnerable Code

python
def find_java_files(self) -> List[Path]:
    """查找项目中所有的 Java 文件"""
    java_files = []
    for root, dirs, files in os.walk(self.project_path):
        # 排除常见的非源代码目录
        dirs[:] = [d for d in dirs if d not in ['.git', '.idea', 'target', 'build', 'node_modules']]
        for file in files:
            if file.endswith('.java'):
                java_files.append(Path(root) / file)
    return java_files
python
def process_file(self, file_path: Path) -> bool:
    """处理单个文件"""
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            content = f.read()

        original_content = content

        # 替换注解
        content, anno_count = self.replace_annotations(content)

        # 替换 import
        content, import_count = self.replace_imports(content)

        self.stats['files_processed'] += 1

        if content != original_content:
            self.stats['files_modified'] += 1
            self.stats['annotations_replaced'] += anno_count
            self.stats['imports_replaced'] += import_count

            if not self.dry_run:
                with open(file_path, 'w', encoding='utf-8') as f:
                    f.write(content)

Technical Analysis

File discovery accepts every entry with a .java suffix without checking whether it is a symbolic link. The subsequent calls to open() follow symbolic links by default. The script also does not resolve each candidate and confirm that its final target remains under the resolved project directory.

Consequently, a symbolic-link file located inside the selected project can refer to a writable Java file outside that project. If the external target contains an annotation or import matching a migration rule, the script reads, transforms, trun ...[truncated 1326 chars]

Remediation
View remediation

Remediation Suggestions

  1. Reject symbolic-link candidates before reading them:
python
if file_path.is_symlink():
    continue
  1. Resolve every candidate and ensure it remains within the project boundary:
python
project_root = self.project_path.resolve()
resolved_file = file_path.resolve(strict=True)

if not resolved_file.is_relative_to(project_root):
    raise ValueError(f"File escapes project root: {file_path}")
  1. Revalidate the path immediately before writing because a file can be replaced with a symbolic link after discovery.
  2. Open files using operating-system protections such as os.open(..., os.O_NOFOLLOW) where supported, then operate through the returned descriptor.
  3. Write through a securely created temporary file in the same directory and atomically replace the intended directory entry. Ensure the destination is revalidated before replacement.
  4. Fail closed and report the unsafe file instead of silently processing it.
  5. Add regression tests covering file symlinks, broken symlinks, links escaping the project, and link-swap attempts.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/migrate_annotations.py:122
Finding

Out-of-Project File Modification Through Symbolic-Link Files in the Annotation Migrator

Content
View full analysis

Vulnerability Details

File Location: scripts/migrate_annotations.py:122-127, 132-156
Vulnerability Type: Symbolic-link following and insufficient path-boundary validation
Risk Level: Medium

Vulnerable Code

python
def find_java_files(self) -> List[Path]:
    """查找项目中所有的 Java 文件"""
    java_files = []
    for root, dirs, files in os.walk(self.project_path):
        dirs[:] = [d for d in dirs if d not in ['.git', '.idea', 'target', 'build', 'node_modules']]
        for file in files:
            if file.endswith('.java'):
                java_files.append(Path(root) / file)
    return java_files
python
def migrate_file(self, file_path: Path) -> bool:
    """迁移单个文件"""
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            content = f.read()

        original = content
        total_replacements = 0

        for rule in self.ANNOTATION_RULES:
            pattern = rule['pattern']
            replacement = rule['replacement']
            new_content, count = re.subn(pattern, replacement, content)
            if count > 0:
                content = new_content
                total_replacements += count
                if self.dry_run:
                    print(f"  [{rule['name']}]: {count} 处替换")

        self.stats['files_processed'] += 1

        if content != original:
            self.stats['files_modified'] += 1
            self.stats['replacements'] = self.stats.get('replacements', 0) + total_replacements

            if not self.dry_run:
                with open(file_path, 'w', encoding='utf-8') as f:
                    f.write(content)

Technical Analysis

The annotation migrator selects files solely by their .java suffix and does not reject symbolic links. Both read and write operations use open(), which follows a file symlink to its target. No canonical-path containment check ensures that the resolved target belongs to the selected project.

A malicious repository can therefore make an a ...[truncated 1200 chars]

Remediation
View remediation

Remediation Suggestions

  • Skip all symbolic-link files during discovery.
  • Resolve each candidate with Path.resolve(strict=True) and require resolved_file.is_relative_to(self.project_path).
  • Repeat containment and link checks immediately before modification to reduce time-of-check/time-of-use exposure.
  • Where supported, use descriptor-based file access with O_NOFOLLOW.
  • Perform updates through a securely created same-directory temporary file followed by an atomic replacement of the validated entry.
  • Treat unsafe paths as errors and display them clearly to the user.
  • Add automated tests proving that external symlink targets remain unchanged in both normal and dry-run modes.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/migrate_imports.py:142
Finding

Out-of-Project File Modification Through Symbolic-Link Files in the Import Migrator

Content
View full analysis

Vulnerability Details

File Location: scripts/migrate_imports.py:142-147, 172-188
Vulnerability Type: Symbolic-link following and insufficient path-boundary validation
Risk Level: Medium

Vulnerable Code

python
def find_java_files(self) -> List[Path]:
    """查找项目中所有的 Java 文件"""
    java_files = []
    for root, dirs, files in os.walk(self.project_path):
        dirs[:] = [d for d in dirs if d not in ['.git', '.idea', 'target', 'build', 'node_modules']]
        for file in files:
            if file.endswith('.java'):
                java_files.append(Path(root) / file)
    return java_files
python
def process_file(self, file_path: Path) -> bool:
    """处理单个文件"""
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            content = f.read()

        original_content = content

        # 迁移 import
        content, import_count = self.migrate_imports_in_file(content)

        self.stats['files_processed'] += 1

        if content != original_content:
            self.stats['files_modified'] += 1
            self.stats['imports_replaced'] += import_count

            if not self.dry_run:
                with open(file_path, 'w', encoding='utf-8') as f:
                    f.write(content)

Technical Analysis

The import migrator does not distinguish regular files from symbolic links. A file symlink with a .java name is added to the processing list, while open() transparently follows it during both reading and writing. There is no verification that the resolved target is beneath the selected project root.

This permits repository content to redirect the migrator's write operation to an external file. The target is modified when at least one Swagger or Jakarta import replacement changes its content.

Attack Path

  1. An attacker places a .java symbolic link inside the target repository.
  2. The symlink points to an external Java file writable by the user who will run the tool.
  3. The external target i ...[truncated 872 chars]
Remediation
View remediation

Remediation Suggestions

  • Require discovered candidates to be regular, non-symlink files.
  • Resolve candidates strictly and verify containment beneath the canonical project root.
  • Recheck file identity and containment directly before writing.
  • Use O_NOFOLLOW or equivalent platform controls for protected file opening.
  • Use secure temporary files and atomic replacement rather than directly truncating the discovered path.
  • Abort processing for any path that changes identity between validation and modification.
  • Add security tests with attacker-controlled repositories containing .java symlinks to external files.
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 (7)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The description promises a broader migration capability: annotation replacements, import updates, and javax-to-jakarta migration for Spring Boot Swagger/OpenAPI upgrades. However, the code explicitly states and enforces that it only handles annotation replacement ('仅迁移注解', '不处理 import 语句'). Its functionality is limited to finding .java files and applying regex substitutions for annotations such as @Api, @ApiOperation, @ApiParam, @ApiModel, @ApiModelProperty, @ApiImplicitParam, and @ApiImplicitParams. There is no logic for changing import statements, no dependency/build-file updates, and no javax.* to jakarta.* transformations. Therefore the declared description materially overstates the actual behavior, making this a mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

This is a description-behavior mismatch because the declared description promises a broader migration capability: Swagger 2/Springfox to OpenAPI 3.0/SpringDoc, including annotation replacements, import updates, and javax-to-jakarta migration. The actual code only handles import statement replacement in .java files and explicitly states that it does not process annotations. It does match part of the declaration—import updates and some javax-to-jakarta migration—but omits a key claimed capability (annotation replacement) and does not show any Springfox/SpringDoc dependency, config, or code migration beyond imports. Therefore the code's actual scope is materially narrower than the declared purpose.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill description, usage instructions, and invocation examples are written entirely in Chinese, including the example prompts the user is expected to use. Under the policy for natural-language violations, this constitutes a language/locale constraint without any documented user opt-in or alternative language option.

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.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

This markdown file contains user-facing guidance entirely in Chinese, including the main title and operational instructions. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation unless the locale restriction is explicitly justified, which is not present here.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The user-facing description, help text, and error messages are written in Chinese throughout the script, with no indication that other languages are supported or that the skill is intentionally limited to a Chinese-speaking context. This can violate language/locale policy when a skill imposes a specific language without user opt-in.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

This code file contains natural-language strings and documentation that consistently force Chinese for descriptions, prompts, and usage guidance. Under the policy, language-specific behavior without opt-in or justification is a natural-language policy violation.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.