{
  "slug": "io-github-w0h1v-virustotal",
  "name": "io.github.w0h1v/virustotal",
  "title": "virustotal",
  "description": "MCP server for querying VirusTotal API with comprehensive security analysis tools.",
  "version": "1.0.35",
  "publishedAt": "2026-10-13T23:52:30.460371Z",
  "updatedAt": "2026-10-13T23:52:30.460371Z",
  "repoUrl": "https://github.com/w0h1v/mcp-virustotal",
  "websiteUrl": null,
  "namespace": {
    "type": "github",
    "owner": "w0h1v"
  },
  "kind": "local",
  "packages": [
    {
      "registryType": "npm",
      "identifier": "@burtthecoder/mcp-virustotal",
      "version": "1.0.35",
      "transport": "stdio",
      "runtimeHint": null
    }
  ],
  "remotes": [],
  "envVars": [
    {
      "name": "VIRUSTOTAL_API_KEY",
      "isSecret": true,
      "isRequired": true,
      "description": "Your VirusTotal API key"
    }
  ],
  "category": "security",
  "subcategory": "threat-intel",
  "company": "",
  "categoryAuto": true,
  "checks": [
    {
      "id": "source-linked",
      "label": "Source repository linked",
      "result": "pass",
      "detail": "https://github.com/w0h1v/mcp-virustotal"
    },
    {
      "id": "version-pinned",
      "label": "Version pinned",
      "result": "pass",
      "detail": "Every package has an explicit version"
    },
    {
      "id": "secrets-requested",
      "label": "Secrets requested",
      "result": "info",
      "detail": "Asks for 1 secret: VIRUSTOTAL_API_KEY"
    },
    {
      "id": "known-registry",
      "label": "Known package registry",
      "result": "pass",
      "detail": "All packages come from npm, PyPI, OCI, NuGet or MCPB"
    },
    {
      "id": "namespace",
      "label": "Namespace ownership",
      "result": "info",
      "detail": "Name verified via GitHub account w0h1v"
    },
    {
      "id": "meta-text",
      "label": "Registry text aimed at the AI",
      "result": "pass",
      "detail": "Description and settings contain no hidden characters or instructions to the AI"
    },
    {
      "id": "install-scripts",
      "label": "Install scripts",
      "result": "pass",
      "detail": "@burtthecoder/mcp-virustotal@1.0.35 runs nothing at install"
    },
    {
      "id": "code-flags",
      "label": "Code red flags",
      "result": "pass",
      "detail": "Read 25 files in @burtthecoder/mcp-virustotal@1.0.35: no obfuscation, encoded execution, credential reads or data-collection endpoints found"
    },
    {
      "id": "ai-text",
      "label": "Text aimed at the AI",
      "result": "pass",
      "detail": "No hidden characters or instructions to the AI in 25 files, including comments and the README"
    }
  ],
  "scannedAt": "2026-10-29",
  "level": "Scanned",
  "install": [
    {
      "label": "Claude Desktop (npm)",
      "kind": "claude_desktop_json",
      "text": "{\n  \"mcpServers\": {\n    \"virustotal\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@burtthecoder/mcp-virustotal@1.0.35\"\n      ],\n      \"env\": {\n        \"VIRUSTOTAL_API_KEY\": \"<your value>\"\n      }\n    }\n  }\n}"
    }
  ],
  "curated": true,
  "stars": 152,
  "archived": false,
  "pushedAt": "2026-10-23T14:54:43Z",
  "review": {
    "package": "@burtthecoder/mcp-virustotal",
    "version": "1.0.35",
    "at": "2026-10-31",
    "files": 23,
    "lines": 1794,
    "verdict": "pass-with-notes",
    "summary": "Looks up URLs, files (by hash), IPs and domains on VirusTotal. It talks only to VirusTotal and sends your API key only there. Note that checking a URL VirusTotal has never seen submits it for a public scan, and every lookup is written to a local log file.",
    "hosts": [
      {
        "host": "www.virustotal.com/api/v3",
        "when": "only when a tool is called",
        "sends": "URLs (base64url), file hashes, IPs, domains, collection ids, search queries; x-apikey header. get_url_report POSTs the URL to /urls for a new scan when VirusTotal has no report yet"
      }
    ],
    "env": [
      {
        "name": "VIRUSTOTAL_API_KEY",
        "sent_to": "www.virustotal.com only (x-apikey header); never logged"
      },
      {
        "name": "MCP_TRANSPORT / MCP_PORT / MCP_HOST / MCP_ENDPOINT",
        "sent_to": "local (optional HTTP transport, default host localhost)"
      },
      {
        "name": "TEMP / TMP",
        "sent_to": "local (fallback log folder on Windows)"
      }
    ],
    "local": "Loads .env via dotenv; reads its own package.json. Creates <package>/logs/mcp-virustotal-server.log (fallback: %TEMP%\\mcp-virustotal-logs or /tmp/mcp-virustotal-logs) and appends every request path, the URLs/domains/hashes looked up and search query text. No other file access; no file uploads.",
    "exec": "None.",
    "telemetry": "None.",
    "tools": [
      "get_url_report",
      "get_url_relationship",
      "get_file_report",
      "get_file_relationship",
      "get_ip_report",
      "get_ip_relationship",
      "get_domain_report",
      "get_domain_relationship",
      "search_vt",
      "get_file_behaviour_summary",
      "get_collection"
    ],
    "toolText": "clean (get_url_report's description discloses that it submits URLs for scanning)",
    "deps": [
      "fastmcp (npm)",
      "axios",
      "zod",
      "dotenv"
    ],
    "findings": [
      {
        "severity": "note",
        "at": "build/handlers/url.js:34-39",
        "what": "If VirusTotal has no report for a URL, get_url_report automatically submits it for scanning with no confirmation. Submitted URLs become part of VirusTotal's corpus and can be seen by other VirusTotal users, so private or internal URLs (with tokens in them) can leak. Disclosed in the tool description."
      },
      {
        "severity": "note",
        "at": "build/utils/logging.js:8-56; handlers/url.js:31,36,62; handlers/search.js:9",
        "what": "Every looked-up URL and search query is written to a log file inside the package folder (or temp), kept forever. The API key is not logged."
      },
      {
        "severity": "info",
        "at": "build/handlers/collection.js:8-9",
        "what": "Collection id goes into the API path unencoded; the host is fixed to virustotal.com, so at most it reaches another VirusTotal endpoint with the user's own key."
      },
      {
        "severity": "info",
        "at": "build/formatters/*.js",
        "what": "Third-party text from VirusTotal (community comments, page titles, threat-actor descriptions) is passed to the AI unfiltered; such text could carry prompt-injection attempts. Normal for a threat-intel tool."
      },
      {
        "severity": "info",
        "at": "build/schemas/index.js",
        "what": "Inputs are validated (URL, IP, hash and domain formats, relationship enums)."
      },
      {
        "severity": "info",
        "at": "package",
        "what": "No package.json in the reviewed copy (read at runtime for the version); dependencies are taken from imports. No obfuscation, install hooks, dynamic code or hard-coded secrets."
      },
      {
        "severity": "info",
        "at": "manifest.tsv",
        "what": "lines_read (1794) is the real line count of every file (Read tool / splitlines). The manifest's 1817 counts one extra empty 'line' after the trailing newline of 23 files; no file or line was skipped."
      }
    ]
  },
  "health": {
    "score": 63,
    "parts": {
      "popularity": 0.5160470606083847,
      "activity": 0.6,
      "issues": null,
      "security": 1
    },
    "fail": 0,
    "warn": 0,
    "raw": {
      "stars": 154,
      "forks": 24,
      "downloads": 590,
      "hn": {
        "n": 0,
        "pts": 0,
        "t": []
      },
      "issuesOpen": 1,
      "issuesClosed": 1,
      "prsMerged": 25,
      "commits90": 0,
      "release": null,
      "pushed": "2026-10-23T14:54:43Z",
      "growth": null,
      "growthDays": null,
      "archived": false
    },
    "at": "2026-10-31T07:35:52.424Z"
  }
}
