T09 · Insecure Skill Coding Practices
- Location
scripts/recipe-to-list.sh:10- Finding
Executable dotenv loading permits arbitrary shell execution and overexposes secrets
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill’s recipe-to-shopping-list purpose is coherent, but it uses broad local credential loading and live Todoist/workspace changes that need review before installation.
Review and harden this skill before installing. Use --dry-run and --no-save first, avoid the shell wrapper or replace its dotenv sourcing with an allowlisted parser for only GEMINI_API_KEY/GOOGLE_API_KEY and TODOIST_API_TOKEN, and require a confirmation step before Todoist updates. Install only if you are comfortable sending recipe photos or extracted text to Gemini and writing recipe files into the workspace.
scripts/recipe-to-list.sh:10Executable dotenv loading permits arbitrary shell execution and overexposes secrets
scripts/recipe_to_list.py:798Untrusted recipe and model content is persisted into an agent-facing knowledge base
scripts/recipe_to_list.py:940Undefined section-handling symbols cause crashes after successful Todoist mutations
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
raw = resp.read().decode("utf-8")
break
except urllib.error.HTTPError as e:
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
raw = resp.read().decode("utf-8")
break
except urllib.error.HTTPError as e:
The documented behavior does not accurately match the implemented or required capabilities: it claims recipe web search/fetch flow and benign recipe processing, but the skill also depends on undeclared external network calls, Todoist CLI execution, and workspace writes. Description-behavior mismatch is dangerous because users and security controls may approve the skill under incomplete assumptions, leading to unanticipated data exfiltration to Gemini or unintended modification of Todoist and local files.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# Load keys/tokens
set -a
[[ -f ~/.clawdbot/.env ]] && source ~/.clawdbot/.env
set +a
python3 "$(dirname "$0")/recipe_to_list.py" --image "$IMG" --project "Shopping" --source "photo:$IMG"
The skill advertises operational behavior that requires sensitive capabilities (environment access, file write, network access, shell/CLI invocation) but does not declare any tool scope or permissions. This weakens reviewability and consent, making it easier for a user or orchestrator to invoke a skill that can modify Todoist data, write workspace files, and send content to external services without explicit capability boundaries.
The skill sends recipe photos or fetched page text to Gemini for ingredient extraction, but the description does not clearly disclose this external transmission. This is a privacy and compliance risk because images or recipe pages may contain personal notes, metadata, subscription content, or other sensitive information that users may not expect to leave their environment.
The skill text describes updating the Shopping list but does not prominently warn that it will create or modify Todoist tasks, which is a state-changing action in a third-party account. In agent settings, insufficient disclosure around write operations can cause accidental data modification, duplicate tasks, or unwanted automation if a user expects analysis only.
The wrapper unconditionally sources ~/.clawdbot/.env and exports every variable into the child process environment. Even if intended to provide API keys, this grants the skill access to all secrets stored in that file, which exceeds the minimal privileges implied by a recipe-to-shopping-list tool and increases the blast radius if the downstream Python script, its dependencies, or any network call mishandles environment data.
The script silently loads local secrets from ~/.clawdbot/.env without informing the user at runtime. In this skill's context, which processes user-supplied recipe images and may call external services, undisclosed secret loading makes the data flow less transparent and can expose unrelated credentials to components the user would not expect.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
def _get_existing_project_tasks(project: str) -> list[dict]:
cp = subprocess.run(
["todoist", "tasks", "--all", "-p", project, "--json"],
capture_output=True,
text=True,
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
# If existing has no qty but new does, set existing to new qty.
if (ex_qty is None or not ex_unit) and (new_qty is not None and new_unit):
new_content = _rewrite_with_total(ex_content, new_qty, new_unit)
cp = subprocess.run(
["todoist", "update", str(ex_id), "--content", new_content, "--json"],
capture_output=True,
text=True,
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
# If existing has no qty but new does, set existing to new qty.
if (ex_qty is None or not ex_unit) and (new_qty is not None and new_unit):
new_content = _rewrite_with_total(ex_content, new_qty, new_unit)
cp = subprocess.run(
["todoist", "update", str(ex_id), "--content", new_content, "--json"],
capture_output=True,
text=True,
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
# If existing has no qty but new does, set existing to new qty.
if (ex_qty is None or not ex_unit) and (new_qty is not None and new_unit):
new_content = _rewrite_with_total(ex_content, new_qty, new_unit)
cp = subprocess.run(
["todoist", "update", str(ex_id), "--content", new_content, "--json"],
capture_output=True,
text=True,
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
total = None
if 'total' in locals() and total is not None:
cp = subprocess.run(
["todoist", "update", str(ex_id), "--content", new_content, "--json"],
capture_output=True,
text=True,
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
if existing_task and skip_overlap:
continue
cp = subprocess.run(
["todoist", "add", title, "--project", project, "--json"],
capture_output=True,
text=True,
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
if existing_task and skip_overlap:
continue
cp = subprocess.run(
["todoist", "add", title, "--project", project, "--json"],
capture_output=True,
text=True,
The manifest says the skill can extract ingredients from either recipe photos or recipe web pages via search and fetch. In this file, the CLI requires an --image argument, verifies that image path exists, and then calls only the image-based Gemini extraction flow; there is no webpage search, fetch, or URL parsing behavior implemented here.
The script automatically uploads the provided recipe photo to Gemini without an explicit runtime warning or confirmation. Because photos may contain personal notes, kitchen surroundings, or other incidental sensitive information, this creates a privacy risk through third-party data disclosure in a consumer productivity skill.
The code comments say the list stays flat, but the function contains live references to target_section and _move_task even though neither is defined in this file. In non-dry-run operation this can raise a NameError after creating or updating tasks, causing partial side effects and unreliable behavior that can corrupt workflow integrity.
The skill mutates the user's Todoist shopping list and can update existing tasks based on model-derived parsing without an explicit confirmation step. In this context, integrity matters: a mistaken extraction or hallucinated quantity can silently alter shopping data and create unintended purchases or loss of existing list accuracy.
No suspicious patterns detected.