Back to skill

Security audit

Circos Plot Generator

Security checks for vulnerabilities and agentic risk

Overview

This skill is scoped to local manuscript anonymization, but it needs review because it can expose removed identifiers in logs, overwrite files, and installs an inconsistent unpinned DOCX dependency.

Install only after reviewing the dependency declaration and use it on copies of manuscripts, not originals. Avoid running it in shared or logged terminals until the summary output is changed to counts-only, and manually verify DOCX acknowledgments, metadata, figures, headers, footers, and supplementary files before submission.

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

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding
Incorrect and Unpinned DOCX Dependency Creates Supply-Chain Risk<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt:1`; related implementation at `scripts/main.py:195-200` and documentation at `SKILL.md:130` **Vulnerability Type**: Dependency-name mismatch and unpinned third-party dependency **Risk Level**: Medium ### Vulnerable Code `requirements.txt:1`: ```text docx ``` `scripts/main.py:195-200`: ```python try: from docx import Document except ImportError: print("Error: python-docx not installed. Run: pip install python-docx") sys.exit(1) ``` The implementation and documentation identify `python-docx` as the required package, while `requirements.txt` declares the differently named `docx` package. The declared package is also not pinned to a reviewed version or protected with an integrity hash. ### Technical Analysis Python distribution names do not necessarily match import names. The correct distribution used to provide the supported `docx` import is documented by this project as `python-docx`, but the installation manifest requests `docx`. Consequently, installing the repository's requirements can retrieve an unintended or incompatible distribution. Because Python package installation may execute build or installation logic, dependency-name mistakes are a supply-chain boundary issue rather than only a reliability defect. The absence of a version constraint and integrity hash also permits future package releases to be selected without repository review. No evidence in the audited project establishes that the declared dependency is itself malicious. The vulnerability is the project's unsafe and inconsistent dependency declaration. ### Attack Path 1. A user or automated build system runs `pip install -r requirements.txt`. 2. The package resolver requests the distribution named `docx`, rather than the documented `python-docx` distribution. 3. The package and its installation/build components run with the permissions of the user or CI worker invoking `pip`. 4. An unintended, compromi ...[truncated 740 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the incorrect dependency name with the reviewed distribution: ```text python-docx==<reviewed-version> ``` 2. Pin an exact version after compatibility and vulnerability review. 3. Generate and enforce cryptographic hashes, for example with a locked requirements file and `pip install --require-hashes`. 4. Configure package installation to use an approved package index. 5. Add a clean-environment test that installs dependencies and verifies: ```python from docx import Document ``` 6. Add dependency vulnerability and provenance scanning to CI. 7. Keep the package name consistent across `requirements.txt`, `SKILL.md`, and runtime error messages. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/main.py:108
Finding
Sanitized Personal Information Is Disclosed in Console Output<![CDATA[ ## Vulnerability Details **File Location**: `scripts/main.py:100-129`, `scripts/main.py:150-156`, and `scripts/main.py:346-354` **Vulnerability Type**: Plaintext disclosure of author-identifying information through logs **Risk Level**: Medium ### Vulnerable Code `scripts/main.py:100-129`: ```python # Match common institution formats for keyword in INSTITUTION_KEYWORDS: # Match "XX University", "University of XX", "XX College", etc.] pattern = rf'\b[A-Z][A-Za-z\s]*{keyword}[A-Za-z\s]*\b|\b{keyword}[\u4e00-\u9fa5]+\b' matches = re.finditer(pattern, result, re.IGNORECASE) for match in list(matches)[::-1]: # Reverse replacement to avoid position changes self.removed_items.append(f"Institution: {match.group()}") result = result[:match.start()] + '[INSTITUTION]' + result[match.end():] return result def _remove_contact_info(self, text: str) -> str: """Remove contact information (email, phone)""" result = text # Remove email matches = re.finditer(EMAIL_PATTERN, result, re.IGNORECASE) for match in list(matches)[::-1]: self.removed_items.append(f"Email: {match.group()}") result = result[:match.start()] + '[EMAIL]' + result[match.end():] # Remove phone matches = re.finditer(PHONE_PATTERN, result) for match in list(matches)[::-1]: self.removed_items.append(f"Phone: {match.group()}") result = result[:match.start()] + '[PHONE]' + result[match.end():] return result ``` `scripts/main.py:150-156`: ```python else: def replace_func(match): self.removed_items.append(f"Self-citation: {match.group()}") return '[PREVIOUS WORK]' result = re.sub(pattern, replace_func, result, flags=re.IGNORECASE) ``` `scripts/main.py:346-354`: ```python print(f"Items processed: {len(sanitizer.removed_items)}") if sanitizer.removed_items: print("Summary:") for item in set(sanitizer.removed_items): count = sanitizer.removed_items.count(item) ...[truncated 1923 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store only redaction categories and aggregate counts: ```python self.removal_counts["email"] += 1 ``` 2. Do not retain `match.group()` for emails, phone numbers, institutions, names, or self-citation text. 3. Print a non-sensitive summary such as: ```text Emails removed: 2 Phone numbers removed: 1 ``` 4. If diagnostic disclosure is essential, require an explicit opt-in flag, display a warning, redact most characters, and write only to an access-controlled destination. 5. Treat console and exception output as potentially persistent. 6. Add tests asserting that known input identifiers do not appear in standard output or standard error. 7. Document logging behavior and applicable retention requirements. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/main.py:205
Finding
DOCX Acknowledgment Section Bodies Remain in Blinded Documents<![CDATA[ ## Vulnerability Details **File Location**: `scripts/main.py:205-213` **Vulnerability Type**: Incomplete anonymization caused by missing section-state tracking **Risk Level**: High ### Vulnerable Code ```python # Process paragraphs for para in doc.paragraphs: # Detect and remove acknowledgments if not self.sanitizer.keep_acknowledgments: if any(re.match(pattern, para.text.strip()) for pattern in ACKNOWLEDGMENT_TITLES): para.text = '[ACKNOWLEDGMENTS REMOVED]' continue # Process text content if para.text.strip(): para.text = self.sanitizer.sanitize_text(para.text) ``` ### Technical Analysis The DOCX processor recognizes an acknowledgment heading but replaces only that individual paragraph. It does not maintain an `in_acknowledgment` state or remove following paragraphs until the next section heading. This differs from the text and Markdown processor, which tracks whether processing is currently inside an acknowledgment section. Consequently, a DOCX output can retain the entire acknowledgment body while displaying an `[ACKNOWLEDGMENTS REMOVED]` marker in place of its heading. Acknowledgments commonly contain author names, collaborators, institutions, grants, project names, ethics approvals, and other identity signals. Generic name removal also depends on the caller supplying a complete author list, and therefore does not mitigate arbitrary collaborator or funder names. ### Attack Path 1. A DOCX manuscript contains an `Acknowledgments` heading. 2. Subsequent paragraphs identify collaborators, laboratories, grant numbers, institutions, or projects. 3. The user runs the sanitizer without `--keep-acknowledgments`. 4. The processor replaces only the heading with `[ACKNOWLEDGMENTS REMOVED]`. 5. The following acknowledgment paragraphs are processed as ordinary document text and remain unless individual patterns happen to match. 6. The user sees the removal marker and may reasonably assume that the ...[truncated 544 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Track acknowledgment-section state across DOCX paragraphs. 2. After detecting an acknowledgment heading, remove or replace all paragraphs until a reliably identified next section heading. 3. Use DOCX paragraph styles, such as heading levels, where available instead of relying only on text patterns. 4. Define conservative behavior for ambiguous section boundaries; flag the document for manual review rather than claiming successful removal. 5. Inspect acknowledgment content in tables, headers, footers, text boxes, footnotes, endnotes, comments, and other DOCX parts. 6. Add regression tests containing: - An acknowledgment heading and multiple body paragraphs. - A following numbered or styled section. - Names and grants not supplied through `--authors`. - Acknowledgment content inside tables. 7. Report acknowledgment sections removed by count without printing their contents. 8. Continue to require human verification before submission. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/main.py:316
Finding
Arbitrary Output Paths Are Silently Overwritten Without Input-Path Protection<![CDATA[ ## Vulnerability Details **File Location**: `scripts/main.py:316-319`; write operations at `scripts/main.py:224` and `scripts/main.py:266-267` **Vulnerability Type**: Unchecked destructive file overwrite **Risk Level**: Medium ### Vulnerable Code `scripts/main.py:316-319`: ```python # Determine output path if args.output: output_path = Path(args.output) else: output_path = input_path.parent / f"{input_path.stem}-blinded{input_path.suffix}" ``` `scripts/main.py:224`: ```python doc.save(output_path) ``` `scripts/main.py:266-267`: ```python with open(output_path, 'w', encoding='utf-8') as f: f.writelines(result_lines) ``` ### Technical Analysis The caller can choose any writable output path. The implementation does not check whether the destination already exists, whether the input and output resolve to the same file, whether the destination is a symbolic link, or whether overwriting was explicitly authorized. Plain-text output is opened with mode `w`, which truncates an existing file immediately. DOCX saving also replaces an existing destination. Neither path uses an atomic temporary file and rename operation, so an error during processing or writing can leave the destination damaged or incomplete. In multi-user or privileged automation environments, symbolic-link and path-selection behavior can extend the overwrite to another file writable by the invoking process. The project does not itself demonstrate privilege escalation, but it fails to preserve least-destructive file semantics. ### Attack Path 1. A caller supplies `--output` pointing to an existing file, the original input file, or a symbolic link to another writable file. 2. The script accepts the path without comparing canonical input and output locations. 3. For text files, opening the destination in `w` mode truncates it before all output is safely committed. 4. For DOCX files, `doc.save()` replaces the selected destination. 5. The original manuscript or another writabl ...[truncated 718 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Resolve and compare canonical paths before processing: ```python if input_path.resolve() == output_path.resolve(): raise ValueError("Input and output paths must differ") ``` 2. Refuse to overwrite an existing destination unless the caller supplies an explicit `--force` option. 3. Use exclusive creation when overwrite is not authorized. 4. Validate that the input is a regular file and that the destination parent is an expected writable directory. 5. Detect and reject symbolic-link destinations where the environment requires strict path isolation. 6. Write output to a securely created temporary file in the destination directory. 7. Flush and synchronize the temporary file as appropriate, then atomically replace the final destination only after successful processing. 8. Preserve the original file on every processing failure. 9. Add tests for equal input/output paths, existing destinations, symbolic links, interrupted writes, and invalid destination directories. ]]>
Vulnerability Patterns
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (8)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The documented description understates and partially misrepresents the actual behavior, including local file processing, identifier removal, and self-citation handling. Security reviewers and users rely on the description to understand risk; when behavior exceeds or differs from the declaration, they may approve or run the skill without recognizing data-modification, privacy, or integrity consequences.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill documents execution of a local Python script that reads and writes user-supplied files, but it does not declare an explicit tool/permission scope. That creates an authorization and review gap: consumers may not realize the skill needs filesystem access, and downstream enforcement systems cannot restrict or audit those capabilities precisely.

Description-Behavior Mismatch

Medium
Confidence
97% confidence
Finding
The tool records removed items including raw institution names, email addresses, phone numbers, and self-citation text, then prints them to stdout in the summary. In an anonymization workflow, that creates a direct information disclosure channel through terminal logs, CI logs, shell history capture, or shared execution environments, undermining the privacy goal of the skill.

Intent-Code Divergence

Low
Confidence
76% confidence
Finding
The manifest repeatedly frames missing identifiers as a bounded stop/fallback condition for safe anonymization, especially at L076-L079 and L138, but the parameter table declares `--authors` optional. This creates an intent mismatch in the skill documentation about whether identifiers are required for reliable operation versus merely improving detection.

Unpinned Dependencies

Low
Category
Supply Chain
Content
docx
Confidence
93% confidence
Finding
The dependency is specified without a version pin, so installs may resolve to different releases over time, including versions with newly introduced vulnerabilities or breaking changes. This weakens build reproducibility and increases supply-chain risk, especially if an upstream package is compromised or a malicious version is published.

Intent-Code Divergence

Low
Confidence
93% confidence
Finding
The top-level documentation says the tool performs 'one-click removal of author names, affiliations, acknowledgments, and excessive self-citations.' In practice, `--keep-acknowledgments` disables acknowledgment removal, so the documentation overstates mandatory behavior and contradicts the actual configurable implementation.

Missing User Warnings

Low
Confidence
86% confidence
Finding
This code performs a file write of sanitized manuscript content via `doc.save(output_path)`. Although the tool's purpose is anonymization, there is no nearby user-facing warning or comment that the processed manuscript will be saved as a new file containing modified content, which is the kind of data-affecting operation SQP-2 asks to disclose.

Missing User Warnings

Low
Confidence
87% confidence
Finding
The code writes transformed text lines to `output_path`, which changes how user manuscript data is stored. While the CLI prints input/output paths later, there is no explicit disclosure near the write path that a sanitized copy of the original content is being created, so the data-affecting write lacks clear warning context under SQP-2.

Static analysis

No suspicious patterns detected.