Back to skill

Security audit

App Store ReviewReply

Security checks for vulnerabilities and agentic risk

Overview

This skill mostly matches its stated App Store review-automation purpose, but its reply-posting workflow can bypass the promised approval boundary and can delete existing public replies before replacements succeed.

Review before installing. Use the least-privileged App Store Connect key possible, avoid storing live API keys in LaunchAgent plists or shell profiles, and do not enable scheduled runs until you are comfortable with the public posting/deletion behavior. The posting workflow should be fixed to require an approved status and approved_reply only, and replacement of existing replies should require explicit confirmation or rollback handling.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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
Findings (6)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/queue_manager.py:327
Finding

Approval-State Bypass Allows Unapproved or Rejected Replies to Be Published

Content
View full analysis
bool: """Manually retry posting an approved reply.""" queue = load_queue() idx, item = find_queue_item(queue, item_id) if not item: print(f"❌ Item #{item_id} not found in queue") return False reply_text = item.get("approved_reply") or item.get("draft_reply") if not reply_text: print(f"❌ No approved reply for item #{item_id}") return False success = post_reply_to_app_store(item["review_id"], reply_text) if success: now = datetime.now(timezone.utc).isoformat() queue[idx]["status"] = "posted" queue[idx]["posted_at"] = now save_queue(queue) update_review_status(item["review_id"], "posted", replied_at=now) print(f"✅ Reply posted!") return success ``` ### Technical Analysis The declared security model requires human approval before an AI-generated reply is posted. However, `action_post()` does not verify that the selected item has an `approved` status. It also falls back to `draft_reply` when `approved_reply` is absent. Consequently, the method can publish items in `pending`, `rejected`, `skipped`, or other states. This is a direct state-machine authorization bypass: possession of local command-execution access to the script is treated as sufficient approval, even though the application explicitly maintains approval state. Partial review-ID matching in `find_queue_item()` can further increase the chance of selecting an unintended item when abbreviated identifiers are used. ### Attack Path 1. A new negative review is fetched and an AI-generated draft is stored with status `pending`. 2. The draft has not been approved, or it is subsequently marked `rejected`. 3. A local caller or an AI agent invokes: ...[truncated 756 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/queue_manager.py:157
Finding

Non-Atomic Reply Replacement Deletes the Existing Public Reply Before the New Reply Succeeds

Content
View full analysis
Remediation
View remediation

T01 · Skill Instruction Hijacking

Warning
Location
scripts/pattern_detector.py:104
Finding

Untrusted App Store Reviews Can Indirectly Manipulate Complaint Clustering and Alerts

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
README.md:404
Finding

LaunchAgent Setup Stores Long-Lived API Credentials in a Plaintext Property List

Content
View full analysis
EnvironmentVariables APP_STORE_KEY_ID YOUR_KEY_ID APP_STORE_ISSUER_ID YOUR_ISSUER_ID APP_STORE_PRIVATE_KEY_PATH /Users/nick/.appstoreconnect/keys/AuthKey_KEYID.p8 ANTHROPIC_API_KEY sk-ant-... TELEGRAM_BOT_TOKEN YOUR_BOT_TOKEN TELEGRAM_CHAT_ID YOUR_CHAT_ID ``` The instructions place this file at: ```text /Library/LaunchAgents/com.talos.reviewreply.monitor.plist ``` ### Technical Analysis The documented LaunchAgent configuration encourages users to place Anthropic and Telegram credentials directly into a persistent plaintext plist. It also discloses App Store credential identifiers and the private-key path. The guide does not prescribe restrictive ownership or permissions for the plist. A file under `/Library/LaunchAgents` may be exposed more broadly than a user-private mode-600 credential file. Environment variables embedded in service definitions may also be visible through local inspection and diagnostic tooling. The private key itself is stored separately with a recommended mode of `600`, which is appropriate, but the other live credentials receive no equivalent protection in the LaunchAgent setup. ### Attack Path 1. A user follows the documented LaunchAgent example. 2. Placeholder values are replaced with real Anthropic and Telegram credentials. 3. The plist persists those values in plaintext across sessions. 4. Another local user or process with read access inspects the plist or related service configuration. 5. The exposed credentials are copied and used outside the intended Skill. ### Impact Assessment A stolen An ...[truncated 434 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/queue_manager.py:365
Finding

Unescaped Review and Model Content Enables Telegram Markdown Spoofing

Content
View full analysis
str: """Format a single queue item for the Telegram digest.""" num_emoji = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟"] num = num_emoji[idx] if idx < len(num_emoji) else f"{idx+1}." created = item.get("created_date", "")[:10] rating = item.get("rating", "?") app = item.get("app_name", "Unknown") reviewer = item.get("reviewer", "Anonymous") title = item.get("review_title", "") body = item.get("review_body", "") review_text = title if title else body if len(review_text) > PREVIEW_CHARS: review_text = review_text[:PREVIEW_CHARS] + "…" draft = item.get("draft_reply", "") if len(draft) > PREVIEW_CHARS: draft_preview = draft[:PREVIEW_CHARS] + "…" else: draft_preview = draft lines = [ f"{'─'*20}", f"{num} *{app}* — {format_star_bar(rating)} by {reviewer} ({created})", f' _{review_text}_', f"", f" 📝 *Draft:*", f' "{draft_preview}"', f"", f" ✅ `/approve_{idx+1}` ✏️ `/edit_{idx+1}` ❌ `/reject_{idx+1}`", ] return "\n".join(lines) ``` The generated message is sent with Markdown parsing enabled at `scripts/queue_manager.py:462-475`: ```python payload = { "chat_id": TELEGRAM_CHAT_ID, "text": text, "parse_mode": "Markdown", } ``` A similar exposure exists for model-generated themes and review titles in `scripts/pattern_detector.py:286-339`. ### Technical Analysis Reviewer names, review text, AI-generated drafts, and model-generated pattern themes are inserted into Telegram messages without escaping Markdown metacharacters. Telegram is then explicitly instructed to parse the message as Markdown. An App Store reviewer can therefore ...[truncated 1339 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
README.md:23
Finding

Security-Sensitive Dependencies Are Installed Without Version or Integrity Pinning

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Rogue AgentSelf-Modification, Session Persistence
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (59)

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/draft_reply.py (reported line 229)May include surrounding context.

python
)

    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            result = json.loads(resp.read())
            return result["content"][0]["text"].strip()
    except urllib.error.HTTPError as e:

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/monitor.py (reported line 119)May include surrounding context.

python
)

    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            return json.loads(resp.read())
    except urllib.error.HTTPError as e:
        body = e.read().decode("utf-8", errors="replace")

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/monitor.py (reported line 142)May include surrounding context.

python
)

    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            return json.loads(resp.read())
    except urllib.error.HTTPError as e:
        body = e.read().decode("utf-8", errors="replace")

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/pattern_detector.py (reported line 155)May include surrounding context.

python
)

    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            result = json.loads(resp.read())
            raw = result["content"][0]["text"].strip()

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/pattern_detector.py (reported line 344)May include surrounding context.

python
)

    try:
        with urllib.request.urlopen(req, timeout=15) as resp:
            result = json.loads(resp.read())
            if not result.get("ok"):
                print(f"  ⚠️  Telegram API error: {result}")

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/queue_manager.py (reported line 202)May include surrounding context.

python
)

    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            result = json.loads(resp.read())
            if result.get("data"):
                return True

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/queue_manager.py (reported line 480)May include surrounding context.

python
)

    try:
        with urllib.request.urlopen(req, timeout=15) as resp:
            result = json.loads(resp.read())
            if not result.get("ok"):
                print(f"⚠️  Telegram error: {result.get('description', result)}")

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The declared description presents an end-to-end automated review monitoring and approval workflow: connecting to App Store Connect, identifying complaint trends, sending a daily Telegram approval queue, and posting approved replies. This code chunk is much narrower. It reads reviews from local JSON, drafts replies via the Anthropic API for 1–3 star reviews, writes drafts to a local queue file, and updates local reply status. Those drafting behaviors align with part of the description, but several core declared capabilities are absent from the supplied code. Because the actual code implements only the drafting subcomponent rather than the broader described workflow, the description does not accurately represent what this code chunk actually does.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The declared description presents a review-response automation tool whose core function is to draft replies for low-rated reviews and route them through a daily Telegram approval queue before sending. This code chunk instead implements only the pattern-detection/bug-signal portion: it loads reviews from a local JSON file, clusters recurring complaints, applies thresholds and cooldowns, and sends immediate Telegram alerts (or prints reports). While multi-app support and complaint pattern detection are consistent with the description, the major advertised capabilities—reply drafting, approval queue management, scheduled 8am delivery, and sending approved replies—are absent. The Telegram behavior is materially different as well: alerts are immediate upon threshold breach, not part of a daily approval queue. Therefore the code does not accurately match the declared purpose.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description promises a production automation workflow around App Store review monitoring, response drafting, bug-signal detection, daily Telegram approval queues, and multi-app handling. The supplied code chunk does none of that. It merely runs two basic tests against another script: Python syntax compilation and a --help invocation. This is a materially different primary purpose, and none of the key declared capabilities are evidenced in the code shown. Therefore, the description does not accurately represent the supplied code chunk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description promises a production feature for App Store review monitoring and response drafting, with external integrations and scheduled delivery. The actual code chunk does none of that. It merely runs two local tests against monitor.py: syntax compilation and a --help invocation. This is a materially different primary purpose and lacks all core declared capabilities. Therefore, the description does not accurately represent the supplied code chunk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The supplied code does not implement the described App Store review monitoring and response workflow. Instead, it is a simple test file that invokes Python compilation checks and a --help smoke test on queue_manager.py. There is no evidence of App Store access, review processing, message drafting, Telegram integration, scheduling, or multi-app support. This is a clear material mismatch in primary purpose and capabilities.

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

md
**Script:** `scripts/queue_manager.py`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
**Script:** `scripts/queue_manager.py`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
**Script:** `scripts/queue_manager.py`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
**Script:** `scripts/queue_manager.py`

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
**Script:** `scripts/queue_manager.py`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
**Script:** `scripts/queue_manager.py`

YARA rule 'agent_skill_prompt_injection_hidden_instructions': Prompt injection or hidden instructions embedded in AI agent skill text [agent_skills]

High
Category
YARA Match
Confidence
80% confidence
Finding

YARA rule matched a hack tool or exploit indicator (offensive tools, reconnaissance, privilege escalation, or exploit frameworks).

Content

Scanner excerpt · scripts/draft_reply.py (reported line 38)May include surrounding context.

python
A_DIR = SKILL_DIR / "data"
TEMPLATES_DIR = SKILL_DIR / "templates"
REFERENCES_DIR = SKILL_DIR / "references"

DATA_DIR.mkdir(exist_ok=True)

REVIEWS_FILE = DATA_DIR / "reviews.json"
QUEUE_FILE = DATA_DIR / "queue.json"
REPLY_PROMPTS_FILE = TEMPLATES_DIR / "reply-prompts.md"
GUIDELINES_FILE = REFERENCES_DIR / "reply-guidelines.md"

ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY", "")
CLAUDE_MODEL = "claude-opus-4-5"
MAX_REPLY_TOKENS = 400  # App Store reply limit is ~5,970 chars; keep concise

# Only draft for these ratings
DRAFT_RATINGS = {1, 2, 3}


# ─── Data Helpers ─────────────────────────────────────────────────────────────

def load_reviews() -> list:
    if REVIEWS_FILE.exists():
        try:
            return json.loads(REVIEWS_FILE.read_text())
        except json.JSONDecodeError:
            return []
    return []


def save_reviews(reviews

Instruction Override

High
Category
Prompt Injection
Confidence
80% confidence
Finding

This pattern attempts to override system instructions or ignore safety constraints. Without LLM analysis, manual review is recommended.

Content

Scanner excerpt · scripts/draft_reply.py (reported line 122)May include surrounding context.

python
text = text[:2000]
    # Remove common prompt injection patterns
    injection_patterns = [
        "ignore previous instructions",
        "ignore all previous",
        "disregard your instructions",
        "you are now",

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · scripts/draft_reply.py (reported line 194)May include surrounding context.

python
Write a single reply (no quotes, no preamble). The reply should be ready to post directly to the App Store.
"""
    return prompt


# ─── Claude API ───────────────────────────────────────────────────────────────

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The README describes sending low-rated reviews to Claude, posting alerts/digests to Telegram, and storing review data locally, but it does not warn that review text and reviewer identifiers are being transferred to third-party services and retained in local JSON files. This creates a real privacy and compliance risk because operators may process personal data without informed handling controls, minimization guidance, or retention safeguards.

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 · README.md (reported line 36)May include surrounding context.

md
**Quick version:**
1. Sign in to [App Store Connect](https://appstoreconnect.apple.com) → Users & Access → Integrations → App Store Connect API
2. Create key with **Customer Support** role
3. Download the `.p8` file (only shown once!)
4. Note your **Key ID** and **Issuer ID**

Session Persistence

Medium
Category
Rogue Agent
Confidence
85% 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 · README.md (reported line 82)May include surrounding context.

6. Schedule with Cron

bash
crontab -e

Add:

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% 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 · README.md (reported line 386)May include surrounding context.

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>