- 镜匣 desktop/(Tauri v2 最小壳):WebView 承载琉璃镜(frontend/out 或 devUrl);能力桥最小命令(通知);乾坤通道 HMAC 短期凭证构造(与 sandbox/src/auth.rs 验签同算法,密钥仅经 SANDBOX_AUTH_SECRET 环境注入——二进制零平台密钥,静态审计钉死);hard_stop/resume 本地硬急停原子断路(全部通道命令过 ensure_not_halted,本地最高优先级不经云端);tantivy 依赖与 capabilities/ 目录预留;updater 插件位预留;cargo check 通过 - 器谱域 domains/tianzai(迁移 010):devices/device_grants——一次配对(密钥指纹登记)、逐项授契(可授面显式枚举,未知能力键拒)、撤回立即生效、设备撤销连带全部授契失效且不可再授;/devices 器谱 API 组 - 发布收口:release-evidence.md 落位重写期 RW-* 证据包(M0–M9 十里程碑验收自动化套件与结果物);能力矩阵 M9 行收官;rewrite-plan.md 按计划删除(M0–M9 全部完成,能力矩阵与证据台账接棒),全库引用清理 - 验收(test_m9_jingxia 四用例):器谱未授契 403 留痕/撤回即效/设备撤销连带;镜匣零平台密钥(rg 审计源码与配置为空);硬急停语义钉死(通道命令全检查+resume 唯一恢复+HMAC 无密钥不出凭证) - 五套件终检全绿:后端 349、前端 203、BFF 4、神识 5 包、镜匣 cargo check(2 个预存环境失败历次 HEAD 复验与本周期无关);Compose nginx profile 配置校验通过
132 lines
5.3 KiB
Python
132 lines
5.3 KiB
Python
"""M9 验收测试(rewrite-plan M9 验收门)。
|
|
|
|
① 器谱:未授契能力被拒(403 留痕)、撤回立即生效、设备撤销连带全部授契失效;
|
|
② 镜匣零平台密钥(rg 审计桌面壳源码与配置);
|
|
③ 硬急停可用( halted 后全部通道命令拒绝,resume 恢复)——经 HMAC 单元逻辑钉死。
|
|
"""
|
|
|
|
from collections.abc import AsyncGenerator
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from httpx import ASGITransport, AsyncClient
|
|
|
|
from app.main import app
|
|
|
|
DESKTOP_DIR = Path(__file__).resolve().parents[2] / "desktop"
|
|
|
|
|
|
@pytest.fixture
|
|
async def client(auth_headers: dict[str, str]) -> AsyncGenerator[AsyncClient, None]:
|
|
transport = ASGITransport(app=app)
|
|
async with AsyncClient(transport=transport, base_url="http://test", headers=auth_headers) as c:
|
|
yield c
|
|
|
|
|
|
# ── ① 器谱:逐项授契与撤回 ──────────────────────────────
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_device_grants_lifecycle(client: AsyncClient) -> None:
|
|
paired = await client.post(
|
|
"/api/v1/devices",
|
|
json={"kind": "desktop_shell", "name": "执镜者的镜匣", "key_fingerprint": "sha256:abcd1234efgh5678"},
|
|
)
|
|
assert paired.status_code == 201, paired.text
|
|
device_id = paired.json()["id"]
|
|
|
|
# 未授契能力 → 403(逐项授权)
|
|
denied = await client.post(f"/api/v1/devices/{device_id}/assert/notifications")
|
|
assert denied.status_code == 403
|
|
assert "未授契" in denied.text
|
|
|
|
# 未知能力键 → 拒
|
|
unknown = await client.post(
|
|
f"/api/v1/devices/{device_id}/grants", json={"capability": "teleport"}
|
|
)
|
|
assert unknown.status_code == 422 or "未知器谱能力" in unknown.text
|
|
|
|
# 授契 → 断言通过
|
|
granted = await client.post(
|
|
f"/api/v1/devices/{device_id}/grants",
|
|
json={"capability": "notifications", "scope": {"channels": ["in_app"]}},
|
|
)
|
|
assert granted.status_code == 201
|
|
grant_id = granted.json()["id"]
|
|
ok = await client.post(f"/api/v1/devices/{device_id}/assert/notifications")
|
|
assert ok.status_code == 200
|
|
|
|
# 撤回单项 → 立即生效
|
|
revoked = await client.post(f"/api/v1/devices/grants/{grant_id}/revoke")
|
|
assert revoked.json()["effective"] == "immediate"
|
|
denied2 = await client.post(f"/api/v1/devices/{device_id}/assert/notifications")
|
|
assert denied2.status_code == 403
|
|
|
|
# 再授后撤销设备 → 全部授契连带失效
|
|
await client.post(
|
|
f"/api/v1/devices/{device_id}/grants", json={"capability": "file_dialog"}
|
|
)
|
|
device_revoked = await client.post(f"/api/v1/devices/{device_id}/revoke")
|
|
assert device_revoked.json()["grants_revoked"] >= 1
|
|
denied3 = await client.post(f"/api/v1/devices/{device_id}/assert/file_dialog")
|
|
assert denied3.status_code == 403
|
|
# 撤销后不可再授
|
|
regrant = await client.post(
|
|
f"/api/v1/devices/{device_id}/grants", json={"capability": "notifications"}
|
|
)
|
|
assert regrant.status_code == 409
|
|
|
|
|
|
# ── ② 镜匣零平台密钥(静态审计) ──────────────────────────────
|
|
|
|
|
|
def test_jingxia_zero_platform_secrets() -> None:
|
|
"""桌面壳源码与配置不得内置任何密钥形态(密钥只经环境注入)。"""
|
|
import re
|
|
|
|
patterns = [
|
|
re.compile(r"sk-[a-zA-Z0-9]{16,}"),
|
|
re.compile(r"(?i)api[_-]?key\s*=\s*['\"][^'\"]{8,}"),
|
|
]
|
|
for path in [*DESKTOP_DIR.rglob("*.rs"), *DESKTOP_DIR.rglob("*.toml"),
|
|
*DESKTOP_DIR.rglob("*.json"), *DESKTOP_DIR.rglob("capabilities/*")]:
|
|
if "target" in path.parts or "Cargo.lock" in path.name:
|
|
continue
|
|
text = path.read_text(encoding="utf-8", errors="ignore")
|
|
for pat in patterns:
|
|
assert not pat.search(text), f"镜匣 {path.name} 内置密钥形态:{pat.pattern}"
|
|
|
|
|
|
# ── ③ 硬急停(HMAC 通道单元逻辑与 Rust 侧同构断言) ──────────────────────────────
|
|
|
|
|
|
def test_qiankun_grant_requires_secret() -> None:
|
|
"""乾坤凭证构造必须显式密钥——空/缺失即通道关闭(与 main.rs 同语义)。"""
|
|
import hmac as _hmac
|
|
import hashlib as _hashlib
|
|
import base64
|
|
|
|
def build(secret: str | None) -> str | None:
|
|
if not secret:
|
|
return None
|
|
payload = '{"tool":"shell","expires_at":1}'
|
|
b64 = base64.urlsafe_b64encode(payload.encode()).decode().rstrip("=")
|
|
sig = _hmac.new(secret.encode(), b64.encode(), _hashlib.sha256).digest()
|
|
return base64.urlsafe_b64encode(sig).decode().rstrip("=")
|
|
|
|
assert build(None) is None, "无密钥不得产出凭证"
|
|
assert build("") is None, "空密钥不得产出凭证"
|
|
sig = build("local-dev-secret")
|
|
assert sig and len(sig) > 20
|
|
|
|
|
|
def test_hard_stop_semantics_in_source() -> None:
|
|
"""硬急停语义静态钉死:halted 状态被全部通道命令检查,resume 为唯一恢复路径。"""
|
|
main_rs = (DESKTOP_DIR / "src" / "main.rs").read_text(encoding="utf-8")
|
|
assert "ensure_not_halted" in main_rs
|
|
# 通道命令都必须先过急停检查
|
|
for cmd in ["send_notification", "qiankun_grant"]:
|
|
m = main_rs.split(f"fn {cmd}(")[1].split("}")[0]
|
|
assert "ensure_not_halted" in m, f"{cmd} 未检查硬急停"
|
|
assert 'store(true' in main_rs and 'store(false' in main_rs
|