Back to skill

Security audit

Hinge Auto-Liker

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it says, but it automates dating-app actions while uploading and retaining sensitive profile data with weak consent, secret-handling, and safety controls.

Review carefully before installing. Use only a dedicated emulator account, assume profile screenshots and generated summaries may be sent to Google Gemini and stored locally, avoid saving recordings unless needed, do not hardcode API keys in cron, and require explicit confirmation before any run that sends likes or comments.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/hinge_android.py:244
Finding
Gemini API Key Exposed Through Process Command-Line Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/hinge_android.py`, lines 244-252 **Vulnerability Type**: Secret exposure through command-line arguments **Risk Level**: Medium ### Vulnerable Code ```python result = subprocess.run( ["curl", "-s", "-X", "POST", f"{GEMINI_URL}?key={GEMINI_API_KEY}", "-H", "Content-Type: application/json", "-d", f"@{tmp_path}"], capture_output=True, text=True, timeout=45 ) os.unlink(tmp_path) ``` ### Technical Analysis The Gemini API key is inserted into the request URL and passed directly as a `curl` command-line argument. Command-line arguments may be visible to other local users or processes through process inspection interfaces and may also be collected by process-monitoring, diagnostic, auditing, or crash-reporting tools. Placing a secret in a URL additionally increases the chance of accidental retention in HTTP diagnostics or tooling logs. Although the request is sent over HTTPS, transport encryption does not protect the key from local command-line observation. ### Attack Path 1. The Skill starts a session and invokes `curl` to analyze a profile. 2. The API key appears in the running process arguments as part of `?key=...`. 3. A local process or user with permission to inspect process metadata reads the `curl` command line while the request is active, or obtains it from monitoring logs. 4. The observer extracts the Gemini API key. 5. The exposed key is reused to issue unauthorized Gemini API requests until it is revoked or restricted. ### Impact Assessment Successful exploitation discloses the Gemini API credential. An attacker could consume the associated API quota, incur usage costs where billing is enabled, disrupt legitimate requests through quota exhaustion, or access other Gemini API operations permitted by the key. This issue does not directly grant host or emulator control. Its scope is limited to the services and projects authorized by the exposed API key. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Replace the external `curl` invocation with an HTTPS client library so the credential never appears in a child process argument list. - Send the API key using the authentication mechanism recommended by the Gemini API, preferably a request header rather than a query parameter where supported. - Apply API-key restrictions, including API scope, project, quota, and source restrictions. - Ensure exception-safe cleanup of the temporary request file by using a `finally` block. - Rotate the existing API key if the Skill has already run on a shared or monitored system. - Redact secrets from application logs, error output, and HTTP diagnostics. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:80
Finding
Documentation Directs Users to Hardcode a Persistent API Credential in Cron Configuration<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 80-84 **Vulnerability Type**: Insecure persistent secret storage **Risk Level**: Medium ### Vulnerable Documentation ```markdown ## Scheduling as a Daily Cron Set up via OpenClaw cron for daily automated runs. Key notes: - **Hardcode GEMINI_API_KEY in the cron payload** — cron shells don't source ~/.zshrc - Use `am start` to launch Hinge, not `monkey` (more reliable) ``` ### Technical Analysis The setup instructions explicitly tell users to hardcode `GEMINI_API_KEY` in a scheduler payload. Scheduler definitions are persistent configuration and may be readable through management interfaces, command output, shell history, configuration backups, support bundles, or audit logs. This practice unnecessarily expands the credential's exposure lifetime and storage surface. The automation only requires access to the key at execution time; it does not require the key itself to be embedded in the scheduled command or payload. The scheduling behavior is described as user-configured automation rather than a hidden persistence mechanism, so it is not classified as a backdoor. The vulnerability is the insecure treatment of the secret. ### Attack Path 1. A user follows the documented setup and embeds the Gemini API key in an OpenClaw cron payload. 2. The scheduler stores the payload persistently. 3. Another user, administrator, backup reader, support tool, or compromised scheduler interface obtains the task definition. 4. The API key is extracted from the plaintext payload. 5. The key is reused for unauthorized API requests. ### Impact Assessment The attacker gains the same Gemini API privileges associated with the disclosed key. Potential effects include unauthorized quota consumption, financial cost, quota exhaustion, and disruption of the Hinge automation. The credential can remain exposed across sessions and system restarts because the scheduler configuration is persistent. This increases t ...[truncated 84 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove the instruction to hardcode the API key in the scheduler payload. - Use the scheduler's dedicated secret-management feature when available. - Otherwise, load the key from an operating-system keychain or a separate environment file readable only by the task owner. - Set restrictive file permissions, such as owner read-only access, on any fallback secret file. - Store only a reference to the secret in the scheduled task. - Ensure scheduler interfaces and logs redact secret values. - Document key rotation and revocation procedures. - Rotate any key that has already been embedded in persistent cron configuration. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/hinge_android.py:292
Finding
Untrusted AI-Generated Comment Is Passed to an Android Shell with Incomplete Escaping<![CDATA[ ## Vulnerability Details **File Location**: `scripts/hinge_android.py`, lines 292-303 **Vulnerability Type**: Potential command injection through untrusted model output **Risk Level**: Medium ### Vulnerable Code ```python if comment: xml = dump_ui() comment_btn = find_button(xml, text="Add a comment") if comment_btn: tap(*comment_btn) time.sleep(0.5) safe_comment = comment.replace("'", "\\'").replace('"', '\\"').replace(" ", "%s") adb_cmd("shell", "input", "text", safe_comment) time.sleep(0.5) ``` The value is ultimately executed through: ```python def adb_cmd(*args): try: result = subprocess.run([ADB] + list(args), capture_output=True, text=True, timeout=30) return result.stdout.strip() except subprocess.TimeoutExpired: log(f" ⚠️ ADB command timed out: {args}") return "" ``` ### Technical Analysis The `comment` value is generated by Gemini from dating-profile screenshots. Profile content is controlled by third parties and can contain visible adversarial instructions intended to manipulate the vision model. Consequently, model output must be treated as untrusted data. The sanitization only transforms single quotes, double quotes, and spaces. It does not enforce a safe character set and does not reject shell-sensitive characters such as semicolons, pipes, ampersands, dollar signs, backticks, backslashes, command substitutions, or newline characters. Although Python invokes the local `adb` executable without `shell=True`, `adb shell` passes the requested command to the Android device's remote shell. Depending on ADB's remote command serialization and shell parsing, metacharacters in the generated text may be interpreted as shell syntax rather than literal input. ### Attack Path 1. An attacker creates a Hinge profile containing text or an image designed to prompt the vision model to return a malicious string in the JSON `comment` field. 2. The Skill captures the p ...[truncated 1269 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Treat every model-generated field as hostile input. - Enforce a strict maximum comment length suitable for Hinge. - Use a conservative character allowlist, such as letters, numbers, spaces, and a small set of punctuation known to be safe. - Reject control characters, newlines, command substitutions, and all shell metacharacters. - Do not rely on manually escaping a subset of characters. - Prefer an input mechanism that does not pass generated text through a shell. For example, transmit encoded data and decode it through a fixed, carefully validated command, or use a device-side API designed for text insertion. - Validate the complete Gemini response against a strict schema, including field types, string lengths, allowed values, and the permitted `which_one` range. - Add adversarial tests covering semicolons, pipes, ampersands, dollar signs, backticks, backslashes, newlines, and command-substitution syntax. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/hinge_android.py:184
Finding
Sensitive Dating-Profile Screenshots and Personal Summaries Are Retained Without Cleanup Controls<![CDATA[ ## Vulnerability Details **File Location**: `scripts/hinge_android.py`, lines 184-202, 397-411, and 419-422 **Vulnerability Type**: Excessive retention of sensitive personal data **Risk Level**: Low ### Vulnerable Code Profile screenshots are written to persistent project storage: ```python def scroll_full_profile(): """Scroll through entire profile, taking screenshots at each position.""" screenshots = [] ts = datetime.now().strftime("%Y%m%d_%H%M%S") path = str(SCREENSHOT_DIR / f"profile_{ts}_top.png") screenshot(path) screenshots.append(path) for i in range(5): swipe(540, 1800, 540, 400, 300) time.sleep(0.6) ts = datetime.now().strftime("%Y%m%d_%H%M%S") path = str(SCREENSHOT_DIR / f"profile_{ts}_scroll{i+1}.png") screenshot(path) screenshots.append(path) return screenshots ``` Derived personal information is added to session records: ```python actions.append({ "profile": profiles_seen, "action": action, "reason": reason, "comment": comment if action == "like" else "", "profile_summary": profile_summary, "which_one": analysis.get("which_one", 1), "best_content": analysis.get("best_content", ""), "timestamp": datetime.now().isoformat() }) ``` The records are then persisted without a retention limit: ```python log_file = LOG_DIR / f"{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.json" with open(log_file, "w") as f: json.dump(summary, f, indent=2) log(f"Log saved: {log_file}") ``` ### Technical Analysis The Skill captures up to six screenshots for each reviewed profile and leaves those images in the `screenshots/` directory after analysis. It also stores model-generated profile summaries, reasons, comments, timestamps, and interaction decisions in JSON logs. Dating-profile screenshots can contain names, ages, photographs, employment information, location-related details, prompts, and other personal data. The implementation has n ...[truncated 1470 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Store analysis screenshots in a temporary directory and delete them immediately after the Gemini request completes. - Use `try`/`finally` cleanup so screenshots are removed after failures and timeouts as well as successful requests. - Minimize logs by excluding names, ages, detailed summaries, and other profile attributes unless the user explicitly requires them. - Introduce a documented retention period and automatically remove expired screenshots and logs. - Create storage directories and files with restrictive owner-only permissions. - Avoid storing session artifacts inside directories likely to be committed, synchronized, or broadly backed up. - Add `screenshots/`, `logs/`, recordings, and temporary payloads to repository-ignore rules. - Clearly notify users that profile screenshots are uploaded to Google Gemini and document the local retention policy. - Consider encrypting retained reports when retention is explicitly requested. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (22)

Missing User Warnings

High
Confidence
97% confidence
Finding
The skill description says it uses Gemini vision to evaluate dating profiles but does not clearly warn that profile images, prompts, and inferred attractiveness data may be transmitted to an external AI service. In this context, the omitted warning is significant because dating profiles contain sensitive personal data and users may not expect third-party processing.

Missing User Warnings

High
Confidence
94% confidence
Finding
The instructions recommend screen recording and pulling session videos to local storage without warning about the sensitivity of captured dating profiles and conversations. Those recordings and logs can contain personal images, bios, app identifiers, and behavioral data, making them highly privacy-sensitive if retained or shared insecurely.

Ae1

High
Category
analysis-evasion
Content
python3 scripts/hinge_android.py --likes 8 --user-desc "a 25yo tech guy in SF who's fit and active"
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Missing User Warnings

High
Confidence
99% confidence
Finding
The skill sends screenshots and profile metadata from a dating app to an external AI provider, including personal images and inferred attributes, without any runtime consent prompt or minimization. In this context, the data is highly sensitive and third-party disclosure can violate privacy expectations, platform rules, or legal obligations.

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill declares no explicit tool scope even though it clearly relies on shell execution, environment variables, and file writes. That mismatch weakens security boundaries and informed consent because an orchestrator or reviewer cannot easily tell that the skill can launch emulators, access secrets, and persist artifacts.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The invocation guidance is broad enough that ordinary conversation like 'check Hinge status' or 'manage daily dating app swiping' could trigger high-impact automation. Because this skill can drive an emulator, send likes, generate comments, and handle sensitive dating data, accidental invocation could cause unintended actions and privacy issues.

Ssd 3

Medium
Confidence
93% confidence
Finding
The skill directs collection and sharing of detailed summaries of liked and skipped profiles plus optional session video recordings. That creates a pipeline for storing and transmitting sensitive third-party personal data beyond what is necessary to automate likes, increasing privacy, compliance, and misuse risk.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The cron guidance omits any warning that hardcoding an API key creates credential exposure risks. In operational environments, cron definitions are often readable by admins, included in backups, and may be copied into tickets or docs, so the missing warning materially increases the chance of secret compromise.

Ssd 3

Medium
Confidence
98% confidence
Finding
Embedding a secret API key directly into cron payloads is unsafe because cron entries, scripts, and execution metadata can leak secrets to other users, support staff, backups, or logs. This is especially risky because the key grants access to an external AI service and could be abused for cost, data access, or service misuse.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
The documentation explicitly tells users to hardcode the Gemini API key into cron payloads. Embedding secrets in cron commands can expose them through process listings, shell history, logs, configuration backups, and accidental sharing, creating a straightforward credential leakage path.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The analysis prompt instructs the model to evaluate profiles using language like 'Is she attractive?' and assumes the user is 'a guy on Hinge,' forcing a specific gendered framing without user choice. This is a natural-language policy issue because it imposes a fixed demographic/interaction model rather than offering configurable or neutral language.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def adb_cmd(*args):
    try:
        result = subprocess.run([ADB] + list(args), capture_output=True, text=True, timeout=30)
        return result.stdout.strip()
    except subprocess.TimeoutExpired:
        log(f"  ⚠️ ADB command timed out: {args}")
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Tainted flow: 'ADB' from os.environ.get (line 29, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
def adb_cmd(*args):
    try:
        result = subprocess.run([ADB] + list(args), capture_output=True, text=True, timeout=30)
        return result.stdout.strip()
    except subprocess.TimeoutExpired:
        log(f"  ⚠️ ADB command timed out: {args}")
Confidence
93% confidence
Finding
The script trusts the ADB executable path from environment or CLI and executes it directly. If an attacker can influence ADB_PATH or --adb, they can cause arbitrary local program execution with the privileges of the agent running the skill.

Tainted flow: 'path' from os.environ.get (line 197, credential/environment) → open (file write)

Medium
Category
Data Flow
Content
def screenshot(path):
    with open(path, "wb") as f:
        subprocess.run([ADB, "exec-out", "screencap", "-p"], stdout=f, timeout=10)
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.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def screenshot(path):
    with open(path, "wb") as f:
        subprocess.run([ADB, "exec-out", "screencap", "-p"], stdout=f, timeout=10)


def dump_ui():
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Tainted flow: 'ADB' from os.environ.get (line 29, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
def screenshot(path):
    with open(path, "wb") as f:
        subprocess.run([ADB, "exec-out", "screencap", "-p"], stdout=f, timeout=10)


def dump_ui():
Confidence
93% confidence
Finding
This subprocess invocation inherits the same untrusted ADB executable path issue as adb_cmd(). A manipulated ADB value can replace the intended Android tooling with any local executable, leading to arbitrary code execution during screenshot capture.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def dump_ui():
    try:
        adb_cmd("shell", "uiautomator", "dump", "/sdcard/ui_dump.xml")
        subprocess.run([ADB, "pull", "/sdcard/ui_dump.xml", "/tmp/ui_dump.xml"],
                       capture_output=True, text=True, timeout=10)
        with open("/tmp/ui_dump.xml") as f:
            return f.read()
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Tainted flow: 'ADB' from os.environ.get (line 29, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
def dump_ui():
    try:
        adb_cmd("shell", "uiautomator", "dump", "/sdcard/ui_dump.xml")
        subprocess.run([ADB, "pull", "/sdcard/ui_dump.xml", "/tmp/ui_dump.xml"],
                       capture_output=True, text=True, timeout=10)
        with open("/tmp/ui_dump.xml") as f:
            return f.read()
Confidence
93% confidence
Finding
The code executes the ADB binary again using a path that may come from environment variables or command-line input. In an agent setting, this is particularly risky because a caller who can shape execution context may turn a mobile-automation helper into a local code-execution vector.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The script stores screenshots of profiles and detailed action logs locally, including summaries and comments tied to individuals. Because the content involves dating profiles, this creates a meaningful privacy and confidentiality risk if the host system is shared, compromised, or backed up insecurely.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
json.dump(payload, tmp)
            tmp_path = tmp.name

        result = subprocess.run(
            ["curl", "-s", "-X", "POST",
             f"{GEMINI_URL}?key={GEMINI_API_KEY}",
             "-H", "Content-Type: application/json",
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Tainted flow: 'GEMINI_URL' from os.environ.get (line 32, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
json.dump(payload, tmp)
            tmp_path = tmp.name

        result = subprocess.run(
            ["curl", "-s", "-X", "POST",
             f"{GEMINI_URL}?key={GEMINI_API_KEY}",
             "-H", "Content-Type: application/json",
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.

Tainted flow: 'log_file' from os.environ.get (line 427, credential/environment) → open (file write)

Medium
Category
Data Flow
Content
# Save JSON log
    log_file = LOG_DIR / f"{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.json"
    with open(log_file, "w") as f:
        json.dump(summary, f, indent=2)
    log(f"Log saved: {log_file}")
Confidence
85% confidence
Finding
LOG_DIR is derived from HINGE_WORK_DIR, which comes from the environment, and is used for file writes without confinement checks. An attacker controlling that environment variable could redirect sensitive logs into arbitrary writable locations, causing unintended file overwrite or privacy exposure.

Static analysis

No suspicious patterns detected.