High risk. Don't ship without significant remediation.
Scanned 6/8/2026, 3:38:57 PMยทCached resultยทDeep Scanยท91 rulesยทHow we decide โ
AIVSS Score
High
Severity Breakdown
0
critical
8
high
41
medium
54
low
MCP Server Information
Findings
This package receives a D grade with a safety score of 45/100 and poses significant security concerns, primarily driven by 8 high-severity issues across readiness and server configuration categories. The 41 medium-severity findings, particularly around resource exhaustion and readiness gaps, indicate the package lacks proper safeguards against denial-of-wallet attacks and resource abuse. You should address these high and medium-severity issues or seek alternative packages before considering installation in a production environment.
AIPer-finding remediation generated by bedrock-claude-haiku-4-5 โ 28 of 28 findings. Click any finding to read.
No known CVEs found for this package or its dependencies.
Scan Details
Done
Sign in to save scan history and re-scan automatically on new commits.
Building your own MCP server?
Same rules, same LLM judges, same grade. Private scans stay isolated to your account and never appear in the public registry. Required for code your team hasnโt shipped yet.
28 of 28 findings
28 findings
Tool 'search_datanexus_tools' contains imperative instruction directed at LLM: 'Use this first' attempts to influence routing priority.
RemediationAI
The problem is that the tool description contains an imperative instruction ('Use this first') that attempts to manipulate LLM routing decisions rather than neutrally describing functionality. Remove the phrase 'Use this first' from the tool description in mcp-manifest.json and replace it with a neutral functional description (e.g., 'Search DataNexus tools by keyword'). This eliminates the attack surface for prompt injection through tool metadata by ensuring all tool descriptions are purely declarative. Verify the fix by reviewing the mcp-manifest.json tool entry and confirming no imperative instructions or priority hints remain in the description field.
Tool 'security_fetch_package_vulnerabilities' returns CVE data from untrusted external sources (Google OSV.dev + NIST NVD) including free-text fields (severity descriptions, CVSS vectors) without provenance delimiters, enabling indirect prompt injection.
RemediationAI
The problem is that CVE data from untrusted external sources (Google OSV.dev and NIST NVD) is returned with free-text fields (severity descriptions, CVSS vectors) that lack provenance delimiters, enabling indirect prompt injection. Wrap all untrusted free-text fields returned by security_fetch_package_vulnerabilities with explicit provenance markers such as `[EXTERNAL_SOURCE: field_name]` or use a structured response envelope that clearly separates trusted metadata from untrusted external content. This prevents the LLM from interpreting injected instructions embedded in CVE descriptions as system directives. Verify by calling the tool and confirming that all external text fields are wrapped with delimiters and that the response structure clearly marks untrusted data.
Tool 'security_fetch_dependency_graph' returns transitive dependency metadata from untrusted external source (deps.dev) without visible provenance markers, risking indirect prompt injection through package names and descriptions.
RemediationAI
The problem is that transitive dependency metadata from deps.dev is returned without visible provenance markers, creating an indirect prompt injection risk through package names and descriptions. Add explicit provenance delimiters (e.g., `[EXTERNAL: deps.dev]`) to all untrusted fields in the security_fetch_dependency_graph response, and structure the output to clearly separate trusted identifiers from external descriptions. This prevents the LLM from treating injected content in package metadata as trusted instructions. Verify by calling the tool and confirming that all external package names and descriptions are marked with provenance labels.
feedback_classifier tool calls classify() which invokes Anthropic SDK without max_tokens cap
Evidence
| 1 | """ |
| 2 | datanexus/agents/feedback_classifier.py โ Haiku Trigger 2: feedback classification. |
| 3 | |
| 4 | Spec: DataNexus_MCP_Spec_v7_4.docx Section 13.4 / Phase 4 |
| 5 | |
| 6 | Most important file in Section 13. Closes the loop between user-reported |
| 7 | feedback and automated data quality triage. |
| 8 | |
| 9 | Rules (CLAUDE.md): |
| 10 | S13-1: This is Haiku Trigger T2. Delegate to haiku_classifier.classify() only. |
| 11 | S13-4: Never raises. All exceptions caught. Always returns structured result. |
| 12 | S13-5: FeedbackRecord.classification is ONE-WAY onl |
RemediationAI
The problem is that the classify() method in feedback_classifier.py calls the Anthropic SDK without a max_tokens cap, allowing unbounded token consumption and potential denial-of-service. Add a max_tokens parameter to the anthropic.messages.create() call in the classify() method (e.g., max_tokens=500) to enforce a hard limit on output tokens. This prevents runaway token usage and protects against resource exhaustion attacks. Verify by calling the feedback classifier and confirming in the Anthropic API response that the max_tokens field is set and respected.
schema_monitor tool calls classify() which invokes Anthropic SDK without max_tokens cap
Evidence
| 1 | """ |
| 2 | datanexus/agents/schema_monitor.py โ Haiku Trigger 3: schema change assessment. |
| 3 | |
| 4 | Spec: DataNexus_MCP_Spec_v7_4.docx Section 13.5 / Phase 5 |
| 5 | |
| 6 | Called when the ingest worker detects a structural change between the |
| 7 | previously-stored schema fingerprint and the newly-fetched payload shape. |
| 8 | Haiku decides whether the change is breaking and what severity to assign. |
| 9 | |
| 10 | Rules (CLAUDE.md): |
| 11 | S13-1: This is Haiku Trigger T3. Delegate to haiku_classifier.classify() only. |
| 12 | S13-4: Never raises. Always return |
RemediationAI
The problem is that the classify() method in schema_monitor.py calls the Anthropic SDK without a max_tokens cap, allowing unbounded token consumption and potential denial-of-service. Add a max_tokens parameter to the anthropic.messages.create() call in the classify() method (e.g., max_tokens=500) to enforce a hard limit on output tokens. This prevents runaway token usage and protects against resource exhaustion attacks. Verify by calling the schema monitor and confirming in the Anthropic API response that the max_tokens field is set and respected.
anomaly_reviewer tool calls classify() which invokes Anthropic SDK without max_tokens cap
Evidence
| 1 | """ |
| 2 | datanexus/agents/anomaly_reviewer.py โ Haiku Trigger 1: anomaly review. |
| 3 | |
| 4 | Spec: DataNexus_MCP_Spec_v7_4.docx Section 13.3 / Phase 3 |
| 5 | |
| 6 | Called when the deterministic validator (validator.py) flags an anomaly |
| 7 | that needs a second-opinion classification before deciding whether to |
| 8 | keep, suppress, or flag the affected record. |
| 9 | |
| 10 | Rules (CLAUDE.md): |
| 11 | S13-1: This is Haiku Trigger T1. Do NOT call Anthropic API directly โ |
| 12 | always delegate to haiku_classifier.classify(). |
| 13 | S13-4: Never raises. All |
RemediationAI
The problem is that the classify() method in anomaly_reviewer.py calls the Anthropic SDK without a max_tokens cap, allowing unbounded token consumption and potential denial-of-service. Add a max_tokens parameter to the anthropic.messages.create() call in the classify() method (e.g., max_tokens=500) to enforce a hard limit on output tokens. This prevents runaway token usage and protects against resource exhaustion attacks. Verify by calling the anomaly reviewer and confirming in the Anthropic API response that the max_tokens field is set and respected.
Tool handlers call Anthropic SDK via haiku_classifier.classify() without passing max_tokens parameter to anthropic.messages.create()
Evidence
| 1 | """ |
| 2 | datanexus/agents/haiku_classifier.py โ Shared Haiku API wrapper for all 4 triggers. |
| 3 | |
| 4 | Spec: DataNexus_MCP_Spec_v7_4.docx Section 13.2 / Phase 2 |
| 5 | |
| 6 | Rules (CLAUDE.md): |
| 7 | S13-1: Called ONLY from the 4 permitted triggers โ never directly from tool handlers. |
| 8 | S13-2: HAIKU_MODEL imported from feedback/config.py. Never hardcoded here. |
| 9 | S13-3: Daily cap HAIKU_MAX_CALLS_PER_DAY (100) enforced before every call. |
| 10 | incr โ expire โ check, in that exact order. |
| 11 | S13-4: On any API error: return err |
RemediationAI
The problem is that haiku_classifier.py's classify() method calls anthropic.messages.create() without passing a max_tokens parameter, allowing unbounded token consumption. Add max_tokens=500 (or an appropriate limit) as a parameter to the anthropic.messages.create() call in datanexus/agents/haiku_classifier.py. This enforces a hard ceiling on token usage across all four trigger tools (feedback_classifier, schema_monitor, anomaly_reviewer, digest_generator) that depend on this shared wrapper. Verify by inspecting the haiku_classifier.py source and confirming the max_tokens parameter is present in the messages.create() call, then test each trigger tool.
digest_generator tool calls classify() which invokes Anthropic SDK without max_tokens cap
Evidence
| 1 | """ |
| 2 | datanexus/agents/digest_generator.py โ Haiku Trigger 4: weekly digest. |
| 3 | |
| 4 | Spec: DataNexus_MCP_Spec_v7_4.docx Section 13.6 / Phase 6 |
| 5 | |
| 6 | Batches ALL feedback records for a tool into ONE Haiku call (cost control) |
| 7 | and produces a DigestItem with data quality score, top issues, suggested |
| 8 | rules, and sprint recommendations. |
| 9 | |
| 10 | Rules (CLAUDE.md): |
| 11 | S13-1: This is Haiku Trigger T4. Delegate to haiku_classifier.classify() only. |
| 12 | S13-4: Never raises. Always returns structured DigestItem. |
| 13 | |
| 14 | Special case โ emp |
RemediationAI
The problem is that the classify() method in digest_generator.py calls the Anthropic SDK without a max_tokens cap, allowing unbounded token consumption and potential denial-of-service. Add a max_tokens parameter to the anthropic.messages.create() call in the classify() method (e.g., max_tokens=1000 for batch digests) to enforce a hard limit on output tokens. This prevents runaway token usage and protects against resource exhaustion attacks. Verify by calling the digest generator and confirming in the Anthropic API response that the max_tokens field is set and respected.
MCP tool file registers a tool, performs a destructive sink (fs.unlink / shutil.rmtree / DROP TABLE / DELETE FROM / TRUNCATE / UPDATE ... SET / HTTP DELETE|PUT|PATCH / subprocess / exec / spawn), and emits no audit event anywhere in the file. Without an audit event, an investigator cannot answer "who deleted record X on day Y?" โ the irreversible action leaves no trail. Closes the OWASP MCP Top 10:2025 MCP08 (Lack of Audit and Telemetry) gap. Distinct from MCP-201 (no confirmation) and MCP-283
Evidence
| 1 | """ |
| 2 | datanexus/tools/security_stateful.py โ Sprint 6 stateful security tools. |
| 3 | |
| 4 | Tools: |
| 5 | fetch_cve_watch โ persistent CVE watchlist (create/check/delete) |
| 6 | audit_sbom_continuous โ continuous SBOM monitoring (register/check/deregister) |
| 7 | |
| 8 | Redis key schema (dn: prefix): |
| 9 | dn:cve_watch:{watch_id} โ Hash (watch data) |
| 10 | dn:cve_watch_ids โ SET (watch index) |
| 11 | dn:sbom_watch:{watch_id} โ Hash (SBOM watch data) |
| 12 | dn:sbom_watch_ids โ SET (SBOM watch index) |
| 13 | |
| 14 | Scheduler: see datanex |
RemediationAI
The problem is that security_stateful.py registers destructive tools (fetch_cve_watch delete, audit_sbom_continuous deregister) but emits no audit event, leaving no trail of who performed the deletion and when. Add an audit logging call (e.g., log_audit_event(actor, action, resource_id, timestamp)) immediately after each destructive operation (fs.unlink, shutil.rmtree, DELETE FROM, etc.) in security_stateful.py, and store the audit record in a separate immutable audit log or Redis key with extended TTL. This enables investigators to answer 'who deleted record X on day Y?' and closes the OWASP MCP08 gap. Verify by performing a delete operation via fetch_cve_watch or audit_sbom_continuous and confirming an audit event appears in the audit log with actor, action, resource, and timestamp.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure โ useful recon for attackers.
Evidence
| 83 | # โโ Structured error response shape โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ |
| 84 | # Return this dict on ANY error. Never raise. Never return str(e). |
| 85 | # error_code must come from ErrorCode enum below. |
| 86 | # |
| 87 | # { |
RemediationAI
The problem is that datanexus/core/schema.py returns full exception details or stack traces to the caller, exposing internal paths, library versions, and query structure useful for reconnaissance. Replace all instances of str(exc) or traceback output with a generic error message from the ErrorCode enum (e.g., 'SCHEMA_VALIDATION_ERROR') and log the full exception internally using logger.exception(). This hides implementation details from the caller while preserving debugging information in server logs. Verify by triggering an error condition in schema.py and confirming the response contains only the generic error code, not the full traceback.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure โ useful recon for attackers.
Evidence
| 180 | "error": str(exc), |
| 181 | "haiku_available": False, |
| 182 | })) |
| 183 | return {"error": str(exc), "haiku_available": False} |
RemediationAI
The problem is that haiku_classifier.py returns full exception details (str(exc)) to the caller, exposing internal library versions and API structure. Replace str(exc) with a generic error message such as 'Classification service unavailable' and log the full exception internally using logger.exception(). This hides implementation details while preserving debugging information in server logs. Verify by triggering an exception in haiku_classifier and confirming the returned error field contains only a generic message, not the full traceback.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 53 | schedule_seconds=_SCHEDULE, |
| 54 | ) |
| 55 | |
| 56 | async def fetch(self) -> bytes: |
| 57 | """Fetch and cache EPO OPS OAuth token; probe fallback sources.""" |
| 58 | client_id = os.environ.get("EPO_CLIENT_ID", "") |
| 59 | client_secret = os.environ.get("EPO_CLIENT_SECRET", "") |
RemediationAI
The problem is that t11_worker.py makes network/IO calls without an explicit timeout, allowing a hung upstream to pin threads and exhaust connection pools. Add timeout=30 (or appropriate seconds) to the httpx.AsyncClient() or httpx.get() call in the fetch() method of t11_worker.py. This enforces a bounded timeout on all HTTP requests to the EPO OPS OAuth endpoint. Verify by inspecting the fetch() method and confirming timeout is set, then test with a slow upstream to confirm the request times out gracefully.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 153 | schedule_seconds=_SCHEDULE, |
| 154 | ) |
| 155 | |
| 156 | async def fetch(self) -> bytes: |
| 157 | """ |
| 158 | Fetch NPPES data for top specialities. Returns a summary JSON blob |
| 159 | for the base class to hash and store. Per-speciality cache entries |
RemediationAI
The problem is that t22_worker.py makes network/IO calls without an explicit timeout, allowing a hung upstream to pin threads and exhaust connection pools. Add timeout=30 (or appropriate seconds) to the httpx.AsyncClient() or httpx.get() call in the fetch() method of t22_worker.py. This enforces a bounded timeout on all HTTP requests to the NPPES data endpoint. Verify by inspecting the fetch() method and confirming timeout is set, then test with a slow upstream to confirm the request times out gracefully.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 64 | schedule_seconds=_SCHEDULE, |
| 65 | ) |
| 66 | |
| 67 | async def fetch(self) -> bytes: |
| 68 | """Pre-seed top keyword categories.""" |
| 69 | seeded = 0 |
| 70 | async with httpx.AsyncClient( |
RemediationAI
The problem is that t18_worker.py makes network/IO calls without an explicit timeout, allowing a hung upstream to pin threads and exhaust connection pools. Add timeout=30 (or appropriate seconds) to the httpx.AsyncClient() call in the fetch() method of t18_worker.py. This enforces a bounded timeout on all HTTP requests to pre-seed keyword categories. Verify by inspecting the fetch() method and confirming timeout is set on the AsyncClient, then test with a slow upstream to confirm the request times out gracefully.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 59 | self.ttl_seconds = ttl_seconds |
| 60 | self.schedule_seconds = schedule_seconds |
| 61 | |
| 62 | async def fetch(self) -> bytes: |
| 63 | """ |
| 64 | Override in subclass. Must return raw upstream response bytes. |
| 65 | Raise any exception on failure โ run_forever will catch it. |
RemediationAI
The problem is that the base ingest_base.py class does not enforce timeouts on network/IO calls in the fetch() method, allowing hung upstreams to pin threads. Add timeout=30 (or appropriate seconds) as a parameter to all httpx.AsyncClient() instantiations and httpx.get() calls in the fetch() method and run_forever() loop of ingest_base.py. This enforces a bounded timeout across all subclass workers. Verify by inspecting ingest_base.py and confirming timeout is set on all HTTP client calls, then test with a slow upstream to confirm the request times out gracefully.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 83 | schedule_seconds=604800, # weekly โ data updates monthly |
| 84 | ) |
| 85 | |
| 86 | async def fetch(self) -> bytes: |
| 87 | """Download all 4 regional CSVs and index every EIN into Redis.""" |
| 88 | |
| 89 | total_rows = 0 |
RemediationAI
The problem is that t04_worker.py makes network/IO calls without an explicit timeout, allowing a hung upstream to pin threads and exhaust connection pools. Add timeout=30 (or appropriate seconds) to the httpx.AsyncClient() or httpx.get() call in the fetch() method of t04_worker.py. This enforces a bounded timeout on all HTTP requests to download regional CSVs. Verify by inspecting the fetch() method and confirming timeout is set, then test with a slow upstream to confirm the request times out gracefully.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 69 | schedule_seconds=_SCHEDULE, # 21600 โ rate limit awareness |
| 70 | ) |
| 71 | |
| 72 | async def fetch(self) -> bytes: |
| 73 | """Pre-seed top regulatory keyword categories.""" |
| 74 | api_key = os.environ.get("REGULATIONS_GOV_KEY", "") |
| 75 | if not api_key: |
RemediationAI
The problem is that t19_worker.py makes network/IO calls without an explicit timeout, allowing a hung upstream to pin threads and exhaust connection pools. Add timeout=30 (or appropriate seconds) to the httpx.AsyncClient() or httpx.get() call in the fetch() method of t19_worker.py. This enforces a bounded timeout on all HTTP requests to the Regulations.gov API. Verify by inspecting the fetch() method and confirming timeout is set, then test with a slow upstream to confirm the request times out gracefully.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 4 | Downloads from the official CISA GitHub mirror (cisagov/kev-data) โ see KEV_URL constant. |
| 5 | and stores in Redis as: |
| 6 | datanexus:kev:catalog โ full catalog JSON (TTL 25h) |
| 7 | datanexus:kev:fetched_at โ ISO timestamp of last successful fetch (TTL 25h) |
| 8 | datanexus:kev:last_refresh_error โ error message if refresh fails (TTL 7d) |
| 9 | |
| 10 | Invoked two ways: |
RemediationAI
The problem is that kev_refresh.py makes network/IO calls without an explicit timeout, allowing a hung upstream to pin threads and exhaust connection pools. Add timeout=30 (or appropriate seconds) to the httpx.get() or httpx.AsyncClient() call in the refresh() function of kev_refresh.py when downloading from the CISA GitHub mirror. This enforces a bounded timeout on all HTTP requests to fetch the KEV catalog. Verify by inspecting the refresh() function and confirming timeout is set, then test with a slow upstream to confirm the request times out gracefully.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 6 | Endpoints: |
| 7 | GET /ops/dashboard โ full HTML dashboard |
| 8 | GET /ops/api/metrics โ JSON metrics snapshot (same data, for fetch()) |
| 9 | GET /ops/health โ {"ok": true} |
| 10 | |
| 11 | The dashboard auto-refreshes every 15 seconds via JavaScript fetch(). |
RemediationAI
The problem is that dashboard.py makes network/IO calls without an explicit timeout, allowing a hung upstream to pin threads and exhaust connection pools. Add timeout=30 (or appropriate seconds) to any httpx.AsyncClient() or httpx.get() calls in the dashboard endpoints (GET /ops/dashboard, GET /ops/api/metrics, GET /ops/health) in datanexus/ops/dashboard.py. This enforces a bounded timeout on all HTTP requests. Verify by inspecting the dashboard endpoints and confirming timeout is set on all HTTP client calls, then test with a slow upstream to confirm the request times out gracefully.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 38 | class IngestBase: |
| 39 | """ |
| 40 | Base ingest worker. Subclasses override fetch() only. |
| 41 | |
| 42 | Args: |
| 43 | tool_id: Tool this worker feeds (e.g. 'T04') |
RemediationAI
The problem is that the IngestBase class does not enforce timeouts on network/IO calls, allowing hung upstreams to pin threads and exhaust connection pools. Add timeout=30 (or appropriate seconds) as a parameter to all httpx.AsyncClient() instantiations and httpx.get() calls in the run_forever() method and any HTTP operations in datanexus/core/ingest_base.py. This enforces a bounded timeout across all subclass workers. Verify by inspecting ingest_base.py and confirming timeout is set on all HTTP client calls, then test with a slow upstream to confirm the request times out gracefully.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 52 | schedule_seconds=_SCHEDULE, |
| 53 | ) |
| 54 | |
| 55 | async def fetch(self) -> bytes: |
| 56 | """Fetch IANA RDAP bootstrap and store in Redis.""" |
| 57 | async with httpx.AsyncClient( |
| 58 | timeout=_TIMEOUT, headers=_HEADERS, follow_redirects=True |
RemediationAI
The problem is that t07_worker.py makes network/IO calls without an explicit timeout, allowing a hung upstream to pin threads and exhaust connection pools. The evidence shows timeout=_TIMEOUT is already set on the httpx.AsyncClient() call; verify that _TIMEOUT is defined with a reasonable value (e.g., 30 seconds) and is not None or infinite. If _TIMEOUT is not defined or is unbounded, add timeout=30 to the AsyncClient() call. Verify by inspecting t07_worker.py and confirming _TIMEOUT is defined with a finite value, then test with a slow upstream to confirm the request times out gracefully.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 106 | schedule_seconds=3600, |
| 107 | ) |
| 108 | |
| 109 | async def fetch(self) -> bytes: |
| 110 | """Pre-seed all 50 packages. Returns last raw response bytes.""" |
| 111 | seeded = 0 |
| 112 | last_raw = b"" |
RemediationAI
The problem is that t10_worker.py makes network/IO calls without an explicit timeout, allowing a hung upstream to pin threads and exhaust connection pools. Add timeout=30 (or appropriate seconds) to the httpx.AsyncClient() or httpx.get() call in the fetch() method of t10_worker.py. This enforces a bounded timeout on all HTTP requests to pre-seed packages. Verify by inspecting the fetch() method and confirming timeout is set, then test with a slow upstream to confirm the request times out gracefully.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 344 | schedule_seconds=86400, |
| 345 | ) |
| 346 | |
| 347 | async def fetch(self) -> bytes: |
| 348 | """Download bulk extract and index all main charities into Redis.""" |
| 349 | log.info(json.dumps({ |
| 350 | "ts": _iso_now(), "event": "uk_bulk_download_start", "tool": self.tool_id, |
RemediationAI
The problem is that t04_worker.py makes network/IO calls without an explicit timeout, allowing a hung upstream to pin threads and exhaust connection pools. Add timeout=30 (or appropriate seconds) to the httpx.AsyncClient() or httpx.get() call in the fetch() method of t04_worker.py. This enforces a bounded timeout on all HTTP requests to download bulk charity data. Verify by inspecting the fetch() method and confirming timeout is set, then test with a slow upstream to confirm the request times out gracefully.
datanexus-mcp.js is a stdio-to-remote transport proxy that forwards MCP JSON-RPC to https://datanexusmcp.com/mcp. The remote hosted server returns tool results and manifest data over MCP protocol; this is standard remote MCP deployment, not per-invocation steering instruction injection.
Evidence
| 1 | #!/usr/bin/env node |
| 2 | /** |
| 3 | * DataNexus MCP โ stdio proxy for Glama and local MCP clients. |
| 4 | * |
| 5 | * Bridges stdio โ the live remote server at https://datanexusmcp.com/mcp |
| 6 | * using mcp-remote. No Python, no Redis, no PostgreSQL required. |
| 7 | * |
| 8 | * Usage: |
| 9 | * npx -y @datanexusmcp/mcp-server |
| 10 | * |
| 11 | * Claude Desktop claude_desktop_config.json: |
| 12 | * { |
| 13 | * "mcpServers": { |
| 14 | * "datanexus": { |
| 15 | * "command": "npx", |
| 16 | * "args": ["-y", "@datanexusmcp/mcp-server"] |
| 17 | * } |
| 18 | * } |
| 19 | * } |
| 20 | */ |
| 21 | |
| 22 | c |
RemediationAI
The problem identified is that datanexus-mcp.js is a stdio-to-remote transport proxy that forwards MCP JSON-RPC to a remote server; this is standard remote MCP deployment and not a per-invocation steering instruction injection vulnerability. No remediation is required; this is a LOW finding indicating the architecture is acceptable. Verify by reviewing the mcp-remote protocol documentation and confirming that the proxy only forwards JSON-RPC messages without modification or injection.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 223 | try: |
| 224 | r = await get_redis() |
| 225 | if r: |
| 226 | await r.set(cache_key, tier, ex=300) |
| 227 | except Exception: |
| 228 | pass |
| 229 | return tier |
| 230 | |
| 231 | except Exception as exc: |
RemediationAI
The problem is that datanexus/main.py contains a bare except Exception: pass clause that silently discards Redis cache-set failures with no log or metric, blinding incident response. Replace the bare except with except Exception as e: logger.warning('Failed to cache tier', exc_info=True) to log the exception at warning level with full traceback. This preserves debugging information in server logs while maintaining graceful degradation. Verify by triggering a Redis connection failure and confirming a warning log entry appears with the exception details.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 99 | # remaining allowlist domains โ any path counts |
| 100 | for allowed in _PATCH_ALLOWLIST: |
| 101 | if host == allowed or host.endswith("." + allowed): |
| 102 | return True |
| 103 | except Exception: |
| 104 | pass |
| 105 | return False |
RemediationAI
The problem is that datanexus/tools/cve_sprint7.py contains a bare except Exception: pass clause in the allowlist domain check that silently discards parsing failures with no log or metric. Replace the bare except with except Exception as e: logger.debug('Allowlist check failed', exc_info=True) to log the exception at debug level. This preserves debugging information in server logs while maintaining safe-fail behavior (returning False). Verify by triggering a malformed URL and confirming a debug log entry appears with the exception details.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 1184 | raw = r.hget(key_feedback(tool_id, rid), "data") |
| 1185 | if raw: |
| 1186 | try: |
| 1187 | records.append(json.loads(raw)) |
| 1188 | except Exception: |
| 1189 | pass |
| 1190 | except Exception: |
| 1191 | pass |
| 1192 | results[tool_id] = records |
RemediationAI
The problem is that feedback/dashboard/server.py contains nested bare except Exception: pass clauses that silently discard JSON parsing and Redis failures with no log or metric. Replace both bare excepts with except Exception as e: logger.warning('Failed to load feedback record', exc_info=True) to log exceptions at warning level with full traceback. This preserves debugging information in server logs while maintaining graceful degradation. Verify by triggering a malformed JSON record in Redis and confirming a warning log entry appears with the exception details.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 59 | try: |
| 60 | r = _get_redis() |
| 61 | if r: |
| 62 | r.set(LAST_ERROR_KEY, msg[:500], ex=7 * 86400) |
| 63 | except Exception: |
| 64 | pass |
| 65 | |
| 66 | |
| 67 | async def refresh() -> bool: |
RemediationAI
The problem is that datanexus/kev_refresh.py contains a bare except Exception: pass clause that silently discards Redis cache-set failures with no log or metric, blinding incident response. Replace the bare except with except Exception as e: logger.warning('Failed to cache KEV refresh error', exc_info=True) to log the exception at warning level with full traceback. This preserves debugging information in server logs while maintaining graceful degradation. Verify by triggering a Redis connection failure and confirming a warning log entry appears with the exception details.