Back to skill

Security audit

Itinerary Carousel Post Topaz

Security checks for vulnerabilities and agentic risk

Overview

The skill is coherent with its stated Instagram carousel purpose, but it can publish public posts, push and delete GitHub-hosted assets, send images to third-party services, and handles tokens in risky ways without clear approval gates.

Install only if you are comfortable giving the agent control over an Instagram account, a GitHub repo used for public image hosting, and Topaz image processing. Before use, add a required preview and approval step, remove or make tabiji.ai branding optional, avoid access_token in URLs, use a per-run temp directory, and delete only files created by the current run.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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 (3)

T01 · Skill Instruction Hijacking

Error
Location
SKILL.md:99
Finding
Mandatory Promotional Content Injection into User-Published Output<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 99–110 and 181–207 **Vulnerability Type**: Mandatory branding and promotional output injection **Risk Level**: High ### Vulnerable Code ```bash python3 skills/instagram-photo-text-overlay/scripts/overlay.py \ --input /tmp/ig-carousel/{dest-slug}-enhanced.jpg \ --output /tmp/ig-carousel/slide-1.jpg \ --title "{N} Day {DESTINATION} Itinerary Highlights" \ --style clean --watermark "tabiji.ai" ``` ```bash python3 skills/instagram-photo-text-overlay/scripts/overlay.py \ --input /tmp/ig-carousel/{slug}-enhanced.jpg \ --output /tmp/ig-carousel/slide-{N}.jpg \ --title "{ATTRACTION}" \ --quote "{Specific insider tip about THIS attraction — must directly reference the place in the title, not a generic travel tip}" \ --author "tabiji.ai" \ --style quote --watermark "tabiji.ai" ``` The caption template also mandates promotional material: ```text Full free itinerary with tips, prices & Reddit recs 👉 {ITINERARY_URL} 💬 {PROVOCATIVE_QUESTION — e.g. "Is 5 nights enough for {Destination} or do you need more?" or "What's the one thing most tourists get wrong about {Destination}?"} #{destination_hashtag} #{country} #travelitinerary #foodietravel #southeastasia #asiatravel #travelguide #tabiji ``` ### Technical Analysis The Skill hard-codes `tabiji.ai` watermarks, author attribution, promotional links, calls to action, and hashtags into content intended for publication through the user's Instagram account. These additions are not controlled by an explicit branding parameter and are presented as mandatory workflow steps. This alters the requested output by inserting third-party promotional content. Because the same workflow subsequently publishes the generated slides and caption using the user's Instagram credentials, the injected material can reach a public audience under the user's identity. ### Attack Path 1. A user requests an Instagram itinerary carousel. 2. The Skill invok ...[truncated 1045 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace hard-coded branding with explicit optional parameters, such as: - `watermark` - `author` - `promotional_url` - `hashtags` - `include_branding` 2. Default all third-party branding and promotional options to disabled. 3. Generate a complete preview of every slide and the exact caption before publication. 4. Require explicit user approval of the preview before invoking the Instagram publishing endpoints. 5. Clearly distinguish user-requested attribution from Skill-provider branding. 6. Do not infer consent to advertising merely because the user requested social-media content. 7. Permit fully unbranded output without changing the workflow or source files. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:147
Finding
Instagram Access Token Exposed in URL Query Strings<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 147–152; `references/instagram-graph-api.md`, lines 36 and 50 **Vulnerability Type**: Bearer credential exposure through query parameters **Risk Level**: High ### Vulnerable Code ```bash curl -s "https://graph.facebook.com/v21.0/${POST_ID}?fields=permalink&access_token=${IG_TOKEN}" ``` ```bash curl -s "https://graph.facebook.com/v21.0/${IG_USER}/media?fields=id,timestamp,permalink&limit=1&access_token=${IG_TOKEN}" ``` The API reference reinforces the same insecure pattern: ```text GET /{post_id}?fields=permalink&access_token={token} ``` ```text Token expiry: Long-lived tokens last 60 days. Check with `GET /me?access_token={token}`. ``` ### Technical Analysis The workflow places the Instagram Graph API bearer token directly in request URLs. URL-contained credentials are more likely to be retained or exposed than credentials supplied through an authorization header. Depending on the execution environment, expanded URLs may be observable through: - Process argument inspection. - Shell tracing or command logging. - Proxy, gateway, and access logs. - HTTP client diagnostics and error reports. - Monitoring or telemetry systems. - Copied terminal output or execution transcripts. Although HTTPS protects the request in transit, it does not prevent exposure through local process metadata or application and infrastructure logs. ### Attack Path 1. The Skill retrieves or otherwise populates `IG_TOKEN`. 2. Shell interpolation expands `${IG_TOKEN}` into the complete curl URL. 3. The command is executed with the credential present in its process arguments. 4. A local process monitor, shell tracing mechanism, proxy, telemetry collector, or logging system captures the URL. 5. An attacker with access to that record extracts the bearer token. 6. The attacker reuses the token against Facebook Graph API endpoints until it expires or is revoked. ### Impact Assessment The attacker's effective privile ...[truncated 544 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Send the access token in an authorization header rather than a URL: ```bash curl -s \ --header "Authorization: Bearer ${IG_TOKEN}" \ "https://graph.facebook.com/v21.0/${POST_ID}?fields=permalink" ``` 2. Apply the same correction to every Graph API example, including `/media`, `/media_publish`, recent-media verification, permalink retrieval, and `/me`. 3. Disable shell tracing before handling credentials and avoid printing expanded commands. 4. Redact authorization headers and token values from errors, telemetry, and audit logs. 5. Store tokens only in an approved secret manager or operating-system credential store. 6. Use the minimum Graph API scopes required for the workflow. 7. Rotate and revoke any token that may already have appeared in command histories or logs. 8. Add automated secret-pattern checks that reject `access_token=` in URLs. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:157
Finding
Overbroad Cleanup Commands Can Delete Unrelated Carousel Assets<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 157–165 **Vulnerability Type**: Unsafe wildcard deletion and shared temporary-directory removal **Risk Level**: Medium ### Vulnerable Code ```bash cd /path/to/tabiji/repo git rm img/instagram/slide-*.jpg git commit -m "cleanup: remove instagram carousel images after publish" git push ``` ```bash rm -rf /tmp/ig-carousel/ ``` ### Technical Analysis The repository cleanup uses the broad wildcard `slide-*.jpg`. It does not restrict deletion to the six files created by the current run, validate them against the generated manifest, or account for concurrent carousel jobs. Any existing or concurrently generated JPEG whose name matches the pattern may be staged for deletion, committed, and pushed. The local workflow also uses a fixed shared directory, `/tmp/ig-carousel/`, and recursively removes the entire directory. Concurrent executions use the same path, so one run can delete files while another run is still processing or publishing them. The `cd` command is also not explicitly checked before the destructive Git operation. While the shown placeholder normally requires substitution, robust instructions should make deletion contingent on successful repository-root validation. ### Attack Path 1. The repository already contains unrelated files matching `img/instagram/slide-*.jpg`, or another carousel job writes matching files concurrently. 2. A Skill run completes publication and starts cleanup. 3. `git rm img/instagram/slide-*.jpg` expands to all matching repository files. 4. The workflow commits and pushes the deletions, removing unrelated tracked assets from the remote repository. 5. `rm -rf /tmp/ig-carousel/` removes all local files in the shared workspace. 6. Other active jobs may fail, use missing assets, or publish incomplete content. An attacker or untrusted concurrent process able to place a matching tracked file in the target directory could increase the deletion scope within that ...[truncated 649 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create an isolated per-run directory: ```bash RUN_DIR="$(mktemp -d /tmp/ig-carousel.XXXXXXXX)" ``` 2. Assign every generated asset a unique run identifier rather than generic `slide-1.jpg` names. 3. Record the exact repository paths created by the current run in the manifest. 4. Delete only those explicitly recorded paths; do not use a broad wildcard. 5. Validate the repository before modifying it: ```bash repo_root="$(git rev-parse --show-toplevel)" || exit 1 test "$repo_root" = "/expected/path/to/tabiji" || exit 1 ``` 6. Use `cd -- "$repo_root" || exit 1` and stop immediately if directory selection fails. 7. Inspect `git diff --cached --name-status` and verify that every staged deletion belongs to the current run before committing. 8. Use locking or separate worktrees when multiple jobs may publish concurrently. 9. Remove only the unique run directory during local cleanup. 10. Require confirmation before pushing a commit containing unexpected deletions. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (10)

Tool Parameter Abuse

High
Category
Tool Misuse
Content
6. **Cleanup hosted images** — after publish is confirmed, delete the images from the tabiji repo and push:
```bash
cd /path/to/tabiji/repo
git rm img/instagram/slide-*.jpg
git commit -m "cleanup: remove instagram carousel images after publish"
git push
```
Confidence
85% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
```
Also clean up local temp files:
```bash
rm -rf /tmp/ig-carousel/
```

Output: Instagram post URL
Confidence
90% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
```
Also clean up local temp files:
```bash
rm -rf /tmp/ig-carousel/
```

Output: Instagram post URL
Confidence
90% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill sends locally stored images to Topaz Labs and later submits content to Facebook/Instagram using credential-backed API calls, but it does not disclose those third-party transfers to the user. This is dangerous because operators may unknowingly transmit copyrighted, sensitive, or regulated content to external vendors and perform actions under privileged accounts.

External Transmission

Medium
Category
Data Exfiltration
Content
TOPAZ_API_KEY=$(security find-generic-password -s "topaz-api-key" -w)

curl --request POST \
  --url https://api.topazlabs.com/image/v1/enhance \
  --header "X-API-Key: ${TOPAZ_API_KEY}" \
  --header 'accept: image/jpeg' \
  --header 'content-type: multipart/form-data' \
Confidence
90% confidence
Finding
The synchronous Topaz enhancement request uploads a local image file to a third-party API using an API key from the macOS Keychain. That behavior is central to the feature, but it is still a real security/privacy concern because it transmits data off-device under stored credentials without any embedded consent or policy guardrails.

External Transmission

Medium
Category
Data Exfiltration
Content
```bash
# Async: submit
RESPONSE=$(curl -s --request POST \
  --url https://api.topazlabs.com/image/v1/enhance/async \
  --header "X-API-Key: ${TOPAZ_API_KEY}" \
  --header 'content-type: multipart/form-data' \
  --form 'model=Low Resolution V2' \
Confidence
90% confidence
Finding
The async submission endpoint also uploads local images to Topaz, creating the same third-party exposure as the sync path. The presence of both paths increases the chance data will be transmitted externally even if one code path fails, which strengthens the need for explicit controls and disclosure.

External Transmission

Medium
Category
Data Exfiltration
Content
# Poll status until Completed
while true; do
  STATUS=$(curl -s --header "X-API-Key: ${TOPAZ_API_KEY}" \
    "https://api.topazlabs.com/image/v1/status/${PROCESS_ID}" | jq -r '.status')
  [ "$STATUS" = "Completed" ] && break
  sleep 3
done
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
done

# Download result
curl -s --header "X-API-Key: ${TOPAZ_API_KEY}" \
  "https://api.topazlabs.com/image/v1/download/${PROCESS_ID}" \
  --output "/tmp/ig-carousel/${slug}-enhanced.jpg"
```
Confidence
87% confidence
Finding
This step downloads processed image data from Topaz, confirming that user-selected images are transmitted through an external service. While the API call itself is expected for the workflow, it is still a genuine external data-transfer surface with privacy, compliance, and content-rights implications if users are not informed.

External Transmission

Medium
Category
Data Exfiltration
Content
# Download result
curl -s --header "X-API-Key: ${TOPAZ_API_KEY}" \
  "https://api.topazlabs.com/image/v1/download/${PROCESS_ID}" \
  --output "/tmp/ig-carousel/${slug}-enhanced.jpg"
```
Confidence
87% confidence
Finding
This download call retrieves processed content from Topaz and is part of the same third-party processing flow. Although it does not newly expose data, it confirms reliance on an external service for handling user content, which remains a real concern when not clearly disclosed or approved.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill automates publication to Instagram and temporarily hosts generated images on a public GitHub URL, but it does not require an explicit user confirmation or warning before making content public. This creates a real risk of unintended disclosure, accidental publishing, or exposing copyrighted/private images to a public endpoint.

Static analysis

No suspicious patterns detected.