Back to skill

Security audit

Literature Search Pro

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent academic search tool, but its Node entry point can turn search inputs into shell commands, creating a serious review-worthy execution risk.

Review before installing or running. The literature-search behavior and academic API use are understandable, but this version should not be exposed to untrusted search inputs until the shell execution is replaced with argument-array process execution and runtime validation. Prefer HTTPS for arXiv and pin the Python requests dependency.

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

T09 · Insecure Skill Coding Practices

Error
Location
index.js:47
Finding

Shell Command Injection Through Unvalidated Search Options

Content
View full analysis

Vulnerability Details

File Location: index.js, lines 47–64
Vulnerability Type: OS command injection
Risk Level: High

Vulnerable Code

javascript
// Build command
const args = [
  `"${query}"`,
  `--limit ${limit}`,
  `--year-min ${yearMin}`,
  `--sources ${sources.join(',')}`,
  deduplicate ? '--deduplicate' : '',
  refresh ? '--refresh' : '',
].filter(Boolean).join(' ');

const command = `python "${SEARCH_SCRIPT}" ${args}`;

const { stdout, stderr } = await execAsync(command, {
  cwd: SKILL_DIR,
  timeout: 300000, // 5 minutes
  maxBuffer: 10 * 1024 * 1024,
});

Technical Analysis

The function constructs a command string by directly interpolating the caller-controlled query, limit, yearMin, and sources values. It then passes that string to child_process.exec, which executes it through a system shell.

Wrapping query in double quotes does not make it safe. Shell substitutions such as $(command) remain active inside double quotes, and embedded quotation marks can terminate the intended argument. The other interpolated fields are not quoted at all. The JSDoc type declarations do not provide runtime validation, so a caller can supply strings or specially crafted array elements despite the documented types.

Attack Path

  1. An attacker gains control over any options passed to the exported search() function, such as through an Agent invocation or an application endpoint that exposes the Skill.
  2. The attacker submits a query containing shell syntax, for example a command substitution expression.
  3. search() interpolates the payload into the command string without shell escaping.
  4. execAsync() invokes the system shell.
  5. The shell evaluates the injected expression before or while launching search.py.
  6. The injected operating-system command executes with the same identity and permissions as the Node.js Agent process.

The same path can be exp ...[truncated 580 chars]

Remediation
View remediation

Remediation Suggestions

Replace child_process.exec with execFile or spawn and provide arguments as an array so that no shell parses caller-controlled values.

javascript
const { execFile } = require('child_process');
const { promisify } = require('util');
const execFileAsync = promisify(execFile);

const allowedSources = new Set([
  'openalex',
  'semantic_scholar',
  'arxiv',
]);

if (typeof query !== 'string' || query.length === 0 || query.length > 1000) {
  throw new TypeError('query must be a non-empty string of at most 1000 characters');
}

if (!Number.isInteger(limit) || limit < 1 || limit > 50) {
  throw new RangeError('limit must be an integer between 1 and 50');
}

if (!Number.isInteger(yearMin) || yearMin < 1000 || yearMin > 9999) {
  throw new RangeError('yearMin must be a four-digit integer');
}

if (
  !Array.isArray(sources) ||
  sources.length === 0 ||
  sources.some(source => !allowedSources.has(source))
) {
  throw new TypeError('sources contains an unsupported source');
}

const args = [
  SEARCH_SCRIPT,
  query,
  '--limit', String(limit),
  '--year-min', String(yearMin),
  '--sources', sources.join(','),
];

if (deduplicate) args.push('--deduplicate');
else args.push('--no-deduplicate');
if (refresh) args.push('--refresh');

const { stdout, stderr } = await execFileAsync('python3', args, {
  cwd: SKILL_DIR,
  timeout: 300000,
  maxBuffer: 10 * 1024 * 1024,
});

In addition:

  • Do not attempt to address this issue only through manual shell escaping; avoiding the shell is safer.
  • Validate every exported function argument at runtime.
  • Enforce the configured maximum result limit.
  • Run the Skill under a dedicated, least-privileged operating-system account.
  • Add regression tests using shell metacharacters, embedded quotes, command substitutions, and malformed sources arrays.

T09 · Insecure Skill Coding Practices

Warning
Location
search.py:62
Finding

Unencrypted HTTP Transport for arXiv Searches

Content
View full analysis

Vulnerability Details

File Location: search.py, line 62; duplicated in config.json, line 22
Vulnerability Type: Cleartext transmission and unauthenticated remote response
Risk Level: Medium

Vulnerable Code

search.py:

python
self.arxiv_url = "http://export.arxiv.org/api/query"

The same insecure endpoint is also declared in config.json:

json
"arxiv": {
  "enabled": true,
  "base_url": "http://export.arxiv.org/api/query",
  "rate_limit": 1,
  "rate_limit_window": 3
}

The endpoint is used by the following request in search.py:

python
response = requests.get(
    self.arxiv_url,
    params={
        'search_query': search_query,
        'start': start,
        'max_results': max_results,
        'sortBy': 'submittedDate',
        'sortOrder': 'descending'
    },
    timeout=30
)
response.raise_for_status()

Technical Analysis

The arXiv request begins over plaintext HTTP rather than authenticated TLS. Search terms are included in the request query parameters, and the returned Atom document contains metadata subsequently parsed, cached, and shown to users.

A network intermediary positioned between the host and the arXiv endpoint can observe the research query or alter the HTTP response. Even if the server normally redirects HTTP traffic to HTTPS, the initial request remains unauthenticated and can be intercepted before a legitimate redirect is received.

raise_for_status() only checks the HTTP status code. It does not establish response authenticity or integrity over a plaintext connection.

Attack Path

  1. A user invokes the Skill with arXiv enabled.
  2. _search_arxiv() sends the query to http://export.arxiv.org/api/query.
  3. An attacker controlling or observing a network segment, proxy, DNS path, or access point intercepts the plaintext connection.
  4. The attacker reads the search terms or returns a forged Atom resp ...[truncated 810 chars]
Remediation
View remediation

Remediation Suggestions

Change the endpoint to HTTPS in every location:

python
self.arxiv_url = "https://export.arxiv.org/api/query"
json
"base_url": "https://export.arxiv.org/api/query"

Additional hardening should include:

  • Require TLS certificate verification and do not disable the default requests verification behavior.
  • Reject redirects that downgrade from HTTPS to HTTP.
  • Restrict redirects to expected arXiv hosts, or disable automatic redirects and validate each target before following it.
  • Centralize endpoint configuration so the runtime code and config.json cannot diverge.
  • Invalidate existing arXiv cache entries after deploying the transport fix because they may have originated from an unauthenticated response.
  • Add a test that verifies the effective arXiv request URL always uses the https scheme.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (7)

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill builds a shell command string and executes it with child_process.exec, interpolating untrusted inputs such as query, limit, yearMin, and sources directly into the command line. Wrapping query in double quotes is not sufficient shell escaping, so an attacker can inject shell metacharacters or command substitutions and achieve arbitrary command execution in the agent environment.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The user-facing output strings in formatResults and getSearchSuggestions are primarily hard-coded in Chinese, which imposes a specific language on all users. This is a natural-language policy concern because the file does not offer a language choice or explain a justified region-specific requirement.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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 · config.json (reported line 12)May include surrounding context.

json
self.cache_dir.mkdir(parents=True, exist_ok=True)
        
        # API endpoints
        self.openalex_url = "https://api.openalex.org/works"
        self.s2_url = "https://api.semanticscholar.org/graph/v1/paper/search"
        self.arxiv_url = "http://export.arxiv.org/api/query"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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 · search.py (reported line 65)May include surrounding context.

python
self.cache_dir.mkdir(parents=True, exist_ok=True)
        
        # API endpoints
        self.openalex_url = "https://api.openalex.org/works"
        self.s2_url = "https://api.semanticscholar.org/graph/v1/paper/search"
        self.arxiv_url = "http://export.arxiv.org/api/query"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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 · config.json (reported line 17)May include surrounding context.

json
# API endpoints
        self.openalex_url = "https://api.openalex.org/works"
        self.s2_url = "https://api.semanticscholar.org/graph/v1/paper/search"
        self.arxiv_url = "http://export.arxiv.org/api/query"
        
    def search(self, query: str, limit: int = 10, year_min: int = 2020,

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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 · search.py (reported line 66)May include surrounding context.

python
# API endpoints
        self.openalex_url = "https://api.openalex.org/works"
        self.s2_url = "https://api.semanticscholar.org/graph/v1/paper/search"
        self.arxiv_url = "http://export.arxiv.org/api/query"
        
    def search(self, query: str, limit: int = 10, year_min: int = 2020,

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
94% confidence
Finding

The peer dependency requests is specified as *, which permits any version to satisfy the requirement, including outdated or vulnerable releases. In a security-sensitive supply chain, this weakens reproducibility and can expose users of the skill to known dependency vulnerabilities or unexpected behavior when environments resolve different versions.

Content

Scanner excerpt · package.json (reported line 24)May include surrounding context.

json
"dependencies": {},
  "peerDependencies": {
    "python": ">=3.9",
    "requests": "*"
  },
  "engines": {
    "node": ">=18",