T09 · Insecure Skill Coding Practices
- Location
scripts/fal_client.py:136- Finding
Plaintext fal.ai API Credential Storage in Workspace Documentation
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill mostly does what it claims, but users should review it because it stores API keys and full request media in local workspace files while sending prompts and media to fal.ai.
Install only if you are comfortable sending prompts, images, and videos to fal.ai. Prefer FAL_KEY in a protected environment variable instead of TOOLS.md, avoid submitting confidential media, and regularly inspect or delete ~/.openclaw/workspace/fal-pending.json because it may retain full request contents.
scripts/fal_client.py:136Plaintext fal.ai API Credential Storage in Workspace Documentation
scripts/fal_client.py:204Persistent Storage of Complete Prompts and Base64-Encoded Private Media
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
}
def get_api_key():
"""Get API key from env, openclaw.json, or TOOLS.md"""
# 1. Environment variable (highest priority)
key = os.environ.get("FAL_KEY")
if key:
The skill exposes capabilities involving environment access, file read/write, shell, and network use, but does not declare any explicit tool scope or permission boundaries. This increases the chance of over-privileged execution and makes it harder for a calling agent or reviewer to understand what the skill is allowed to access, especially since it handles API keys, local files, and outbound requests.
The description uses broad invocation language such as 'use when asked to generate images' and 'anything involving fal.ai,' which can cause the skill to trigger in situations that only loosely match the user's request. Over-broad routing increases the risk of unnecessary external API use, unintended data transmission, or execution of file/network operations when a simpler or local response would have been safer.
The documentation instructs users to submit images and videos to fal.ai, including local files converted to data URIs, but does not clearly warn that this sends potentially sensitive media to a third-party service. In context, this skill is specifically built to process user-provided media, so omission of an external-sharing warning materially increases privacy and confidentiality risk.
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
## Adding New Models
1. Find the model on fal.ai and check its `/api` page
2. Add entry to `references/models.json` with input/output schema
3. Test with a simple request
**Note:** Queue URLs use base model path (e.g., `fal-ai/flux` not `fal-ai/flux/dev/image-to-image`). The script handles this automatically.
The output path for pending request state is derived from the FAL_PENDING_FILE environment variable and then written without validation. If an attacker can influence the environment in which this skill runs, they can redirect writes to arbitrary filesystem locations writable by the process, causing file clobbering or persistence in unexpected paths.
def save_pending(data):
"""Save pending requests to file"""
PENDING_FILE.parent.mkdir(parents=True, exist_ok=True)
PENDING_FILE.write_text(json.dumps(data, indent=2))
def submit(model_id: str, input_data: dict, skip_validation: bool = False) -> dict:
"""Submit a request to the queue with validation"""
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.
PENDING_FILE.parent.mkdir(parents=True, exist_ok=True)
PENDING_FILE.write_text(json.dumps(data, indent=2))
def submit(model_id: str, input_data: dict, skip_validation: bool = False) -> dict:
"""Submit a request to the queue with validation"""
# Validate input
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.
PENDING_FILE.parent.mkdir(parents=True, exist_ok=True)
PENDING_FILE.write_text(json.dumps(data, indent=2))
def submit(model_id: str, input_data: dict, skip_validation: bool = False) -> dict:
"""Submit a request to the queue with validation"""
# Validate input
This client can transform arbitrary local files into data URIs and submit them to the fal.ai service, enabling exfiltration of local file contents if higher-level tooling passes attacker-influenced paths. In a skill context, outbound transmission of local content to a third-party API is especially sensitive because users may not realize local files are being embedded and sent off-host.
The manifest describes a client for generating images and media through the fal.ai API with queue polling. Spawning a local binary via subprocess.run adds host-execution capability beyond straightforward API interaction, and that capability is not mentioned in the skill description.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
# Try to get video info with ffprobe
try:
result = subprocess.run(
['ffprobe', '-v', 'quiet', '-print_format', 'json', '-show_streams', file_path],
capture_output=True, text=True
)
The entry keyed as "fal-ai/nano-banana-pro" is labeled in natural language as "Gemini 3 Pro Image," and the edit variant repeats that naming mismatch. While not a security flaw by itself, this is a user-facing natural-language inconsistency that could mislead users about which model/provider is being used.
The client reads API credentials from unrelated local files, including TOOLS.md, instead of limiting secret intake to a dedicated secret source such as environment variables or a secure config store. This broadens the skill’s access to local sensitive data and can unintentionally harvest or misuse credentials present elsewhere in the workspace.
Scanning TOOLS.md for an API key accesses local file content that may contain credentials or unrelated secrets, without a clear user-facing warning. While the immediate use is to find the fal.ai key, this pattern normalizes secret scraping from free-form documents and increases the chance of unintended credential exposure.
No suspicious patterns detected.