send_raw safety pattern を audit し、 Rei stack SafetyGate (benchtop-mcp v0.5.0-alpha, STEP 1345) と 比較。 Rei-scout Finding B (2026-08-27 report) の pattern-learning 実行。 Rei-AIOS 側 は Rigol hardware 未所有 = direct wire-up rejection、 pattern 抽出 のみが 実 audit 価値。
| System | Design goal | Threat prevented |
|---|---|---|
| rigol-mcp | 制御 target hardware (scope) with escape hatch | Instrument state pollution / arbitrary SCPI discoverability |
| Rei stack SafetyGate | Import 外部 provenance record + 保存前 validate | Physical damage (short circuit / DUT destruction) via unit-confusion (Kikusui PLZ-5W CR mode Siemens vs Ω) |
20+ dedicated tools (screenshot / idn / measure / get_waveform / run / stop / autoscale etc.) each with bounded semantics。 Arbitrary SCPI (send_raw) is NOT among defaults。
RIGOL_ENABLE_SEND_RAW を truthy にした場合のみ send_raw を expose。 2 checkpoint defense-in-depth:
_SEND_RAW_ENV = "RIGOL_ENABLE_SEND_RAW"
def _send_raw_enabled() -> bool:
return os.environ.get(_SEND_RAW_ENV, "").strip().lower() \
not in ("", "0", "false", "no", "off")
# server.py:398 — list_tools() filter
if not _send_raw_enabled():
tools = [t for t in tools if t.name != "send_raw"]
# server.py:540 — call_tool() re-check
if name == "send_raw":
if not _send_raw_enabled():
raise ValueError(
f"send_raw is disabled. Set {_SEND_RAW_ENV}=1 to enable ..."
)
Client が cached tool schema や 直接 construction で bypass しても call path で reject。 error message に fix hint (env var 名) 埋込。
# scope.py:347
def send_raw(scope, command: str) -> str:
if command.strip().endswith("?"):
return scope.query(command).strip()
scope.write(command)
if err := check_scpi_error(scope):
raise RuntimeError(f"SCPI error after '{command}': {err}")
check_scpi_error は queue を drain (最初の 1 件でなく 全件)、 stale error が 次 tool call に leak するのを防止。
class SafetyGate:
def __init__(self, rules=None):
self.rules = rules or [_rule_kikusui_plz5w_conductance_hazard]
def check_scpi(self, vendor, model, command, args) -> SafetyVerdict:
for rule in self.rules:
verdict = rule(vendor, model, command, args)
if verdict is not None:
return verdict # fail-fast
return SafetyVerdict(ok=True, severity="info",
rule_id="no-rule-matched", ...)
Hazard: CR mode は CONDuctance (Siemens) を 使う、 RESistance (Ω) ではない。 caller が CONDuctance 100 を 「100Ω」 のつもりで 送ると 実際は 100S = 0.01 Ω = 短絡 (DUT 破壊)、 syntax error にならず そのまま 実行。
Rule threshold: > 1 S ⇒ < 1 Ω を severity: dangerous で reject、 suggested_fix に CONDuctance {1/value} 埋込。 Kikusui マニュアル (「ジーメンス」 明記) と 突合済。
class SafetyVerdict:
ok: bool
severity: Literal["info", "warn", "dangerous"]
rule_id: str
reason: str
suggested_fix: str | None
detail: dict[str, Any] | None
| Dimension | rigol-mcp | Rei stack SafetyGate |
|---|---|---|
| Default posture | Whitelist 20+ tool; arbitrary SCPI hidden | Rule-match のみ; arbitrary-SCPI wrapper なし |
| Gate mechanism | env var (process 全体 toggle) | per-command rule dispatch (regex + threshold) |
| Defense-in-depth | 2 checkpoint (discover + call) | 1 checkpoint per call |
| Fix hint | ValueError string 内 env var 名 | structured suggested_fix field |
| Post-write verify | error queue auto-drain + raise | N/A (import-only、 direct write なし) |
| Override | env var toggle (session level) | 未実装 (spike scope-out) |
| Rule extensibility | N/A (gate 1 個) | Composable SafetyGate(rules=[...]) |
| Hazard granularity | Coarse (any SCPI → gate) | Fine (vendor/model/command/value → rule) |
| Blast radius | Instrument state pollution | Physical damage (short circuit) |
Different threat model at different pipeline stage:
Production write-capable MCP server は 両方持つべき: (1) rigol-mcp-style gate at write path (whitelist + env opt-in for escape hatch) + (2) Rei-stack-style SafetyGate on write pre-check + on import path。
全 defer、 subsequent STEP directive 待ち:
write_scpi() tool 追加時、 BENCHTOP_ENABLE_WRITE_SCPI=1 gate (rigol-mcp mirror)。 Blocked on write_scpi design。import_external_session は 常時 discover 可能。 BENCHTOP_ALLOW_IMPORT_WRITE=1 env var で list_tools() filter 追加 candidate。 Effort 15-30 min。suggested_fix field 全 caller 露出 audit — grep で 落とし 発見 + fix。 Effort 15 min。BENCHTOP_* prefix (rigol-mcp RIGOL_* mirror)。 Effort 10 min。Rei-scout Finding B は MEDIUM priority で 「Rigol hardware 導入 予定 なし → Rejection candidate」 と 記録。 本 audit で direct wire-up rejection is correct 確認:
Wire-up は hardware ownership 依存で block 継続。 学べた design pattern が 実 deliverable。
.env.example + README は GitHub API 経由 (local install なし)。send_raw gate は construction 上 well-tested (single boolean + 2 checkpoint) だが env var 一般 caveat 適用: os.environ mutation access ある process は enable 可能。bandit / ansible-lint pattern mirror。