Back to skill

Security audit

Earthquake Monitor

Security checks for vulnerabilities and agentic risk

Overview

This earthquake alert skill mostly does what it says, but it makes misleading security and data-source claims that could expose webhook secrets or overstate alert reliability.

Review this skill before installing. Do not store token-bearing webhook URLs in its config unless you are comfortable with plaintext local storage, and treat its earthquake alerts as dependent on a third-party aggregator rather than direct verified government feeds. Network polling and local config writes are expected for this kind of skill, but the documentation should be corrected before sensitive use.

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

Warning
Location
src/config.js:74
Finding
Plaintext Storage and Exposure of Webhook Credentials<![CDATA[ ## Vulnerability Details **File Location**: `src/config.js:74-79, 171-173` **Vulnerability Type**: Plaintext storage of sensitive credentials **Risk Level**: Medium ### Vulnerable Code ```javascript function saveConfig(config) { try { const dir = path.dirname(CONFIG_PATH); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2)); return true; } catch (e) { console.error('[Config] Save error:', e.message); return false; } } ``` ```javascript if (newConfig.webhook !== undefined) { config.webhook = newConfig.webhook; } ``` The public configuration API also returns the complete configuration object, including the webhook value: ```javascript async function config(newConfig = null) { if (newConfig) { // Support location string for convenience if (typeof newConfig.location === 'string') { const parsed = parseLocation(newConfig.location); if (parsed) { newConfig.location = parsed; } } const config = await setConfig(newConfig); return { success: true, message: '✅ Configuration updated', config }; } return await getConfig(); } ``` ### Technical Analysis Webhook URLs frequently contain bearer-style access tokens or other credentials in their query strings. The implementation copies the supplied webhook URL directly into the configuration and serializes it to `config.json` without encryption, redaction, or an explicitly restrictive file mode. Although `.gitignore` excludes `config.json`, this only reduces accidental version-control commits. It does not protect the credential from other local users, processes, backups, support bundles, filesystem snapshots, or package copies. The `config()` API also returns the stored value without redaction. The project documentation is inconsistent. `SECURITY.md` correctly states that version 1.1.1 stores webhook URLs in plaintext, while `SKILL.md` and `skill.json` ...[truncated 1233 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the webhook option if it is not used. The current monitoring implementation only constructs a webhook payload and does not transmit it. 2. Prefer an environment variable or platform-managed secret store instead of persistent JSON storage. 3. If file storage is unavoidable: - Store the secret in a separate file. - Create the file with mode `0600`. - Verify ownership and permissions before every read. - Avoid placing the secret inside the distributable project directory. 4. Redact the webhook from all API responses: ```javascript function redactConfig(config) { return { ...config, webhook: config.webhook ? '[REDACTED]' : null }; } ``` 5. Never log the complete webhook URL or include it in thrown errors. 6. Provide a dedicated operation for replacing or deleting the secret rather than returning it through the general configuration API. 7. Correct `SKILL.md` and `skill.json` to state that the webhook is stored in plaintext unless secure storage is actually implemented. 8. Treat any webhook previously stored by the affected version as potentially exposed and rotate its token. ]]>

T08 · Insecure Dependencies

Warning
Location
src/cenc.js:12
Finding
Safety-Critical Earthquake Data Is Trusted from an Undisclosed Third-Party Aggregator<![CDATA[ ## Vulnerability Details **File Location**: `src/cenc.js:12-31`, `src/cwa.js:12-33`, `src/jma.js:12-31` **Vulnerability Type**: Unsafe reliance on a single unverified third-party data source **Risk Level**: Medium ### Vulnerable Code `src/cenc.js`: ```javascript async function getCENCData() { // 1分钟缓存 if (cachedData.length > 0 && lastUpdate && (Date.now() - lastUpdate) < 60000) { return cachedData; } try { const { stdout } = await execPromise('curl -s --max-time 10 "https://api.wolfx.jp/cenc_eqlist.json"'); const json = JSON.parse(stdout); const data = []; for (let i = 1; i <= 50; i++) { const key = 'No' + i; if (json[key]) data.push(json[key]); } if (data.length > 0) { cachedData = data; lastUpdate = Date.now(); return cachedData; } } catch (e) { console.error('[CENC] Error:', e.message); } return cachedData; } ``` `src/cwa.js`: ```javascript async function getCWAData() { // 30秒缓存 if (cachedWarning && lastUpdate && (Date.now() - lastUpdate) < 30000) { return cachedWarning; } try { const { stdout } = await execPromise('curl -s --max-time 10 "https://api.wolfx.jp/cwa_eew.json"'); // 检查是否返回 HTML(被 Cloudflare 拦截) if (!stdout || stdout.trim().startsWith('<')) { console.log('[CWA] API temporarily unavailable'); return cachedWarning; } const data = JSON.parse(stdout); if (data && data.ID) { cachedWarning = data; lastUpdate = Date.now(); return cachedWarning; } } catch (e) { console.log('[CWA] Error:', e.message); } return cachedWarning; } ``` `src/jma.js`: ```javascript async function getJMAData() { // 1分钟缓存 if (cachedData.length > 0 && lastUpdate && (Date.now() - lastUpdate) < 60000) { return cachedData; } try { const { stdout } = await execPromise('curl -s --max-time 10 "https://api.wolfx.jp/jma_eqlist.json"'); const json = JSON.parse(stdout ...[truncated 2712 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Retrieve data directly from documented official CENC, CWA, and JMA endpoints where technically and legally available. 2. If the intermediary must remain: - Disclose it clearly in `SKILL.md`, `SECURITY.md`, and Skill metadata. - Do not represent the network connection as direct communication with government agencies. - Document the intermediary's ownership, data provenance, availability guarantees, and security model. 3. Validate every response against a strict schema before use: - Require expected identifiers and timestamps. - Reject non-finite magnitudes, depths, and coordinates. - Enforce latitude and longitude ranges. - Enforce plausible magnitude and depth ranges. - Reject stale or future-dated records outside a defined tolerance. - Bound response size and record count. 4. Cross-check high-severity alerts against an independent authoritative source before presenting them as confirmed. 5. Distinguish unverified aggregator data from confirmed official data in alert messages. 6. Implement explicit failure states instead of silently returning stale cached data as though it were current. 7. Pin and monitor the expected hostname and redirect behavior. Configure `curl` to fail on HTTP errors and return useful status information, for example with `--fail-with-body`. 8. Prefer Node.js `fetch()` with explicit timeout, response-size limits, status validation, and schema validation over launching a shell process. ]]>
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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (19)

Intent-Code Divergence

High
Confidence
98% confidence
Finding
The documentation is internally inconsistent: the changelog says encryption for webhook storage was removed, while the Security section still claims webhook URLs are AES-256-CBC encrypted at rest. This can mislead users into storing sensitive webhook secrets under false assumptions, causing credential exposure if configs are plaintext or otherwise recoverable.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill advertises shell-capable behavior via required binaries like curl but does not declare any explicit tool scope or permissions boundary. This weakens reviewability and can lead users or hosts to grant broader execution/network capabilities than expected, increasing the risk of misuse or unnoticed command execution.

Missing User Warnings

Medium
Confidence
85% confidence
Finding
The skill exposes a configurable webhook feature but does not prominently warn that earthquake data and alert content will be transmitted to an external endpoint controlled by the user. Without clear disclosure, users may unintentionally send sensitive operational context or tokens to third-party services, especially since webhook URLs themselves often embed secrets.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
This config sets the language to "en" explicitly, which can constitute a language/locale policy violation when there is no visible opt-in, alternative choice, or documented justification. In a region-specific context centered on a Chinese location, forcing English may conflict with expected user locale behavior.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
The skill's stated purpose is real-time earthquake monitoring, which justifies network access to fetch feeds, but not spawning shell commands. Using child_process.exec with curl introduces an execution capability broader than necessary for this monitoring function.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The configuration sets `language: 'zh'` as the default, which imposes a specific language choice before the user has selected a preference. The policy allows locale constraints only when users are given a choice or when the restriction is clearly justified, neither of which is present here.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The skill's purpose is real-time earthquake monitoring, which justifies outbound network access, but not spawning a shell to run curl. Invoking child_process adds an unnecessary execution capability that is broader than needed for retrieving remote JSON and is not justified by the manifest description.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The module auto-detects output language solely from the data source and falls back to 'zh' for unknown sources. This can force a specific language/locale on users without offering a choice, which matches the policy's language/locale violation criteria.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
The skill's stated purpose is real-time earthquake monitoring using external data feeds, which justifies network access, but spawning a subprocess to run curl is not an obvious or necessary capability for that purpose. Using child_process expands the skill's effective capability beyond straightforward data retrieval and introduces command-execution behavior unrelated to the manifest's described monitoring role.

External Transmission

Medium
Category
Data Exfiltration
Content
}
  
  try {
    const { stdout } = await execPromise('curl -s --max-time 10 "https://api.wolfx.jp/jma_eqlist.json"');
    const json = JSON.parse(stdout);
    
    const data = [];
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
}
  
  try {
    const { stdout } = await execPromise('curl -s --max-time 10 "https://api.wolfx.jp/jma_eqlist.json"');
    const json = JSON.parse(stdout);
    
    const data = [];
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
}
  
  try {
    const { stdout } = await execPromise('curl -s --max-time 10 "https://api.wolfx.jp/jma_eqlist.json"');
    const json = JSON.parse(stdout);
    
    const data = [];
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
94% confidence
Finding
The code hard-codes `zh` as the fallback language when no user language is configured. This creates a locale policy issue because the skill defaults to a specific language rather than offering a user choice or requiring explicit opt-in.

Missing User Warnings

Low
Confidence
77% confidence
Finding
The `start(options)` section states that the skill performs proactive monitoring with auto-alerts every 60 seconds, which indicates recurring polling and possible downstream notifications. The markdown does not explicitly warn users that enabling this behavior may generate continuous network requests and automated alerts until stopped.

Natural-Language Policy Violations

Low
Confidence
91% confidence
Finding
The initialization API documents a default language of 'zh' and the user-facing initialization message presents that language setting as the default behavior. This can violate language/locale policy expectations when a skill selects a specific language without first asking the user or clearly making it opt-in.

Unpinned Dependencies

Low
Category
Supply Chain
Content
},
  "readme": "SKILL.md",
  "dependencies": {
    "ws": "^8.19.0"
  }
}
Confidence
95% confidence
Finding
The dependency uses a caret range (^8.19.0) rather than an exact pinned version, which makes builds non-reproducible and can cause different installations to resolve to different ws releases over time. In a network-facing skill that relies on WebSocket handling, this increases supply-chain and patch-state uncertainty, though by itself it is not direct code execution.

Unverifiable Dependency: ws has 7 known advisory(ies) (CVE-2016-10518 (Remote Memory Disclosure in ws); CVE-2024-37890 (ws affected by a DoS when handling a request with many HTTP headers); CVE-2026-45736 (ws: Uninitialized memory disclosure) +4 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
88% confidence
Finding
The manifest references ws without pinning an exact version, and ws has a history of security advisories including denial-of-service and memory disclosure issues. Because the exact resolved version is not fixed here, it is impossible to verify from this file alone whether deployments will avoid affected releases, which is risky for a real-time earthquake monitoring skill likely to maintain network connections and process remote data.

Missing User Warnings

Low
Confidence
83% confidence
Finding
The module persists configuration data to disk via `fs.writeFileSync`, which affects user/system data, but the only surrounding messaging is an internal error log on failure. There is no confirmation prompt, user-facing notice, or explanatory comment/docstring warning that configuration changes will be written to `config.json`.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
This file contains natural-language text in a single language ('距离计算 - Haversine 公式') with no indication that the skill offers a language choice or is intentionally limited to a Chinese-speaking context. The policy requires flagging language or locale constraints when they are forced without user opt-in or clear justification.

Static analysis

No suspicious patterns detected.