Back to skill

Security audit

Exa

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent Exa search and research integration, but its structured-output option can upload arbitrary local JSON files to Exa with only a weak filename-based guard.

Review this skill before installing if you will let agents choose SCHEMA_FILE values. Use it only with a dedicated non-sensitive schema file, avoid confidential queries or URLs, override or unset LOCATION if NL is not appropriate, and prefer setting EXA_API_KEY through the environment rather than storing it beside the skill.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/research_create.sh:26
Finding
Bypassable Sensitive-File Upload Guard for Research Output Schemas<![CDATA[ ## Vulnerability Details **File Location**: `scripts/research_create.sh:26-66` **Vulnerability Type**: Insufficient local-file validation leading to unintended data disclosure **Risk Level**: Medium ### Vulnerable Code ```bash if [ -n "${SCHEMA_FILE:-}" ]; then # Security guard: refuse obviously sensitive local files from being uploaded as outputSchema. _schema_lc="$(printf '%s' "$SCHEMA_FILE" | tr '[:upper:]' '[:lower:]')" case "$_schema_lc" in .env|.env.*|*.env|*.env.*|*.key|*.pem|*.p12|*.pfx|*.jks|*.keystore|*.der|*.crt|*.cer|*id_rsa*|*id_ecdsa*|*id_ed25519*) echo "Error: Refusing SCHEMA_FILE path that looks sensitive: $SCHEMA_FILE" >&2 echo "Use a dedicated JSON schema file (for example: schema.json)." >&2 exit 1 ;; esac if [ ! -f "$SCHEMA_FILE" ]; then echo "Error: SCHEMA_FILE does not exist: $SCHEMA_FILE" >&2 exit 1 fi # Guard: reject files larger than 50MB _size="$(wc -c < "$SCHEMA_FILE")" if [ "$_size" -gt 52428800 ]; then echo "Error: SCHEMA_FILE exceeds 50MB limit: $SCHEMA_FILE" >&2 exit 1 fi OUTPUT_SCHEMA_JSON="$(jq -c '.' "$SCHEMA_FILE")" PAYLOAD="$(jq -n \ --arg instructions "$INSTRUCTIONS" \ --arg model "$MODEL" \ --argjson outputSchema "$OUTPUT_SCHEMA_JSON" \ '{ instructions: $instructions, model: $model, outputSchema: $outputSchema }')" else PAYLOAD="$(jq -n \ --arg instructions "$INSTRUCTIONS" \ --arg model "$MODEL" \ '{ instructions: $instructions, model: $model }')" fi curl -s -X POST 'https://api.exa.ai/research/v1' \ -H "x-api-key: $EXA_API_KEY" \ -H 'Content-Type: application/json' \ -d "$PAYLOAD" ``` ### Technical Analysis The script accepts a caller-controlled `SCHEMA_FILE`, reads its complete JSON content, inserts that content into the `outputSchema` request property, and transmits it to `https://api.exa.ai/research/v1`. The protective control is a filename denylist. It does not establish that the selected file is ...[truncated 2484 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Replace the filename denylist with a trusted-location policy** - Store schemas in a dedicated directory controlled by the Skill. - Canonicalize the selected path with `realpath`. - Verify that the canonical path remains inside the trusted schema directory. 2. **Reject symbolic links** - Test the supplied path with `[ -L "$SCHEMA_FILE" ]` and fail if it is a symlink. - Perform validation again after canonicalization to reduce path-race and path-aliasing risks. 3. **Validate actual JSON Schema structure** - Require an object at the document root. - Validate expected schema properties such as `$schema`, `type`, `properties`, or other supported output-schema fields. - Reject arbitrary JSON documents that do not conform to the accepted schema format. 4. **Reduce the size limit** - Replace the 50 MB threshold with a small schema-specific maximum, such as hundreds of kilobytes, based on the Exa API's documented limits. 5. **Require explicit upload approval** - Display the canonical path, size, and destination before uploading local file content. - In Agent-controlled workflows, require direct user confirmation rather than relying solely on instructions supplied to the Agent. 6. **Minimize local-file access** - Prefer accepting structured schema content through a narrowly scoped input mechanism. - If file input remains necessary, open only files from the approved schema directory and with restrictive ownership and permission checks. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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
Findings (24)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The description claims a broader Exa integration covering web/code search, content extraction, and asynchronous research workflows. The actual code chunk is narrowly focused on synchronous search against the Exa search API. While it does include web search-related options and result content fields (text/highlights/summary snippets in search results), it does not implement standalone content extraction, code-specific search behavior, or any async research orchestration/outputSchema handling. Because the declared purpose materially overstates the capabilities visible in the supplied code chunk, this is a mismatch.

Ae1

High
Category
analysis-evasion
Content
bash scripts/research_poll.sh "researchId" | jq
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Credential Access

High
Category
Privilege Escalation
Content
#!/bin/bash
# Load EXA_API_KEY from .env if not already set
if [ -z "${EXA_API_KEY:-}" ]; then
  _env_file="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/../.env"
  if [ -f "$_env_file" ]; then
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
#!/bin/bash
# Load EXA_API_KEY from .env if not already set
if [ -z "${EXA_API_KEY:-}" ]; then
  _env_file="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/../.env"
  if [ -f "$_env_file" ]; then
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
#!/bin/bash
# Load EXA_API_KEY from .env if not already set
if [ -z "${EXA_API_KEY:-}" ]; then
  _env_file="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/../.env"
  if [ -f "$_env_file" ]; then
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
#!/bin/bash
# Load EXA_API_KEY from .env if not already set
if [ -z "${EXA_API_KEY:-}" ]; then
  _env_file="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/../.env"
  if [ -f "$_env_file" ]; then
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
#!/bin/bash
# Load EXA_API_KEY from .env if not already set
if [ -z "${EXA_API_KEY:-}" ]; then
  _env_file="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/../.env"
  if [ -f "$_env_file" ]; then
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
#!/bin/bash
# Load EXA_API_KEY from .env if not already set
if [ -z "${EXA_API_KEY:-}" ]; then
  _env_file="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/../.env"
  if [ -f "$_env_file" ]; then
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
#!/bin/bash
# Load EXA_API_KEY from .env if not already set
if [ -z "${EXA_API_KEY:-}" ]; then
  _env_file="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/../.env"
  if [ -f "$_env_file" ]; then
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
#!/bin/bash
# Load EXA_API_KEY from .env if not already set
if [ -z "${EXA_API_KEY:-}" ]; then
  _env_file="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/../.env"
  if [ -f "$_env_file" ]; then
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
#!/bin/bash
# Load EXA_API_KEY from .env if not already set
if [ -z "${EXA_API_KEY:-}" ]; then
  _env_file="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/../.env"
  if [ -f "$_env_file" ]; then
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
#!/bin/bash
# Load EXA_API_KEY from .env if not already set
if [ -z "${EXA_API_KEY:-}" ]; then
  _env_file="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/../.env"
  if [ -f "$_env_file" ]; then
    # Safe parse: only extract EXA_API_KEY lines, never execute .env content
    _val="$(grep -E '^(export[[:space:]]+)?EXA_API_KEY=' "$_env_file" | tail -n1 | sed 's/^export[[:space:]]*//' | cut -d'=' -f2-)"
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

External Transmission

Medium
Category
Data Exfiltration
Content
## Safety and Data Handling

- `SCHEMA_FILE` content is sent to `https://api.exa.ai/research/v1` as `outputSchema`.
- Never use sensitive local files for `SCHEMA_FILE` (for example: `.env`, key/cert files, secrets, internal confidential docs).
- `research_create.sh` blocks obvious sensitive paths/suffixes (for example: `.env`, `.pem`, `.key`, `.p12`, `.pfx`, `id_rsa`).
Confidence
92% confidence
Finding
The skill explicitly sends `SCHEMA_FILE` contents to the external Exa API as `outputSchema`, creating a real data-exfiltration path if a user supplies a sensitive local file. Although the documentation warns against using secrets and mentions blocking obvious sensitive filenames, filename-based checks are incomplete and do not prevent transmission of sensitive data stored under non-obvious names.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
This shell script constructs a JSON payload from the user-supplied query and sends it to https://api.exa.ai/search. Although the API key requirement is disclosed, there is no visible user-facing notice, confirmation, or comment warning that query data will be transmitted to a third-party network service.

External Transmission

Medium
Category
Data Exfiltration
Content
fi

  raw="$(
    LC_ALL=C curl -sS --fail-with-body \
      --retry "$retry_count" \
      --retry-delay "$retry_delay" \
      -X POST "$endpoint" \
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
'{ instructions: $instructions, model: $model }')"
fi

curl -s -X POST 'https://api.exa.ai/research/v1' \
  -H "x-api-key: $EXA_API_KEY" \
  -H 'Content-Type: application/json' \
  -d "$PAYLOAD"
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
exit 1
fi

URL="https://api.exa.ai/research/v1/${RESEARCH_ID}"

if [ "${EVENTS:-false}" = "true" ]; then
  URL="${URL}?events=true"
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
exit 1
fi

URL="https://api.exa.ai/research/v1/${RESEARCH_ID}"

if [ "${EVENTS:-false}" = "true" ]; then
  URL="${URL}?events=true"
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
exit 1
fi

URL="https://api.exa.ai/research/v1/${RESEARCH_ID}"

if [ "${EVENTS:-false}" = "true" ]; then
  URL="${URL}?events=true"
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
exit 1
fi

URL="https://api.exa.ai/research/v1/${RESEARCH_ID}"

if [ "${EVENTS:-false}" = "true" ]; then
  URL="${URL}?events=true"
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
exit 1
fi

URL="https://api.exa.ai/research/v1/${RESEARCH_ID}"

if [ "${EVENTS:-false}" = "true" ]; then
  URL="${URL}?events=true"
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The script sends the user-provided query, location, and other search parameters to https://api.exa.ai/search via `exa_post_json`, but there is no confirmation prompt or user-facing notice immediately before the transmission. For a code file, external network transmission of user data should have some visible disclosure unless clearly warned elsewhere in the skill description, which is not present in this file.

Natural-Language Policy Violations

Low
Confidence
96% confidence
Finding
The script silently assigns a default location of NL and includes it in requests, which may misrepresent the user’s locale and transmit metadata the user did not explicitly choose to share. In a search skill, this is more of a privacy-by-default issue than a direct exploit, but it still creates unnecessary data disclosure and behavior the user may not expect.

Static analysis

No suspicious patterns detected.