Back to skill

Security audit

Social Media Autopilot

Security checks for vulnerabilities and agentic risk

Overview

This social-media publishing skill is purpose-aligned, but its scripts have serious input-handling and publication-integrity flaws that could execute unintended local code or falsely mark failed posts as published.

Review this skill before installing, especially if it will publish to real brand or personal accounts. Do not run it on untrusted post text or untrusted post IDs, avoid enabling auto_approve, and treat OAuth tokens as sensitive credentials. The scripts should be fixed to safely encode JSON, validate post IDs and paths, preserve drafts on publication failure, and verify platform API responses before being used for production posting.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • 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
Findings (5)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/draft-post.sh:69
Finding
Arbitrary Python Code Execution Through Draft Post Text<![CDATA[ ## Vulnerability Details **File Location**: `scripts/draft-post.sh`, line 69 **Vulnerability Type**: Python source-code injection **Risk Level**: High ### Vulnerable Code ```bash cat > "$DRAFTS_DIR/$POST_ID.json" << EOF { "id": "$POST_ID", "platforms": $PLATFORMS_JSON, "text": $(python3 -c "import json; print(json.dumps('''$TEXT'''))"), "media": $MEDIA_JSON, "scheduled_at": $SCHEDULE_JSON, "status": "draft", "created_at": "$NOW", "approved": false, "tags": $TAGS_JSON, "thread": $THREAD } EOF ``` ### Technical Analysis The value supplied through `--text` is interpolated directly into Python source code inside a triple-quoted string. Shell quoting does not make the resulting Python program safe. An attacker who can influence the post text can insert a triple-quote terminator and additional Python statements. When `draft-post.sh` invokes `python3 -c`, the injected statements execute with the same operating-system privileges and environment as the user running the skill. This is not limited to corrupting the generated JSON. Injected Python can invoke system commands, read or modify files accessible to the current user, inspect environment variables containing social-media tokens, or establish additional network connections. ### Attack Path 1. An attacker supplies or causes the agent to use malicious content as the `--text` value. 2. The content closes the `'''...'''` Python string used by the script. 3. The content appends valid Python statements and comments out the remaining generated source. 4. `python3 -c` parses the attacker-controlled statements as executable code. 5. The payload runs with the privileges and environment of the skill process. A conceptual payload has the following structure: ```text '''); ATTACKER_CONTROLLED_PYTHON; # ``` ### Impact Assessment Successful exploitation provides arbitrary local code execution as the account running the skill. The attacker could: - Read or alter files available to that acc ...[truncated 414 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Never interpolate post content into executable Python source. Pass it as a positional argument or through standard input: ```bash TEXT_JSON=$(python3 -c 'import json, sys; print(json.dumps(sys.argv[1]))' "$TEXT") ``` Then use the encoded result when generating the document: ```bash "text": $TEXT_JSON, ``` A stronger design is to construct the entire post document in one Python program and pass every external value through `sys.argv`, environment variables, or standard input. Alternatively, use `jq --arg` to create the JSON object. Add regression tests containing: - Triple quotes. - Single and double quotes. - Newlines and backslashes. - Shell metacharacters. - Text resembling Python statements. The tests should verify that all such values are stored literally and never executed. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/approve-post.sh:5
Finding
Unvalidated Post IDs Permit Path Traversal and Unsafe Python Source Construction<![CDATA[ ## Vulnerability Details **File Location**: `scripts/approve-post.sh`, lines 5–19 **Vulnerability Type**: Path traversal and Python source injection **Risk Level**: High ### Vulnerable Code ```bash BASE_DIR="${SOCIAL_MEDIA_DIR:-$HOME/.openclaw/workspace/social-media}" POST_ID="${1:-}" [ -z "$POST_ID" ] && { echo "Usage: approve-post.sh <post-id>"; exit 1; } DRAFT_FILE="$BASE_DIR/drafts/$POST_ID.json" [ ! -f "$DRAFT_FILE" ] && { echo "❌ Draft not found: $POST_ID"; exit 1; } python3 -c " import json with open('$DRAFT_FILE') as f: post = json.load(f) post['approved'] = True post['approved_at'] = '$(date -u +%Y-%m-%dT%H:%M:%SZ)' with open('$DRAFT_FILE', 'w') as f: json.dump(post, f, indent=2) print(f'✅ Post {post[\"id\"]} approved') print(f' Platforms: {\", \".join(post[\"platforms\"])}') print(f' Text: {post[\"text\"][:100]}...' if len(post['text']) > 100 else f' Text: {post[\"text\"]}') " "$DRAFT_FILE" ``` The same unsafe pattern is also present in: ```bash # scripts/publish-post.sh DRAFT_FILE="$DRAFTS_DIR/$POST_ID.json" [ ! -f "$DRAFT_FILE" ] && { echo "❌ Draft not found: $POST_ID"; exit 1; } APPROVED=$(python3 -c "import json; d=json.load(open('$DRAFT_FILE')); print(d.get('approved', False))") PLATFORMS=$(python3 -c "import json; d=json.load(open('$DRAFT_FILE')); print(' '.join(d['platforms']))") TEXT=$(python3 -c "import json; d=json.load(open('$DRAFT_FILE')); print(d['text'])") ``` ```bash # scripts/analytics.sh POST_ID="$PARAM" PUBLISHED_FILE="$PUBLISHED_DIR/$POST_ID.json" if [ -f "$PUBLISHED_FILE" ]; then echo "📊 Post Analytics: $POST_ID" python3 -c " import json with open('$PUBLISHED_FILE') as f: post = json.load(f) ``` ### Technical Analysis The scripts treat a user-supplied post ID as a filename component without validating that it is a UUID or even a simple basename. Values containing `../` can escape the intended `drafts` or `published` directory. The scripts append `.json`, but this does not prevent traversal ...[truncated 1916 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Require post IDs to match the format generated by the application. For UUIDs, apply a strict allowlist before constructing any path: ```bash if [[ ! "$POST_ID" =~ ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$ ]]; then echo "Invalid post ID" >&2 exit 1 fi ``` Apply this validation consistently in `approve-post.sh`, `publish-post.sh`, and `analytics.sh`. Canonicalize the resulting path and verify that it remains under the expected directory before reading, writing, or deleting it. Do not rely only on string prefixes without canonicalization. Pass paths to Python as arguments rather than embedding them into source: ```bash python3 - "$DRAFT_FILE" <<'PY' import json import sys draft_file = sys.argv[1] with open(draft_file, encoding="utf-8") as handle: post = json.load(handle) PY ``` Before deletion, repeat the containment check and ensure that the target is a regular file rather than a symbolic link. Consider opening files with protections against symlink traversal where the runtime environment supports them. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/publish-post.sh:43
Finding
LinkedIn API Request Body Injection Through Unescaped Post Text<![CDATA[ ## Vulnerability Details **File Location**: `scripts/publish-post.sh`, lines 43–58 **Vulnerability Type**: JSON request-body injection **Risk Level**: Medium ### Vulnerable Code ```bash if [ -n "${LINKEDIN_ACCESS_TOKEN:-}" ]; then RESPONSE=$(curl -s -X POST "https://api.linkedin.com/v2/ugcPosts" \ -H "Authorization: Bearer $LINKEDIN_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"author\": \"urn:li:person:${LINKEDIN_PERSON_ID:-me}\", \"lifecycleState\": \"PUBLISHED\", \"specificContent\": { \"com.linkedin.ugc.ShareContent\": { \"shareCommentary\": {\"text\": \"$TEXT\"}, \"shareMediaCategory\": \"NONE\" } }, \"visibility\": {\"com.linkedin.ugc.MemberNetworkVisibility\": \"PUBLIC\"} }" 2>&1) || true echo " LinkedIn response: $RESPONSE" else echo " ⚠️ LINKEDIN_ACCESS_TOKEN not set." fi ``` ### Technical Analysis `TEXT` is inserted directly into a JSON document without JSON escaping. Post text containing double quotes, backslashes, control characters, or crafted JSON fragments can terminate the intended string or make the document invalid. Approval does not neutralize the content. Once the draft is approved, `publish-post.sh` retrieves the stored text and places it into the API request exactly as shown. This flaw primarily enables manipulation or corruption of the JSON sent to LinkedIn. It is not shell command injection because the variable is expanded inside a quoted shell argument, but it can alter the structure and semantics of the remote API request. ### Attack Path 1. An attacker controls or influences draft text containing JSON metacharacters. 2. A user approves the draft. 3. `publish-post.sh` reads the malicious text into `TEXT`. 4. The script concatenates the text into a manually constructed JSON body. 5. The resulting request is malformed or contains attacker-influenced JSON structure. 6. LinkedIn rejects the request or processes un ...[truncated 564 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Generate the complete request body with a JSON-aware tool. For example: ```bash PAYLOAD=$(jq -n \ --arg author "urn:li:person:${LINKEDIN_PERSON_ID:-me}" \ --arg text "$TEXT" \ '{ author: $author, lifecycleState: "PUBLISHED", specificContent: { "com.linkedin.ugc.ShareContent": { shareCommentary: {text: $text}, shareMediaCategory: "NONE" } }, visibility: { "com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC" } }') ``` Submit the serialized document without further interpolation: ```bash curl --fail-with-body --silent --show-error \ -X POST "https://api.linkedin.com/v2/ugcPosts" \ -H "Authorization: Bearer $LINKEDIN_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ --data-binary "$PAYLOAD" ``` Validate platform length limits before submission. Add tests for quotes, newlines, Unicode, backslashes, and strings resembling nested JSON properties. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
references/instagram-setup.md:18
Finding
OAuth Credentials Exposed Through URLs and Command-Line Arguments in Setup Guidance<![CDATA[ ## Vulnerability Details **File Location**: `references/instagram-setup.md`, lines 18–28 and 42–49 **Vulnerability Type**: Insecure credential handling **Risk Level**: Medium ### Vulnerable Code ```bash curl "https://graph.facebook.com/v18.0/oauth/access_token?grant_type=fb_exchange_token&client_id=APP_ID&client_secret=APP_SECRET&fb_exchange_token=SHORT_LIVED_TOKEN" ``` ```bash curl "https://graph.facebook.com/v18.0/me/accounts?access_token=TOKEN" # Get the Page ID, then: curl "https://graph.facebook.com/v18.0/PAGE_ID?fields=instagram_business_account&access_token=TOKEN" ``` ```bash # Step 1: Create container curl -X POST "https://graph.facebook.com/v18.0/$INSTAGRAM_BUSINESS_ID/media" \ -d "image_url=PUBLIC_IMAGE_URL&caption=Your+caption&access_token=TOKEN" # Step 2: Publish curl -X POST "https://graph.facebook.com/v18.0/$INSTAGRAM_BUSINESS_ID/media_publish" \ -d "creation_id=CONTAINER_ID&access_token=TOKEN" ``` ### Technical Analysis The documentation instructs users to place client secrets and access tokens directly in URL query strings or command-line data arguments. Sensitive command arguments may be exposed through: - Interactive shell history. - Process inspection while `curl` is running. - Terminal capture and command auditing. - Debug logs and support transcripts. - Proxy, gateway, or HTTP request logs that record URLs. - Browser or monitoring systems that retain query strings. HTTPS protects the request while it is in transit, but it does not prevent local command-history exposure or logging of the URL at endpoints and intermediaries. ### Attack Path 1. A user follows the documented commands and replaces placeholders with live credentials. 2. The complete command is retained in shell history or captured by process or terminal monitoring. 3. Another local principal, administrator, support process, or log reader obtains the token or client secret. 4. The exposed credential is reused against the Meta Graph API before it expires or ...[truncated 654 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Revise the documentation to avoid literal credentials in command history. Load secrets from files with restrictive permissions or prompt for them without echoing: ```bash read -rsp "Meta access token: " INSTAGRAM_ACCESS_TOKEN printf '\n' ``` Use authorization headers wherever the API supports them: ```bash curl --fail-with-body --silent --show-error \ -H "Authorization: Bearer $INSTAGRAM_ACCESS_TOKEN" \ "https://graph.facebook.com/v18.0/me/accounts" ``` When an endpoint requires a form field, use a protected configuration mechanism and warn users that command arguments may remain visible. Consider generating a temporary curl configuration file with mode `0600`, removing it immediately after use, and avoiding verbose request logging. The documentation should also instruct users to: - Keep `.env` and credential files outside version control. - Apply restrictive file permissions. - Disable or remove sensitive shell-history entries. - Rotate any credentials accidentally pasted into logs or terminals. - Grant only the minimum required OAuth scopes. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/publish-post.sh:30
Finding
Failed Publications Are Falsely Archived as Successful and Source Drafts Are Deleted<![CDATA[ ## Vulnerability Details **File Location**: `scripts/publish-post.sh`, lines 30–88 **Vulnerability Type**: Improper error handling and destructive state transition **Risk Level**: High ### Vulnerable Code ```bash for platform in $PLATFORMS; do echo " → $platform..." case $platform in x) # Use xurl if available, otherwise X API directly if command -v xurl &>/dev/null; then RESPONSE=$(xurl post tweets -d "{\"text\": $(python3 -c "import json; print(json.dumps('$TEXT'))")}" 2>&1) || true echo " X response: $RESPONSE" else echo " ⚠️ xurl not found. Set up X API credentials or install xurl skill." fi ;; linkedin) if [ -n "${LINKEDIN_ACCESS_TOKEN:-}" ]; then RESPONSE=$(curl -s -X POST "https://api.linkedin.com/v2/ugcPosts" \ -H "Authorization: Bearer $LINKEDIN_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"author\": \"urn:li:person:${LINKEDIN_PERSON_ID:-me}\", \"lifecycleState\": \"PUBLISHED\", \"specificContent\": { \"com.linkedin.ugc.ShareContent\": { \"shareCommentary\": {\"text\": \"$TEXT\"}, \"shareMediaCategory\": \"NONE\" } }, \"visibility\": {\"com.linkedin.ugc.MemberNetworkVisibility\": \"PUBLIC\"} }" 2>&1) || true echo " LinkedIn response: $RESPONSE" else echo " ⚠️ LINKEDIN_ACCESS_TOKEN not set." fi ;; instagram) if [ -n "${INSTAGRAM_ACCESS_TOKEN:-}" ] && [ -n "${INSTAGRAM_BUSINESS_ID:-}" ]; then echo " ⚠️ Instagram requires media. Use the media workflow." else echo " ⚠️ Instagram credentials not set." fi ;; *) echo " ⚠️ Unknown platform: $platform" ;; esac done # Move to published python3 -c " import json with open('$DRAFT_FILE') as f: post = json.load(f) post['status'] ...[truncated 2134 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Remove `|| true` from publication commands and explicitly validate each result. For HTTP requests, use: ```bash curl --fail-with-body --silent --show-error ``` Capture the HTTP status and parse the response to confirm that the platform returned a valid post identifier. For `xurl`, require a successful exit status and validate its structured response. Maintain per-platform results: ```json { "x": {"status": "published", "remote_id": "..."}, "linkedin": {"status": "failed", "error": "..."} } ``` Only mark the overall post as `published` when all requested platforms succeed. If any publication fails: - Preserve the draft. - Set `status` to `failed` or `partially_published`. - Store a sanitized error message and retry metadata. - Do not claim success. - Avoid automatically retrying platforms that already succeeded unless duplicate prevention is implemented. Write updated records atomically using a temporary file followed by a rename. Delete the original draft only after the archive has been safely written and all required remote publications have been confirmed. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (14)

Credential Access

High
Category
Privilege Escalation
Content
- Note your **Client ID** and **Client Secret** from the Auth tab
- Add redirect URL: `http://localhost:3000/callback` (or your domain)

## 3. Generate Access Token
Request authorization:
```
https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT&scope=openid%20profile%20w_member_social
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
- Note your **Client ID** and **Client Secret** from the Auth tab
- Add redirect URL: `http://localhost:3000/callback` (or your domain)

## 3. Generate Access Token
Request authorization:
```
https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT&scope=openid%20profile%20w_member_social
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
- Note your **Client ID** and **Client Secret** from the Auth tab
- Add redirect URL: `http://localhost:3000/callback` (or your domain)

## 3. Generate Access Token
Request authorization:
```
https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT&scope=openid%20profile%20w_member_social
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill instructs the agent to run shell scripts and read/write workspace files, but the manifest does not declare any explicit tool scope or permission boundaries. This increases the chance the skill will be granted broader-than-necessary file and shell access, making accidental misuse or prompt-injection-driven abuse more likely.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
3. Only approved posts get published
4. Use `scripts/approve-post.sh <post-id>` or `scripts/reject-post.sh <post-id> --reason "..."`

**Never auto-publish without explicit approval** unless the user has configured `auto_approve: true` in `social-media/config.json`.

### 3. Publishing
Confidence
85% confidence
Finding
The `auto_approve: true` exception weakens the human-approval control for publishing and enables autonomous posting to external accounts. In a social-media publishing skill, this is risky because a prompt-injected, mistaken, or maliciously crafted draft could be published directly to public brand channels without a final user check.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
This markdown file instructs the user to generate, exchange, and store long-lived Instagram access tokens, including passing tokens in curl command lines and exporting them as environment variables. The document does not warn that these values are sensitive credentials that may be exposed via shell history, process lists, logs, or shared terminals.

External Transmission

Medium
Category
Data Exfiltration
Content
- Exchange for a long-lived token (60 days):

```bash
curl "https://graph.facebook.com/v18.0/oauth/access_token?grant_type=fb_exchange_token&client_id=APP_ID&client_secret=APP_SECRET&fb_exchange_token=SHORT_LIVED_TOKEN"
```

## 3. Get Instagram Business Account ID
Confidence
60% 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
Exchange code for token:
```bash
curl -X POST https://www.linkedin.com/oauth/v2/accessToken \
  -d "grant_type=authorization_code&code=AUTH_CODE&redirect_uri=YOUR_REDIRECT&client_id=YOUR_CLIENT_ID&client_secret=YOUR_SECRET"
```
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
This shell script creates a new JSON draft file and, when scheduling is used, also updates a calendar JSON file. Although it prints success messages after the fact, there is no prior warning, confirmation, or explanatory comment/docstring disclosing that the skill will write persistent data under the user's social-media workspace.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
if [ ! -f "$BASE_DIR/config.json" ]; then
  cat > "$BASE_DIR/config.json" << 'EOF'
{
  "auto_approve": false,
  "default_platforms": ["x"],
  "timezone": "America/Los_Angeles",
  "posting_hours": {"start": 9, "end": 18},
Confidence
85% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

External Transmission

Medium
Category
Data Exfiltration
Content
;;
    linkedin)
      if [ -n "${LINKEDIN_ACCESS_TOKEN:-}" ]; then
        RESPONSE=$(curl -s -X POST "https://api.linkedin.com/v2/ugcPosts" \
          -H "Authorization: Bearer $LINKEDIN_ACCESS_TOKEN" \
          -H "Content-Type: application/json" \
          -d "{
Confidence
70% 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
;;
    linkedin)
      if [ -n "${LINKEDIN_ACCESS_TOKEN:-}" ]; then
        RESPONSE=$(curl -s -X POST "https://api.linkedin.com/v2/ugcPosts" \
          -H "Authorization: Bearer $LINKEDIN_ACCESS_TOKEN" \
          -H "Content-Type: application/json" \
          -d "{
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Low
Confidence
84% confidence
Finding
The --report path creates and writes to a file in the analytics directory, which is a state-changing filesystem operation. While the script prints the saved path after writing, there is no prior user-facing warning or confirmation that running this mode will create or overwrite a local report file.

Natural-Language Policy Violations

Low
Confidence
92% confidence
Finding
The default configuration sets "timezone": "America/Los_Angeles", which imposes a specific locale choice in a user-facing config template. The file does not indicate that this is optional at creation time or explain why this region-specific default is required, so it may violate language/locale policy expectations.