Back to skill

Security audit

Tavily Quota Router

Security checks across malware telemetry and agentic risk

Overview

This Tavily router is not malicious, but it needs review because it stores and uses live API keys while its retry and diagnostic guidance can expose keys or burn quota.

Install only if you specifically need multi-key Tavily routing. Before using live keys, add your own .gitignore for config/keys.json and state/quota.json, avoid raw curl or parallel key tests, and review the 429 behavior. The code also casts cooldown_minutes to int even though docs recommend 0.5, so patch or test that fallback before relying on cooldown behavior with real quota.

SkillSpector

By NVIDIA
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (8)

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill clearly documents file read/write and outbound network behavior, yet no permissions are declared. That creates a trust-boundary problem: consumers may load the skill expecting documentation-only behavior while it can read credential files, rewrite config, and contact external services using secrets. In a skill ecosystem, undeclared capabilities materially increase the chance of unsafe deployment and accidental secret exposure.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The documented purpose promises strict stop-on-first-429/no-retry behavior, but the finding indicates the skill also rewrites local config, includes extra administrative behaviors, and may retry/fail over after search failures. That mismatch is dangerous because operators will make risk decisions based on the description; hidden mutation of config or unexpected retry behavior can burn quota, alter local state, and bypass operational safeguards. In security terms, misleading documentation weakens informed consent and can conceal risky side effects.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The search loop continues after receiving HTTP 429 by recording cooldown state and then retrying with another key, which contradicts the documented rule to stop on first 429 and avoid further retries during cooldown. In this quota-routing context, that behavior can rapidly burn through all configured API keys, defeat rate-limit protections, and increase the chance of account throttling or service disruption.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The document explicitly states that real `tvly-dev-...` keys were stored in plaintext and identifies the exact file path and storage location. Even though full secrets are not printed, this materially increases exposure by revealing where live credentials reside and how many exist, which can aid targeted theft from backups, shared directories, or repositories.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The document includes account-identifying information from a real operational run, specifically an email address tied to a specific API key and account context. Even though the API keys are masked, this creates an unnecessary privacy leak and can aid account correlation, social engineering, or targeted abuse when combined with other operational details in the same file.

External Transmission

Medium
Category
Data Exfiltration
Content
1. `cat state/quota.json | python3 -c "import json,sys; d=json.load(sys.stdin); [print(k['cooldown_until'], k.get('disabled'), k.get('plan_usage'),'/',k.get('plan_limit')) for k in d['keys']]"`
  2. 对比当前时间 vs `cooldown_until` —— 如果 cooldown_until < now → **cooldown 已过**,**不是真死**
  3. 对比 `plan_usage / plan_limit` —— < 80% 不算满
  4. 直接 `curl https://api.tavily.com/search -H "Authorization: Bearer $KEY" -d '{"query":"ping"}'` 实测 → 200 = quota 正常
  5. 都过了才报告"quota 真死";不然重试 1 次再判定

  **真实陷阱**(2026-07-14):router 报 "all disabled, cooled, or quota exhausted",实际 quota.json 里 4 个 key 都 `cooldown_until: 21:54`,当前 22:22 —— **cooldown 早过了 28 分钟**,月度配额只用 37/1000 (3.7%)。router **没刷新缓存**导致误判。**老大原话"你的检查机制是什么"** —— 答案是先看 quota.json,**不**信 router 报错。
Confidence
80% confidence
Finding
curl https://api.tavily.com/search -H "Authorization: Bearer $KEY" -d

External Transmission

Medium
Category
Data Exfiltration
Content
- **`last_sync_at` ≠ real-time health.** The state file snapshots when `/usage` was last called. If it's months old, key health may have changed in either direction (recovered or newly banned). Always refresh before reasoning about a key's status.
**老大 (2026-07-14) "你的检查机制是什么" — answer:** when web_search fails or router reports "no available key", the FIRST move is always `cat state/quota.json | python3 -m json.tool` and compute `now() - cooldown_until`. Reporting "quota exhausted" or "Tavily down" without this is the same class of error as "I assumed it was Ollama" — verify before stating. The router's error message is a **local cache state**, not a Tavily API verdict.

- **`cooldown_until` ≠ backend dead.** A cooldown marker was written after a single 429 burst, but the upstream may have recovered seconds later. **Never report "all keys exhausted / Tavily down" based on `state/quota.json` alone.** Before declaring the backend dead, parallel-curl each key against `https://api.tavily.com/search` directly (bypassing the router). If all return 200, the state is stale — run `reset-month` or `test-keys` to refresh, then proceed. The router state is a local cache, not ground truth. Verified 2026-07-10: four keys with `cooldown_until` 10 min in the future were all 200 OK on direct curl; reporting "exhausted" without testing is the same class of error as "I assumed it was Ollama." Test before reporting.
- **Router 错误消息 ≠ quota 真实状态**(2026-07-14 老大原话 "怎么可能额度用完了")。router 报 "no available key (all disabled, cooled, or quota exhausted)" 时**第一步永远是看 quota.json**,**不**直接信 router 报错。**诊断顺序**:
  1. `cat state/quota.json | python3 -c "import json,sys; d=json.load(sys.stdin); [print(k['cooldown_until'], k.get('disabled'), k.get('plan_usage'),'/',k.get('plan_limit')) for k in d['keys']]"`
  2. 对比当前时间 vs `cooldown_until` —— 如果 cooldown_until < now → **cooldown 已过**,**不是真死**
Confidence
80% confidence
Finding
https://api.tavily.com/

External Transmission

Medium
Category
Data Exfiltration
Content
1. `cat state/quota.json | python3 -c "import json,sys; d=json.load(sys.stdin); [print(k['cooldown_until'], k.get('disabled'), k.get('plan_usage'),'/',k.get('plan_limit')) for k in d['keys']]"`
  2. 对比当前时间 vs `cooldown_until` —— 如果 cooldown_until < now → **cooldown 已过**,**不是真死**
  3. 对比 `plan_usage / plan_limit` —— < 80% 不算满
  4. 直接 `curl https://api.tavily.com/search -H "Authorization: Bearer $KEY" -d '{"query":"ping"}'` 实测 → 200 = quota 正常
  5. 都过了才报告"quota 真死";不然重试 1 次再判定

  **真实陷阱**(2026-07-14):router 报 "all disabled, cooled, or quota exhausted",实际 quota.json 里 4 个 key 都 `cooldown_until: 21:54`,当前 22:22 —— **cooldown 早过了 28 分钟**,月度配额只用 37/1000 (3.7%)。router **没刷新缓存**导致误判。**老大原话"你的检查机制是什么"** —— 答案是先看 quota.json,**不**信 router 报错。
Confidence
80% confidence
Finding
https://api.tavily.com/

VirusTotal

VirusTotal findings are pending for this skill version.

View on VirusTotal

Static analysis

No suspicious patterns detected.