Back to skill

Security audit

PowPow Simple — 旅行記を地図にピン留めして、会話できるデジタルヒューマンを作成

Security checks for vulnerabilities and agentic risk

Overview

Review recommended: the skill is purpose-aligned but asks for PowPow credentials in chat and can publish, upload, delete, expose locations, and spend account badges using a stored session.

Install only if you are comfortable letting the assistant handle your PowPow login and perform public account actions. Use a dedicated PowPow account if possible, review the exact draft, images, account, badge cost, and map location before any publish/create/delete step, and remove the stored session from POWPOW_STATE_DIR when finished.

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

Public Image Upload and Post Publication Lack an Enforced Confirmation Gate

Content
View full analysis

Vulnerability Details

File Location: scripts/publish.js, lines 114–170
Vulnerability Type: Unconfirmed public publication and data upload
Risk Level: Medium

Technical Analysis

The executable publication path does not require a confirmation flag, interactive prompt, dry-run approval, or confirmation artifact. Once invoked with a valid session and draft path, it uploads every local image referenced by the draft manifest and submits the resulting content as a public post.

Relevant code:

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

SKILL.md lines 302–321 instruct the Agent to obtain explicit approval before publishing. However, this is only a documentary control. publish.js can be called directly and contains no technical mechanism proving that the account owner approved the final draft, images, location, or account identity.

The trust boundary is crossed when local files leave the machine and content is published under the authenticated user’s identity. Authentication and server-side moderation do not establish consent for a particular draft.

A further sequencing concern is ...[truncated 1494 chars]

Remediation
View remediation

Remediation Suggestions

  1. Require a short-lived confirmation artifact before any upload or publication.
  2. Bind the artifact cryptographically or structurally to:
    • The final draft hash.
    • The manifest hash and exact image paths.
    • The authenticated account ID.
    • The selected location and exposure status.
    • An expiration time and one-time nonce.
  3. Reject publication if the draft or manifest changes after approval.
  4. Validate the complete draft and manifest before uploading any image.
  5. Add a --dry-run mode that performs all validation and displays the exact account, images, location, and content without network side effects.
  6. For direct CLI use, require an interactive final prompt unless a valid confirmation artifact is supplied.
  7. Make image upload and post creation transactional where the platform supports it, or provide cleanup for uploads when post creation fails.

T09 · Insecure Skill Coding Practices

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

Digital-Human Creation Can Spend Badges and Publish Data Without Enforced Confirmation

Content
View full analysis

Vulnerability Details

File Location: scripts/create-digital-human.js, lines 154–190
Vulnerability Type: Unconfirmed irreversible resource consumption and public creation
Risk Level: Medium

Technical Analysis

The script documents that creating a digital human consumes two non-refundable badges and that the Agent must confirm with the user before execution. The executable path, however, only checks that the balance is sufficient. It does not check that the user approved the finalized persona, location, avatar, badge cost, or expiration terms.

Relevant code:

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

The balance check prevents creation when funds are insufficient but is not a consent gate. Local avatar or reference images can also be uploaded before the final creation request.

The t ...[truncated 1401 chars]

Remediation
View remediation

Remediation Suggestions

  1. Require an executable confirmation gate after all inputs have been validated but before any upload or badge-consuming request.
  2. Display and bind confirmation to:
    • Authenticated account identity.
    • Exact badge cost and current balance.
    • Non-refundable status.
    • Name and complete persona.
    • Location and coordinates.
    • Avatar source.
    • Thirty-day expiration.
  3. Use a short-lived, one-time confirmation artifact rather than relying solely on Agent instructions.
  4. Reject execution if any confirmed field changes.
  5. Delay local image uploads until confirmation has been verified.
  6. Add a dry-run mode that returns the complete proposed payload and estimated side effects without uploading or creating anything.
  7. For direct CLI use, require an interactive confirmation unless a valid pre-authorized artifact is supplied.

T09 · Insecure Skill Coding Practices

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

Authenticated Post Deletion Lacks an Enforced Confirmation Gate

Content
View full analysis

Vulnerability Details

File Location: scripts/delete-post.js, lines 8–17
Vulnerability Type: Unconfirmed destructive operation
Risk Level: Medium

Technical Analysis

The deletion entry point accepts a post ID and immediately performs an authenticated DELETE request. It has no interactive confirmation, dry-run mode, target preview, or authorization artifact proving the user intended to delete that specific post.

Complete vulnerable operation:

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

Server-side ownership checks reportedly restrict deletion to the authenticated user’s own posts. That control prevents cross-account deletion but does not prevent accidental or Agent-initiated deletion within the current account.

The trust boundary is crossed when a caller-provided identifier is converted directly into permanent deletion of remote account content. The script’s stated test-cleanup purpose is not technically enforced.

Attack Path

  1. A valid PowPow session exists.
  2. An Agent, automation flow, or caller obtains or supplies the ID of a post owned by the logged-in account.
  3. The caller invokes:
    bash
    node scripts/delete-post.js <post-id>
    
  4. The script immediately issues:
    http
    DELETE /api/posts/<post-id>
    
  5. The server deletes the post without the script requiring current user confirmation or verifying that the post is a disposable test post.

Impact Assessment

Successful triggering can delete content owned by the authenticated user. The server-side ownership restriction limits the scope to that account and prevents deletion of another user’s posts.

Potential consequences include:

  • Loss of a legitimate published post.
  • Removal of associated public content a ...[truncated 257 chars]
Remediation
View remediation

Remediation Suggestions

  1. Retrieve and display the target post before deletion, including its ID, author, creation time, location, and a content preview.
  2. Require an interactive confirmation that includes the exact post ID.
  3. For Agent-driven operation, require a short-lived confirmation artifact bound to the account and post ID.
  4. Add a --dry-run option that performs retrieval and ownership validation without deletion.
  5. If the helper is intended only for test cleanup, mark test posts at creation and refuse to delete unmarked posts unless a stronger explicit override is supplied.
  6. Where supported, implement soft deletion or a recovery window instead of immediate irreversible deletion.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (30)

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

md
ジオコーディング → マッチング → 組み立て → 公開 → 検証)のドキュメント化された
  1ステップであり、独立した隠しツールではありません。
- `delete-post.js` はログインユーザー自身の投稿のみを削除します
  (`DELETE /api/posts/{id}`。JWTでスコープされ、サーバー側で強制されます)。
  テスト後片付けのために SKILL.md ステップ8でドキュメント化されています。
  この能力はスキルの description でも宣言されています。
- 機密情報は同梱されていません。`config.json` にはデフォルト値のみが含まれます。

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

md
ジオコーディング → マッチング → 組み立て → 公開 → 検証)のドキュメント化された
  1ステップであり、独立した隠しツールではありません。
- `delete-post.js` はログインユーザー自身の投稿のみを削除します
  (`DELETE /api/posts/{id}`。JWTでスコープされ、サーバー側で強制されます)。
  テスト後片付けのために SKILL.md ステップ8でドキュメント化されています。
  この能力はスキルの description でも宣言されています。
- 機密情報は同梱されていません。`config.json` にはデフォルト値のみが含まれます。

Tp2

High
Category
MCP Tool Poisoning
Confidence
85% confidence
Finding

Mixing characters from multiple Unicode scripts in a single identifier is a common technique to create visually ambiguous tool names.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The skill instructs the user to send their PowPow username and password through the assistant conversation, which creates a direct credential-harvesting risk and unnecessarily exposes secrets to the assistant platform, logs, and any downstream retention systems. Even though the skill discusses careful handling once received, the dangerous step is collecting raw credentials via chat at all.

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 "<トピック>" --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 "<トピック>" --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 218)May include surrounding context.

md
node scripts/list-digital-humans.js "<名前>" --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 "<名前>" --json # 名前で検索

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 # --city 北京 で絞り込み可

Missing User Warnings

High
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill mandates that any specified location be published on a public map and explicitly forbids offering a hide-from-map option, while the description does not present this as a prominent privacy consequence. This can cause unintended disclosure of sensitive travel history, home/work patterns, or other location-linked personal data.

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
- **自動画像検索**(Unsplashキー設定時のみ): `--image search:<英語キーワード>`(または `node scripts/search-image.js "<キーワード>"`)

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
**必須: 投稿HTMLは必ず `scripts/compose.js` で組み立てる。コンポーネントの 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
**必須: 投稿HTMLは必ず `scripts/compose.js` で組み立てる。コンポーネントの 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 \

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` — ローカル画像のアップロード(multipart `file`。ログイン必須)
- `POST /api/posts` — 投稿の作成(ログイン必須。サーバーがレート制限とモデレーションを強制)
- `GET  /api/posts/{id}` — 投稿の検証
- `DELETE /api/posts/{id}` — 自身の投稿の削除(テスト後片付け)
- `POST /api/digital-humans` — デジタルヒューマンの作成(ログイン必須。バッジ2個を消費、
  30日で失効)
- `POST /api/digital-humans/generate-avatar` — ペルソナからのAIアバター生成

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

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

Ssd 3

Medium
Category
Not specified by scanner
Confidence
68% confidence
Finding

This line tells the agent to have the user paste their own prior writing and then analyze rhythm, vocabulary, habits, and punctuation to reproduce that voice. Although framed as personalization, it is a natural-language instruction to collect and reuse user-provided content characteristics, which can expose or propagate sensitive personal writing patterns beyond the immediate task.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The instruction says to use this file 'as needed at runtime' and to send links when the user says they want to see the finished product, but it does not clearly bound activation conditions or exclusions. Phrases like wanting an 'image' or to 'see' something are common in ordinary conversation, which can cause unintended invocation of this skill.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The screenshots are explicitly described as showing public-map and digital-human pages, and the detailed example notes visible location/address information. Sending those images without warning can expose sensitive or personally identifying location context to users unexpectedly, especially if the screenshots depict real or realistic addresses tied to a person-like profile.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
80% confidence
Finding

The document instructs readers not to mention the actual provider/path to users and to use a specific neutral term instead. More broadly, the file is entirely written in Japanese and these lines prescribe fixed terminology for communication without any opt-in or stated regional/compliance reason, which can violate language/locale choice expectations.

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

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The file sets DEFAULT_ACCEPT_LANGUAGE to zh-CN,zh;q=0.9,en;q=0.8 and uses it automatically during login and subsequent API calls. This imposes a specific language/locale preference by default, and the nearby user-facing error string is also Chinese, with no indication that the user can choose or opt into this locale.

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