Back to skill

Security audit

Expo App Store Screenshots

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it says, but it documents an unsafe eval-based setup step and includes high-impact store upload actions that can replace public screenshots.

Install only if you trust the projects it will run against and you are comfortable giving the agent simulator/device control plus store-upload credentials. Avoid the documented eval setup on untrusted Expo config, use a virtual environment with pinned dependencies, and require explicit review before any App Store Connect or Google Play upload that may replace screenshots.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:82
Finding

Arbitrary Shell Command Execution Through Unescaped Configuration Output

Content
View full analysis
/dev/null 2>&1; then return 1; fi if [[ ! -f "$file" ]]; then return 1; fi scheme=$(jq -r '.expo.scheme // empty' "$file" 2>/dev/null || true) # `scheme` may be an array; pick the first if so if [[ -z "$scheme" ]]; then scheme=$(jq -r '.expo.scheme[0] // empty' "$file" 2>/dev/null || true) fi ios_bundle=$(jq -r '.expo.ios.bundleIdentifier // empty' "$file" 2>/dev/null || true) android_pkg=$(jq -r '.expo.android.package // empty' "$file" 2>/dev/null || true) [[ -n "$scheme" || -n "$ios_bundle" || -n "$android_pkg" ]] } ``` `assets/detect-app-config.sh:41-52`: ```bash read_from_expo_cli() { if ! command -v npx >/dev/null 2>&1; then return 1; fi local json json=$(npx --no-install expo config --type public --json 2>/dev/null) || return 1 if ! command -v jq >/dev/null 2>&1; then return 1; fi scheme=$(printf '%s' "$json" | jq -r '.scheme // empty' 2>/dev/null || true) if [[ -z "$scheme" ]]; then scheme=$(printf '%s' "$json" | jq -r '.scheme[0] // empty' 2>/dev/null || true) fi ios_bundle=$(printf '%s' "$json" | jq -r '.ios.bundleIdentifier // empty' 2>/dev/null || true) android_pkg=$(printf '%s' "$json" | jq -r '.android.package // empty' 2>/dev/null || true) [[ -n "$scheme" || -n "$ios_bundle" || -n "$android_pkg" ]] } ``` `assets/detect-app-config.sh:62-64`: ```bash printf 'APP_SCHEME=%s\n' "$scheme" printf 'IOS_BUNDLE_ID=%s\n' "$ios_bundle" printf 'ANDROID_PACKAGE=%s\n' "$android_pkg" ``` ### Technical Analysis The dete ...[truncated 2334 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
SKILL.md:184
Finding

Unpinned Third-Party Dependencies Create Supply-Chain and Reproducibility Risk

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
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (25)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The code chunk does not implement the declared screenshot-generation behavior. Instead, it performs route discovery for Expo Router projects by walking the filesystem and emitting route metadata. While route enumeration could be a supporting step in a broader screenshot workflow, this chunk’s primary and only observable behavior is route detection, not screenshot capture/preparation. Therefore the description materially overstates and misrepresents what this code actually does.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description promises a comprehensive screenshot generation pipeline for app stores, including device/simulator control and screenshot capture. The actual code only implements one narrow supporting step: resizing already-existing PNG images with ImageMagick. Resizing is consistent with a small subset of the description, but the primary purpose and most advertised capabilities are absent from this code chunk. Therefore the description does not accurately represent what this specific code actually does.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The description presents a broad screenshot-generation automation skill centered on capturing and preparing store screenshots across iOS and Android. This code chunk does not perform capture or preparation. Instead, its primary behavior is App Store Connect management: API authentication, locating an editable iOS version, ensuring localization/screenshot-set existence, deleting existing screenshots unless told not to, and uploading local PNGs. While the description briefly mentions uploading screenshots to App Store Connect or Google Play as a use case, this code is much narrower than the declared primary purpose and lacks the core simulator/device-driving and image-preparation behavior. Therefore the description does not accurately represent what this code chunk actually does.

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

md
| [`assets/write-summary.sh`](assets/write-summary.sh) | Write `summary.md` into a device folder (model, OS, resolution, screen list). |

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
| [`assets/write-summary.sh`](assets/write-summary.sh) | Write `summary.md` into a device folder (model, OS, resolution, screen list). |

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
| [`assets/write-summary.sh`](assets/write-summary.sh) | Write `summary.md` into a device folder (model, OS, resolution, screen list). |

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
| [`assets/write-summary.sh`](assets/write-summary.sh) | Write `summary.md` into a device folder (model, OS, resolution, screen list). |

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding

The skill documents capabilities that read environment variables, access local files, and perform network uploads, but it does not declare any explicit tool scope or permission boundary. In an agent setting, that makes the skill easier to invoke with broader-than-expected authority, increasing the risk of unintended credential access or remote modification of App Store / Play listing data.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The trigger description includes broad adjacent phrasing that could cause the skill to activate for loosely related screenshot requests. In context, this skill can lead to device control, file writes, environment-secret use, and optional store uploads, so over-broad activation increases the chance of unintended high-impact actions.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
81% confidence
Finding

Referencing npx expo without a pinned version can cause the skill to execute whatever package version is currently resolved from the environment or registry. That creates a supply-chain risk where behavior changes unexpectedly or a malicious package version could be pulled and run during detection.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
81% confidence
Finding

The detection helper again relies on unpinned npx expo, which inherits the same supply-chain and reproducibility risks. In a skill that may be run automatically, executing an unpinned remote package is more dangerous because it can happen without close user review.

Content

No source excerpt is available for this finding.

Whitespace Padding

Medium
Category
Prompt Injection
Confidence
70% confidence
Finding

Large whitespace padding was detected (a block of blank lines or a long run of spaces). This can push injected instructions below or to the right of the visible area so a human reviewer never sees them while the agent still reads them. Manual review of the hidden content is recommended.

Content

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

md
## Store target dimensions

| Device          | Required size | Notes                                                                                    |
| --------------- | ------------- | ---------------------------------------------------------------------------------------- |
| `iphone`        | 1284×2778     | App Store 6.5" display. Capture on iPhone 16 Pro Max sim (1320×2868) and resize.         |
| `ipad`          | 2064×2752     | App Store 13" display. iPad Pro 13" M4 captures natively at this size.                   |

Whitespace Padding

Medium
Category
Prompt Injection
Confidence
70% confidence
Finding

Large whitespace padding was detected (a block of blank lines or a long run of spaces). This can push injected instructions below or to the right of the visible area so a human reviewer never sees them while the agent still reads them. Manual review of the hidden content is recommended.

Content

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

md
## Scripts (all live under `assets/`)

| Script                                                                                       | Purpose                                                                                                  |
| -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| [`assets/detect-app-config.sh`](assets/detect-app-config.sh)                 | Discover `APP_SCHEME`, `IOS_BUNDLE_ID`, `ANDROID_PACKAGE` from the Expo config.                          |
| [`assets/detect-routes.sh`](assets/detect-routes.sh)                         | Walk an Expo Router `app/` (or `src/app/`) tree and print every route as `<url>\t<group>\t<source-file>`. |

Whitespace Padding

Medium
Category
Prompt Injection
Confidence
70% confidence
Finding

Large whitespace padding was detected (a block of blank lines or a long run of spaces). This can push injected instructions below or to the right of the visible area so a human reviewer never sees them while the agent still reads them. Manual review of the hidden content is recommended.

Content

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

md
## Scripts (all live under `assets/`)

| Script                                                                                       | Purpose                                                                                                  |
| -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| [`assets/detect-app-config.sh`](assets/detect-app-config.sh)                 | Discover `APP_SCHEME`, `IOS_BUNDLE_ID`, `ANDROID_PACKAGE` from the Expo config.                          |
| [`assets/detect-routes.sh`](assets/detect-routes.sh)                         | Walk an Expo Router `app/` (or `src/app/`) tree and print every route as `<url>\t<group>\t<source-file>`. |

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The App Store upload workflow instructs use of sensitive API credentials and performs remote modification of store assets, but it does not prominently warn about secret handling, least privilege, or the destructive replace-by-default behavior. In an agent context, that raises the risk of credential leakage through logs/env inspection and unintended production metadata changes.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The Google Play workflow similarly uses a highly sensitive service-account key to modify public store listing screenshots without a prominent warning about credential sensitivity and side effects. Because the script opens and commits store edits, misuse can alter production-facing metadata and expose privileged cloud credentials if handled unsafely.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding

npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
95% confidence
Finding

The script executes npx --no-install expo config --type public --json, which relies on whatever expo CLI is available in the local project or environment without validating a pinned version. In a security-sensitive automation context, this can execute an unexpected or compromised CLI version, and expo config may evaluate dynamic app config code such as app.config.js/ts, increasing the risk of arbitrary code execution from an untrusted repository.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill manifest focuses on capturing and preparing screenshots on simulators/emulators and resizing them, with upload mentioned only as a secondary trigger phrase. This script performs substantial remote management of App Store Connect state: it creates appStoreVersionLocalizations and appScreenshotSets, deletes existing screenshots, and uploads new assets via the App Store Connect API.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
86% confidence
Finding

For a skill presented primarily as driving simulators/devices to capture marketing screenshots, handling external API credentials and performing authenticated remote API mutations is a distinct capability. While upload support is mentioned in the manifest, credential-driven integration with App Store Connect is not clearly scoped as part of the skill's main behavior.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · assets/upload-app-store.py (reported line 55)May include surrounding context.

python
import jwt
import requests

BASE = "https://api.appstoreconnect.apple.com/v1"

# device shorthand -> ASC screenshotDisplayType
DEVICE_DISPLAY_TYPE = {

Tainted flow: 'url' from requests.post (line 115, network input) → requests.post (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · assets/upload-play-store.py (reported line 116)May include surrounding context.

python
for p in pngs:
        data = p.read_bytes()
        url = f"{upload_edits_url}{listing_path}?uploadType=media"
        r = requests.post(
            url,
            headers={**h, "Content-Type": "image/png"},
            data=data,

Tainted flow: 'edit_id' from requests.post (line 101, network input) → requests.post (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · assets/upload-play-store.py (reported line 125)May include surrounding context.

python
sys.exit(f"upload {p.name} → {r.status_code}\n{r.text}")
        print(f"  uploaded {p.name}")

    r = requests.post(f"{edits_url}/{edit_id}:commit", headers=h)
    if not r.ok:
        sys.exit(f"commit edit → {r.status_code}\n{r.text}")
    print(f"done — committed {len(pngs)} images to {args.locale}/{args.image_type}")

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

This code creates local directories/files and deletes a temporary file on the connected Android device, but it does not emit any confirmation, logging, or explicit warning at execution time about those side effects. While these actions are part of the script's purpose, the device-side deletion and local write behavior are only implicit in the implementation and not disclosed to the user except through minimal comments.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · assets/upload-app-store.py (reported line 247)May include surrounding context.

python
args = ap.parse_args()

    for attr, flag in (("key_id", "--key-id"), ("issuer_id", "--issuer-id"), ("key_path", "--key-path")):
        if not getattr(args, attr):
            sys.exit(f"missing {flag} (or ASC_{attr.upper()} env var)")

    pngs = sorted(Path(args.dir).glob("*.png"))