Back to skill

Security audit

Job Hunter

Security checks for vulnerabilities and agentic risk

Overview

This job-search skill is coherent overall, but one bundled search script has an unsafe command-execution vulnerability and the skill stores personal job-search profile data without clear privacy controls.

Review before installing. Use this skill only in a workspace where saving job-search profile data is acceptable, avoid putting highly sensitive personal information in profile.json, and do not run artifact/scripts/search_jobs.sh until its Python interpolation issues are fixed. If you use Brave search, assume search terms and location details may be sent to Brave via the configured API key.

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

Error
Location
scripts/search_jobs.sh:31
Finding
Arbitrary Code Execution Through Python Source Injection<![CDATA[ ## Vulnerability Details **File Location**: `scripts/search_jobs.sh`, lines 31, 67, 91-101, and 104-109 **Vulnerability Type**: Python source-code injection caused by unsafe shell interpolation **Risk Level**: High ### Vulnerable Code The user-controlled search query is embedded directly inside Python source: ```bash ENCODED_QUERY=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$QUERY'))") ``` The derived search query is embedded through the same unsafe pattern: ```bash for sq in "${SEARCH_QUERIES[@]}"; do ENCODED_SQ=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$sq'))") ``` JSON assembled from Brave Search API responses is also inserted into executable Python source: ```bash ALL_RESULTS=$(python3 -c " import json, sys a = json.loads('$ALL_RESULTS') b = json.loads(sys.stdin.read()) seen = set(x['url'] for x in a) for item in b: if item['url'] not in seen: a.append(item) seen.add(item['url']) print(json.dumps(a)) " <<< "$PARSED" 2>/dev/null || echo "$ALL_RESULTS") ``` Finally, the user-controlled `--limit` value is inserted as an executable Python expression: ```bash # Trim to limit echo "$ALL_RESULTS" | python3 -c " import json, sys results = json.load(sys.stdin)[:${LIMIT}] print(json.dumps(results, indent=2)) " ``` ### Technical Analysis The script builds Python programs using shell-expanded strings and passes them to `python3 -c`. Values such as `QUERY`, `sq`, `ALL_RESULTS`, and `LIMIT` are treated as Python source code instead of data. Quoting the shell variable does not make interpolation into Python source safe. An attacker can include Python quote delimiters, statement separators, expressions, and comments in an argument. Once interpolated, these characters can terminate the intended string or slice expression and append arbitrary Python statements. The vulnerable data flows are: 1. The first positional command-line argument is assigned to `QUERY`. 2. `QUERY` is placed inside a s ...[truncated 4084 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Never construct executable Python source by interpolating shell or remote values. Pass all values as command-line arguments, environment variables, or standard input. ### 1. Pass search strings through `sys.argv` Replace line 31 with: ```bash ENCODED_QUERY=$( python3 -c \ 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1]))' \ "$QUERY" ) ``` Replace line 67 with: ```bash ENCODED_SQ=$( python3 -c \ 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1]))' \ "$sq" ) ``` In both cases, the Python program remains constant and the untrusted value is handled only as data. ### 2. Validate numeric arguments strictly Validate `DAYS` and `LIMIT` after argument parsing: ```bash if [[ ! "$DAYS" =~ ^[0-9]+$ ]]; then printf 'Error: --days must be a non-negative integer\n' >&2 exit 2 fi if [[ ! "$LIMIT" =~ ^[0-9]+$ ]]; then printf 'Error: --limit must be a non-negative integer\n' >&2 exit 2 fi ``` Pass the validated limit as an argument rather than interpolating it: ```bash echo "$ALL_RESULTS" | python3 -c ' import json import sys limit = int(sys.argv[1]) results = json.load(sys.stdin)[:limit] print(json.dumps(results, indent=2)) ' "$LIMIT" ``` A reasonable upper bound should also be enforced to prevent excessive resource consumption: ```bash if (( LIMIT > 100 )); then printf 'Error: --limit must not exceed 100\n' >&2 exit 2 fi ``` ### 3. Merge JSON without embedding it into source Provide both JSON documents through files, separate file descriptors, or arguments. A robust option is to use temporary files created securely: ```bash tmp_all=$(mktemp) tmp_parsed=$(mktemp) trap 'rm -f "$tmp_all" "$tmp_parsed"' EXIT printf '%s' "$ALL_RESULTS" > "$tmp_all" printf '%s' "$PARSED" > "$tmp_parsed" ALL_RESULTS=$( python3 - "$tmp_all" "$tmp_parsed" <<'PY' import json import sys with open(sys.argv[1], encoding="utf-8") as handle: existing = js ...[truncated 1134 chars]
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (6)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill invokes network access (`web_search`) and shell commands, but it does not declare any explicit tool scope or permissions. That creates an overbroad execution surface where an agent may use capabilities beyond what a user or platform reviewer would reasonably expect, reducing containment and auditability.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The activation description is very broad and overlaps with many ordinary career-help requests, which can cause the skill to trigger in situations where the user did not intend to invoke file-writing, web searching, or shell-assisted workflows. In context, this increases the chance that higher-risk capabilities are brought into routine conversations without clear user awareness.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The skill directs the agent to collect and save a detailed candidate profile to `profile.json` without warning that it may contain sensitive personal and employment data such as location, salary expectations, exclusions, and career history. Persisting that information by default increases privacy risk, accidental retention, and possible downstream exposure to other tools or processes in the workspace.

External Transmission

Medium
Category
Data Exfiltration
Content
for sq in "${SEARCH_QUERIES[@]}"; do
        ENCODED_SQ=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$sq'))")
        RESPONSE=$(curl -s "https://api.search.brave.com/res/v1/web/search?q=${ENCODED_SQ}&count=10&freshness=${FRESHNESS}" \
            -H "Accept: application/json" \
            -H "X-Subscription-Token: ${BRAVE_API_KEY}" 2>/dev/null || echo '{}')
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Vague Triggers

Low
Confidence
78% confidence
Finding
This JSON manifest/template contains many generic profile fields but does not specify when or how it is intended to be used, nor any constraints or negative examples limiting activation or applicability. For manifest-scope review under vague triggers, the absence of any explicit trigger scope can lead to overly broad interpretation of when this profile template should apply.

Missing User Warnings

Low
Confidence
88% confidence
Finding
The script sends the user-provided job search query and optional location to an external web API via curl when BRAVE_API_KEY is set. While the header comments mention Brave API availability, there is no runtime confirmation, warning, or user-facing notice that query data will be transmitted to a third-party service.

Static analysis

No suspicious patterns detected.