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<![CDATA[ ## Vulnerability Details **File Location**: `scripts/queue_manager.py:327-346` **Vulnerability Type**: Authorization and workflow-state validation failure **Risk Level**: High ### Vulnerable Code ```python def action_post(item_id: str) -> 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
<![CDATA[ ## Remediation Suggestions - Require the item to have exactly `status == "approved"` before posting. - Require a non-empty `approved_reply`; never fall back to `draft_reply` in the retry operation. - Reject posting for `pending`, `rejected`, `skipped`, and `posted` entries. - Use immutable, full queue-item identifiers rather than ambiguous prefixes or changing sequential positions. - Record approval metadata, including approval time and approving identity or channel. - Add tests proving that pending, rejected, skipped, and already-posted items cannot be posted. Example hardening: ```python if item.get("status") != "approved": print(f"❌ Item is not approved (status: {item.get('status')})") return False reply_text = item.get("approved_reply") if not isinstance(reply_text, str) or not reply_text.strip(): print("❌ Approved reply is empty") return False ``` ]]>

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<![CDATA[ ## Vulnerability Details **File Location**: `scripts/queue_manager.py:157-208` **Vulnerability Type**: Destructive, non-atomic remote update **Risk Level**: High ### Vulnerable Code ```python # Check if a response already exists, delete it if so url = f"{ASC_BASE_URL}/customerReviews/{review_id}/response" check_req = urllib.request.Request( url, headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, ) try: with urllib.request.urlopen(check_req, timeout=15) as resp: existing = json.loads(resp.read()) if existing.get("data"): # Delete existing response first existing_id = existing["data"]["id"] del_req = urllib.request.Request( f"{ASC_BASE_URL}/customerReviewResponses/{existing_id}", headers={"Authorization": f"Bearer {token}"}, method="DELETE", ) urllib.request.urlopen(del_req, timeout=15) except urllib.error.HTTPError as e: if e.code != 404: print(f" ⚠️ Could not check existing response: {e.code}") # Post new response payload = { "data": { "type": "customerReviewResponses", "attributes": {"responseBody": reply_text}, "relationships": { "review": { "data": {"type": "customerReviews", "id": review_id} } }, } } data = json.dumps(payload).encode("utf-8") req = urllib.request.Request( f"{ASC_BASE_URL}/customerReviewResponses", data=data, headers={ "Authorization": f"Bearer {token}", "Content-Type": "application/json", }, method="POST", ) try: with urllib.request.urlopen(req, timeout=30) as resp: result = json.loads(resp.read()) if result.get("data"): return True print(f" ⚠️ Unexpected API response: {result}") return False except urllib.error.HTTPError as e: body = e.read().decode("utf-8", errors="replace") p ...[truncated 1563 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Prefer an official update endpoint if App Store Connect supports updating an existing customer-review response. - If replacement necessarily requires delete-and-create: - Validate reply length, encoding, and policy constraints before deletion. - Preserve the existing response body and response identifier locally. - Require explicit user confirmation before replacing an existing response. - Attempt to restore the previous response if creation fails. - Record an auditable replacement state so interrupted operations can be recovered. - Distinguish between “create new response” and “replace existing response” in both the CLI and approval workflow. - Add failure-injection tests covering timeouts, HTTP 4xx/5xx responses, and rollback behavior. ]]>

T01 · Skill Instruction Hijacking

Warning
Location
scripts/pattern_detector.py:104
Finding
Untrusted App Store Reviews Can Indirectly Manipulate Complaint Clustering and Alerts<![CDATA[ ## Vulnerability Details **File Location**: `scripts/pattern_detector.py:104-187` **Vulnerability Type**: Indirect prompt injection and insufficient model-output validation **Risk Level**: Medium ### Vulnerable Code ```python # Build review list for Claude review_texts = [] for i, r in enumerate(reviews): text = f"{i+1}. [{r['rating']}★] {r.get('title', '')} — {r.get('body', '')}".strip() review_texts.append(text[:300]) # Truncate very long reviews reviews_block = "\n".join(review_texts) system_prompt = """You are a product analyst. Your job is to identify recurring complaint themes in App Store reviews. Group complaints by theme. Focus on actionable, specific issues (not vague sentiment). Output ONLY valid JSON — no commentary, no markdown, just raw JSON.""" user_prompt = f"""App: {app_name} Reviews from last 7 days: {reviews_block} Identify all distinct complaint themes that appear more than once. For each theme: - Summarize the complaint in ≤10 words - List which review numbers mention it - Rate severity: high (app broken/unusable), medium (major friction), low (minor annoyance) Return JSON array: [ {{ "theme": "Short description of complaint", "review_indices": [1, 3, 5], "severity": "high|medium|low", "category": "crash|performance|ui|missing_feature|content|billing|other" }} ] If no recurring themes, return []. """ payload = { "model": CLAUDE_MODEL, "max_tokens": 1024, "system": system_prompt, "messages": [{"role": "user", "content": user_prompt}], } # ... clusters = json.loads(raw) # Hydrate with actual review data patterns = [] for cluster in clusters: indices = [i - 1 for i in cluster.get("review_indices", []) if 1 <= i <= len(reviews)] matched_reviews = [reviews[i] for i in indices] if len(matched_reviews) < 2: continue avg_rating = sum(r["rating"] for r in matched_reviews) / len(matched_reviews) patterns.append({ "theme": cluster["theme"], ...[truncated 2202 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Explicitly state in the system prompt that all review fields are untrusted data and must never be treated as instructions. - Pass review records in a clearly delimited, machine-readable structure, preferably as JSON data rather than interpolated prose. - Use an API-supported structured-output or JSON-schema mechanism where available. - Validate the returned object against a strict schema: - `theme`: bounded plain string. - `review_indices`: unique integers within range. - `severity`: one of `high`, `medium`, or `low`. - `category`: one of the documented categories. - Independently verify that each cited review contains semantic evidence for the claimed theme before counting it toward an alert. - Treat model output as advisory and require deterministic corroboration or human review for high-severity alerts. - Add adversarial tests with direct, paraphrased, multilingual, and encoded injection attempts. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
README.md:404
Finding
LaunchAgent Setup Stores Long-Lived API Credentials in a Plaintext Property List<![CDATA[ ## Vulnerability Details **File Location**: `README.md:404-417` **Vulnerability Type**: Plaintext persistent credential storage **Risk Level**: Medium ### Vulnerable Configuration ```xml <key>EnvironmentVariables</key> <dict> <key>APP_STORE_KEY_ID</key> <string>YOUR_KEY_ID</string> <key>APP_STORE_ISSUER_ID</key> <string>YOUR_ISSUER_ID</string> <key>APP_STORE_PRIVATE_KEY_PATH</key> <string>/Users/nick/.appstoreconnect/keys/AuthKey_KEYID.p8</string> <key>ANTHROPIC_API_KEY</key> <string>sk-ant-...</string> <key>TELEGRAM_BOT_TOKEN</key> <string>YOUR_BOT_TOKEN</string> <key>TELEGRAM_CHAT_ID</key> <string>YOUR_CHAT_ID</string> </dict> ``` 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
<![CDATA[ ## Remediation Suggestions - Do not embed live API secrets in a LaunchAgent plist. - Prefer a per-user LaunchAgent under `~/Library/LaunchAgents` when system-wide installation is unnecessary. - Retrieve secrets at runtime from the operating-system keychain or another dedicated secret manager. - If a credential file must be used: - Store it outside the project directory. - Set mode `600`. - Ensure it is owned by the account running the agent. - Load it through a minimal wrapper without printing its contents. - Keep only non-sensitive configuration, such as file paths and application IDs, in the plist. - Document credential rotation and immediate revocation procedures. - Warn users that shell profiles and service files containing secrets must not be committed, backed up insecurely, or made group/world-readable. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/queue_manager.py:365
Finding
Unescaped Review and Model Content Enables Telegram Markdown Spoofing<![CDATA[ ## Vulnerability Details **File Location**: `scripts/queue_manager.py:365-388` **Vulnerability Type**: Messaging markup injection **Risk Level**: Medium ### Vulnerable Code ```python def format_digest_item(idx: int, item: dict) -> 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
<![CDATA[ ## Remediation Suggestions - Prefer plain-text Telegram messages when rich formatting is not necessary. - If Markdown is retained, escape every dynamic value according to the exact Telegram Markdown version in use. - Apply escaping to app names, reviewer names, review text, drafts, themes, categories, and all other externally or model-controlled values. - Keep operational controls visually and structurally separate from untrusted content. - Consider Telegram inline keyboard buttons with authenticated callback handling instead of command-like strings embedded in message text. - Add tests with underscores, asterisks, brackets, parentheses, backticks, backslashes, links, and command-looking review titles. ]]>

T08 · Insecure Dependencies

Warning
Location
README.md:23
Finding
Security-Sensitive Dependencies Are Installed Without Version or Integrity Pinning<![CDATA[ ## Vulnerability Details **File Location**: `README.md:23-27` **Vulnerability Type**: Unpinned third-party dependency installation **Risk Level**: Medium ### Vulnerable Installation Guidance ```bash # Python 3.9+ python3 --version # JWT library for App Store Connect auth pip3 install PyJWT cryptography ``` The same unpinned command is repeated in `references/app-store-connect-api.md:84-92`: ```bash pip3 install PyJWT cryptography ``` ### Technical Analysis `PyJWT` and `cryptography` process the App Store private key and create authentication tokens. The project installs whichever package versions happen to be current at installation time, with no lockfile, reviewed version constraints, hashes, or isolated environment. No evidence shows that the named packages are currently malicious. The weakness is that builds are non-reproducible and automatically trust future releases and package-index resolution. A compromised upstream release, account takeover, dependency-resolution anomaly, or incompatible update could affect authentication and key handling. ### Attack Path 1. A user follows the setup guide and executes the unpinned `pip3 install` command. 2. The package index resolves the latest available versions at that time. 3. A compromised, malicious, or unexpectedly incompatible release is selected. 4. Installation or import executes package-controlled code under the installing user’s privileges. 5. That code may access the same user environment, including API environment variables and the configured App Store private-key file. ### Impact Assessment The maximum scope is the privilege level of the account performing installation or running the Skill. A compromised dependency could read local files available to that account, access API credentials, alter JWT generation, or execute arbitrary user-level code. The finding does not demonstrate an active supply-chain compromise; it identifies missing controls around security-critical dependencies. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Create a reviewed dependency manifest with exact versions. - Generate and enforce cryptographic hashes, for example with a hash-locked requirements file. - Install into a dedicated virtual environment rather than the global interpreter. - Use: ```bash python3 -m venv .venv . .venv/bin/activate python3 -m pip install --require-hashes -r requirements.txt ``` - Add a documented process for reviewing and updating pinned versions. - Run dependency vulnerability and provenance checks in continuous integration. - Ensure package installation is not performed with `sudo` or unnecessary elevated privileges. ]]>
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
Content
)

    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:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
)

    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")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
)

    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")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
)

    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            result = json.loads(resp.read())
            raw = result["content"][0]["text"].strip()
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
)

    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}")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
)

    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            result = json.loads(resp.read())
            if result.get("data"):
                return True
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
)

    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)}")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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.

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.

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.

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.

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.

Ae1

High
Category
analysis-evasion
Content
**Script:** `scripts/queue_manager.py`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
**Script:** `scripts/queue_manager.py`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
**Script:** `scripts/queue_manager.py`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
**Script:** `scripts/queue_manager.py`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
**Script:** `scripts/queue_manager.py`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
**Script:** `scripts/queue_manager.py`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

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
Content
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
Confidence
80% confidence
Finding
YARA rule matched a hack tool or exploit indicator (offensive tools, reconnaissance, privilege escalation, or exploit frameworks).

Instruction Override

High
Category
Prompt Injection
Content
text = text[:2000]
    # Remove common prompt injection patterns
    injection_patterns = [
        "ignore previous instructions",
        "ignore all previous",
        "disregard your instructions",
        "you are now",
Confidence
80% confidence
Finding
This pattern attempts to override system instructions or ignore safety constraints. Without LLM analysis, manual review is recommended.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
Write a single reply (no quotes, no preamble). The reply should be ready to post directly to the App Store.
"""
    return prompt


# ─── Claude API ───────────────────────────────────────────────────────────────
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Missing User Warnings

Medium
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.

Session Persistence

Medium
Category
Rogue Agent
Content
**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**
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.

Session Persistence

Medium
Category
Rogue Agent
Content
### 6. Schedule with Cron

```bash
crontab -e
```

Add:
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.

Session Persistence

Medium
Category
Rogue Agent
Content
```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>
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.