Back to skill

Security audit

SubwaySkill

Security checks for vulnerabilities and agentic risk

Overview

This looks like a legitimate NYC subway times skill, but users should review it because installation fetches mutable remote code and the CLI downloads and caches transit data with limited safeguards.

Install only if you trust the upstream GitHub module at install time. Prefer a pinned reviewed version or checksum-backed release, and expect the tool to make outbound requests to MTA-related endpoints and write GTFS cache files under `~/.cache/subwayskill/`. Consider running it with normal user privileges and resource limits if used in automation.

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)

T08 · Insecure Dependencies

Warning
Location
SKILL.md:23
Finding
Mutable and Unpinned Remote Installation Target<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:23-25`; `README.md:9-12` **Vulnerability Type**: Unpinned remote dependency installation **Risk Level**: Medium ### Vulnerable Code `SKILL.md:23-25`: ```yaml install: - go: github.com/nyluke/subwayskill@latest ``` `README.md:9-12`: ```text ## Install go install github.com/nyluke/subwayskill@latest ``` ### Technical Analysis The installation instructions retrieve `@latest` from a remote repository instead of installing the exact revision represented by the audited artifact. The meaning of `latest` can change after the Skill has been reviewed. The supplied artifact also does not include `go.mod` or `go.sum`, preventing the audit from confirming the exact direct and transitive dependency versions used by the remotely installed release. Although the Go module ecosystem provides checksum verification for published module versions, it does not ensure that a mutable `@latest` selection corresponds to the source code audited here. This is a supply-chain weakness rather than evidence that the current source contains a malicious dependency. ### Attack Path 1. A user or Agent follows the manifest or README installation instruction. 2. The Go tool resolves `github.com/nyluke/subwayskill@latest` at installation time. 3. An attacker compromises the upstream repository or maintainer account, or a future release introduces unsafe code. 4. The resolved release differs from the audited source. 5. The downloaded package is compiled and installed as `subwayskill`. 6. Subsequent Skill invocations execute the substituted code with the privileges and data access of the Agent process. ### Impact Assessment A malicious upstream release could execute arbitrary code under the account performing installation or running the resulting binary. Its practical scope would include files, environment variables, network access, and credentials available to that account. No privilege escalation beyond the invoking accou ...[truncated 45 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace `@latest` with a reviewed, immutable semantic version: ```yaml install: - go: github.com/nyluke/subwayskill@v1.0.0 ``` 2. Ensure the documented installation command uses the same pinned version. 3. Distribute `go.mod` and `go.sum` with the project so direct and transitive dependencies can be reproduced and verified. 4. Pin critical dependencies to reviewed versions and run dependency vulnerability scanning in CI. 5. Sign release artifacts or publish checksums and verify them before installation. 6. Require release review before updating the version referenced by the Skill manifest. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
internal/schedule/schedule.go:80
Finding
Unbounded Network Responses and GTFS Archive Processing<![CDATA[ ## Vulnerability Details **File Location**: `internal/feed/feed.go:91-102`; `internal/schedule/schedule.go:80-105` **Vulnerability Type**: Uncontrolled resource consumption from remote data **Risk Level**: Medium ### Vulnerable Code `internal/feed/feed.go:91-102`: ```go func fetchFeed(url string) ([]byte, error) { client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Get(url) if err != nil { return nil, fmt.Errorf("fetching feed: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("feed returned status %d", resp.StatusCode) } return io.ReadAll(resp.Body) } ``` `internal/schedule/schedule.go:80-105`: ```go fmt.Fprintf(os.Stderr, "Downloading %s...\n", filename) client := &http.Client{Timeout: 60 * time.Second} resp, err := client.Get(url) if err != nil { return nil, fmt.Errorf("downloading %s: %w", filename, err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("downloading %s: status %d", filename, resp.StatusCode) } data, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("reading %s: %w", filename, err) } // Cache it if err := os.MkdirAll(dir, 0o755); err == nil { _ = os.WriteFile(cachePath, data, 0o644) } return data, nil } func parseGTFSZip(data []byte, stopID string, routes []string, direction string, targetTime time.Time, windowMin int) ([]feed.Departure, error) { r, err := zip.NewReader(bytes.NewReader(data), int64(len(data))) ``` ### Technical Analysis Both HTTP download paths use `io.ReadAll` without imposing a maximum response size. A timeout limits request duration but does not bound the amount of data that can be received within that period. A sufficiently fast response can therefore cause the process to allocate excessive memory. Realtime feed data is subsequently passed to Protocol Buffers unmarshalling, which can create additional allocations based on attacker-controlled message structure. ...[truncated 1854 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce a conservative maximum response size before calling parsers: ```go const maxFeedSize = 32 << 20 limited := io.LimitReader(resp.Body, maxFeedSize+1) data, err := io.ReadAll(limited) if int64(len(data)) > maxFeedSize { return nil, fmt.Errorf("response exceeds maximum size") } ``` 2. Reject responses whose declared `Content-Length` exceeds the configured limit. 3. Use separate limits for realtime protobuf feeds and static GTFS archives based on expected production sizes. 4. Configure `CheckRedirect` to restrict redirects to approved HTTPS hosts and limit the number of redirects. 5. Validate response content types where the upstream service provides reliable content-type metadata. 6. Inspect ZIP metadata before opening entries and reject archives with excessive entry counts, uncompressed sizes, or compression ratios. 7. Wrap each opened ZIP entry in a limiting reader and cap the total uncompressed bytes processed across all entries. 8. Limit CSV record sizes and the number of trips, services, stop times, and departures retained in memory. 9. Write downloads to a private temporary file, validate them, call `Sync` where appropriate, and atomically rename them into the cache only after successful validation. 10. Set restrictive cache permissions, handle cache-write errors, and impose a maximum cache size. 11. Add process-level memory and CPU limits when the CLI is executed by an Agent runtime. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (3)

Lp1

High
Category
MCP Least Privilege
Confidence
75% confidence
Finding
The skill uses 'file_write' capability that is not listed in its permissions. This may indicate deceptive intent or missing permission declarations.

External Transmission

Medium
Category
Data Exfiltration
Content
## Data sources

- **Realtime**: MTA GTFS-RT feeds (no API key required)
- **Schedule**: [MTA static GTFS](https://api.mta.info/) — regular + supplemented (7-day lookahead with planned service changes)

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

Missing User Warnings

Low
Confidence
95% confidence
Finding
The README discloses that the tool automatically downloads and caches GTFS data under the user's home directory, but it does not clearly warn users up front that running certain commands causes network access and filesystem writes. This is a real transparency and consent issue: users, automation, or sandboxed environments may assume a read-only/local CLI and unintentionally permit outbound requests or persistent disk writes.

Static analysis

No suspicious patterns detected.