feat(m4): verify signed surface bundles across hosts

This commit is contained in:
2026-08-04 01:51:23 +08:00
parent 4fc83e5b13
commit d8aa087bc8
21 changed files with 1135 additions and 8 deletions
+53 -1
View File
@@ -16,7 +16,10 @@ import { HttpTransportAdapter, type TransportAdapter } from "./runtime/transport
import { TrustedDOMRendererContext } from "./runtime/trusted-dom-renderers";
import { SurfaceRegistry } from "./runtime/surface-registry";
import { SurfaceInstanceManager } from "./runtime/surface-instance-manager";
import { IsolatedSurfaceHost } from "./runtime/isolated-surface-host";
import { CachedVerifiedSurfaceDocumentResolver, IsolatedSurfaceHost } from "./runtime/isolated-surface-host";
import { ProductionSurfaceManifestRegistry } from "./runtime/production-surface-manifest";
import { ProductionSurfacePolicy } from "./runtime/production-surface-policy";
import { TrustedSurfaceBundleDownloader, VerifiedSurfaceBundleCache, WebCryptoEd25519SurfaceManifestSignatureVerifier } from "./runtime/surface-bundle-cache";
import { CapabilityRegistry } from "./runtime/capability-registry";
import { ClientInventoryPublisher } from "./runtime/client-inventory";
import { ArtifactState } from "./runtime/artifact-state";
@@ -35,6 +38,17 @@ type Session = {
active: boolean;
};
declare global {
interface ImportMeta {
readonly env: Readonly<{ DEV: boolean }>;
}
interface Window {
/** Development-only M4 signed-bundle browser acceptance controller. */
__lineupM4Fixture?: Readonly<{ patch: () => void; close: () => void }>;
}
}
const DEFAULT_API = "http://127.0.0.1:8090";
let session: Session = { api: localStorage.getItem("lineup.api") || DEFAULT_API, uid: "", agentUID: "agent_hermes_main", channelID: "agent_default_channel", channelType: 2, lastSeq: 0, active: false };
let transport: TransportAdapter | undefined;
@@ -104,6 +118,43 @@ const surfaceHost = new IsolatedSurfaceHost(messages, {
});
$<HTMLInputElement>("#api").value = session.api;
/**
* A pre-signed public fixture used only by the headed M4 acceptance route.
* It contains no private key, is excluded from production builds, and forces
* the actual Ed25519 → size → SHA-256 → cache → exact resolver path.
*/
async function mountM4SignedFixture(): Promise<void> {
if (!import.meta.env.DEV || !new URLSearchParams(location.search).has("m4-signed-surface-e2e")) return;
const encodedBundle = "PG1haW4-PGgyIGlkPSJtNC1zaWduZWQiPk00IOW3suetvuWQjSBTdXJmYWNlPC9oMj48cCBpZD0ic3RhdGUiPuetieW-heeKtuaAgTwvcD48c2NyaXB0PndpbmRvdy5hZGRFdmVudExpc3RlbmVyKCJtZXNzYWdlIixlPT57Y29uc3QgZD1lLmRhdGE7aWYoZCYmZC50eXBlPT09ImxpbmV1cC5zdXJmYWNlLnYxLnN0YXRlIilkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCIjc3RhdGUiKS50ZXh0Q29udGVudD1TdHJpbmcoZC5zdGF0ZT8uc3RhdHVzfHwi6L-Q6KGM5LitIil9KTt3aW5kb3cucGFyZW50LnBvc3RNZXNzYWdlKHt2OjEsdHlwZToibGluZXVwLnN1cmZhY2UudjEucmVhZHkiLGluc3RhbmNlX2lkOndpbmRvdy5fX0xJTkVVUF9TVVJGQUNFX0lOU1RBTkNFX199LCIqIik8L3NjcmlwdD48L21haW4-";
const standardBase64 = encodedBundle.replace(/-/g, "+").replace(/_/g, "/");
const bundle = Uint8Array.from(atob(standardBase64 + "=".repeat((4 - standardBase64.length % 4) % 4)), value => value.charCodeAt(0));
const manifest = {
v: 1, app_id: "lineup.m4-signed-fixture", version: "1.0.0",
artifact: { artifact_id: "m4-signed-fixture", sha256: "98cbcf29d38cf08ae6385539b3ac7359710309a1b752a14b60c8cfc8a39f1a37", size_bytes: 396 },
min_host_version: "0.1.0", permissions: ["surface.event.cancel"], key_id: "m4-e2e-key",
signature: "dCORfPL6LTxR8CB5mVss5fEeGozTdfN3L7Td27BYgxJ518yNWAmI4FEhfrjEoc_Wovwn8u8HI5BLBjWSUxZ6Dg",
} as const;
const cache = new VerifiedSurfaceBundleCache(new WebCryptoEd25519SurfaceManifestSignatureVerifier({ "m4-e2e-key": "AKx_a1NVmsJKbqY9Jw55hLp8r01TcCadjZNp1u2POLI" }));
const policy = new ProductionSurfacePolicy(new ProductionSurfaceManifestRegistry({ host_version: "0.1.0", trusted_key_ids: ["m4-e2e-key"] }), cache);
const downloader = new TrustedSurfaceBundleDownloader("https://m4-e2e.lineup.invalid/release", async url => ({ ok: true, url: url.toString(), async bytes() { return bundle; } }));
if ((await policy.downloadAndEnable(manifest, downloader)).disposition !== "installed") throw new Error("M4 signed fixture verification failed.");
const instances = new SurfaceInstanceManager(policy);
const host = new IsolatedSurfaceHost(messages, {
ready(instanceID) { instances.ready(instanceID, new Date().toISOString()); },
event() { /* fixture grants no outbound capabilities or transport events */ },
}, new CachedVerifiedSurfaceDocumentResolver(cache));
const opened = instances.open({ instance_id: "m4:signed-fixture", app: { app_id: manifest.app_id, version: manifest.version }, state: { status: "签名验证后已挂载" } }, currentConversationKey(), new Date().toISOString());
if (opened.disposition !== "accepted" || !opened.instance) throw new Error(`M4 signed fixture open rejected: ${opened.disposition}`);
host.mount(opened.instance);
window.__lineupM4Fixture = {
patch() {
const result = instances.patch("m4:signed-fixture", { status: "已通过可信 patch 更新" }, new Date().toISOString());
if (result.disposition === "accepted" && result.instance) host.update(result.instance);
},
close() { host.unmount("m4:signed-fixture"); instances.close("m4:signed-fixture"); },
};
}
function newLocalID(prefix: string): string {
// `crypto.randomUUID` is absent from older Safari / WKWebView versions.
// A local item id needs uniqueness within this browser store, not a
@@ -738,6 +789,7 @@ $<HTMLFormElement>("#login-form").addEventListener("submit", async event => {
}
for (const instance of surfaceInstances.snapshot().instances) surfaceHost.mount(instance);
rendererContext.restoreExecutionSummaries(snapshot.execution_summaries);
await mountM4SignedFixture();
rendererContext.renderSystemMessage("已进入与 AI Agent 的对话"); rendererContext.setPresence("正在同步消息…"); void publishInventory(); void flushOutbox(); void syncLoop();
} catch (reason) { error.textContent = loginFailureMessage(reason, endpoint); }
finally { submit.disabled = false; submit.textContent = "进入 LineUp"; }