Back to skill

Security audit

PowPow Simple — post travelogues to the map, create chat-capable digital humans

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent PowPow publishing helper, but it asks for account passwords in chat and gives direct scripts authenticated power to publish, create paid-badge objects, or delete posts without script-enforced confirmation.

Review carefully before installing. Use only if you are comfortable giving this assistant PowPow credentials, storing a temporary local session token, and allowing confirmed actions to publish public posts, expose chosen locations, upload selected images, spend PowPow badges, or delete your own posts. Avoid sensitive home/work locations or private images, and clear the PowPow session when done.

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

Warning
Location
scripts/publish.js:94
Finding

Public post publishing lacks code-enforced user confirmation

Content
View full analysis

Vulnerability Details

File Location: scripts/publish.js:94-173
Vulnerability Type: Missing authorization confirmation for an externally visible operation
Risk Level: Medium

Vulnerable Code

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.forEach(i => console
...[truncated 2598 chars]
Remediation
View remediation

Remediation Suggestions

  1. Require a short-lived approval artifact before publication.
  2. Bind the approval artifact to:
    • The authenticated account identifier.
    • A cryptographic hash of the final HTML.
    • A hash and path list for all local images.
    • The location and public-map visibility settings.
    • An expiration time and one-time nonce.
  3. Have the preview or confirmation stage create the artifact only after explicit user approval.
  4. Reject publication when the artifact is absent, expired, reused, or does not match the current draft.
  5. Alternatively, add an interactive confirmation immediately before the first upload and default to refusal in non-interactive environments.
  6. Provide a --dry-run mode that performs all local validation without uploading images or creating a post.
  7. Revalidate the draft and image hashes after confirmation to prevent changes between approval and publication.

T09 · Insecure Skill Coding Practices

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

Digital-human creation can consume non-refundable badges without code-enforced confirmation

Content
View full analysis

Vulnerability Details

File Location: scripts/create-digital-human.js:144-190
Vulnerability Type: Missing authorization confirmation for resource-consuming operation
Risk Level: Medium

Vulnerable Code

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.badgesRemaining;

Technical Analysis

...[truncated 1801 chars]

Remediation
View remediation

Remediation Suggestions

  1. Require a one-time confirmation token before resolving or uploading the avatar.
  2. Bind the token to:
    • The authenticated user ID.
    • The exact two-badge cost.
    • Name and persona hashes.
    • Avatar strategy and source.
    • Final coordinates and location name.
    • Expiration time and nonce.
  3. Reject execution when the confirmation token is missing or does not match the final payload.
  4. If an interactive design is preferred, show the complete payload, current balance, resulting balance, non-refundable status, and 30-day expiry immediately before submission.
  5. Require an explicit response such as CREATE and fail closed when standard input is unavailable.
  6. Separate preparation from creation so avatar validation and local preview can occur in a non-charging --dry-run mode.
  7. Repeat the balance check immediately before submission and display any balance change that occurred after confirmation.

T09 · Insecure Skill Coding Practices

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

Authenticated post deletion lacks confirmation and test-post scope enforcement

Content
View full analysis

Vulnerability Details

File Location: scripts/delete-post.js:9-18
Vulnerability Type: Unconfirmed destructive remote operation
Risk Level: Medium

Vulnerable Code

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 is described as a test-cleanup utility, but it accepts any supplied post identifier and immediately sends an authenticated DELETE request. It does not verify that the target was created by the current workflow, that it is marked as a test post, or that the user approved deleting it.

Server-side JWT ownership enforcement prevents deletion of another user’s post, but it does not distinguish disposable test content from valuable content belonging to the authenticated user. The absence of an interactive confirmation, dry-run mode, or creation-bound cleanup token makes the destructive action directly reachable from a single command.

Attack Path

  1. The user has a valid stored PowPow session.
  2. An Agent, injected workflow instruction, or another local caller obtains the identifier of a post owned by that user.
  3. The caller invokes:
    bash
    node scripts/delete-post.js <post-id>
    
  4. The script performs no metadata preview, test-post validation, or confirmation.
  5. It sends DELETE /api/posts/<post-id> with the user’s JWT.
  6. The server deletes the user-owned post.

Impact Assessment

A post owned by the authenticated user can be deleted without executable proof of consent. The reachable scope is limited by server-side ownership checks to posts belonging to that account, but it is not limited to test posts despite the utility’s documented cleanup purpose.

The direct impact is loss of remote user content and its public availability. No evidence sh ...[truncated 93 chars]

Remediation
View remediation

Remediation Suggestions

  1. Fetch and display the target post’s author, creation time, text summary, location, and identifier before deletion.
  2. Require explicit confirmation bound to the exact post ID, defaulting to refusal.
  3. Add a --dry-run mode that retrieves and displays the target without deleting it.
  4. For automated test cleanup, record a one-time cleanup token when the test post is created and require that token for deletion.
  5. Verify that the token is bound to the current authenticated account and post ID.
  6. Refuse automated cleanup for posts that lack the test-workflow marker; require a separate interactive deletion path for other posts.
  7. Log the approved post ID and deletion result without recording the JWT or other credentials.
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 63)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

Missing User Warnings

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill normalizes asking users to provide their account password directly to the assistant and explicitly discourages meaningful user-facing warning or safer alternatives. This is dangerous because it trains users to surrender credentials in conversational interfaces and removes informed-consent friction around a highly sensitive secret.

Content

No source excerpt is available for this finding.

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

Missing User Warnings

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill mandates that any provided location be published publicly on the map and forbids offering a hide/opt-out choice. This creates a privacy and safety risk because users may share a place name for drafting convenience without realizing it will be exposed publicly, potentially revealing sensitive travel patterns, home/work locations, or real-time whereabouts.

Content

No source excerpt is available for this finding.

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
89% confidence
Finding

The skill declares executable/network-capable behavior but does not define an explicit tool/permission scope. In practice this widens the trusted surface area: the agent is instructed to use Node scripts, environment variables, persistent state, and outbound network access without a machine-readable restriction boundary, increasing the chance of unintended tool use or policy bypass.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
78% confidence
Finding

The skill advertises account login and session management as part of routine operation, implying persistent authenticated state across runs. Persisted sessions are not automatically unsafe, but they become a real risk here because the skill handles high-sensitivity actions on a public platform and stores bearer-style session material locally, which could be reused if the state directory is accessible to other processes or users.

Content

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

md
---
name: "PowPow Simple EN - Publish posts & digital humans to the public map"
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.1
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):

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The skill explicitly tells the agent to solicit a user's PowPow username and password in chat, then pipe the password into a login script. Even if the script tries to avoid shell-history exposure, collecting primary credentials through the assistant creates a phishing-like flow and exposes secrets to the chat system, logs, model context, and any downstream handling mistakes.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
91% confidence
Finding

The skill instructs storing the login token in a predictable local path under the user's home directory. A persistent local session token is sensitive authentication material; if the file is readable by other local users, backup agents, malware, or overly broad tooling, an attacker could reuse it to publish/delete content as the victim without needing the password.

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.

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
91% confidence
Finding

The activation description says to use this at runtime 'on demand' when the user 'has no concept of PowPow, or needs to "see" what the finished result looks like,' which is broad and subjective. It does not define clear trigger boundaries or exclusions, so the skill could be invoked for many ordinary explanation requests without a precise scope.

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}`);

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 · scripts/geocode.js (reported line 81)May include surrounding context.

js
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}`);