T09 · Insecure Skill Coding Practices
- Location
SKILL.md:79- Finding
API Keys Requested Through Logged Agent Conversations
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill mostly matches its stated video-clipping purpose, but it asks for powerful publishing credentials through chat and stores them in a plaintext project file, so it should be reviewed before installation.
Install only if you are comfortable giving the skill Gemini and Upload-Post credentials, uploading full videos and transcripts to Gemini, and allowing Upload-Post to schedule social posts after approval. Prefer configuring secrets through a local secret manager or protected terminal instead of pasting keys into chat, lock down any .env file permissions, review connected Upload-Post profiles, keep TikTok direct posting disabled unless explicitly needed, and treat the learning files as persistent local history of your clips and analytics.
SKILL.md:79API Keys Requested Through Logged Agent Conversations
SKILL.md:39API Credentials Stored in a Plaintext Environment File Without Enforced Access Controls
autoshorts.py:834Source Video Filenames Unnecessarily Disclosed to Gemini During Analytics Learning
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
with video.open("rb") as fh:
files = {"video": (video.name, fh, "video/mp4")}
res = requests.post(
f"{UPLOAD_POST_BASE}/upload",
headers={"Authorization": f"Apikey {api_key}"},
data=data,
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
continue
url = f"{UPLOAD_POST_BASE}/uploadposts/post-analytics/{rid}"
try:
r = requests.get(url, headers={"Authorization": f"Apikey {api_key_up}"}, timeout=30)
except requests.RequestException as e:
print(f"[learn] {rid}: HTTP error {e}", file=sys.stderr)
continue
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
├── README.md ← you are here
├── autoshorts.py ← CLI: pick / transcribe / analyze / extract / hook / publish / mark-processed
├── requirements.txt
├── .env ← secrets (gitignored)
├── .env.example
├── input/ ← drop long videos here
├── output/
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
from dotenv import load_dotenv
ROOT = Path(__file__).resolve().parent
load_dotenv(ROOT / ".env")
INPUT_FOLDER = Path(os.getenv("INPUT_FOLDER", ROOT / "input")).expanduser()
OUTPUT_FOLDER = Path(os.getenv("OUTPUT_FOLDER", ROOT / "output")).expanduser()
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
from dotenv import load_dotenv
ROOT = Path(__file__).resolve().parent
load_dotenv(ROOT / ".env")
INPUT_FOLDER = Path(os.getenv("INPUT_FOLDER", ROOT / "input")).expanduser()
OUTPUT_FOLDER = Path(os.getenv("OUTPUT_FOLDER", ROOT / "output")).expanduser()
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
from dotenv import load_dotenv
ROOT = Path(__file__).resolve().parent
load_dotenv(ROOT / ".env")
INPUT_FOLDER = Path(os.getenv("INPUT_FOLDER", ROOT / "input")).expanduser()
OUTPUT_FOLDER = Path(os.getenv("OUTPUT_FOLDER", ROOT / "output")).expanduser()
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
from dotenv import load_dotenv
ROOT = Path(__file__).resolve().parent
load_dotenv(ROOT / ".env")
INPUT_FOLDER = Path(os.getenv("INPUT_FOLDER", ROOT / "input")).expanduser()
OUTPUT_FOLDER = Path(os.getenv("OUTPUT_FOLDER", ROOT / "output")).expanduser()
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
from dotenv import load_dotenv
ROOT = Path(__file__).resolve().parent
load_dotenv(ROOT / ".env")
INPUT_FOLDER = Path(os.getenv("INPUT_FOLDER", ROOT / "input")).expanduser()
OUTPUT_FOLDER = Path(os.getenv("OUTPUT_FOLDER", ROOT / "output")).expanduser()
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
from dotenv import load_dotenv
ROOT = Path(__file__).resolve().parent
load_dotenv(ROOT / ".env")
INPUT_FOLDER = Path(os.getenv("INPUT_FOLDER", ROOT / "input")).expanduser()
OUTPUT_FOLDER = Path(os.getenv("OUTPUT_FOLDER", ROOT / "output")).expanduser()
The dependency is pinned to a Pillow version flagged with multiple known security advisories, so this is a real supply-chain vulnerability rather than a false positive. In this skill, Pillow is likely used to process images/text overlays for short-form video generation, which can involve untrusted media inputs; vulnerable image/font handling can enable denial of service, memory corruption, or command injection depending on the affected code path and platform.
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.
1. Clone or `git pull` this repo into `~/Documents/skill-autoshorts/` (or whatever path the user prefers).
2. Make sure `ffmpeg` is installed (`brew install ffmpeg` on macOS, `apt install ffmpeg` on Linux). Verify with `ffmpeg -version`.
3. Create the venv and install Python deps: `python3 -m venv venv && ./venv/bin/pip install -r requirements.txt`.
4. Register `SKILL.md` with whichever agent harness is running. For Claude Code, copy or symlink it into `~/.claude/skills/autoshorts/SKILL.md`. For Hermes / Openclaw follow their skill registration docs.
5. Create `.env` from `.env.example` and ask the user to paste the values you need:
- `GEMINI_API_KEY` — https://aistudio.google.com/apikey (free tier is enough).
Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.
1. Clone or `git pull` this repo into `~/Documents/skill-autoshorts/` (or whatever path the user prefers).
2. Make sure `ffmpeg` is installed (`brew install ffmpeg` on macOS, `apt install ffmpeg` on Linux). Verify with `ffmpeg -version`.
3. Create the venv and install Python deps: `python3 -m venv venv && ./venv/bin/pip install -r requirements.txt`.
4. Register `SKILL.md` with whichever agent harness is running. For Claude Code, copy or symlink it into `~/.claude/skills/autoshorts/SKILL.md`. For Hermes / Openclaw follow their skill registration docs.
5. Create `.env` from `.env.example` and ask the user to paste the values you need:
- `GEMINI_API_KEY` — https://aistudio.google.com/apikey (free tier is enough).
- `UPLOAD_POST_API_KEY` and `UPLOAD_POST_PROFILE` — https://app.upload-post.com → Settings → API Keys + Manage Users (free tier available, no credit card required).
The skill integrates with an external service (Upload-Post) and the README instructs the agent to verify the API key by contacting the remote API and summarizing connected platforms. This is expected functionality, but it still represents real external data transmission and account metadata exposure; in an agent setting, users may not realize their connected social-account details are being fetched and surfaced.
- `GEMINI_API_KEY` — https://aistudio.google.com/apikey (free tier is enough).
- `UPLOAD_POST_API_KEY` and `UPLOAD_POST_PROFILE` — https://app.upload-post.com → Settings → API Keys + Manage Users (free tier available, no credit card required).
- Default `INPUT_FOLDER` and `OUTPUT_FOLDER` to `~/Documents/skill-autoshorts/input` and `.../output` unless the user says otherwise. Default `WHISPER_MODEL=medium` and `TIMEZONE=Europe/Madrid` (override if the user is in another timezone).
6. Verify the Upload-Post key works: `curl -s -H "Authorization: Apikey $UPLOAD_POST_API_KEY" https://api.upload-post.com/api/uploadposts/users` should return the user's profile and connected platforms.
7. Read `SKILL.md` end-to-end. That's the canonical daily workflow — visual QA, weekly `learn`, all the operational rules.
**After install, do nothing else.** Don't transcribe, don't call Gemini, don't publish. Tell the user everything is ready, summarize what's connected on Upload-Post, and wait. The user will forward videos to you in chat going forward — your job is to save each one into `INPUT_FOLDER` and invoke the skill (`/autoshorts` or equivalent) when they ask.
The README explicitly states that the full video and transcript are sent to Gemini in the cloud, but it does not clearly warn users about privacy implications, data handling, or the risk of uploading sensitive footage or speech content to a third party. In this skill's context, videos may contain personal, confidential, or client material, so omission of an explicit consent/privacy notice creates a real data exposure risk.
The skill requires shell, filesystem, environment-variable, and network access but does not declare any explicit tool/permission scope. In an agent harness, that mismatch can cause the skill to be invoked with broader-than-expected capabilities, increasing the blast radius for credential handling, file operations, and external publishing actions.
The activation phrases are broad enough to match common user requests about clips, reels, or automation, which can cause unintended skill invocation. In this skill, accidental activation is more serious because it can lead to environment checks, credential collection prompts, file copying, network uploads, and publishing workflow execution.
The skill is explicitly designed to transmit data and credentials to third-party services, including Upload-Post and Gemini. External transmission is expected in context, but it remains security-relevant because the workflow handles API keys, uploads user media, and sends derived metadata off-host; compromise or misuse could expose private content or enable unauthorized posting.
- Connect TikTok, Instagram (Business/Creator account linked to a Facebook Page), and YouTube via OAuth in the dashboard.
- In **Manage Users**, create a profile — its name is `UPLOAD_POST_PROFILE` (NOT the social handle).
- Generate an API key in **Settings**.
- Verify: `curl -H "Authorization: Apikey $UPLOAD_POST_API_KEY" https://api.upload-post.com/api/uploadposts/me`.
## Orchestration model
The instruction to perform setup actions 'without asking' authorizes autonomous shell execution and package installation steps before explicit user approval. Even if limited to creating a venv and installing dependencies, autonomous execution against a repository and network package sources increases supply-chain and unintended-change risk.
Before doing any work, check that the environment is ready and ask the user for whatever is missing:
1. **venv** — does `~/Documents/skill-autoshorts/venv/bin/python` exist? If not, run setup step 1 from the Setup section. (You can do this without asking — it's mechanical.)
2. **`ffmpeg`** in `PATH` — if missing, ask the user to `brew install ffmpeg` (do not install yourself; system-wide installs deserve confirmation).
3. **`.env` file** — check that every required key is set and non-empty:
- `GEMINI_API_KEY` → if missing, ask: *"Falta la API key de Gemini. Pégamela (la generas en https://aistudio.google.com/apikey)."*
The file hardcodes Spanish prompt text when asking for missing API credentials, regardless of the user's preferred language or locale. This is a natural-language policy concern because it imposes a specific language without documenting opt-in, choice, or region-specific justification.
L107 instructs the operator to remove an entry from state/processed.json before rerunning pick, while L309 says the file should never be edited programmatically except via mark-processed and that reprocessing should be done by asking the user to remove the entry manually. These instructions contradict each other about the permitted mechanism for altering processing state.
L308 says that if pick reports "all videos already processed," the skill should stop and require a new video. But L070, L103, and L105 explicitly describe a cyclic workflow where, after all videos are processed in a cycle, a new cycle starts automatically and previously processed videos become eligible again. This is an active documentation contradiction about core pipeline behavior.
The skill description frames the tool as a human-gated one-video clip workflow, but the code also performs broader autonomous learning and reflection over historical post analytics. This mismatch can mislead operators about the scope of data processing and automation, causing unanticipated collection, profiling, and third-party transmission of creator performance data.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
VIDEO_EXTS = {".mp4", ".mov", ".mkv", ".m4v", ".webm"}
GEMINI_MODEL = "gemini-3-flash-preview"
UPLOAD_POST_BASE = "https://api.upload-post.com/api"
def append_jsonl(path: Path, record: dict) -> None:
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
def run_ffmpeg(args: list[str]) -> None:
cmd = ["ffmpeg", "-y", "-hide_banner", "-loglevel", "error", *args]
res = subprocess.run(cmd, capture_output=True, text=True)
if res.returncode != 0:
sys.stderr.write(res.stderr)
raise SystemExit(f"ffmpeg failed: {' '.join(cmd)}")
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
def ffprobe_duration(video: Path) -> float:
res = subprocess.run(
[
"ffprobe", "-v", "error",
"-show_entries", "format=duration",
No suspicious patterns detected.