Back to skill

Security audit

tenk-connect

Security checks for vulnerabilities and agentic risk

Overview

The TenK skill matches its stated account-tracking purpose, but its logging script has confirmed input-handling flaws that could let crafted values run local commands.

Review before installing. The skill's account access and TenK API calls are expected, but avoid using it until the logging script is fixed to pass all user and API values as data, validate minutes strictly, and confirm state-changing log actions.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/tenk.sh:136
Finding
Python Code Injection Through the Skill Query<![CDATA[ ## Vulnerability Details **File Location**: `scripts/tenk.sh`, lines 136–155 **Vulnerability Type**: Python source-code injection **Risk Level**: High ### Vulnerable Code ```bash skill_id=$(echo "$skills_resp" | python3 -c " import json, sys q = '$skill_query'.lower() skills = json.load(sys.stdin).get('data', []) match = next((s for s in skills if q in s['name'].lower()), None) if match: print(match['id']) " 2>/dev/null) skill_name=$(echo "$skills_resp" | python3 -c " import json, sys q = '$skill_query'.lower() skills = json.load(sys.stdin).get('data', []) match = next((s for s in skills if q in s['name'].lower()), None) if match: print(match['name']) " 2>/dev/null) ``` ### Technical Analysis The caller-controlled `skill_query` value is interpolated directly into two programs passed to `python3 -c`. Shell quoting protects how the shell passes the overall program argument, but it does not make the interpolated content safe Python syntax. A skill query containing a single quote and additional Python statements can terminate the intended string literal and alter the program executed by Python. Because Python exposes operating-system functionality through modules such as `os` and `subprocess`, successful injection can become arbitrary local command execution. The same unsafe construction appears twice: once while resolving the skill identifier and again while resolving the skill name. ### Attack Path 1. An attacker influences the skill name supplied to the `log` command, potentially through a chat request processed by the Agent. 2. The Agent invokes `tenk.sh log` with the crafted value as `skill_query`. 3. Bash substitutes that value into the source string passed to `python3 -c`. 4. The crafted value closes the intended Python string and introduces attacker-selected Python statements. 5. Python executes those statements with the privileges and environment of the user running the Agent. ### Impact Assessment Successful exploitation provide ...[truncated 457 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Never insert command-line input into dynamically generated Python source. Keep the Python program constant and pass the query as a positional argument: ```bash skill_id=$(printf '%s' "$skills_resp" | python3 -c ' import json import sys query = sys.argv[1].lower() skills = json.load(sys.stdin).get("data", []) match = next( (skill for skill in skills if query in skill.get("name", "").lower()), None, ) if match: print(match["id"]) ' "$skill_query") ``` Resolve both the identifier and name in one fixed Python invocation where practical. In addition: - Treat command-line arguments and chat-derived values as untrusted. - Use `sys.argv` or environment variables only as data channels. - Do not use `eval`, generated source, or source-string interpolation. - Add regression tests using values containing quotes, semicolons, newlines, backslashes, and Python syntax. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/tenk.sh:164
Finding
Python Code Injection Through API-Derived Skill Data<![CDATA[ ## Vulnerability Details **File Location**: `scripts/tenk.sh`, lines 164–186 **Vulnerability Type**: Injection of remote data into executable Python source **Risk Level**: High ### Vulnerable Code ```bash local body body=$(python3 -c " import json, sys print(json.dumps({'skillId': '$skill_id', 'duration': $duration_sec, 'notes': sys.argv[1]})) " "$note") || die "Error al construir payload" local resp; resp=$(curl -sf -X POST "$API/sessions" \ -H "Authorization: Bearer $(token_get)" \ -H "Content-Type: application/json" \ -d "$body") || die "Error al registrar sesión" echo "$resp" | python3 -c " import json, sys d = json.load(sys.stdin) if d.get('success'): print(f\"✅ Registrado: $skill_name — ${minutes} min\") else: print(f\"❌ Error: {d}\") " ``` ### Technical Analysis The script obtains `skill_id` and `skill_name` from the TenK API and subsequently interpolates them into source code passed to `python3 -c`. If either field contains Python string delimiters and crafted source text, it can alter the Python program. JSON encoding the final request does not prevent this vulnerability because the unsafe value is interpreted as Python source before `json.dumps` runs. The `skill_name` value is likewise inserted into an f-string in the response-rendering program. Escaping requirements differ across Bash, Python source, Python strings, and JSON; manual interpolation across these contexts is unsafe. Exploitation requires control over relevant API-returned skill data or compromise of the trusted API response path. A user-created or otherwise attacker-influenced skill name may provide such control, depending on server-side validation. ### Attack Path 1. A crafted skill record is stored in the TenK account or returned by a compromised API. 2. The user or Agent invokes `tenk.sh log` and selects the crafted skill. 3. The script extracts the remote `id` and `name` fields. 4. It inserts these values into programs passed to `python3 -c`. 5. Craf ...[truncated 640 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Pass all API-derived values as data rather than embedding them into Python source. For payload construction, use positional arguments: ```bash body=$(python3 -c ' import json import sys print(json.dumps({ "skillId": sys.argv[1], "duration": int(sys.argv[2]), "notes": sys.argv[3], })) ' "$skill_id" "$duration_sec" "$note") || die "Error building payload" ``` Render the confirmation using arguments or process the entire response in fixed Python code: ```bash printf '%s' "$resp" | python3 -c ' import json import sys data = json.load(sys.stdin) skill_name = sys.argv[1] minutes = sys.argv[2] if data.get("success"): print(f"Registered: {skill_name} — {minutes} min") else: print(f"Error: {data}") ' "$skill_name" "$minutes" ``` Additional hardening should include: - Validate the schema and type of every API field before use. - Reject identifiers that do not conform to the server's documented identifier format. - Avoid generating executable source from local or remote data. - Add tests containing quotes, braces, newlines, backslashes, and Python expressions in skill names. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/tenk.sh:126
Finding
Bash Arithmetic Injection Through Unvalidated Minutes<![CDATA[ ## Vulnerability Details **File Location**: `scripts/tenk.sh`, lines 126–127 and 161 **Vulnerability Type**: Unsafe evaluation of untrusted input as a Bash arithmetic expression **Risk Level**: High ### Vulnerable Code ```bash local minutes="${2:-}" local note="${3:-}" [[ -z "$skill_query" ]] && die "Uso: tenk.sh log <habilidad> <minutos> [nota]" [[ -z "$minutes" ]] && die "Uso: tenk.sh log <habilidad> <minutos> [nota]" ``` ```bash local duration_sec=$(( minutes * 60 )) ``` ### Technical Analysis The `minutes` argument is checked only for emptiness. It is then referenced by name inside Bash arithmetic expansion. Bash arithmetic contexts evaluate arithmetic expressions rather than treating their contents strictly as decimal integer data. Crafted values can introduce variable references, array subscripts, substitutions, or other arithmetic syntax with side effects. Consequently, an argument intended to represent a duration can be interpreted as executable shell-related expression content. Even when command execution is not achieved, the absence of numeric and range validation allows negative, zero, excessively large, malformed, or overflow-prone durations to reach the API. ### Attack Path 1. An attacker influences the minutes argument supplied to `tenk.sh log`. 2. The script verifies only that the argument is nonempty. 3. The value is evaluated through Bash arithmetic expansion in `minutes * 60`. 4. Crafted arithmetic syntax causes unintended expansion or side effects in the shell. 5. Any resulting commands execute as the Agent user; alternatively, an invalid duration is submitted to the service. ### Impact Assessment Successful command-oriented exploitation can execute local commands with the privileges of the invoking Agent user. The attacker could access user-readable data, modify files, or retrieve the locally stored bearer token. Independently of command execution, malformed and out-of-range values can corrupt practice records or cause ...[truncated 56 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Validate the argument as a canonical positive decimal integer before using any arithmetic context, then enforce a reasonable upper bound: ```bash [[ "$minutes" =~ ^[1-9][0-9]*$ ]] || die "Minutes must be a positive integer" (( 10#$minutes <= 1440 )) || die "Minutes exceed the allowed limit" local duration_sec=$((10#$minutes * 60)) ``` The maximum should match the TenK API's documented business rules. Also: - Reject zero, negative values, signs, whitespace, exponents, and expression syntax. - Use `10#` after validation to force decimal interpretation. - Check server-side validation rather than relying solely on the client. - Test inputs such as negative numbers, very large integers, leading zeros, array syntax, substitutions, and ordinary nonnumeric text. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/tenk.sh:8
Finding
Bearer Token File Is Secured Only After Creation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/tenk.sh`, lines 8–14 **Vulnerability Type**: Insecure sensitive-file creation and local race window **Risk Level**: Low ### Vulnerable Code ```bash CONFIG_DIR="${HOME}/.config/tenk-connect" TOKEN_FILE="${CONFIG_DIR}/token" mkdir -p "$CONFIG_DIR" # ── helpers ──────────────────────────────────────────────────────────────── token_get() { [[ -f "$TOKEN_FILE" ]] && cat "$TOKEN_FILE" || echo ""; } token_save() { echo -n "$1" > "$TOKEN_FILE"; chmod 600 "$TOKEN_FILE"; } ``` ### Technical Analysis The token is written to the destination file before `chmod 600` is applied. The initial mode is therefore determined by the process umask. Under a permissive umask, the file may briefly be readable by other local users. The configuration directory is also created without explicitly enforcing mode `700`. Existing directory permissions are not corrected, and the direct redirection is not an atomic secret-file installation procedure. Because the token is a bearer credential, possession may be sufficient to access the user's TenK account until the token expires or is revoked. ### Attack Path 1. The CLI successfully receives a bearer token during device authentication. 2. `echo` creates or truncates the token file using permissions derived from the current umask. 3. Before the subsequent `chmod 600` completes, another local process monitoring the path reads the file. 4. The local attacker reuses the copied token against the TenK API. This path requires local access to the same system and sufficiently permissive initial file or directory permissions. ### Impact Assessment A successful attacker may impersonate the affected TenK user for the lifetime of the token and perform operations allowed by that token, including reading account information and potentially creating practice sessions. The issue does not expose system administrator privileges. Its scope is the TenK account and any other access granted to ...[truncated 28 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Apply restrictive permissions before any secret is created and use an atomic replacement operation: ```bash umask 077 mkdir -p "$CONFIG_DIR" chmod 700 "$CONFIG_DIR" token_save() { local tmp tmp=$(mktemp "$CONFIG_DIR/token.XXXXXX") || die "Cannot create token file" printf '%s' "$1" > "$tmp" chmod 600 "$tmp" mv -f "$tmp" "$TOKEN_FILE" } ``` Further hardening should include: - Verify that the configuration directory is owned by the current user. - Reject symbolic links and unexpected non-regular files at the token path. - Use `printf` instead of `echo` for credential data. - Consider an operating-system credential store when available. - Ensure logout and authentication failures remove temporary token files. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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
Findings (6)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill clearly instructs the agent to execute shell commands, but it does not declare any explicit tool scope or allowed-tools/permissions boundary. That creates an overbroad execution surface where an agent or runtime may permit shell access without a documented least-privilege contract, increasing the chance of unintended command execution or unsafe chaining with user prompts.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The phrase 'when the user says something like "log 45 minutes of guitar"' is a broad natural-language trigger that can cause the skill to activate from loosely related conversation without clear confirmation, boundaries, or exclusions. In a skill that performs account actions and writes activity records, this can lead to unintended state-changing operations based on ambiguous or injected text.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
# ── helpers ────────────────────────────────────────────────────────────────
token_get()  { [[ -f "$TOKEN_FILE" ]] && cat "$TOKEN_FILE" || echo ""; }
token_save() { echo -n "$1" > "$TOKEN_FILE"; chmod 600 "$TOKEN_FILE"; }

api_get() {
  local path="$1"
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
# ── helpers ────────────────────────────────────────────────────────────────
token_get()  { [[ -f "$TOKEN_FILE" ]] && cat "$TOKEN_FILE" || echo ""; }
token_save() { echo -n "$1" > "$TOKEN_FILE"; chmod 600 "$TOKEN_FILE"; }

api_get() {
  local path="$1"
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

External Transmission

Medium
Category
Data Exfiltration
Content
print(json.dumps({'skillId': '$skill_id', 'duration': $duration_sec, 'notes': sys.argv[1]}))
" "$note") || die "Error al construir payload"

  local resp; resp=$(curl -sf -X POST "$API/sessions" \
    -H "Authorization: Bearer $(token_get)" \
    -H "Content-Type: application/json" \
    -d "$body") || die "Error al registrar sesión"
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Natural-Language Policy Violations

Low
Confidence
97% confidence
Finding
This shell script includes natural-language output such as "Iniciando autenticación con TenK..." and continues in Spanish throughout the CLI flow. Because the file does not offer language selection or explain that the tool is intended only for Spanish-speaking users, it creates a language/locale policy issue under the natural-language policy category.

Static analysis

No suspicious patterns detected.