Back to skill

Security audit

powpow-integration

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real PowPow publishing helper, but it needs Review because it asks for account passwords in chat and can publish, delete, or create public content using a stored session without code-enforced confirmation.

Before installing, be comfortable with the agent receiving your PowPow password in chat and storing a short-lived PowPow session token locally. Use a dedicated account or delete the state directory on shared machines, review drafts carefully, and only run publish, delete, or digital-human creation after you personally confirm the exact content, images, location, account, and badge cost. Maintainer Supabase credentials should only be used by the platform maintainer on a trusted machine.

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:97
Finding

Post publication and image upload proceed without code-enforced confirmation

Content
View full analysis

Vulnerability Details

File Location: scripts/publish.js:97-100, 114-175
Vulnerability Type: Missing confirmation gate for public publication
Risk Level: Medium

Vulnerable Code

js
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`);
js
// 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;
}
js
const postData = {
  type: 'text',
  content,
  ...(locInfo
    ? {
        lng: locInfo.lng,
        lat: locInfo.lat,
        locationName: locInfo.name,
        isLocationExposed: true,
      }
    : { isLocationExposed: false }),
};

console.log('\nPublishing...');
try {
  const post = await publishWithRetry(postData);

Technical Analysis

SKILL.md requires the Agent to show a preview and obtain an explicit publication decision before posting. However, this control exists only as an instruction to the Agent. The executable entry point accepts only an HTML path and does not require a confirmation flag, one-time token, preview digest, or interactive approval.

Once invoked with a valid stored session, the script immediately processes the draft. If the draft contains powpow-local://N references, it uploads the corresponding local files before submitting the post. It then sends the content to POST /api/posts, publishing it u ...[truncated 1392 chars]

Remediation
View remediation

Remediation Suggestions

  1. Require a non-default confirmation argument, such as --confirm-publish <token>, before any upload or publication occurs.
  2. Generate the token only after displaying the final account, content digest, image list, location, and public-map status.
  3. Bind the token to:
    • The authenticated account ID.
    • A cryptographic digest of the final HTML.
    • A digest of the image manifest.
    • The selected location and exposure setting.
    • A short expiration time.
  4. Validate the token before calling uploadLocalImages, ensuring that unconfirmed execution cannot upload local files.
  5. Alternatively, require an interactive confirmation when attached to a terminal and reject non-interactive execution unless a valid confirmation token is supplied.
  6. Preserve the server-side authentication, moderation, and rate-limit controls as defense in depth.

T09 · Insecure Skill Coding Practices

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

Digital-human creation consumes badges and publishes a map entity without code-enforced confirmation

Content
View full analysis

Vulnerability Details

File Location: scripts/create-digital-human.js:147-180
Vulnerability Type: Missing confirmation gate for a non-refundable, public state change
Risk Level: Medium

Vulnerable Code

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

Technical Analysis

The documented workflow states that creating a digital human costs two non-refundable badges, expires after 30 days, and must be confirmed before execution. The script itself only verifies that the balance is sufficient. It does not require a confirmation flag or validate that the user approved the exact name, persona, avatar, location, cost, and expiration terms.

After the balance check, the script may upload a local image or reference ...[truncated 1545 chars]

Remediation
View remediation

Remediation Suggestions

  1. Add a mandatory --confirm-create <token> parameter that is rejected by default when absent.
  2. Before issuing the token, present:
    • The authenticated account.
    • The exact two-badge, non-refundable cost.
    • The current balance.
    • The 30-day expiration.
    • The final persona, avatar strategy, and public location.
  3. Bind the confirmation token to a digest of the final payload and authenticated user ID so it cannot authorize altered arguments.
  4. Validate confirmation before uploading reference images, generating an avatar, or making any other network-side change.
  5. Use short-lived, single-use confirmation tokens to prevent replay.
  6. Retain the existing server-side balance enforcement as a separate control rather than treating it as user confirmation.

T09 · Insecure Skill Coding Practices

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

Authenticated post deletion proceeds without a confirmation gate

Content
View full analysis

Vulnerability Details

File Location: scripts/delete-post.js:9-17
Vulnerability Type: Missing confirmation for destructive deletion
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 accepts a post ID and immediately sends an authenticated DELETE request. It does not retrieve and display the target post, verify that it is a test post, require a confirmation flag, or prompt the user before deletion.

Server-side ownership checks appropriately limit deletion to posts owned by the authenticated user. However, they do not protect the user from an erroneous or manipulated invocation that targets one of their legitimate posts. The script's comment describes test-post cleanup as its intended use, but this intent is not represented by a technical restriction.

Attack Path

  1. The user has a valid stored PowPow session.
  2. A mistaken Agent action, hostile instruction, or other local caller obtains or supplies the ID of a post owned by that user.
  3. The caller runs node scripts/delete-post.js <post-id>.
  4. The script immediately calls DELETE /api/posts/<post-id>.
  5. The server confirms account ownership and deletes the post.
  6. No executable confirmation step intervenes before the destructive operation.

Impact Assessment

The ownership control prevents deletion of other users' posts, so the impact is limited to the authenticated account. Within that boundary, an invocation can permanently remove legitimate user content rather than only test posts.

Potential consequences include content loss, removal of a publicly shared post, and loss of engagement or references associated with that post. The evidence supports an unguarded destruc ...[truncated 45 chars]

Remediation
View remediation

Remediation Suggestions

  1. Require an explicit confirmation argument tied to the post ID, such as --confirm-delete <post-id>.
  2. Fetch the post before deletion and display its author, creation date, content summary, location, and URL.
  3. Require fresh interactive confirmation after showing that summary, or accept a short-lived signed confirmation token in non-interactive Agent workflows.
  4. If automated cleanup is required, mark test posts at creation time and reject deletion through this helper when that marker is absent.
  5. Consider supporting a reversible archive or soft-delete operation where the platform API permits it.
  6. Keep the existing JWT ownership enforcement, but do not treat ownership validation as confirmation of destructive intent.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (32)

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 56)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

Ssd 3

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The skill explicitly instructs the agent to request the user's PowPow username and password directly in chat. Collecting raw credentials through the conversational channel trains users to reveal passwords to an agent, exposes secrets to logging/retention layers, and creates a phishing-like pattern that can compromise the user's account beyond this workflow.

Content

No source excerpt is available for this finding.

Ssd 3

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

This setup step operationalizes the insecure pattern by telling the agent to send a plain message asking for credentials. Even if the skill avoids echoing or persisting the password locally, the credential still traverses the chat system and may be stored in transcripts, analytics, or model context, making account takeover possible if exposed.

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 222)May include surrounding context.

md
node scripts/match-digital-human.js "<topic>" --limit 3 --json # 排序建议

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 # 排序建议

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
node scripts/list-digital-humans.js "<name>" --json # 按名搜索

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 # 按名搜索

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
node scripts/geocode.js "潮白河大桥" --limit 5 # 加 --city 北京 收窄

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- **自动搜图**(仅配了 Unsplash key):`--image search:<english keywords>`(或 `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 273)May include surrounding context.

md
**强制:永远用 `scripts/compose.js` 组装帖子 HTML。绝不手寫组件 span,绝不自己写胶水代码绕开 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
**强制:永远用 `scripts/compose.js` 组装帖子 HTML。绝不手寫组件 span,绝不自己写胶水代码绕开 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 \

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
node scripts/check-theotopia-gods.js # 总数/分册统计 + 完整性检查

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
node scripts/check-theotopia-gods.js # 总数/分册统计 + 完整性检查

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
node scripts/check-theotopia-gods.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 60)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
87% confidence
Finding

The skill has documented capabilities that require environment-variable access and outbound network access, but it does not declare an explicit tool scope or permission boundary. In an agent environment, this weakens least-privilege controls and can let a broadly-privileged runtime execute sensitive operations the user did not clearly authorize.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

Line L296 specifies that the generated preview is '一份中文预览', making Chinese mandatory regardless of the user's language preference. This conflicts with the stated policy category for language/locale constraints because the file elsewhere says users may prefer Chinese or English, but this preview path does not offer a choice.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The natural-language note says the preview page 'is now Chinese', which indicates a fixed language choice rather than offering the user a language or locale option. The policy for this audit flags language/locale requirements when they are imposed without explicit user opt-in or documented regional justification.

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)
│   ├── check-theotopia-gods.js       # maintainer-only read-only inspector for theotopia god rows (Supabase REST, GET-only)
│   ├── 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)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
81% confidence
Finding

The file says the user's password is used to obtain a JWT but does not clearly warn that credentials are transmitted to the platform API and should only be entered when the user trusts the endpoint and transport security. In a skill context, omitting that warning can normalize credential entry without sufficient user awareness, increasing phishing and credential-handling risk.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The document states that a user's JWT session token is written to disk in a predictable location, but it does not explicitly warn about local compromise risk, token theft by other local processes/users, or the need for filesystem protections. Because this token can authenticate as the user for about 7 days, disclosure could enable unauthorized posting or other account actions until expiry.

Content

No source excerpt is available for this finding.

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