T09 · Insecure Skill Coding Practices
- Location
scripts/post-threads.sh:20- Finding
Executable credential file loaded with shell source
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill mostly matches its social-posting purpose, but the Threads script runs unreviewed local code and executes a credential file, so it should be reviewed before installation.
Install only if you are comfortable with automated public posting and third-party Imgur hosting. Before use, remove the hardcoded external clean_md.py call or bundle a reviewed formatter, avoid sourcing ~/.openclaw/.env, keep Meta tokens out of shell startup files, and rotate tokens if they may have appeared in logs or URLs.
scripts/post-threads.sh:20Executable credential file loaded with shell source
scripts/post-threads.sh:38Execution of an unaudited external Python utility
scripts/post-reels.sh:97Meta access token included in a URL query string
The supplied code chunk has a narrower actual behavior than the declared description. It specifically handles Instagram feed posting only: it validates an image path and caption file, uploads the image to Imgur, then calls the Instagram Graph API endpoints for media creation and publishing. This is consistent with part of the description ('Feed' posting and Imgur hosting), but the broader declared capabilities—Story, Reels, Carousel, and Threads posting—are not represented in this code chunk. There are no materially undeclared harmful capabilities; local logging is just a supporting detail. Because the description claims multiple posting modes/platforms that this code does not implement, the description does not accurately represent this chunk.
The code chunk is narrowly focused on Threads posting: it accepts a caption file and optional image URL, creates a Threads media container, and publishes it using endpoints under graph.threads.net. It uses THREADS_ACCESS_TOKEN or INSTAGRAM_ACCESS_TOKEN only as authentication material and requires THREADS_USER_ID. There is no code for Instagram-specific endpoints, no support for Feed/Story/Reels/Carousel workflows, and no Imgur upload/hosting functionality. Because the declared description presents a broader primary purpose than the actual implemented behavior, this is a meaningful description-to-behavior mismatch.
The skill instructs users to place long-lived access tokens in shell startup files or a general .env location, which can increase accidental exposure through local file disclosure, shell history, backups, or overly broad process/environment access. In agent or shared-workstation contexts, environment-stored API credentials can be harvested and abused to post content or access linked account resources.
# ~/.openclaw/.env 또는 ~/.zshrc에 추가
export INSTAGRAM_ACCESS_TOKEN="your_token_here"
export INSTAGRAM_BUSINESS_ACCOUNT_ID="your_account_id_here"
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
- `instagram_basic`
- `instagram_content_publish`
- `pages_read_engagement`
5. **Access Token** 발급:
- Graph API Explorer: https://developers.facebook.com/tools/explorer/
- 장기 토큰(Long-lived token)으로 교환: 60일 유효
6. **Business Account ID** 확인:
The script automatically sources ~/.openclaw/.env into the current shell context, executing any shell code present in that file rather than safely parsing key-value pairs only. If that file is modified by another local process, user, or compromised workflow, running this script will execute attacker-controlled commands with the privileges of the caller and may also expose credentials loaded from the file.
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
err() { log "❌ ERROR: $*"; exit 1; }
if [ -f ~/.openclaw/.env ]; then source ~/.openclaw/.env; fi
# ── 인자 확인 ──────────────────────────────────────────────
The skill documentation declares a network-capable integration with Meta Graph API and Imgur but does not specify any explicit tool scope or allowed-tools restrictions. In an agent environment, that omission can permit broader-than-expected outbound network use and reduces the user's ability to understand or constrain what external actions the skill may take.
The skill says Imgur is required for media hosting, but it does not provide a prominent warning that local images or videos will be uploaded to a third-party public hosting service before posting. That can lead users to unintentionally expose private media or sensitive metadata to an external service, which is especially risky in an automation context.
The skill explicitly depends on transmitting user media and related data to an external third party, Imgur, to obtain a public URL. External transmission is expected for this skill's purpose, but it remains security-relevant because it expands data exposure beyond Meta and can leak user content if the upload is public or not adequately disclosed.
Instagram Graph API는 공개 URL로만 미디어를 업로드할 수 있어 Imgur가 필요합니다.
1. https://api.imgur.com/oauth2/addclient 접속
2. **Application name**: 원하는 이름 (예: `raon-instagram`)
3. **Authorization type**: `Anonymous usage without user authorization` 선택
4. **Authorization callback URL**: `https://localhost` (Anonymous이므로 형식만 맞추면 됨)
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
curl "https://graph.facebook.com/v21.0/me/accounts?access_token=YOUR_TOKEN"
> 💡 **Imgur Client ID**: https://api.imgur.com/oauth2/addclient (Anonymous usage 선택)
---
The script uploads local image files to Imgur before posting to Instagram, which transmits user-provided local content to a third-party service outside Meta. While this is functionally intended by the skill description, the lack of an explicit warning, consent prompt, or clear documentation increases the risk of unintended data disclosure if users provide sensitive images.
This code performs external transmission of local image contents to Imgur using an HTTPS request. In this skill context, outbound transfer is expected, but it is still security-relevant because local files are exfiltrated to a third party and users may not realize that Instagram posting depends on separate hosting through Imgur.
with open(img_path, "rb") as f:
img_b64 = base64.b64encode(f.read()).decode()
data = urllib.parse.urlencode({"image": img_b64, "type": "base64"}).encode()
req = urllib.request.Request("https://api.imgur.com/3/image", data=data, headers={"Authorization": f"Client-ID {client_id}"})
with urllib.request.urlopen(req) as resp:
print(resp.read().decode())
PYEOF
The script uploads the local image to Imgur and sends the caption and media URL to Meta Graph API, but it does not provide an explicit disclosure or confirmation that user-supplied content will be transmitted to third-party services. In an agent-skill context, this can cause unintended leakage of sensitive images or text because the behavior is automatic and not prominently surfaced at the point of use.
The script exfiltrates the full image content to Imgur over the network as part of its workflow. While this is aligned with the skill's stated purpose, it is still a real data-transmission risk because local files may contain sensitive or regulated content, and the script does not constrain, minimize, or explicitly confirm this disclosure before upload.
img_b64 = base64.b64encode(f.read()).decode()
data = urllib.parse.urlencode({"image": img_b64, "type": "base64"}).encode()
req = urllib.request.Request(
"https://api.imgur.com/3/image",
data=data,
headers={"Authorization": f"Client-ID {client_id}"}
)
The script uploads the provided video to Imgur as an intermediate hosting step but does not clearly warn the user that their media is being transmitted to a third-party service outside Meta/Instagram. Because the content may be publicly accessible or retained by Imgur, this can cause unintended disclosure of sensitive or private media, which is especially risky in a posting automation skill.
This code transmits the full video file to an external domain, api.imgur.com, which is not strictly necessary from a least-privilege/privacy perspective if the user expects direct posting to Instagram only. In the context of a social-posting skill, external transmission is expected to Meta, but routing media through an additional third party increases confidentiality and compliance risk, especially because the upload appears to use Imgur's public upload API.
boundary = b"----FormBoundary"
body = boundary + b"\r\nContent-Disposition: form-data; name=\"video\"; filename=\"video.mp4\"\r\n\r\n" + video_data + b"\r\n" + boundary + b"--"
req = urllib.request.Request(
"https://api.imgur.com/3/video",
data=body,
headers={"Authorization": f"Client-ID {client_id}", "Content-Type": f"multipart/form-data; boundary={boundary.decode().lstrip('-').strip()}"}
)
The script uploads the provided image to Imgur as an intermediate hosting step before posting to Instagram, but it gives no explicit warning, consent prompt, or visibility control to the user. This can cause unintended disclosure of sensitive or private media to a third-party service, which is especially relevant because the skill context handles user-supplied images and external publishing workflows.
The script transmits full image contents to an external domain (Imgur) as base64 data. In this skill's context, external transmission is expected for social posting, but the danger is increased because the transmission is to an additional third party not strictly necessary from the user's perspective and is not clearly disclosed, creating privacy and data-handling risk.
img_b64 = base64.b64encode(f.read()).decode()
data = urllib.parse.urlencode({"image": img_b64, "type": "base64"}).encode()
req = urllib.request.Request(
"https://api.imgur.com/3/image",
data=data,
headers={"Authorization": f"Client-ID {client_id}"}
)
The natural-language comments and usage/help text are written entirely in Korean, including the invocation guidance and environment variable descriptions. Under the policy, forcing a specific language without user opt-in or a documented regional justification is a locale-policy violation.
The primary descriptive instructions are presented in Korean, which can impose a language constraint on users without opt-in or justification. Under the language/locale policy, skills should offer a language choice or clearly document why a specific language is required.
The script’s descriptive strings and usage guidance are written only in Korean, which imposes a specific language/locale on users without any opt-in or explanation that the skill is region-specific. Under the policy, language constraints should be optional or clearly justified.
Comments, usage text, and error messages are written exclusively in Korean, which imposes a specific language on users without opt-in. Under the policy, language constraints should either be user-selectable or clearly documented as a justified locale-specific limitation.
The natural-language comments, usage text, and runtime messages are written in Korean only, with no indication that language selection is optional or region-specific. This can violate language/locale policy when a skill imposes a single language without user opt-in or documented justification.
Comments, usage text, and error messages are written in Korean only, which imposes a specific language on users without opt-in or alternative locale support. Under the policy, hard-coding a single language without user choice is a natural-language policy violation unless the locale restriction is clearly documented and justified.
No suspicious patterns detected.