← All MCP servers
MCP server

André — Analyse électorale française

mcp:joelgombin/andre-mcp

Résultats des élections françaises 1999-2026 par bureau de vote, socio-démo INSEE et cartes.

Verdict
Suspicious

Risky behavior the analysis could not rule out.

Analyzed Oct 26, 2026, 11:18 UTC at 4e0414e
3
findings
high
highest severity
3
threat classes
29
files scanned
247/247
checks satisfied
0
package advisories

Why this verdict

  • suspicious: finding mcp-server:.:src/andre_mcp/server.py:1139:RECON
  • suspicious: finding mcp-server:.:src/andre_mcp/server.py:874:PERSIST
  • suspicious: finding mcp-server:.:src/andre_mcp/server.py:881:EXFIL

Security findings 3

Persistence

highContextual riskmedium confidence

export_data builds the output path from an unsanitized filename parameter, so path traversal could write attacker-influenced content outside ~/Downloads (with a .csv/.json/.tsv suffix); this is undeclared and not needed for the stated export purpose.

output_path = output_dir / f"{filename}{ext}"
vector:code
Counter-argument considered

The filename parameter is concatenated into the output path without sanitization, so '../' sequences can write outside ~/Downloads. The content is attacker-controlled data or a copied file, and only the suffix is constrained.

Data exfiltration

highContextual risklow confidence

The export_data tool, described only as exporting electoral data, copies any caller-chosen server-side file (source_path) into ~/Downloads under a caller-controlled filename that is not sanitized, so it can read arbitrary local files and write outside the intended directory.

shutil.copy2(str(src), str(output_path))
vector:code
Counter-argument considered

The 'exfil' framing is overstated, since the copy stays local and the source_path mode is documented. The unsanitized filename is a real traversal, though: it lets a caller-chosen file be copied to an arbitrary location, so the finding is not fully refuted.

Reconnaissance

lowContextual riskmedium confidence

query_sql is advertised as read-only, but its blocklist only checks whether the query starts with a few write keywords. DuckDB file functions such as read_csv/read_text and statements such as COPY...TO, ATTACH, INSTALL and LOAD are not blocked, so arbitrary host files could be read and returned or written.

forbidden = ["INSERT", "UPDATE", "DELETE", "DROP", "ALTER", "CREATE", "TRUNCATE"]
vector:code
Counter-argument considered

The strongest refutation is that the DB is opened read_only=True and writes are blocked. But db.py sets no enable_external_access=false and locks no configuration, and the keyword check only tests the query prefix. read_csv/read_text, COPY TO, ATTACH, INSTALL and LOAD therefore stay reachable from agent-supplied SQL. That contradicts the 'read-only' claim and is not part of the declared election-data capability.

Open questions 2

What the analysis could not settle from the code alone.

[file request query_elections.py] The server runs scripts from the parent project root (query_elections.py, generate_map.py, etc.) that are not shown here; what do they do (network, file writes, exec)?
Code execution
[file request query_elections.py] Does the project-root script that _run_script launches exist, and how does it handle the CLI arguments the tools pass (for example --candidate, --condition, --sort-by)? Do any of them reach SQL or shell construction or file writes?
Code execution
Refuted findings 5

Candidates the review stage examined and rejected, kept for transparency.

Data exfiltration

highRefutedmedium confidence

The served landing page loads a Matomo analytics script from a third-party host and tracks page views and link clicks of visitors; this is undeclared telemetry, though limited to the HTML landing page and not to MCP tool use.

g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
Why it was refuted

The Matomo snippet is first-party analytics in the browser-served landing page and is labelled as such. It tracks only page views and link clicks, with no sensitive data flow and no agent or tool involvement.

Credential access

highRefutedlow confidence

export_data copies any existing file named by the caller-supplied `source_path` (for example key or config files) to a new location via shutil.copy2, with no path restriction.

shutil.copy2(str(src), str(output_path))
Why it was refuted

The source_path mode is openly documented in the tool docstring, and the file is only copied into the local ~/Downloads directory. There is no credential read-out or transmission sink.

Supply chain

mediumRefutedlow confidence

The landing page tells users to add an MCP server launched via `npx -y mcp-remote@latest`, an unpinned package fetched and run on each launch, pointing at a remote MCP endpoint (undeclared install surface in served HTML).

"args": ["-y", "mcp-remote@latest",
Why it was refuted

This is user-facing connection documentation on the landing page, openly presented as setup steps for Claude Desktop and Claude Code. No agent instruction runs it, and it is not concealed.

Reconnaissance

lowRefutedlow confidence

The export_data tool accepts an arbitrary source_path from the agent and copies any readable local file into ~/Downloads, which election-data export does not need (undeclared, unrestricted file staging; could be steered by prompt injection).

shutil.copy2(str(src), str(output_path))
Why it was refuted

The copy-from-path behavior is the documented source_path export mode. The file lands locally in ~/Downloads and its contents are never returned to the agent, so there is no recon sink.

Reconnaissance

lowRefutedlow confidence

The visualize tool reads and JSON-parses an arbitrary local file given by data_file (undeclared path restriction), which can surface local file contents to the agent/model beyond the election-chart purpose.

data = json_mod.loads(p.read_text(encoding="utf-8"))
Why it was refuted

Loading chart data from a data_file is the declared assisted mode of visualize, and only chart output is produced. It is not an undeclared file-reading capability.