Back to skill

Security audit

Dualgap

Security checks for vulnerabilities and agentic risk

Overview

DualGap appears purpose-built for PDF literature analysis, but it sends bulk research content and API credentials to a configurable endpoint and can execute an unverified sibling validator script.

Review before installing. Use this only with PDFs you are allowed to send to the configured LLM provider, use an HTTPS endpoint you trust, use a restricted API key, keep the output directory private, and avoid running self_validate_skill.py in a workspace where a sibling skill-creator directory could be untrusted.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Tool Hijacking and SpoofingModifies or replaces tools so legitimate-looking calls execute attacker logic
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/run_literature_workflow.py:78
Finding

Unrestricted API Endpoint Can Expose Credentials and Research Data Over Insecure Transport

Content
View full analysis
str: base = base_url.rstrip("/") if base.endswith("/v1"): return base + "/chat/completions" return base + "/v1/chat/completions" def call_chat( config: dict[str, str], messages: list[dict[str, str]], max_tokens: int, temperature: float = 0.2, retries: int = 5, timeout: int = 180, ) -> str: payload = { "model": config["model"], "messages": messages, "temperature": temperature, "max_tokens": max_tokens, } data = json.dumps(payload).encode("utf-8") url = chat_url(config["base_url"]) headers = { "Authorization": f"Bearer {config['api_key']}", "Content-Type": "application/json", } for attempt in range(retries): try: request = urllib.request.Request(url, data=data, headers=headers, method="POST") with urllib.request.urlopen(request, timeout=timeout) as response: body = json.loads(response.read().decode("utf-8")) return body["choices"][0]["message"]["content"] except ( urllib.error.HTTPError, urllib.error.URLError, TimeoutError, socket.timeout, ConnectionResetError, OSError, http.client.HTTPException, KeyError, json.JSONDecodeError, ) as exc: if attempt == retries - 1: raise RuntimeError(f"LLM call failed after {retries} attempts: {exc}") from exc time.sleep(min(30, 2 ** attempt) + random.random()) raise RuntimeError("unreachable") ``` ### Technical Analysis The configured `base_url` is used without validating its scheme, destination, or trust level. The co ...[truncated 2177 chars]
Remediation
View remediation

T01 · Skill Instruction Hijacking

Warning
Location
scripts/run_literature_workflow.py:235
Finding

Untrusted PDF Content Is Embedded Directly into LLM Instructions

Content
View full analysis
list[dict[str, str]]: retry = f"\nPrevious reviewer feedback to fix:\n{retry_reason}\n" if retry_reason else "" return [ { "role": "system", "content": "You are a senior researcher writing critical literature notes for research-gap discovery. Use your own words, avoid boilerplate, and ground claims in the supplied paper text.", }, { "role": "user", "content": f""" We are comparing two research directions: - Direction A: {direction_a} - Direction B: {direction_b} Current paper: - Direction: {paper.direction_name} - File: {paper.pdf_path.name} {retry} Write a Chinese Markdown paper note. It must follow this structure: # [Inferred paper title] ## 1. Basic Information - File - Direction - Paper type - Whether it strictly belongs to this direction - Main task - Data / benchmark - Confidence: high / medium / low ## 2. Background And Motivation Explain the paper-specific motivation. Do not write generic claims like "privacy is important". ## 3. Core Challenge What exactly fails in previous methods? Why is it nontrivial? ## 4. Method: Step-By-Step Mechanism Describe the workflow concretely enough that a reader can reconstruct the algorithm. If it is FL/distributed: client state, server state, upload, download, aggregation/routing, local training, inference. If it is skill discovery/evolution: trajectory/data source, skill representation, generator, verifier, optimizer/editor, retrieval/dependency, evolution phase, inference/use phase. ## 5. Why The Method Could Work Explain the mechanism, not just component names. ## 6. Experiments And Evidence Separate s ...[truncated 2960 chars]
Remediation
View remediation

T07 · Tool Hijacking and Spoofing

Warning
Location
scripts/self_validate_skill.py:29
Finding

Self-Validation Automatically Executes an Unverified Sibling Script

Content
View full analysis
dict[str, Any]: quick_validate = skill_dir.parent / "skill-creator" / "scripts" / "quick_validate.py" if not quick_validate.exists(): return internal_skill_validate(skill_dir, f"Missing skill-creator validator: {quick_validate}") result = subprocess.run( [sys.executable, str(quick_validate), str(skill_dir)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8", errors="replace", ) if result.returncode != 0 and "No module named 'yaml'" in (result.stdout + result.stderr): return internal_skill_validate(skill_dir, "skill-creator quick_validate.py could not import PyYAML; used built-in fallback validator.") return { "passed": result.returncode == 0, "evidence": (result.stdout + result.stderr).strip(), } ``` ### Technical Analysis The self-validation script computes a path outside the audited Skill directory and executes the Python file found there. Path existence is the only authenticity check. The target is not bundled with this project, and its hash, signature, ownership, or trusted installation root is not verified. This behavior creates a local tool-hijacking opportunity. A malicious or compromised sibling directory can provide a counterfeit `quick_validate.py` that appears to be the expected validator but executes arbitrary Python code with the current user's privileges. The project documentation explicitly instructs users to run `scripts/self_validate_skill.py`, making this path reachable during normal use. Validation functionality does not require silently trusting any file that happens to occupy the sibling path; therefore, the behavior exceeds the minimum privil ...[truncated 1223 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Note
Location
requirements.txt:1
Finding

Dependency Version Is Open-Ended and Lacks Integrity Pinning

Content
View full analysis
=4.0.0 ``` ### Technical Analysis The dependency declaration sets only a minimum version. A future installation can therefore resolve to any later `pypdf` release available from the configured package index. No exact version or package hash is supplied. The package name is legitimate and no evidence shows that the currently declared dependency is malicious. The security weakness is that installation results are not reproducible and future releases are accepted without project-specific review. Because the instructions recommend running `python -m pip install -r requirements.txt`, dependency installation is part of the normal workflow. This creates exposure to compromised future releases, newly introduced vulnerabilities, or an untrusted package index. It is a supply-chain hardening issue rather than evidence of an intentionally malicious dependency. ### Attack Path 1. A future dependency release is compromised, contains a severe vulnerability, or behaves incompatibly. 2. Alternatively, a user installs from a malicious or misconfigured Python package index. 3. The user follows the project instructions and runs `python -m pip install -r requirements.txt`. 4. The package resolver selects the latest version satisfying `>=4.0.0`. 5. Unreviewed package code is installed and later imported by `extract_pdf_text`. 6. Any malicious package behavior executes with the installing or workflow user's privileges. ### Impact Assessment The possible impact is arbitrary code execution under the installing user's account if a malicious dependency release or package source is involved. A merely vulnerable or incompatible release could instead cause: - Incorrect PDF extraction - Workflow failures - Unexpected parsing behavior - Exposure to dependency-specific vulnerabilities - Non-reproducibl ...[truncated 168 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (17)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared purpose is literature-analysis over PDFs, but the skill also advertises self-validation, compilation checks, synthetic output generation, and package/integrity QA behaviors that are outside the primary user-facing description. This mismatch can cause operators to underestimate what the skill will execute, which weakens informed consent, review, and sandboxing decisions.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/run_literature_workflow.py (reported line 826)May include surrounding context.

python
parser.add_argument("--name-b", required=True)
    parser.add_argument("--out", required=True)
    parser.add_argument("--config", default="local_config/llm.json")
    parser.add_argument("--env-file", default="", help="Optional .env file to load before reading env-prefix variables")
    parser.add_argument("--env-prefix", default="", help="Use env vars such as QWEN_API_KEY/QWEN_BASE_URL/QWEN_MODEL")
    parser.add_argument("--agenda", default="", help="User research agenda, priorities, or exclusions to enforce in synthesis")
    parser.add_argument("--agenda-file", default="", help="Markdown/text file with research agenda and exclusions")

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · HOW_TO_USE.md (reported line 43)May include surrounding context.

json
{
  "base_url": "https://api.example.com/v1",
  "api_key": "your_api_key_here",
  "model": "model-name"
}

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill describes behavior that uses environment variables, filesystem access, shell execution, and network/API access, but it does not declare any explicit tool scope or permission boundary. This increases the chance an agent runtime will grant broader capabilities than are necessary, making misuse, accidental data exposure, or unsafe command execution harder to constrain or audit.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
80% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

Scanner excerpt · SKILL.md (reported line 37)May include surrounding context.

md
- `--config` JSON with `base_url`, `api_key`, and `model`
  - already-set environment variables

Never ask the user to paste a real API key into generated notes, logs, examples, or committed files. Prefer a local env file outside output directories.

## Agent Workflow

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The prompt explicitly instructs use of "an OpenAI-compatible API from environment variables," which imposes a specific provider/interface choice in natural language. Under the policy, forcing a particular language/locale or similar user-facing constraint without opt-in should be flagged; this is analogous to imposing a fixed platform choice rather than offering a user selection.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The manifest describes a literature-analysis skill that works on PDFs and produces notes/syntheses, but this implementation falls back to spawning a local subprocess to run pdftotext. While PDF text extraction is in scope, invoking an external system binary is a stronger capability than the skill's stated research purpose justifies and introduces host-environment dependence not disclosed by the manifest description.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/run_literature_workflow.py (reported line 138)May include surrounding context.

python
pass

    try:
        result = subprocess.run(
            ["pdftotext", "-layout", str(pdf_path), "-"],
            check=True,
            stdout=subprocess.PIPE,

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The prompt instructs the model to 'Write a Chinese Markdown paper note,' and similar language appears in synthesis generation, forcing a specific language regardless of user preference. This is a natural-language locale constraint with no opt-in, fallback, or explanation that the tool is intended only for a Chinese-language context.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The workflow sends extracted PDF text to an external chat-completions API, which can disclose unpublished papers, licensed content, confidential annotations, or sensitive research agendas to a third-party service. In this skill context, the danger is elevated because the whole purpose is bulk processing of local research PDFs, and users may reasonably assume local-only analysis unless explicitly warned.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The synthesis prompt explicitly says 'Write a Chinese Markdown document titled: {title}', which imposes a language policy across all generated synthesis documents. Because the script does not expose a language-selection argument or clearly justify a region-specific requirement, this is a locale policy violation.

Content

No source excerpt is available for this finding.

Tainted flow: 'content' from pathlib.Path.read_text (line 692, file read) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/run_literature_workflow.py (reported line 699)May include surrounding context.

python
retries=args.api_retries,
            timeout=args.api_timeout,
        )
        path.write_text(content, encoding="utf-8")
        review = review_synthesis_document(config, title, path, notes, required, agenda, args)
        if review.get("pass") is not True:
            report = out_dir / "quality_failure_report.md"

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/self_validate_skill.py (reported line 36)May include surrounding context.

python
quick_validate = skill_dir.parent / "skill-creator" / "scripts" / "quick_validate.py"
    if not quick_validate.exists():
        return internal_skill_validate(skill_dir, f"Missing skill-creator validator: {quick_validate}")
    result = subprocess.run(
        [sys.executable, str(quick_validate), str(skill_dir)],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,

Vague Triggers

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

This is a JSON manifest-style file, so vague trigger checks apply. The prompt phrase "Turn my two research directions into a research-gap study" is a broad natural-language request without explicit scope constraints or negative examples, which could map to many ordinary research-assistance requests if reused as activation wording.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
93% confidence
Finding

The dependency is specified as pypdf>=4.0.0 without an upper bound or exact pin, which makes builds non-reproducible and can silently pull in vulnerable or incompatible future releases. In a skill that processes untrusted PDFs, dependency drift increases exposure to parser bugs and denial-of-service issues in whatever version gets installed.

Content

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

text
pypdf>=4.0.0

Unverifiable Dependency: pypdf has 16 known advisory(ies) (CVE-2026-84310 (pypdf: Possible long runtimes/large memory usage when retrieving outlines); CVE-2026-48156 (pypdf: Possible long runtimes for zero-only width values in cross-reference stre); CVE-2026-24688 (pypdf has possible Infinite Loop when processing outlines/bookmarks) +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

pypdf has multiple known advisories, and because the manifest does not pin a version, it is not possible to verify whether installation will select a patched or affected release. This is more concerning in this skill's context because it explicitly handles PDF inputs, a high-risk file format where parser flaws can be triggered by malicious documents to cause hangs, excessive memory use, or service disruption.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
78% confidence
Finding

The workflow persists extracted text, notes, review JSONL files, synthesis documents, manifests, and audit reports under the output directory. While writing output is part of the script's workflow, there is no explicit warning or disclosure in the code that source paper text and model-generated analyses will be stored locally.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.