Compare commits

..

1 Commits

Author SHA1 Message Date
Mason Huang
93fc591af9 ci: add process exec codeql security shard 2026-06-13 20:38:21 +08:00
7 changed files with 118 additions and 76 deletions

View File

@@ -0,0 +1,61 @@
name: openclaw-codeql-process-exec-boundary-critical-security
disable-default-queries: true
queries:
- uses: security-extended
query-filters:
- include:
precision:
- high
- very-high
tags contain: security
security-severity: /([7-9]|10)\.(\d)+/
paths:
- src/process
- src/tui/tui-local-shell.ts
- src/tui/tui.ts
- src/plugin-sdk/windows-spawn.ts
- packages/agent-core/src/harness/env
- packages/memory-host-sdk/src/host
- extensions/acpx/src
- extensions/bonjour/src/advertiser.ts
- extensions/browser/src/browser/chrome-mcp.ts
- extensions/browser/src/browser/chrome.executables.ts
- extensions/browser/src/browser/chrome.ts
- extensions/codex/src/app-server/sandbox-exec-server
- extensions/codex/src/app-server/transport-stdio.ts
- extensions/codex/src/node-cli-sessions.ts
- extensions/codex-supervisor/src/json-rpc-client.ts
- extensions/file-transfer/src
- extensions/google-meet/src
- extensions/imessage/src
- extensions/memory-core/src/memory/qmd-manager.ts
- extensions/memory-wiki/src/obsidian.ts
- extensions/microsoft-foundry/cli.ts
- extensions/ollama/src/wsl2-crash-loop-check.ts
- extensions/qa-lab/src
- extensions/signal/src/daemon.ts
- extensions/tts-local-cli/speech-provider.ts
- extensions/voice-call/src
- scripts
paths-ignore:
- "**/node_modules"
- "**/coverage"
- "**/*.generated.ts"
- "**/*.bundle.js"
- "**/*-runtime.js"
- "**/*.test.ts"
- "**/*.test.tsx"
- "**/*.spec.ts"
- "**/*.spec.tsx"
- "**/*.e2e.test.ts"
- "**/*.e2e.test.tsx"
- "**/*test-support*"
- "**/*test-helper*"
- "**/*mock*"
- "**/*fixture*"
- "**/*bench*"

View File

@@ -17,7 +17,28 @@ on:
- ".github/actions/**"
- ".github/codeql/**"
- ".github/workflows/**"
- "extensions/acpx/src/**"
- "extensions/bonjour/src/advertiser.ts"
- "extensions/browser/src/browser/chrome-mcp.ts"
- "extensions/browser/src/browser/chrome.executables.ts"
- "extensions/browser/src/browser/chrome.ts"
- "extensions/codex/src/app-server/sandbox-exec-server/**"
- "extensions/codex/src/app-server/transport-stdio.ts"
- "extensions/codex/src/node-cli-sessions.ts"
- "extensions/codex-supervisor/src/json-rpc-client.ts"
- "extensions/file-transfer/src/**"
- "extensions/google-meet/src/**"
- "extensions/imessage/src/**"
- "extensions/memory-core/src/memory/qmd-manager.ts"
- "extensions/memory-wiki/src/obsidian.ts"
- "extensions/microsoft-foundry/cli.ts"
- "extensions/ollama/src/wsl2-crash-loop-check.ts"
- "extensions/qa-lab/src/**"
- "extensions/signal/src/daemon.ts"
- "extensions/tts-local-cli/speech-provider.ts"
- "extensions/voice-call/src/**"
- "packages/**"
- "scripts/**"
- "src/**"
push:
branches:
@@ -26,7 +47,28 @@ on:
- ".github/actions/**"
- ".github/codeql/**"
- ".github/workflows/**"
- "extensions/acpx/src/**"
- "extensions/bonjour/src/advertiser.ts"
- "extensions/browser/src/browser/chrome-mcp.ts"
- "extensions/browser/src/browser/chrome.executables.ts"
- "extensions/browser/src/browser/chrome.ts"
- "extensions/codex/src/app-server/sandbox-exec-server/**"
- "extensions/codex/src/app-server/transport-stdio.ts"
- "extensions/codex/src/node-cli-sessions.ts"
- "extensions/codex-supervisor/src/json-rpc-client.ts"
- "extensions/file-transfer/src/**"
- "extensions/google-meet/src/**"
- "extensions/imessage/src/**"
- "extensions/memory-core/src/memory/qmd-manager.ts"
- "extensions/memory-wiki/src/obsidian.ts"
- "extensions/microsoft-foundry/cli.ts"
- "extensions/ollama/src/wsl2-crash-loop-check.ts"
- "extensions/qa-lab/src/**"
- "extensions/signal/src/daemon.ts"
- "extensions/tts-local-cli/speech-provider.ts"
- "extensions/voice-call/src/**"
- "packages/**"
- "scripts/**"
- "src/**"
schedule:
- cron: "0 6 * * *"
@@ -73,6 +115,11 @@ jobs:
runs_on: blacksmith-4vcpu-ubuntu-2404
timeout_minutes: 25
config_file: ./.github/codeql/codeql-mcp-process-tool-boundary-critical-security.yml
- language: javascript-typescript
category: process-exec-boundary
runs_on: blacksmith-4vcpu-ubuntu-2404
timeout_minutes: 25
config_file: ./.github/codeql/codeql-process-exec-boundary-critical-security.yml
- language: javascript-typescript
category: plugin-trust-boundary
runs_on: blacksmith-4vcpu-ubuntu-2404

View File

@@ -452,7 +452,7 @@ For normal PRs, follow scoped CI/check evidence instead of treating parity as a
The `CodeQL` workflow is intentionally a narrow first-pass security scanner, not the full repository sweep. Daily, manual, and non-draft pull request guard runs scan Actions workflow code plus the highest-risk JavaScript/TypeScript surfaces with high-confidence security queries filtered to high/critical `security-severity`.
The pull request guard stays light: it only starts for changes under `.github/actions`, `.github/codeql`, `.github/workflows`, `packages`, or `src`, and it runs the same high-confidence security matrix as the scheduled workflow. Android and macOS CodeQL stay out of PR defaults.
The pull request guard stays light: it only starts for changes under `.github/actions`, `.github/codeql`, `.github/workflows`, `packages`, `scripts`, `src`, or process-owning bundled plugin runtime paths, and it runs the same high-confidence security matrix as the scheduled workflow. Android and macOS CodeQL stay out of PR defaults.
### Security categories
@@ -462,6 +462,7 @@ The pull request guard stays light: it only starts for changes under `.github/ac
| `/codeql-security-high/channel-runtime-boundary` | Core channel implementation contracts plus the channel plugin runtime, gateway, Plugin SDK, secrets, audit touchpoints |
| `/codeql-security-high/network-ssrf-boundary` | Core SSRF, IP parsing, network guard, web-fetch, and Plugin SDK SSRF policy surfaces |
| `/codeql-security-high/mcp-process-tool-boundary` | MCP servers, process execution helpers, outbound delivery, and agent tool-execution gates |
| `/codeql-security-high/process-exec-boundary` | Local shell, process spawn helpers, subprocess-owning bundled plugin runtimes, and workflow script glue |
| `/codeql-security-high/plugin-trust-boundary` | Plugin install, loader, manifest, registry, package-manager install, source-loading, and Plugin SDK package contract trust surfaces |
### Platform-specific security shards

View File

@@ -346,7 +346,6 @@ describe("runCronIsolatedAgentTurn message tool policy", () => {
},
messageToolEnabled: true,
messageToolForced: false,
requireExplicitMessageTargetEvidence: true,
directFallback: true,
}),
skillsSnapshot: emptySkillsSnapshot,
@@ -901,35 +900,14 @@ describe("runCronIsolatedAgentTurn message tool policy", () => {
});
});
it("uses cron fallback delivery when the message tool returns no target evidence", async () => {
mockRunCronFallbackPassthrough();
resolveCronDeliveryPlanMock.mockReturnValue(makeAnnounceDeliveryPlan());
runEmbeddedAgentMock.mockResolvedValue(makeMessageToolRunResult([]));
const result = await runCronIsolatedAgentTurn({
...makeParams(),
job: makeAnnounceMessageToolJob({
id: "message-tool-no-target-evidence",
name: "Message Tool No Target Evidence",
}),
});
expect(dispatchCronDeliveryMock).toHaveBeenCalledTimes(1);
expectDispatchFields({
deliveryRequested: true,
sourceDeliveryOutcome: {
visibleDeliveries: [],
verifiedMessageToolDelivery: false,
satisfiesSourceDelivery: false,
unverifiedMessageToolDelivery: false,
it("skips cron fallback delivery when the message tool sends to the bound target", async () => {
await expectCronFallbackSkippedForMessageToolDelivery({
sentTargets: [],
job: {
id: "message-tool-bound-target",
name: "Message Tool Bound Target",
},
});
expectDeliveryFields(result.delivery, {
intended: { channel: "messagechat", to: "123", source: "explicit" },
resolved: { ok: true, channel: "messagechat", to: "123", source: "explicit" },
fallbackUsed: true,
delivered: true,
});
});
it("rewrites generic message provider to resolved channel in delivery trace", async () => {

View File

@@ -335,7 +335,6 @@ function resolveCronSourceDeliveryPlan(params: {
target,
messageToolEnabled: true,
messageToolForced: false,
requireExplicitMessageTargetEvidence: true,
directFallback: true,
skipFallbackWhenMessageToolSentToTarget: params.resolvedDelivery.ok,
});

View File

@@ -145,46 +145,6 @@ describe("source delivery plan", () => {
expect(outcome.unverifiedMessageToolDelivery).toBe(false);
});
it("synthesizes the planned target for legacy message-tool sends by default", () => {
const contract = createSourceDeliveryPlan({
owner: "message_tool_then_direct_fallback",
reason: "cron_announce",
target: { channel: "slack", to: "channel:C1" },
});
const outcome = resolveSourceDeliveryOutcome(contract, {
didSendViaMessageTool: true,
});
expect(outcome.visibleDeliveries).toEqual([
{
via: "message_tool",
verifiedTarget: true,
target: { tool: "message", provider: "slack", to: "channel:C1" },
},
]);
expect(outcome.verifiedMessageToolDelivery).toBe(true);
expect(outcome.satisfiesSourceDelivery).toBe(true);
});
it("does not synthesize the planned target when explicit target evidence is required", () => {
const contract = createSourceDeliveryPlan({
owner: "message_tool_then_direct_fallback",
reason: "cron_announce",
target: { channel: "slack", to: "channel:C1" },
requireExplicitMessageTargetEvidence: true,
});
const outcome = resolveSourceDeliveryOutcome(contract, {
didSendViaMessageTool: true,
});
expect(outcome.visibleDeliveries).toEqual([]);
expect(outcome.verifiedMessageToolDelivery).toBe(false);
expect(outcome.satisfiesSourceDelivery).toBe(false);
expect(outcome.unverifiedMessageToolDelivery).toBe(false);
});
it("does not synthesize an implicit target without a concrete recipient", () => {
const contract = createSourceDeliveryPlan({
owner: "direct_fallback",

View File

@@ -69,7 +69,6 @@ export type SourceDeliveryPlan = {
enabled: boolean;
force: boolean;
requireExplicitTarget: boolean;
requireExplicitTargetEvidence: boolean;
defaultTarget: boolean;
};
fallback: {
@@ -175,7 +174,6 @@ export function createSourceDeliveryPlan(params: {
messageToolEnabled?: boolean;
messageToolForced?: boolean;
requireExplicitMessageTarget?: boolean;
requireExplicitMessageTargetEvidence?: boolean;
directFallback?: boolean;
skipFallbackWhenMessageToolSentToTarget?: boolean;
fallbackBestEffort?: boolean;
@@ -199,7 +197,6 @@ export function createSourceDeliveryPlan(params: {
enabled: params.messageToolEnabled ?? messageToolOwnsDelivery,
force: params.messageToolForced ?? messageToolOwnsDelivery,
requireExplicitTarget: params.requireExplicitMessageTarget ?? false,
requireExplicitTargetEvidence: params.requireExplicitMessageTargetEvidence ?? false,
defaultTarget: Boolean(params.target?.channel || params.target?.to),
},
fallback: {
@@ -242,12 +239,11 @@ export function resolveSourceDeliveryOutcome(
): SourceDeliveryOutcome {
const didSendViaMessageTool = params.didSendViaMessageTool === true;
const explicitTargets = params.messageToolSentTargets ?? [];
// Cron completion accounting needs concrete target evidence. Legacy
// message-tool-owned flows may still use the plan target as the implicit send.
// A send without explicit target metadata still counts when the plan has a default target.
const sentTargets =
explicitTargets.length > 0
? explicitTargets
: didSendViaMessageTool && !plan.messageTool.requireExplicitTargetEvidence
: didSendViaMessageTool
? [resolveImplicitMessageToolDeliveryTarget(plan)].filter(
(target): target is SourceDeliveryMessageToolTarget => Boolean(target),
)