Back to skill

Security audit

Mac Reminder Bridge

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to manage macOS Reminders as advertised, but its default HTTP bridge security is too broad for real reminder data and deletion/update authority.

Review this before installing. Use it only on a trusted local machine, set a strong BRIDGE_SECRET, narrow BRIDGE_ALLOWED_IPS to the exact client address or Docker subnet, avoid exposing port 5000 to remote networks, and do not use direct HTTP for cloud access. Treat the bridge as granting read, edit, complete, and delete access to your real macOS Reminders data.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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 (3)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
listener.py:43
Finding
Authentication Is Optional on a Service Bound to All Network Interfaces## Vulnerability Details **File Location**: `listener.py:43-47`, `listener.py:92-112`, `listener.py:716-728` **Vulnerability Type**: Missing authentication and overly broad network exposure **Risk Level**: High ### Vulnerable Code ```python API_SECRET = os.environ.get("BRIDGE_SECRET", "") PORT = int(os.environ.get("BRIDGE_PORT", 5000)) DRY_RUN = os.environ.get("DRY_RUN", "").strip() == "1" _raw_ips = os.environ.get("BRIDGE_ALLOWED_IPS", "172.0.0.0/8,127.0.0.1,::1") ``` ```python def require_auth(f): """IP allowlist + optional shared-secret check.""" @wraps(f) def wrapper(*args, **kwargs): client_ip = request.remote_addr or "" try: addr = ipaddress.ip_address(client_ip) allowed = any(addr in net for net in ALLOWED_NETWORKS) except ValueError: allowed = False if not allowed: log.warning("Rejected %s — not in IP allowlist", client_ip) return jsonify({"error": "Forbidden"}), 403 if API_SECRET and request.headers.get("X-Bridge-Secret") != API_SECRET: log.warning("Rejected %s — bad or missing X-Bridge-Secret", client_ip) return jsonify({"error": "Unauthorized"}), 401 return f(*args, **kwargs) return wrapper ``` ```python if API_SECRET: log.info("🔒 Auth : X-Bridge-Secret required") else: log.warning("⚠️ Auth : No BRIDGE_SECRET — anyone on allowed IPs can call this API") app.run(host="0.0.0.0", port=PORT, debug=False) ``` ### Technical Analysis The Flask application binds to `0.0.0.0`, making it available through every network interface. Authentication is only checked when `BRIDGE_SECRET` is nonempty. Consequently, the default configuration provides no credential-based authentication. The IP allowlist is the only access control under the default configuration, and it trusts the entire `172.0.0.0/8` address range. This is significantly broader than a single int ...[truncated 2273 chars]
Remediation
## Remediation Suggestions 1. Require `BRIDGE_SECRET` at startup and terminate with an error if it is absent outside an explicitly enabled development mode. 2. Compare submitted secrets using `hmac.compare_digest` rather than ordinary string inequality. 3. Bind the service to the narrowest usable interface instead of `0.0.0.0`. 4. Replace the default `172.0.0.0/8` rule with the exact Docker subnet or specific source addresses required by the intended agent. 5. Reject an empty or entirely invalid `BRIDGE_ALLOWED_IPS` configuration instead of silently continuing. 6. Add separate authorization controls for read and destructive operations where practical. 7. Require explicit confirmation or stable reminder identifiers for broad fuzzy update and deletion operations. 8. Add rate limiting and security event logging for rejected and destructive requests. 9. Update the documentation so unauthenticated operation is described only as an isolated development option, not a normal deployment mode.

T09 · Insecure Skill Coding Practices

Error
Location
send_reminder.sh:12
Finding
Sensitive Reminder Data and Authentication Secrets Are Transmitted over Plaintext HTTP## Vulnerability Details **File Location**: `send_reminder.sh:12-13`, `send_reminder.sh:40-48`, `SKILL.md:12`, `SKILL.md:126-148`, `README.md:68-76`, `README.md:85-90` **Vulnerability Type**: Plaintext transmission of sensitive data and credentials **Risk Level**: High for remote deployments; Medium for isolated local deployments ### Vulnerable Code ```bash HOST="${BRIDGE_HOST:-http://host.docker.internal:5000}" SECRET="${BRIDGE_SECRET:-}" ``` ```bash CURL_ARGS=( -s -w "\n%{http_code}" -X POST "$HOST/add_reminder" -H "Content-Type: application/json" -d "$PAYLOAD" ) if [ -n "$SECRET" ]; then CURL_ARGS+=(-H "X-Bridge-Secret: $SECRET") fi ``` The Skill specification also declares a plaintext endpoint: ```text Base URL: `http://host.docker.internal:5000` ``` The documented remote deployment guidance permits a cloud server address to be added to the allowlist while the listener continues to serve ordinary HTTP. ### Technical Analysis Requests and responses can contain reminder titles, notes, due dates, alarms, priorities, completion states, and list names. The optional shared secret is sent in the `X-Bridge-Secret` header. All of this information is transmitted through unencrypted HTTP. The default Docker-to-host path may remain inside a local virtualization network, which reduces but does not eliminate exposure. The risk becomes high when following the documented remote or cloud configuration because traffic may traverse external networks. IP allowlisting authenticates neither the transport nor intermediate network infrastructure and does not provide confidentiality, integrity, or replay protection. Any party capable of observing the traffic may read the reminder data and authentication secret. Once captured, the static secret can be replayed to invoke sensitive or destructive API operations. ### Attack Path 1. The user configures a remote agent or cloud host and exposes or forwards port 5000 to the bridge. 2. The client ...[truncated 1196 chars]
Remediation
## Remediation Suggestions 1. Do not expose the Flask development server directly to remote networks. 2. Place the service behind a properly configured HTTPS reverse proxy and validate certificates on all clients. 3. Prefer an authenticated private tunnel, such as SSH forwarding, a mutually authenticated VPN, or mTLS, for remote deployments. 4. Change the documented base URL to HTTPS for any non-local scenario. 5. Configure `curl` to fail securely and reject invalid certificates; do not recommend certificate-verification bypasses. 6. Use short-lived, scoped authentication tokens where feasible instead of one indefinitely reusable shared secret. 7. Rotate `BRIDGE_SECRET` immediately if it may previously have crossed an untrusted network. 8. Clearly distinguish isolated Docker-host communication from remote deployment in the documentation and prohibit direct public HTTP exposure.

T09 · Insecure Skill Coding Practices

Note
Location
listener.py:340
Finding
Private Reminder Metadata Is Persisted in Plaintext Log Files## Vulnerability Details **File Location**: `listener.py:70-73`, `listener.py:340-341`, `listener.py:476`, `listener.py:522` **Vulnerability Type**: Sensitive information exposure through application logging **Risk Level**: Low ### Vulnerable Code ```python _fh = logging.handlers.RotatingFileHandler( _LOG_FILE, maxBytes=1_000_000, backupCount=3, encoding="utf-8" ) _fh.setFormatter(_fmt) log.addHandler(_fh) ``` ```python log.info("Created: '%s' due=%s alarm=%s priority=%s list=%s", task, due or "—", remind_at or "—", priority, list_name or get_default_list()) ``` ```python log.info("Updated %d reminder(s) matching '%s'", count, task) ``` ```python log.info("Deleted %d reminder(s) matching '%s'", count, task) ``` ### Technical Analysis The application writes reminder titles, due dates, alarm times, priorities, and list names to `reminder_bridge.log`. The rotating handler retains the active file and up to three backups. Reminder titles and list names can contain medical, financial, employment, travel, relationship, or other private information. Recording this content is not necessary to determine whether an operation succeeded. The code does not explicitly configure restrictive file permissions or redact sensitive values. Rotation limits file size but does not protect confidentiality. Copies may remain accessible through local filesystem permissions, backups, synchronization tools, diagnostic archives, or incident-support bundles. ### Attack Path 1. A user creates, updates, or deletes a reminder containing sensitive information. 2. The bridge writes the reminder title and related metadata into `reminder_bridge.log`. 3. Rotated copies preserve earlier entries after the active log changes. 4. Another local account, compromised process, backup operator, synchronization service, or recipient of a diagnostic archive obtains access to the log. 5. The reader reconstructs private reminder activities and associated dates ...[truncated 516 chars]
Remediation
## Remediation Suggestions 1. Remove reminder titles, dates, alarms, priorities, and list names from normal operational logs. 2. Log only an opaque request identifier, operation type, status, count, and sanitized error category. 3. Make file logging opt-in or use a less verbose production log level. 4. Create log files with owner-only permissions and verify the containing directory is not broadly readable. 5. Document retention behavior and provide a mechanism to disable or delete historical logs. 6. Avoid including log files in backups, synchronization services, support archives, or telemetry unless the user explicitly consents. 7. Apply structured redaction to exception messages before returning or logging them, because future AppleScript errors could also contain user-controlled data.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (10)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
A description-behavior mismatch is dangerous because reviewers and users may approve the skill based on declared capabilities while the actual implementation behaves differently. That gap can hide unsafe operations, cause incorrect trust decisions, or lead an agent to perform actions the user did not reasonably expect.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The markdown explicitly advertises full CRUD support, which includes deleting reminders, but it does not warn users that agent/API actions may permanently remove or alter reminder data. For markdown files, destructive behavior that can affect user data should be disclosed so users understand the impact before enabling or using the skill.

External Transmission

Medium
Category
Data Exfiltration
Content
### Health Check
```bash
curl http://host.docker.internal:5000/health
```

### Create a Reminder
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
该 README 全文以中文撰写,未说明这是面向特定中文区域用户的专用文档,也没有提供语言选择或英文替代入口。根据规则,若技能在自然语言层面强制特定语言而没有用户选择或明确、合理的限定,属于语言/locale 政策风险。

External Transmission

Medium
Category
Data Exfiltration
Content
### 检查服务状态
```bash
curl http://host.docker.internal:5000/health
```

### 创建一个提醒
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill invokes network and shell capabilities (`curl` to a local HTTP bridge) but does not declare any explicit tool scope or permissions boundary. This increases the chance that an agent can use broader capabilities than intended, making review, policy enforcement, and containment harder if the skill is misused or modified.

Vague Triggers

Medium
Confidence
90% confidence
Finding
The trigger phrases are very broad, covering common everyday language like 'remind me' and 'show reminders' without clear constraints. This can cause over-triggering, where the skill activates in contexts the user did not intend, potentially sending reminder contents or issuing modification requests to the bridge unexpectedly.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The skill sends reminder content, notes, and timing data over plain HTTP to `host.docker.internal:5000` without an explicit warning to the user. Even though this is presented as a local bridge, reminder data may include sensitive personal information, and plaintext transport plus implicit local trust can expose that data to interception or misuse on the host or local environment.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
return "0", "", 0

    try:
        result = subprocess.run(
            ["osascript", "-e", script],
            capture_output=True, text=True, timeout=15
        )
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Unverifiable Dependency: flask has 10 known advisory(ies) (CVE-2025-47278 (Flask uses fallback key instead of current signing key); CVE-2018-1000656 (Flask is vulnerable to Denial of Service via incorrect encoding of JSON data); CVE-2019-1010083 (Pallets Project Flask is vulnerable to Denial of Service via Unexpected memory u) +7 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
95% confidence
Finding
The dependency is specified as a broad range (flask>=3.0.0,<4.0.0) rather than a fully pinned version, so builds are not reproducible and may resolve to different Flask releases over time, including vulnerable ones. In a network-facing HTTP bridge that manages macOS Reminders, this increases supply-chain and patch-management risk because the exposed service may run with whatever matching version is installed at deployment time.

Static analysis

No suspicious patterns detected.