Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
81a8c2b
Speed up MXC startup attestation
Copilot Sep 9, 2026
0b3b9a8
Fail closed on degraded MXC containment
Copilot Sep 11, 2026
adb113b
Revalidate MXC launch dependencies
Copilot Sep 11, 2026
25c567c
Fail closed before unavailable MXC backends
Copilot Oct 9, 2026
04b4829
Add explicit MXC containment tiers
Copilot Oct 9, 2026
4d9d78a
Fix Limited MXC startup smoke timing
Copilot Oct 12, 2026
1ff427a
Simplify Limited MXC security status
Copilot Oct 12, 2026
9cc196e
Enforce MXC native filesystem tool denial
Copilot Oct 12, 2026
57c8779
Enable contained MXC Web access
Copilot Oct 12, 2026
d51f51c
Broker WeChat access under MXC
Copilot Oct 12, 2026
6a6c3b3
Pause Cron safely under MXC
Copilot Oct 12, 2026
2364e08
Pause Hooks and Webhooks under MXC
Copilot Oct 12, 2026
04973b8
Fix Hooks compatibility preload lint
Copilot Oct 12, 2026
43581bb
Reject Hooks requests explicitly under MXC
Copilot Oct 12, 2026
b9efef2
Add fail-closed MXC Canvas Phase 1
Copilot Oct 13, 2026
603de64
Keep unreviewed memory plugin disabled under MXC
Copilot Oct 13, 2026
5490b75
Disable the default Gateway memory slot under MXC
Copilot Oct 13, 2026
5305416
Fix standalone MXC Canvas shell
Copilot Oct 13, 2026
2bdd28f
Add MXC capability disclosure consent
Copilot Oct 14, 2026
bedb339
Harden MXC Cron transition policy
Copilot Oct 14, 2026
fb4b662
Fix merged MXC policy test import
Copilot Oct 14, 2026
4d6a9fd
Harden MXC Hooks pause policy
Copilot Oct 14, 2026
94a3151
Update MXC policy promotion fixtures
Copilot Oct 14, 2026
3fc0119
Fail closed for WeChat MXC ingress
Copilot Oct 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 82 additions & 5 deletions desktop/renderer/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,26 @@ interface AppSettings {
};
}

type WindowsNodeMxcAmbientAccessCategory =
| "desktop"
| "documents"
| "downloads"
| "microclaw-state"
| "openclaw-state"
| "openclaw-roaming-state"
| "ssh"
| "gnupg"
| "aws"
| "azure"
| "kube"
| "gcloud"
| "windows-credentials"
| "powershell-history"
| "browser-chrome"
| "browser-edge"
| "browser-brave"
| "browser-firefox";

interface ChatAttachment {
type: "image" | "file";
mimeType: string;
Expand Down Expand Up @@ -256,7 +276,11 @@ interface OpenClawAPI {
onExecCommand?(callback: (data: { shell: string; command: string }) => void): () => void;
};
cron: {
list(): Promise<{ jobs?: unknown[] }>;
list(): Promise<{
jobs?: unknown[];
pausedByMxc?: boolean;
requestedMode?: "off" | "full" | "limited-dacl";
}>;
};
agents: {
list(): Promise<{ agents?: { id: string; name: string; description?: string }[] }>;
Expand All @@ -276,6 +300,9 @@ interface OpenClawAPI {
weixin: {
getStatus(): Promise<{
enabled: boolean;
requestedEnabled: boolean;
effectiveEnabled: boolean;
pausedByMxc: boolean;
installed: boolean;
loggedIn: boolean;
loginInProgress: boolean;
Expand Down Expand Up @@ -348,8 +375,24 @@ interface OpenClawAPI {
};
windowsNodeMxc: {
getStatus(): Promise<{
requestedMode: "off" | "full" | "limited-dacl";
effectiveStrength: "off" | "full" | "limited-dacl";
effectiveTier: string | null;
desiredEnabled: boolean;
effectiveEnabled: boolean;
hooks: {
requestedExternalEnabled: boolean;
requestedInternalEnabled: boolean;
effectiveExternalEnabled: boolean;
effectiveInternalEnabled: boolean;
pausedByMxc: boolean;
};
canvas: {
state: "off" | "paused" | "active";
commandCount: 8;
nativeA2uiOnly: true;
localFilesAllowed: false;
};
lifecycleState: {
phase:
| "idle"
Expand All @@ -359,6 +402,7 @@ interface OpenClawAPI {
| "starting-locked"
| "smoking-locked"
| "starting-active"
| "promoting-active"
| "smoking-active"
| "verifying-active"
| "starting-standard"
Expand Down Expand Up @@ -440,24 +484,57 @@ interface OpenClawAPI {
needsDaclAugmentation: boolean;
degraded: boolean;
warnings: string[];
probes: {
baseContainerApiPresent: boolean | null;
bfscfgPresent: boolean | null;
bfsCompiledIn: boolean | null;
};
reason: string | null;
};
fullAvailability: {
available: boolean;
summary: string | null;
blockers: string[];
remediation: string[];
};
limitedAvailability: {
available: boolean;
summary: string | null;
blockers: string[];
remediation: string[];
};
ambientAccess: {
contract: "microclaw.windows-node-mxc-ambient-access.v1";
hash: string;
checkedAt: string;
entries: Array<{
category: WindowsNodeMxcAmbientAccessCategory;
status: "denied" | "read" | "read-write";
}>;
} | null;
smoke: {
profile: "full-smoke" | "limited-minimal-smoke";
gatewayGeneration: string;
nodeId: string;
settingsFingerprint: string;
probeTier: string;
containmentStrength: "full" | "limited-dacl";
ambientAccessHash: string | null;
checkedAt: string;
hostname: { outcome: string; reason: string };
powershell: { outcome: string; reason: string };
deniedOutsideRoot: { outcome: string; reason: string };
execution: { outcome: string; reason: string };
deniedOutsideRoot: { outcome: string; reason: string } | null;
} | null;
blockers: string[];
warnings: string[];
remediation: string[];
}>;
preflight(params?: {
requestedMode?: "off" | "full" | "limited-dacl";
}): Promise<unknown>;
setEnabled(params: {
enabled: boolean;
enabled?: boolean;
requestedMode?: "off" | "full" | "limited-dacl";
acknowledgeLimitedFilesystem?: boolean;
nodeId?: string;
}): Promise<Awaited<ReturnType<OpenClawAPI["windowsNodeMxc"]["getStatus"]>>>;
validateFolderPolicy(draft: {
Expand Down
58 changes: 49 additions & 9 deletions desktop/renderer/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@
:status="gateway.status"
:connected="chatStore.wsConnected"
:warming="gateway.warming"
:errorMessage="gateway.lastError"
:mxcDesired="windowsNodeMxcStatus?.desiredEnabled ?? false"
:mxcPhase="windowsNodeMxcStatus?.lifecycleState.phase"
:mxcDetail="windowsNodeMxcStatus?.lifecycleState.detail"
:error-message="gateway.lastError"
:mxc-desired="windowsNodeMxcStatus?.requestedMode !== 'off'"
:mxc-phase="windowsNodeMxcStatus?.lifecycleState.phase"
:mxc-detail="windowsNodeMxcStatus?.lifecycleState.detail"
:mxc-requested-mode="windowsNodeMxcStatus?.requestedMode"
:mxc-effective-strength="windowsNodeMxcStatus?.effectiveStrength"
:mxc-effective-tier="windowsNodeMxcStatus?.effectiveTier"
@retry="handleRetry"
@disable-mxc="disableWindowsNodeMxc"
/>
Expand Down Expand Up @@ -239,7 +242,7 @@ async function handleRetry() {
if (windowsNodeMxcStatus.value?.desiredEnabled) {
try {
windowsNodeMxcStatus.value = await window.openclaw.windowsNodeMxc.setEnabled({
enabled: true,
requestedMode: windowsNodeMxcStatus.value.requestedMode,
});
} catch (error) {
gateway.addLog(
Expand All @@ -256,7 +259,7 @@ async function disableWindowsNodeMxc() {
chatStore.wsConnected = false;
try {
windowsNodeMxcStatus.value = await window.openclaw.windowsNodeMxc.setEnabled({
enabled: false,
requestedMode: "off",
});
} catch (error) {
gateway.addLog(`[error] ${error instanceof Error ? error.message : String(error)}`);
Expand All @@ -280,6 +283,8 @@ interface PermissionRequestData {
source?: "sandbox" | "windows-node-mxc";
allowedDecisions?: Array<"deny" | "allow-once" | "allow-always">;
declaredAccess?: Array<{ access: "ro" | "rw"; path: string }>;
containmentStrength?: "full" | "limited-dacl";
ambientAccess?: NonNullable<WindowsNodeMxcStatus["ambientAccess"]>["entries"];
}
const permissionQueue = ref<PermissionRequestData[]>([]);
const currentPermission = computed(() =>
Expand All @@ -290,7 +295,7 @@ type WindowsNodeMxcApproval = NonNullable<
Awaited<ReturnType<typeof window.openclaw.windowsNodeMxc.getPendingApproval>>
>;

function enqueueWindowsNodeMxcApproval(request: WindowsNodeMxcApproval | null) {
async function enqueueWindowsNodeMxcApproval(request: WindowsNodeMxcApproval | null) {
if (!request) {
permissionQueue.value = permissionQueue.value.filter(
(pending) => pending.source !== "windows-node-mxc",
Expand All @@ -305,6 +310,31 @@ function enqueueWindowsNodeMxcApproval(request: WindowsNodeMxcApproval | null) {
) {
return;
}
if (!windowsNodeMxcStatus.value) {
try {
applyWindowsNodeMxcStatus(await window.openclaw.windowsNodeMxc.getStatus());
} catch (error) {
gateway.addLog(`[error] ${error instanceof Error ? error.message : String(error)}`);
}
}
if (
permissionQueue.value.some(
(pending) =>
pending.source === "windows-node-mxc" && pending.requestId === request.id,
)
) {
return;
}
const containmentStrength =
windowsNodeMxcStatus.value &&
windowsNodeMxcStatus.value.effectiveStrength !== "limited-dacl" &&
windowsNodeMxcStatus.value.requestedMode !== "limited-dacl"
? "full"
: "limited-dacl";
const allowedDecisions =
containmentStrength === "limited-dacl"
? request.allowedDecisions.filter((decision) => decision !== "allow-always")
: request.allowedDecisions;
const command = request.commandText ?? [request.executable, ...request.arguments].join(" ");
permissionQueue.value.push({
requestId: request.id,
Expand All @@ -314,10 +344,20 @@ function enqueueWindowsNodeMxcApproval(request: WindowsNodeMxcApproval | null) {
command,
declaredAccess: request.declaredAccess,
source: "windows-node-mxc",
allowedDecisions: request.allowedDecisions,
containmentStrength,
ambientAccess:
containmentStrength === "limited-dacl"
? (windowsNodeMxcStatus.value?.ambientAccess?.entries ?? [])
: undefined,
allowedDecisions,
});
if (chatStore.streaming) {
chatStore.addPendingToolCall(request.id, "Contained MXC command approval");
chatStore.addPendingToolCall(
request.id,
containmentStrength === "limited-dacl"
? "Limited filesystem containment approval"
: "Contained MXC command approval",
);
}
}

Expand Down
42 changes: 41 additions & 1 deletion desktop/renderer/src/browser-openclaw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,24 @@ export function createBrowserOpenClawMock(): OpenClawAPI {
},
windowsNodeMxc: {
getStatus: async () => ({
requestedMode: "off" as const,
effectiveStrength: "off" as const,
effectiveTier: null,
desiredEnabled: false,
effectiveEnabled: false,
hooks: {
requestedExternalEnabled: false,
requestedInternalEnabled: false,
effectiveExternalEnabled: false,
effectiveInternalEnabled: false,
pausedByMxc: false,
},
canvas: {
state: "off" as const,
commandCount: 8 as const,
nativeA2uiOnly: true as const,
localFilesAllowed: false as const,
},
lifecycleState: {
phase: "idle" as const,
detail: null,
Expand Down Expand Up @@ -113,13 +129,34 @@ export function createBrowserOpenClawMock(): OpenClawAPI {
needsDaclAugmentation: false,
degraded: false,
warnings: [],
probes: {
baseContainerApiPresent: null,
bfscfgPresent: null,
bfsCompiledIn: null,
},
reason: "Unavailable in browser development",
},
fullAvailability: {
available: false,
summary: "Unavailable in browser development",
blockers: ["Unavailable in browser development"],
remediation: [],
},
limitedAvailability: {
available: false,
summary: "Unavailable in browser development",
blockers: ["Unavailable in browser development"],
remediation: [],
},
ambientAccess: null,
smoke: null,
blockers: ["Unavailable in browser development"],
warnings: [],
remediation: [],
}),
preflight: async () => {
throw new Error("Unavailable in browser development");
},
setEnabled: async () => {
throw new Error("Unavailable in browser development");
},
Expand Down Expand Up @@ -220,7 +257,7 @@ export function createBrowserOpenClawMock(): OpenClawAPI {
onToolEvent: noopSub,
onExecCommand: noopSub,
},
cron: { list: async () => ({ jobs: [] }) },
cron: { list: async () => ({ jobs: [], pausedByMxc: false, requestedMode: "off" as const }) },
channels: { list: async () => ({ channels: [] }) },
model: {
selectDefault: async () => {
Expand Down Expand Up @@ -291,6 +328,9 @@ export function createBrowserOpenClawMock(): OpenClawAPI {
weixin: {
getStatus: async () => ({
enabled: false,
requestedEnabled: false,
effectiveEnabled: false,
pausedByMxc: false,
installed: false,
loggedIn: false,
loginInProgress: false,
Expand Down
23 changes: 23 additions & 0 deletions desktop/renderer/src/components/GatewayLoading.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,39 @@ describe("GatewayLoading MXC lifecycle", () => {
warming: false,
mxcDesired: true,
mxcPhase: "smoking-locked",
mxcRequestedMode: "limited-dacl",
mxcEffectiveStrength: "off",
},
});

expect(wrapper.find(".loading-status").text()).toContain(
"Running internal probes in locked generation",
);
expect(wrapper.find(".loading-status").text()).not.toContain("Ready");
expect(wrapper.find(".loading-mxc-strength").text()).toBe(
"Limited containment (DACL) — Starting",
);
expect(wrapper.find(".loading-mxc-strength").text()).not.toContain("Attested");
expect(wrapper.text()).not.toMatch(/approve|permission|allow once/i);
});

it("shows in-process policy promotion as a guarded startup phase", () => {
const wrapper = shallowMount(GatewayLoading, {
props: {
status: "running",
connected: true,
warming: false,
mxcDesired: true,
mxcPhase: "promoting-active",
},
});

expect(wrapper.find(".loading-status").text()).toContain(
"Promoting verified policy in the current generation",
);
expect(wrapper.find(".loading-status").text()).not.toContain("Ready");
});

it("shows fail-closed recovery without labeling MXC failure as a Gateway outage", async () => {
const wrapper = shallowMount(GatewayLoading, {
props: {
Expand Down
Loading
Loading