Back to skill

Security audit

PowPow Publisher — turn photos into travelogues, pin them to the map, create chat-capable digital humans

Security checks for vulnerabilities and agentic risk

Overview

The skill matches its stated PowPow publishing purpose, but it needs Review because a stored login token lets its scripts publish public content, upload images, create badge-consuming digital humans, or delete posts without an executable confirmation gate.

Review before installing. Use it only if you are comfortable letting an agent handle PowPow credentials and a reusable session token. Confirm the state config points to the official PowPow site, protect or delete ~/.powpow/session.json on shared machines, and require a visible preview plus explicit approval before running publish, create-digital-human, or delete-post commands.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/publish.js:96
Finding

Public post publication is not technically gated by user confirmation

Content
View full analysis

Vulnerability Details

File Location: scripts/publish.js, lines 96–166
Vulnerability Type: Missing executable authorization gate for a public publishing operation
Risk Level: High

Complete Code Snippet

js
async function main() {
  const [, , htmlPath] = process.argv;
  if (!htmlPath) {
    console.error('Usage: node publish.js <html-file-path>');
    process.exit(1);
  }

  let content = fs.readFileSync(htmlPath, 'utf-8');
  console.log(`Content length: ${content.length} chars`);

  let session;
  try {
    session = requireSession();
  } catch (err) {
    if (err instanceof SessionExpiredError) {
      console.error(`❌ ${err.message}`);
      process.exitCode = 2;
      return;
    }
    throw err;
  }
  console.log(`Publishing as: ${session.username}`);

  // Deferred local-image upload: this is the ONLY moment the images
  // leave the user's machine (compose.js keeps everything local).
  try {
    content = await uploadLocalImages(content, htmlPath);
  } catch (err) {
    if (err instanceof SessionExpiredError) {
      console.error(`❌ ${err.message}`);
      process.exitCode = 2;
      return;
    }
    console.error(`\n❌ Image upload failed: ${err.message}`);
    console.error('   Nothing was posted. Fix the issue and re-run publish.js ' +
      '(already-uploaded images are reused, not re-uploaded).');
    process.exitCode = 1;
    return;
  }

  const dhInfo = extractDigitalHuman(content);
  if (dhInfo) console.log(`Digital Human: ${dhInfo.name} | ID: ${dhInfo.id}`);

  const locInfo = extractLocation(content);
  if (locInfo) console.log(`Location: ${locInfo.name} (${locInfo.lng}, ${locInfo.lat})`);

  // Guard: interactive components must use the editor's exact HTML structure,
  // otherwise the web frontend renders them as plain text.
  const formatIssues = validateEditorFormat(content);
  if (formatIssues.length > 0) {
    console.error('\n❌ Component HTML does not match the platform editor format:');
    formatIssues.forEac
...[truncated 2724 chars]
Remediation
View remediation

Remediation Suggestions

  1. Make the default execution mode a dry run that displays the exact account, content hash, images, and location.
  2. After preview approval, issue a short-lived confirmation token bound to:
    • The authenticated account.
    • A cryptographic hash of the final draft.
    • The image manifest and resolved image paths.
    • The public-map location and exposure setting.
  3. Require and validate that token immediately before the first image upload and again before POST /api/posts.
  4. Invalidate the token after one use, after expiry, or whenever the draft or manifest changes.
  5. For interactive use, add a final explicit prompt immediately before external side effects.
  6. Keep the existing HTML validation, but do not treat it as authorization evidence.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/create-digital-human.js:145
Finding

Badge-consuming digital-human creation lacks an executable confirmation gate

Content
View full analysis

Vulnerability Details

File Location: scripts/create-digital-human.js, lines 145–192
Vulnerability Type: Missing authorization enforcement for a non-refundable resource-consuming operation
Risk Level: Medium

Complete Code Snippet

js
async function main() {
  const opts = parseArgs(process.argv.slice(2));

  let description = opts.desc;
  if (!description && opts.descFile) {
    if (!fs.existsSync(opts.descFile)) throw new Error(`--desc-file not found: ${opts.descFile}`);
    description = fs.readFileSync(opts.descFile, 'utf-8');
  }

  const { lng, lat } = validate(opts, description);
  const session = requireSession();

  // Badge pre-check: creating costs BADGES_REQUIRED badges and deletion does
  // NOT refund them - fail fast before uploading anything.
  if (session.userId) {
    const bal = await api('GET', `/api/badges/balance?userId=${session.userId}`);
    const current = bal.balance ? bal.balance.balance : 0;
    if (current < BADGES_REQUIRED) {
      throw new Error(`Badge balance too low: ${current} available, ${BADGES_REQUIRED} required. Earn badges on the platform first (see https://global.powpow.online).`);
    }
    console.log(`  💰 Badge balance: ${current} (creating a digital human consumes ${BADGES_REQUIRED})`);
  } else {
    console.warn('  ⚠️ No userId in session - skipping local badge pre-check (server will still reject if insufficient).');
  }

  const { avatarUrl, referenceImageUrl } = await resolveAvatar(opts, description);

  console.log('  ⏳ Creating digital human...');
  const payload = {
    name: opts.name.trim(),
    description: description.trim(),
    avatarUrl,
    lng,
    lat,
  };
  if (opts.locationName && opts.locationName.trim()) payload.locationName = opts.locationName.trim();
  if (referenceImageUrl) payload.referenceImageUrl = referenceImageUrl;

  const body = await api('POST', '/api/digital-humans', payload);
  const dh = body.digitalHuman || {};
  const badgesRemaining = body.badgesRemaini
...[truncated 1594 chars]
Remediation
View remediation

Remediation Suggestions

  1. Split creation into preparation and commit phases.
  2. During preparation, resolve the intended avatar, persona, location, account, expiry, and exact badge cost without creating the digital human.
  3. Generate a short-lived confirmation token bound to the complete payload, authenticated account, and stated cost.
  4. Require that token for the commit phase and validate it immediately before uploads, avatar generation, and the creation API call.
  5. Reject reused, expired, or payload-mismatched tokens.
  6. Make dry-run behavior the default for Agent-driven execution.
  7. If interactive execution is supported, require an explicit confirmation prompt that names the two-badge non-refundable cost.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/delete-post.js:9
Finding

Authenticated post deletion can be executed without confirmation or dry-run protection

Content
View full analysis

Vulnerability Details

File Location: scripts/delete-post.js, lines 9–17
Vulnerability Type: Missing confirmation gate for a destructive remote operation
Risk Level: Medium

Complete Code Snippet

js
async function main() {
  const [, , postId] = process.argv;
  if (!postId) {
    console.error('Usage: node delete-post.js <post-id>');
    process.exit(1);
  }

  try {
    const body = await api('DELETE', `/api/posts/${postId}`);
    console.log(`✅ Post deleted: ${postId}`);

Technical Analysis

The script performs an authenticated deletion as soon as it receives a post ID. It does not first retrieve and display the target post, require a dry run, prompt for approval, or validate confirmation evidence tied to the selected post.

Server-side JWT scoping is useful counter-evidence: the caller can only delete posts permitted to the logged-in account. However, ownership authorization and user-intent confirmation are distinct controls. The current implementation protects other accounts but does not protect the authenticated user’s content from mistaken or manipulated Agent actions.

Attack Path

  1. A valid PowPow session exists.
  2. An Agent, automation workflow, or injected cleanup instruction identifies one of the user’s post IDs.
  3. It invokes:
    bash
    node scripts/delete-post.js <post-id>
    
  4. The script immediately sends DELETE /api/posts/<post-id>.
  5. If the server recognizes the post as belonging to the authenticated user, it is deleted without a local approval gate.

Impact Assessment

The action is limited to posts the authenticated account is authorized to delete. Within that scope, exploitation can cause deletion of legitimate user content and loss of its public availability.

No cross-account privilege escalation is established, and the code does not demonstrate malicious intent. The risk is an unguarded destructive action within the user’s own remote account.

Remediation
View remediation

Remediation Suggestions

  1. Default to a dry run that retrieves and displays the post’s author, excerpt, location, and publication time.
  2. Require a short-lived confirmation token bound to the account and exact post ID.
  3. Validate and consume the token immediately before the DELETE request.
  4. Reject deletion if the post changed after confirmation or if the token expired.
  5. For interactive use, require the user to confirm the specific post rather than accepting a generic cleanup instruction.
  6. Preserve server-side ownership checks as defense in depth.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (33)

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
80% 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).

Content

Scanner excerpt · README.md (reported line 65)May include surrounding context.

md
publishing workflow (login → self-check → geocode → match → compose →
  publish → verify), not an independent hidden tool.
- `delete-post.js` deletes only the logged-in user's own post
  (`DELETE /api/posts/{id}`, JWT-scoped, enforced server-side) and is
  documented in SKILL.md Step 8 for test cleanup. This capability is also
  declared in the skill description.
- No secrets are bundled. `config.json` ships defaults only. The session JWT

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 217)May include surrounding context.

md
node scripts/match-digital-human.js "<topic>" --limit 3 --json # ranked suggestions

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 377)May include surrounding context.

md
node scripts/match-digital-human.js "<topic>" --limit 3 --json # ranked suggestions

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 218)May include surrounding context.

md
node scripts/list-digital-humans.js "<name>" --json # search by name

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 379)May include surrounding context.

md
node scripts/list-digital-humans.js "<name>" --json # search by name

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 230)May include surrounding context.

md
node scripts/geocode.js "地坛公园" --limit 5 # add --city 北京 to narrow

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 260)May include surrounding context.

md
- **Automatic image search** (only if an Unsplash key is configured): `--image search:<english keywords>` (or `node scripts/search-image.js "<keywords>"`)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 269)May include surrounding context.

md
**Mandatory: always use `scripts/compose.js` to assemble post HTML. Never hand-write component spans, never write glue code to bypass html-formatter.js.**

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 284)May include surrounding context.

md
**Mandatory: always use `scripts/compose.js` to assemble post HTML. Never hand-write component spans, never write glue code to bypass html-formatter.js.**

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 326)May include surrounding context.

md
node scripts/verify.js <post-id>

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 349)May include surrounding context.

md
node scripts/delete-post.js <post-id>

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 370)May include surrounding context.

md
node scripts/create-digital-human.js \

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
80% 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).

Content

Scanner excerpt · references/security.md (reported line 42)May include surrounding context.

md
- `POST /api/upload/post-image` — local image upload (multipart `file`; login required)
- `POST /api/posts` — create post (login required; server enforces limits & moderation)
- `GET  /api/posts/{id}` — verify a post
- `DELETE /api/posts/{id}` — delete own post (test cleanup)
- `POST /api/digital-humans` — create a digital human (login required;
  consumes 2 badges, expires after 30 days)
- `POST /api/digital-humans/generate-avatar` — AI avatar from the persona

Tool Parameter Abuse

High
Category
Tool Misuse
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).

Content

Scanner excerpt · scripts/delete-post.js (reported line 2)May include surrounding context.

js
/**
 * Delete one of your own posts (DELETE /api/posts/[id], JWT-scoped).
 * Useful for cleaning up test posts. You can only delete your own posts.
 *
 * Usage: node delete-post.js <post-id>

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill declares no explicit tool scope even though it clearly expects environment access and outbound network access. In a least-privilege execution environment, missing scope declarations can cause the agent runtime to grant broader capabilities than necessary or make review of sensitive capabilities harder, especially because the skill handles credentials, session tokens, uploads, and publishing to a public service.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
88% confidence
Finding

The skill includes account login and session management and is designed to persist a JWT-backed session across runs. Persistent sessions increase the window in which a stolen local token, weakly protected state directory, or unintended cross-user environment reuse could let an attacker publish content or act as the user without reauthentication.

Content

Scanner excerpt · SKILL.md (reported line 3)May include surrounding context.

md
---
name: powpow-publisher
description: Publish posts/travelogues to PowPow (global.powpow.online), and create digital humans pinned to the public map. Triggers when the user wants to publish travel content (photos / travelogue / trip stories) to PowPow, e.g. "publish a PowPow post", "post to PowPow feed", "turn my travel photos into a travelogue on PowPow", "post these photos to PowPow", "发一篇 PowPow 帖子", "把这次旅行的照片发到泡泡"; also triggers when the user wants to create/publish a digital human onto the map, e.g. "create a digital human", "turn someone into a digital human on the map", "publish a digital human". Requires a PowPow account (register first if none). Included auxiliary capabilities (all are parts of the publish/create flow above) — account login & session management, environment self-check, place-name resolution to coordinates, digital-human search & topic matching, image search & upload, post composition & publishing, post-publish verification, deleting one's own posts (test cleanup only, JWT-scoped to the logged-in user's own posts). Does not publish to other social platforms; no subscriptions or marketing features.
version: 1.0.5
metadata:
  openclaw:

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
75% 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.

Content

Scanner excerpt · SKILL.md (reported line 102)May include surrounding context.

md
1. **What I can do**: turn your photos or material into a travelogue and publish it to PowPow for you.
  2. **What the result looks like**: a post can carry digital-human tags (a red capsule — tap the avatar to jump into a chat), location tags (pin + place name — tap to open the map), and images; a post with a location appears on the public map as a "bubble" and slowly fades out over time — that's the core of how PowPow works. (Want them to see it right away? Send screenshots — see "Product Screenshots".)
  3. **What's needed to start**: digital humans, locations, and publishing all require the platform, so step one is a one-time login with your PowPow account. No account yet? Register: https://global.powpow.online/register (To learn what PowPow is first, watch this intro video — in Chinese: https://www.bilibili.com/video/BV1Wu826UEVz/ )
  4. **The flow (compressed to 3 beats)**: ① log in → ② you give me material, I write and format → ③ you confirm, only then I publish. Everything is editable until you confirm; nothing goes live without confirmation.
- **Digital-human intent** → short opening: "Give me a name + persona + location, and I'll turn them into a chat-capable digital human pinned to the public map. This costs 2 badges and expires after 30 days — confirm and I'll do it. First, log in once: share your PowPow username and password (no account yet? Register: https://global.powpow.online/register )."

Template (use as a starting point, adapt to the user's language — don't parrot it into template-speak):

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The skill explicitly instructs the agent to ask users for their PowPow username and password directly in chat, while also discouraging a user-facing security warning. Collecting primary credentials through the assistant creates a phishing-like pattern, increases the blast radius if the chat or agent is compromised, and trains users to share passwords with intermediaries instead of authenticating directly with the service.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
91% confidence
Finding

This line confirms that a login token is written to disk in a state directory. Even though the skill avoids persisting the password, disk-persisted bearer tokens are still sensitive secrets; if the host, workspace, or state directory is accessible to other processes or users, the token can be reused to impersonate the account and publish or manage content.

Content

Scanner excerpt · SKILL.md (reported line 168)May include surrounding context.

md
node scripts/login.js <username>
     ```
     then type the password at the prompt (the script reads it hidden, no echo). Either way, **never put the password in a command-line argument**.
   - Never echo the password, never write it to disk, never keep it in a shell variable.
   - The login token lands in the state directory (`POWPOW_STATE_DIR`, default `~/.powpow/session.json`), not inside the skill directory.
   - This is an internal handling rule — don't explain it to the user.
3. **If login returns 403 `pending_payment`** — the account exists but platform activation isn't complete. Stop, don't retry, say: "Your account isn't fully activated yet, so publishing isn't possible right now. Please open PowPow, log in, and follow the on-page instructions to finish activation; tell me when it's done and we'll continue." This is neither a transient error nor a login problem — don't make the user re-enter credentials, and don't keep collecting material.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill states that any provided location is always exposed on the public map and instructs the agent not to offer a hide-from-map option. Although public-map behavior is mentioned elsewhere, this design removes meaningful consent at the point where sensitive location data is transformed into a public post, creating a privacy risk and possible unintended disclosure of whereabouts or habits.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
81% confidence
Finding

This markdown file states the skill publishes posts and digital humans to the public map, which can affect user data visibility and privacy. In the provided text, there is no accompanying warning or caution about the public nature of the action or its implications.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

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.

Content

Scanner excerpt · references/file-structure.md (reported line 19)May include surrounding context.

md
│   ├── geocode.js                    # place name → WGS-84 candidates (user confirms)
│   ├── list-digital-humans.js        # list/search with health filtering
│   ├── match-digital-human.js        # topic → ranked healthy candidates
│   ├── create-digital-human.js       # create a map digital human (2 badges, 30-day expiry; avatar: generate/upload/URL)
│   ├── search-image.js               # Unsplash (user's own key, optional; also resolves photo-page links)
│   ├── upload-image.js               # local image → platform storage → public URL (used by publish.js)
│   └── lib/

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The instruction 'Used at runtime, on demand' and the condition 'when the user has no concept of PowPow, or needs to "see" what the finished result looks like' are subjective and open-ended. This lacks clear trigger boundaries or exclusion conditions, which could cause the skill to be invoked in ordinary conversation more often than intended.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The documentation explicitly states that a live session JWT is persisted to disk in a predictable location under the user's home directory. Even if this is normal application behavior, storing bearer tokens locally without an explicit warning about persistence, file permissions, shared-machine risk, and token theft increases the chance that users or integrators mishandle a credential that can be reused to act as the logged-in user until expiry.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
80% 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.

Content

Scanner excerpt · SKILL.md (reported line 412)May include surrounding context.

md
console.log('Present these to the user to pick. If none fit, in this order:');
    console.log('  1) retry with different wording or --city <城市>;');
    console.log('  2) widen --limit and help the user recognise one from its district/address;');
    console.log('  3) publish without a location. Never ask the user for coordinates.');
  } catch (err) {
    if (err instanceof SessionExpiredError) {
      console.error(`❌ ${err.message}`);

Static analysis

Detected: suspicious.potential_exfiltration

Sensitive-looking file read is paired with a network send.

Warn
Code
suspicious.potential_exfiltration
Location
scripts/lib/api-client.js:66