Back to skill

Security audit

亚马逊Listing优化 · 评论驱动文案

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to be a real ARI Amazon review/listing assistant, but it has review-worthy billing, account-setting, monitoring, and local export powers beyond a narrow listing-optimization headline.

Review this skill before installing if you do not want an agent to spend ARI credits under account auto-confirm rules, alter future confirmation thresholds, manage monitoring or workbench state, or write exports to arbitrary local paths. If installed, set autoconfirm off, use "only quote, do not execute" for price checks, and give explicit file paths only when you are comfortable overwriting them.

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 (1)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/ari.py:1474
Finding
Arbitrary Local File Overwrite Through the Export Output Path<![CDATA[ ## Vulnerability Details **File Location**: `scripts/ari.py:1474-1475`, `scripts/ari.py:1614-1627`, and `scripts/ari.py:1929` **Vulnerability Type**: Unrestricted file write and destructive overwrite **Risk Level**: Medium ### Vulnerable Code ```python def request_download(path, params, dest): """Download a non-JSON response to a local file.""" query = {"method": "GET", "path": path, "params": {k: v for k, v in (params or {}).items() if v not in (None, "")}, "payload": None} url = base_url() + path if query["params"]: url += "?" + urllib.parse.urlencode(query["params"], doseq=True) headers = {"Authorization": "Bearer " + require_key(), "User-Agent": user_agent()} try: req = urllib.request.Request(url, headers=headers, method="GET") with urllib.request.urlopen(req, timeout=TIMEOUT_SEC) as resp: note_release(resp.headers) ctype = resp.headers.get("Content-Type", "") body = resp.read() if "application/json" in ctype: out = json.loads(body.decode("utf-8", errors="replace")) if isinstance(out, dict): out["_query"] = query return out tail = body[-300:].decode("utf-8", errors="replace") if "# export error:" in tail: return error_obj("ARI_EXPORT_ERROR", 200, tail.split("# export error:", 1)[1].strip(), "Export failed before completion; the incomplete file was not saved.", query) with open(dest, "wb") as fh: fh.write(body) return {"success": True, "data": {"savedTo": os.path.abspath(dest), "bytes": len(body), "contentType": ctype.split(";")[0].strip()}, "_query": query, "links": links()} ``` ```python def cmd_export(args): ...[truncated 3672 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Use a dedicated export directory by default** - Create a directory such as `~/.ari/exports`. - Resolve the canonical destination and ensure it remains inside that directory. 2. **Prevent silent replacement** - Open new files with exclusive creation mode, such as `open(path, "xb")`. - Return an error when the destination already exists. - Add an explicit `--force` option for intentional replacement. 3. **Defend against symbolic-link attacks** - Reject destinations that are symbolic links. - On supported systems, use `os.open()` with `O_NOFOLLOW`. - Validate both the resolved parent directory and final destination. 4. **Require explicit approval for destinations outside the export directory** - Display the canonical absolute path. - Require a dedicated flag or direct user confirmation before writing there. - Agents should not infer this approval from unrelated export requests. 5. **Use atomic writes** - Download into a securely created temporary file in the destination directory. - Validate the response type and completion status. - Flush and synchronize the file if required. - Atomically rename it to the final destination only after all validation succeeds. 6. **Apply restrictive permissions** - Create exported files with conservative permissions, such as `0600`, unless sharing is explicitly requested. ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (29)

Tainted flow: 'req' from os.environ.get (line 1459, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
headers = {"Authorization": "Bearer " + require_key(), "User-Agent": user_agent()}
    try:
        req = urllib.request.Request(url, headers=headers, method="GET")
        with urllib.request.urlopen(req, timeout=TIMEOUT_SEC) as resp:
            note_release(resp.headers)
            ctype = resp.headers.get("Content-Type", "")
            body = resp.read()
Confidence
74% confidence
Finding
request_download() sends the Bearer API key to base_url(), and unlike the listing-optimization core, export writes data to local disk after contacting a potentially overridden endpoint. Although custom base use requires ARI_ALLOW_CUSTOM_BASE=1, once enabled the CLI will transmit credentials and downloaded content to any HTTPS host the environment specifies, which is risky in a skill context handling paid account data and exports.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill declares no permissions while instructing use of shell execution, network access, environment variables, and local file writes. This creates a transparency and sandboxing gap: a reviewer or runtime may underestimate the skill’s ability to execute commands, persist secrets, or export data locally.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The published description frames the skill as a narrow listing-optimization tool, but the body authorizes a much broader operational surface including billing-affecting actions, account checks, monitor management, exports, and workflow execution. This mismatch can mislead users into invoking a tool that performs materially different and more privileged actions than expected.

Description-Behavior Mismatch

Medium
Confidence
90% confidence
Finding
The skill metadata says it is for Amazon listing optimization from reviews, but the code exposes much broader account-level capabilities: product operations, monitoring watches, alerts, exports, leaderboard queries, workbench status updates, and competitor management. This scope expansion violates least privilege and increases the blast radius if the skill is invoked or prompted unexpectedly, especially because several commands are state-changing or paid.

Context-Inappropriate Capability

Medium
Confidence
82% confidence
Finding
The skill persists API keys in ~/.ari/config.json and supports writing exports to arbitrary local paths, neither of which is clearly justified by a narrow 'listing optimization' skill description. Persistent local writes expand the trust boundary from transient analysis to long-lived secret and data storage, raising the risk of credential leakage, accidental syncing, or misuse by other local processes.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
Watch management, monitoring, digest retrieval, event feeds, and related state-changing account features are outside the advertised listing-copy optimization purpose. In a skill ecosystem, this mismatch is dangerous because users or orchestrating agents may grant trust based on the manifest while the code can perform broader account actions and surveillance-like monitoring.

Description-Behavior Mismatch

Medium
Confidence
88% confidence
Finding
The documentation exposes and normalizes a much broader capability set than the declared skill purpose of Amazon listing optimization from buyer reviews. This kind of scope drift is dangerous because an agent may be induced to perform unrelated high-impact actions such as data export, monitoring, paid analysis, or account operations under the trusted cover of a narrowly named skill, weakening user consent and least-privilege expectations.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
The skill documentation includes monitoring, alerts, watch management, exports, benchmarking, and operations workflows that exceed what a listing-optimization skill should need. In skill-based agent environments, this is dangerous because it expands the action surface to persistent automation, competitive tracking, and potentially billable or sensitive account actions that a user would not reasonably expect from the advertised context.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
1. 运行 `check`,确认账户、邮箱验证状态和可用积点。
2. 用户要 VOC / 评论分析报告时,默认运行 `voc <ASIN> --site <站点>`。
   **返回里有 `autoConfirmed: true` 就说明已经直接生成了**(1.4.5 起:服务端对前几次小额
   付费操作免确认,用户先拿到结果再谈钱),此时把报告讲给用户,并转述 `autoConfirmNote`
   (本次扣了多少、还剩几次免确认、之后会先问)。**不要在拿到结果后再补问「要不要生成」。**
3. 返回 `confirmationRequired: true` 才需要用户确认:报出 `totalCredits` 与余额,
Confidence
98% confidence
Finding
This instruction permits automatic execution of paid report generation when the service returns autoConfirmed, without obtaining transaction-specific consent at runtime. Even if the backend supports this mode, the agent is being directed to trigger billable actions on behalf of the user based on a service-side policy rather than fresh user approval.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
1. 运行 `check`,确认账户、邮箱验证状态和可用积点。
2. 用户要 VOC / 评论分析报告时,默认运行 `voc <ASIN> --site <站点>`。
   **返回里有 `autoConfirmed: true` 就说明已经直接生成了**(1.4.5 起:服务端对前几次小额
   付费操作免确认,用户先拿到结果再谈钱),此时把报告讲给用户,并转述 `autoConfirmNote`
   (本次扣了多少、还剩几次免确认、之后会先问)。**不要在拿到结果后再补问「要不要生成」。**
3. 返回 `confirmationRequired: true` 才需要用户确认:报出 `totalCredits` 与余额,
   用户同意后运行 `voc <ASIN> --site <站点> --confirm`。该命令会自动补齐采集、等待任务完成、
Confidence
97% confidence
Finding
The workflow tells the agent to proceed directly from a quote/analysis request into a confirmable paid execution path once a minimal acknowledgment is received, increasing the chance of ambiguous or coerced consent. In a conversational setting, weak confirmation handling can lead to unintended charges or overbroad execution.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
用户只说自然语言。网页是补充视图(图表、分享链接、海报),不是把人送走的地方。

**确认与扣点**
- 报价返回 `autoConfirm: true` 时直接生成,不要再问「要不要」。生成后一句话交代:本次扣了多少、
  还剩几次免确认(或「免费版小额不问」)。策略由服务端决定:免费版小额不问;付费版前几次不问,之后先问。
- 用户说「以后别问了 / 50 以内直接做」→ 运行 `autoconfirm 50`;说「以后每次先问我」→ `autoconfirm off`;
  说「恢复默认」→ `autoconfirm default`。这是唯一需要你代用户设置的东西,设完复述一句当前规则。
Confidence
98% confidence
Finding
The skill explicitly instructs the agent to generate paid outputs immediately when autoConfirm is returned, removing an important user-consent checkpoint. This is dangerous because it normalizes backend-driven spending decisions and may train users to accept charges after the fact.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
**确认与扣点**
- 报价返回 `autoConfirm: true` 时直接生成,不要再问「要不要」。生成后一句话交代:本次扣了多少、
  还剩几次免确认(或「免费版小额不问」)。策略由服务端决定:免费版小额不问;付费版前几次不问,之后先问。
- 用户说「以后别问了 / 50 以内直接做」→ 运行 `autoconfirm 50`;说「以后每次先问我」→ `autoconfirm off`;
  说「恢复默认」→ `autoconfirm default`。这是唯一需要你代用户设置的东西,设完复述一句当前规则。
- 报价需要确认时,只说两个数:这次多少积点、余额多少,然后等用户一个「好」。采集是**固定单价**:直接说「15 积点/页 × 3 页 = 45 积点」,不要说成「预计 / 最多」——价格不会浮动;商品评论不够这么多页时只收实际采到的页数,差额自动退回(`pricingNote` 已写好这句)。不要罗列参数。
Confidence
95% confidence
Finding
The same line couples conversational interpretation with a privileged settings mutation (`autoconfirm off/default/threshold`), which expands risk beyond the current session. Persistent consent-state changes are more sensitive than one-off actions and should not be inferred from terse chat phrases without hard confirmation.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
**确认与扣点**
- 报价返回 `autoConfirm: true` 时直接生成,不要再问「要不要」。生成后一句话交代:本次扣了多少、
  还剩几次免确认(或「免费版小额不问」)。策略由服务端决定:免费版小额不问;付费版前几次不问,之后先问。
- 用户说「以后别问了 / 50 以内直接做」→ 运行 `autoconfirm 50`;说「以后每次先问我」→ `autoconfirm off`;
  说「恢复默认」→ `autoconfirm default`。这是唯一需要你代用户设置的东西,设完复述一句当前规则。
- 报价需要确认时,只说两个数:这次多少积点、余额多少,然后等用户一个「好」。采集是**固定单价**:直接说「15 积点/页 × 3 页 = 45 积点」,不要说成「预计 / 最多」——价格不会浮动;商品评论不够这么多页时只收实际采到的页数,差额自动退回(`pricingNote` 已写好这句)。不要罗列参数。
Confidence
95% confidence
Finding
The same line couples conversational interpretation with a privileged settings mutation (`autoconfirm off/default/threshold`), which expands risk beyond the current session. Persistent consent-state changes are more sensitive than one-off actions and should not be inferred from terse chat phrases without hard confirmation.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
| `watch digest` | product-operations/watch-digest(GET) | 否;确定性摘要,`creditsUsed: 0` |
| `watch events` | product-operations/events(GET) | 否;读取确定性变化事件 |
| `analyze` | analysis/voc·keywords·insight·trend·variant·compare | 是;`--confirm` 或服务端 autoConfirm 命中 |
| `autoconfirm [N\|off\|default]` | user/autoconfirm(GET/PUT) | 否;设置免确认阈值(1.4.5) |
| `deepdive` | products + charts + reviews + reports + VOC quote/analysis | 默认否;`--confirm` 才分析 |
| `reports` / `report` | reports | 否 |
| `alerts` | alerts(`--mark-read` 时 alerts/read) | 否 |
Confidence
77% confidence
Finding
The same `autoconfirm` capability at this line is materially risky because it modifies a persistent consent control rather than performing a one-time action. If an agent can invoke it, later paid `voc`/`analyze` operations may execute automatically under the altered threshold, creating billing and consent issues.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
| `watch digest` | product-operations/watch-digest(GET) | 否;确定性摘要,`creditsUsed: 0` |
| `watch events` | product-operations/events(GET) | 否;读取确定性变化事件 |
| `analyze` | analysis/voc·keywords·insight·trend·variant·compare | 是;`--confirm` 或服务端 autoConfirm 命中 |
| `autoconfirm [N\|off\|default]` | user/autoconfirm(GET/PUT) | 否;设置免确认阈值(1.4.5) |
| `deepdive` | products + charts + reviews + reports + VOC quote/analysis | 默认否;`--confirm` 才分析 |
| `reports` / `report` | reports | 否 |
| `alerts` | alerts(`--mark-read` 时 alerts/read) | 否 |
Confidence
77% confidence
Finding
The same `autoconfirm` capability at this line is materially risky because it modifies a persistent consent control rather than performing a one-time action. If an agent can invoke it, later paid `voc`/`analyze` operations may execute automatically under the altered threshold, creating billing and consent issues.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
}


def cmd_autoconfirm(args):
    """免确认阈值:不带参数=查看;`autoconfirm 50`=50 积点以内不问;`autoconfirm off`=每次都问;`autoconfirm default`=恢复默认。"""
    value = (args.value or "").strip().lower()
    if value:
Confidence
84% confidence
Finding
The skill exposes a command to change autoconfirm thresholds for paid operations, allowing future chargeable actions to proceed without per-action user confirmation. In an agent skill context, modifying this account-level setting broadens the agent's autonomy and can normalize silent spending beyond the narrow listing-optimization purpose.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
def cmd_autoconfirm(args):
    """免确认阈值:不带参数=查看;`autoconfirm 50`=50 积点以内不问;`autoconfirm off`=每次都问;`autoconfirm default`=恢复默认。"""
    value = (args.value or "").strip().lower()
    if value:
        if value in ("off", "ask", "0"):
Confidence
84% confidence
Finding
This command processes user-supplied values to disable or relax confirmation requirements for future paid actions. In a conversational/agent setting, that increases the chance of unintended charges or policy changes that persist beyond the current task.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
def cmd_autoconfirm(args):
    """免确认阈值:不带参数=查看;`autoconfirm 50`=50 积点以内不问;`autoconfirm off`=每次都问;`autoconfirm default`=恢复默认。"""
    value = (args.value or "").strip().lower()
    if value:
        if value in ("off", "ask", "0"):
Confidence
84% confidence
Finding
This command processes user-supplied values to disable or relax confirmation requirements for future paid actions. In a conversational/agent setting, that increases the chance of unintended charges or policy changes that persist beyond the current task.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
def cmd_autoconfirm(args):
    """免确认阈值:不带参数=查看;`autoconfirm 50`=50 积点以内不问;`autoconfirm off`=每次都问;`autoconfirm default`=恢复默认。"""
    value = (args.value or "").strip().lower()
    if value:
        if value in ("off", "ask", "0"):
Confidence
84% confidence
Finding
This command processes user-supplied values to disable or relax confirmation requirements for future paid actions. In a conversational/agent setting, that increases the chance of unintended charges or policy changes that persist beyond the current task.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
limit = int(value)
            except ValueError:
                emit(error_obj("ARI_BAD_ARGUMENT", 0, "参数不对",
                               "用法:autoconfirm 50(50 积点以内不问)/ autoconfirm off(每次都问)/ autoconfirm default(恢复默认)"),
                     args.compact)
                return
        out = request_json("PUT", "/api/v1/user/autoconfirm", {"limit": limit})
Confidence
86% confidence
Finding
The PUT to /api/v1/user/autoconfirm is a state-changing account action that alters how future paid operations are authorized. Because this setting persists and affects subsequent commands, it is more dangerous than a one-off confirmation bypass and exceeds expected behavior for a review-copy optimization tool.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
limit = int(value)
            except ValueError:
                emit(error_obj("ARI_BAD_ARGUMENT", 0, "参数不对",
                               "用法:autoconfirm 50(50 积点以内不问)/ autoconfirm off(每次都问)/ autoconfirm default(恢复默认)"),
                     args.compact)
                return
        out = request_json("PUT", "/api/v1/user/autoconfirm", {"limit": limit})
Confidence
86% confidence
Finding
The PUT to /api/v1/user/autoconfirm is a state-changing account action that alters how future paid operations are authorized. Because this setting persists and affects subsequent commands, it is more dangerous than a one-off confirmation bypass and exceeds expected behavior for a review-copy optimization tool.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
limit = int(value)
            except ValueError:
                emit(error_obj("ARI_BAD_ARGUMENT", 0, "参数不对",
                               "用法:autoconfirm 50(50 积点以内不问)/ autoconfirm off(每次都问)/ autoconfirm default(恢复默认)"),
                     args.compact)
                return
        out = request_json("PUT", "/api/v1/user/autoconfirm", {"limit": limit})
Confidence
86% confidence
Finding
The PUT to /api/v1/user/autoconfirm is a state-changing account action that alters how future paid operations are authorized. Because this setting persists and affects subsequent commands, it is more dangerous than a one-off confirmation bypass and exceeds expected behavior for a review-copy optimization tool.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
if not ok(quote):
        return quote
    q_data = data_of(quote) or {}
    # 首次体验免确认(服务端策略 skill.autoConfirm):前几次小额直接生成,不再多问一轮。
    auto_confirmed = False
    if not confirm and q_data.get("autoConfirm") and q_data.get("sufficient"):
        confirm = True
Confidence
89% confidence
Finding
run_analysis() automatically flips confirm to true when the server says autoConfirm is allowed and the balance is sufficient, causing paid analysis to execute without an explicit per-request confirmation flag. In a CLI this may be a convenience feature, but in an agent skill it weakens the user's expectation that omission of --confirm prevents spending.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
return quote
    q_data = data_of(quote) or {}
    # 首次体验免确认(服务端策略 skill.autoConfirm):前几次小额直接生成,不再多问一轮。
    auto_confirmed = False
    if not confirm and q_data.get("autoConfirm") and q_data.get("sufficient"):
        confirm = True
        auto_confirmed = True
Confidence
89% confidence
Finding
This branch marks an analysis as auto-confirmed and proceeds to a chargeable action without explicit immediate confirmation from the caller. Persistent or implicit spending behavior is especially risky in an agent-mediated environment where prompts and intent can be ambiguous.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
q_data = data_of(quote) or {}
    # 首次体验免确认(服务端策略 skill.autoConfirm):前几次小额直接生成,不再多问一轮。
    auto_confirmed = False
    if not confirm and q_data.get("autoConfirm") and q_data.get("sufficient"):
        confirm = True
        auto_confirmed = True
    if not confirm:
Confidence
89% confidence
Finding
The code converts a non-confirmed request into a confirmed one based on server policy, enabling autonomous execution of a billable analysis. This is dangerous because it changes the safety invariant from 'no --confirm means quote only' to 'sometimes runs anyway.'

Static analysis

No suspicious patterns detected.