MCPSafe.io
RegistryThreatsMethodologyDocsPricingScanSign in
MCPSafe.io

Security checks for MCP servers — public packages and private repos, fast or deep.

Legal

Privacy PolicyCookie PolicyTerms of ServiceSecurity disclosure

Resources

State of MCP SecuritySupportSystem statusMade in Germany 🇩🇪

© 2026 MCPSafe. All rights reserved.

GDPR — Privacy Policy
← Threat Catalog

Server Implementation

Weak password-hashing primitive

MEDIUMAIVSS 9.2CWE: CWE-916OWASP: LLM06Agentic: T09Rule: MCP-050

Password storage uses MD5 or SHA-1 — fast, GPU-accelerated, unsalted by default — or bcrypt with fewer than 10 rounds. None of these are password hashes; cracking the resulting digest with a modern GPU is a matter of hours, not years.

What it is

Cryptographic hash functions and password-hashing functions solve different problems. MD5 and SHA-1 are designed to be fast — exactly the wrong property when an attacker who exfiltrates the hash table will run billions of guesses per second on rented hardware. bcrypt, scrypt, and argon2id deliberately slow themselves down with a tunable cost parameter ("rounds") so the attacker's brute-force loop is throttled too. bcrypt with rounds<10 falls back into the fast-hash regime and stops being a password hash in any meaningful sense.

Why it matters for MCP

MCP servers occasionally store user credentials directly — for self-hosted deployments, admin login pages, or per-tool API keys assigned to teammates. Authors reach for `hashlib.md5(password)` because it is the first hashing primitive the standard library exposes, and the surrounding code looks plausible. The bug rarely shows up in security review because the surface is small and the test case ("login works") doesn't exercise the threat model. When a database is leaked, the difference between MD5 and argon2id is the difference between every password being public and most passwords surviving.

Vulnerable example

example.py
1
import hashlib
2
3
def store_password(password: str) -> str:
4
    # MD5 is a fast hash, not a password hash — GPU-crackable.
5
    return hashlib.md5(password.encode()).hexdigest()

Secure example

example.py
1
from argon2 import PasswordHasher
2
3
ph = PasswordHasher()  # tuned defaults: argon2id, t=3, m=64MB, p=4
4
5
def store_password(password: str) -> str:
6
    return ph.hash(password)
7
8
def verify_password(stored: str, candidate: str) -> bool:
9
    try:
10
        return ph.verify(stored, candidate)
11
    except Exception:
12
        return False

How MCPSafe detects this

MCPSafe flags `hashlib.md5(...)` / `hashlib.sha1(...)` whose argument name matches `password` / `passwd` / `pwd` / `secret`, and `crypto.createHash("md5"|"sha1")` in JavaScript. Also flags `bcrypt.gensalt(rounds=N)` / `bcrypt.gensalt(N)` where `N < 10`. Hashing constants or non-password identifiers (e.g. ETag, content-addressable IDs) is not flagged.

See the full threat catalog for every documented detection.

Framework alignment

OWASP LLM Top-10 (2025)
LLM06 — Excessive Agency
OWASP Agentic AI Top-10
T09 — Identity Spoofing
AIVSS v0.5
9.2 (CRITICAL)AIVSS:1.0/S:CRITICAL/AV:N/AU:N/BR:H/CD:D

Further reading

  • CWE-916: Use of Password Hash With Insufficient Computational Effort
  • OWASP Password Storage Cheat Sheet

Scan an MCP server for this issue

MCPSafe runs this check — and every other rule in the catalog — on any MCP server you paste in.

Scan now