From 901c22474f5c5d9a673e232de8fe665107a2e1be Mon Sep 17 00:00:00 2001 From: Iftach Yakar Date: Mon, 23 Mar 2026 11:59:54 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20improve=20circuit=20ID=20privacy=20?= =?UTF-8?q?=E2=80=94=20128-bit=20UUIDs=20+=20privacy=20notice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Expand circuit IDs from 8 to 32 hex chars (128-bit UUID), eliminating brute-force enumeration attacks on the shared server - Add privacy_notice field to hello_quantum() response explaining the shared-server trust model - Add Privacy & Security section to README recommending local MCP for sensitive work - Update tests to expect 32-char circuit IDs Co-Authored-By: Claude Sonnet 4.6 --- README.md | 6 +++ .../2026-03-23/circuit-id-privacy/report.md | 37 +++++++++++++++++++ src/stim_mcp_server/circuit_store.py | 2 +- src/stim_mcp_server/server.py | 6 +++ tests/test_server.py | 4 +- 5 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 docs/qa/2026-03-23/circuit-id-privacy/report.md diff --git a/README.md b/README.md index 415493b..296090c 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,12 @@ gcloud run deploy stim-mcp \ | `MCP_HOST` | `0.0.0.0` | Bind address for HTTP mode | | `MCP_PORT` | `8080` | Port for HTTP mode | +## Privacy & Security + +Circuit IDs are 128-bit random tokens (UUID v4), making them practically impossible to guess. However, the shared server has **no authentication and no access control** — anyone who obtains a circuit ID can read and modify that circuit. + +**If you care about the privacy of your circuits, run the MCP server locally** (see [Running locally](#running-locally)). The remote server is intended for experimentation and learning, not sensitive work. + ## Examples > "Create a Bell state and sample it 1000 times" diff --git a/docs/qa/2026-03-23/circuit-id-privacy/report.md b/docs/qa/2026-03-23/circuit-id-privacy/report.md new file mode 100644 index 0000000..cf17afc --- /dev/null +++ b/docs/qa/2026-03-23/circuit-id-privacy/report.md @@ -0,0 +1,37 @@ +# QA Report — 2026-03-23 + +## Feature Tested +Circuit privacy improvements: 128-bit UUID circuit IDs (B) and privacy notice in `hello_quantum` + README (A). + +## Target Server +Local — Stim version: 1.15.0 + +## Changes Analyzed +Branch: master (uncommitted) +Files changed: `circuit_store.py`, `server.py`, `README.md` + +## Test Results + +| # | Description | Tool Called | Inputs | Response | Result | Notes | +|---|-------------|-------------|--------|----------|--------|-------| +| 1 | hello_quantum returns privacy_notice | `hello_quantum` | `{}` | `{"status":"ok","stim_version":"1.15.0","active_sessions":0,"privacy_notice":"..."}` | PASS | All 4 keys present | +| 2 | circuit_id is 32 hex chars (128-bit) | `create_circuit` | Bell state circuit | `{"circuit_id":"c398bf98775b46e583e9b242b9cd131b"}` | PASS | len=32, valid hex | +| 3 | sample_circuit works with 32-char ID | `sample_circuit` | `circuit_id=c398...`, `shots=500` | `{"flip_rates":[0.5,0.5]}` | PASS | Bell state ~50% correct | +| 4 | get_circuit_diagram works with 32-char ID | `get_circuit_diagram` | `circuit_id=c398...`, `diagram_type=text` | ASCII diagram returned | PASS | | +| 5 | append_operation works with 32-char ID | `append_operation` | `circuit_id=c398...`, `DETECTOR rec[-1]` | `{"success":true}` | PASS | | +| 6 | inject_noise returns 32-char noisy circuit ID | `inject_noise` | `circuit_id=c398...`, `DEPOLARIZE1`, `p=0.01` | `{"noisy_circuit_id":"6bca8139d5a74082906d5c0c4df22ce8"}` | PASS | New ID also 32 chars | +| 7 | analyze_errors handles noisy circuit gracefully | `analyze_errors` | `circuit_id=6bca...` | `{"success":false,"error":"non-deterministic detectors..."}` | PASS | Expected Stim error for this circuit shape | +| 8 | Invalid 32-char circuit ID returns graceful error | `sample_circuit` | `circuit_id=deadbeef...deadbeef` | `{"success":false,"error":"No circuit found with id '...'"}` | PASS | | + +## Summary +Total: 8 | Passed: 8 | Failed: 0 | Warnings: 0 + +## Issues Found +None. + +## Passed Checks +- ✅ `hello_quantum` returns `privacy_notice` field +- ✅ Circuit IDs are 32 hex characters (128-bit, was 8) +- ✅ All tools (`sample_circuit`, `get_circuit_diagram`, `append_operation`, `inject_noise`, `analyze_errors`) accept and return 32-char IDs correctly +- ✅ `inject_noise` derivative circuits also get 32-char IDs +- ✅ Unknown circuit IDs return graceful `success: false` errors (no crash) diff --git a/src/stim_mcp_server/circuit_store.py b/src/stim_mcp_server/circuit_store.py index 6d16a60..7f47346 100644 --- a/src/stim_mcp_server/circuit_store.py +++ b/src/stim_mcp_server/circuit_store.py @@ -23,7 +23,7 @@ def __init__(self) -> None: def create(self, circuit: stim.Circuit) -> str: """Store a circuit and return a new session ID.""" - circuit_id = uuid.uuid4().hex[:8] + circuit_id = uuid.uuid4().hex self._sessions[circuit_id] = CircuitSession(circuit=circuit) return circuit_id diff --git a/src/stim_mcp_server/server.py b/src/stim_mcp_server/server.py index 0b54740..b76ce15 100644 --- a/src/stim_mcp_server/server.py +++ b/src/stim_mcp_server/server.py @@ -37,6 +37,12 @@ def hello_quantum() -> str: "status": "ok", "stim_version": stim.__version__, "active_sessions": len(_store.list_ids()), + "privacy_notice": ( + "This is a shared server. Circuit IDs are 128-bit random tokens " + "and are not guessable, but there is no user authentication or " + "access control. Do not use this server for sensitive circuits. " + "For private use, run the MCP server locally." + ), } ) diff --git a/tests/test_server.py b/tests/test_server.py index 93f118e..77dffd5 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -30,7 +30,7 @@ def test_create_and_get(self): store = CircuitStore() circuit = stim.Circuit("H 0\nM 0") cid = store.create(circuit) - assert len(cid) == 8 + assert len(cid) == 32 session = store.get(cid) assert session.circuit == circuit @@ -87,7 +87,7 @@ class TestCreateCircuit: def test_valid_circuit(self): result = json.loads(create_circuit(BELL_CIRCUIT)) assert result["success"] is True - assert len(result["circuit_id"]) == 8 + assert len(result["circuit_id"]) == 32 assert result["num_qubits"] == 2 assert result["num_measurements"] == 2