mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-19 21:22:05 +08:00
Compare commits
5 Commits
codex/prob
...
codex/prob
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
764fc3916e | ||
|
|
1c33856130 | ||
|
|
c0d525c8a0 | ||
|
|
c66c404d58 | ||
|
|
10a3417bd3 |
8
.github/CODEOWNERS
vendored
8
.github/CODEOWNERS
vendored
@@ -11,10 +11,10 @@
|
||||
/.github/workflows/codeql.yml @openclaw/openclaw-secops
|
||||
/.github/workflows/codeql-android-critical-security.yml @openclaw/openclaw-secops
|
||||
/.github/workflows/codeql-critical-quality.yml @openclaw/openclaw-secops
|
||||
/.github/workflows/dependency-change-awareness.yml @openclaw/openclaw-secops
|
||||
/test/scripts/dependency-change-awareness-workflow.test.ts @openclaw/openclaw-secops
|
||||
/test/scripts/dependency-change-awareness-script.test.ts @openclaw/openclaw-secops
|
||||
/scripts/github/dependency-change-awareness.mjs @openclaw/openclaw-secops
|
||||
/.github/workflows/dependency-guard.yml @openclaw/openclaw-secops
|
||||
/test/scripts/dependency-guard-workflow.test.ts @openclaw/openclaw-secops
|
||||
/test/scripts/dependency-guard-script.test.ts @openclaw/openclaw-secops
|
||||
/scripts/github/dependency-guard.mjs @openclaw/openclaw-secops
|
||||
/package-lock.json @openclaw/openclaw-secops
|
||||
/npm-shrinkwrap.json @openclaw/openclaw-secops
|
||||
/extensions/*/package-lock.json @openclaw/openclaw-secops
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Dependency Change Awareness
|
||||
name: Dependency Guard
|
||||
|
||||
on:
|
||||
pull_request_target: # zizmor: ignore[dangerous-triggers] checks trusted base script only; never checks out PR head
|
||||
@@ -10,11 +10,11 @@ permissions:
|
||||
issues: write
|
||||
|
||||
concurrency:
|
||||
group: dependency-change-awareness-${{ github.event.pull_request.number }}
|
||||
group: dependency-guard-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
dependency-change-awareness:
|
||||
dependency-guard:
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
@@ -30,4 +30,4 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
OPENCLAW_SECURITY_APPROVERS: vincentkoc,steipete,joshavant
|
||||
OPENCLAW_SECURITY_TEAM_SLUG: openclaw-secops
|
||||
run: node scripts/github/dependency-change-awareness.mjs
|
||||
run: node scripts/github/dependency-guard.mjs
|
||||
@@ -40,9 +40,15 @@ type BrowserActRequest = Parameters<typeof browserAct>[1];
|
||||
type BrowserActRequestWithTimeout = BrowserActRequest & { timeoutMs?: number };
|
||||
|
||||
function normalizePositiveTimeoutMs(value: unknown): number | undefined {
|
||||
return typeof value === "number" && Number.isFinite(value) && value > 0
|
||||
? Math.floor(value)
|
||||
: undefined;
|
||||
return readPositiveIntegerParam({ value }, "value", {
|
||||
message: "timeoutMs must be a positive integer.",
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeNonNegativeDurationMs(value: unknown): number | undefined {
|
||||
return readNonNegativeIntegerParam({ value }, "value", {
|
||||
message: "timeMs must be a non-negative integer.",
|
||||
});
|
||||
}
|
||||
|
||||
function supportsBrowserActTimeout(request: BrowserActRequest): boolean {
|
||||
@@ -114,7 +120,7 @@ function resolveActProxyTimeoutMs(request: BrowserActRequest): number | undefine
|
||||
candidateTimeouts.push(explicitTimeout + BROWSER_ACT_REQUEST_TIMEOUT_SLACK_MS);
|
||||
}
|
||||
if (request.kind === "wait") {
|
||||
const waitDuration = normalizePositiveTimeoutMs(request.timeMs);
|
||||
const waitDuration = normalizeNonNegativeDurationMs(request.timeMs);
|
||||
if (waitDuration !== undefined) {
|
||||
candidateTimeouts.push(waitDuration + BROWSER_ACT_REQUEST_TIMEOUT_SLACK_MS);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
optionalFiniteNumberSchema,
|
||||
optionalNonNegativeIntegerSchema,
|
||||
optionalPositiveIntegerSchema,
|
||||
optionalStringEnum,
|
||||
@@ -61,15 +62,15 @@ const BrowserActSchema = Type.Object({
|
||||
doubleClick: Type.Optional(Type.Boolean()),
|
||||
button: Type.Optional(Type.String()),
|
||||
modifiers: Type.Optional(Type.Array(Type.String())),
|
||||
x: Type.Optional(Type.Number()),
|
||||
y: Type.Optional(Type.Number()),
|
||||
x: optionalFiniteNumberSchema(),
|
||||
y: optionalFiniteNumberSchema(),
|
||||
// type
|
||||
text: Type.Optional(Type.String()),
|
||||
submit: Type.Optional(Type.Boolean()),
|
||||
slowly: Type.Optional(Type.Boolean()),
|
||||
// press
|
||||
key: Type.Optional(Type.String()),
|
||||
delayMs: Type.Optional(Type.Number()),
|
||||
delayMs: optionalNonNegativeIntegerSchema(),
|
||||
// drag
|
||||
startRef: Type.Optional(Type.String()),
|
||||
endRef: Type.Optional(Type.String()),
|
||||
@@ -78,10 +79,10 @@ const BrowserActSchema = Type.Object({
|
||||
// fill - use permissive array of objects
|
||||
fields: Type.Optional(Type.Array(Type.Object({}, { additionalProperties: true }))),
|
||||
// resize
|
||||
width: Type.Optional(Type.Number()),
|
||||
height: Type.Optional(Type.Number()),
|
||||
width: optionalPositiveIntegerSchema(),
|
||||
height: optionalPositiveIntegerSchema(),
|
||||
// wait
|
||||
timeMs: Type.Optional(Type.Number()),
|
||||
timeMs: optionalNonNegativeIntegerSchema(),
|
||||
selector: Type.Optional(Type.String()),
|
||||
url: Type.Optional(Type.String()),
|
||||
loadState: Type.Optional(Type.String()),
|
||||
@@ -131,20 +132,20 @@ export const BrowserToolSchema = Type.Object({
|
||||
doubleClick: Type.Optional(Type.Boolean()),
|
||||
button: Type.Optional(Type.String()),
|
||||
modifiers: Type.Optional(Type.Array(Type.String())),
|
||||
x: Type.Optional(Type.Number()),
|
||||
y: Type.Optional(Type.Number()),
|
||||
x: optionalFiniteNumberSchema(),
|
||||
y: optionalFiniteNumberSchema(),
|
||||
text: Type.Optional(Type.String()),
|
||||
submit: Type.Optional(Type.Boolean()),
|
||||
slowly: Type.Optional(Type.Boolean()),
|
||||
key: Type.Optional(Type.String()),
|
||||
delayMs: Type.Optional(Type.Number()),
|
||||
delayMs: optionalNonNegativeIntegerSchema(),
|
||||
startRef: Type.Optional(Type.String()),
|
||||
endRef: Type.Optional(Type.String()),
|
||||
values: Type.Optional(Type.Array(Type.String())),
|
||||
fields: Type.Optional(Type.Array(Type.Object({}, { additionalProperties: true }))),
|
||||
width: Type.Optional(Type.Number()),
|
||||
height: Type.Optional(Type.Number()),
|
||||
timeMs: Type.Optional(Type.Number()),
|
||||
width: optionalPositiveIntegerSchema(),
|
||||
height: optionalPositiveIntegerSchema(),
|
||||
timeMs: optionalNonNegativeIntegerSchema(),
|
||||
textGone: Type.Optional(Type.String()),
|
||||
loadState: Type.Optional(Type.String()),
|
||||
fn: Type.Optional(Type.String()),
|
||||
|
||||
@@ -1372,6 +1372,40 @@ describe("browser tool act compatibility", () => {
|
||||
expect(request.params?.body).toEqual({ kind: "wait", timeMs: 20_000, timeoutMs: 45_000 });
|
||||
expect(request.params?.timeoutMs).toBe(45_000 + 5_000);
|
||||
});
|
||||
|
||||
it("honors string act request timeouts when sizing node proxy calls", async () => {
|
||||
mockSingleBrowserProxyNode();
|
||||
const tool = createBrowserTool();
|
||||
await tool.execute?.("call-1", {
|
||||
action: "act",
|
||||
target: "node",
|
||||
request: { kind: "wait", timeMs: "20000", timeoutMs: "45000" },
|
||||
});
|
||||
|
||||
const { options, request } = lastNodeInvokeCall();
|
||||
expect(options.timeoutMs).toBe(55_000);
|
||||
expect(request.params?.path).toBe("/act");
|
||||
expect(request.params?.body).toEqual({
|
||||
kind: "wait",
|
||||
timeMs: "20000",
|
||||
timeoutMs: "45000",
|
||||
});
|
||||
expect(request.params?.timeoutMs).toBe(50_000);
|
||||
});
|
||||
|
||||
it("rejects fractional act request timeouts before node proxy calls", async () => {
|
||||
mockSingleBrowserProxyNode();
|
||||
const tool = createBrowserTool();
|
||||
|
||||
await expect(
|
||||
tool.execute?.("call-1", {
|
||||
action: "act",
|
||||
target: "node",
|
||||
request: { kind: "wait", timeMs: "20000", timeoutMs: "45000.5" },
|
||||
}),
|
||||
).rejects.toThrow("timeoutMs must be a positive integer.");
|
||||
expect(gatewayMocks.callGatewayTool).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("browser tool snapshot labels", () => {
|
||||
|
||||
@@ -26,7 +26,7 @@ describe("createWhatsAppLoginTool", () => {
|
||||
const tool = createWhatsAppLoginTool();
|
||||
const result = await tool.execute("tool-call-1", {
|
||||
action: "wait",
|
||||
timeoutMs: 5000,
|
||||
timeoutMs: "5000",
|
||||
accountId,
|
||||
currentQrDataUrl: "data:image/png;base64,current-qr",
|
||||
});
|
||||
@@ -56,6 +56,39 @@ describe("createWhatsAppLoginTool", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("passes string timeoutMs through to start actions", async () => {
|
||||
startWebLoginWithQrMock.mockResolvedValueOnce({
|
||||
connected: false,
|
||||
message: "Scan this QR in WhatsApp → Linked Devices.",
|
||||
qrDataUrl: "data:image/png;base64,current-qr",
|
||||
});
|
||||
|
||||
const tool = createWhatsAppLoginTool();
|
||||
await tool.execute("tool-call-start", {
|
||||
action: "start",
|
||||
timeoutMs: "6000",
|
||||
accountId: "account-3",
|
||||
});
|
||||
|
||||
expect(startWebLoginWithQrMock).toHaveBeenCalledWith({
|
||||
accountId: "account-3",
|
||||
timeoutMs: 6000,
|
||||
force: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects fractional timeoutMs before login actions", async () => {
|
||||
const tool = createWhatsAppLoginTool();
|
||||
|
||||
await expect(
|
||||
tool.execute("tool-call-start", {
|
||||
action: "start",
|
||||
timeoutMs: "6000.5",
|
||||
}),
|
||||
).rejects.toThrow("timeoutMs must be a positive integer");
|
||||
expect(startWebLoginWithQrMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not retain QR state across tool actions", async () => {
|
||||
const accountId = "account-2";
|
||||
startWebLoginWithQrMock.mockResolvedValueOnce({
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
optionalPositiveIntegerSchema,
|
||||
readPositiveIntegerParam,
|
||||
} from "openclaw/plugin-sdk/channel-actions";
|
||||
import type { ChannelAgentTool } from "openclaw/plugin-sdk/channel-contract";
|
||||
import { Type } from "typebox";
|
||||
import { startWebLoginWithQr, waitForWebLogin } from "../login-qr-api.js";
|
||||
@@ -20,7 +24,7 @@ export function createWhatsAppLoginTool(): ChannelAgentTool {
|
||||
type: "string",
|
||||
enum: ["start", "wait"],
|
||||
}),
|
||||
timeoutMs: Type.Optional(Type.Number()),
|
||||
timeoutMs: optionalPositiveIntegerSchema(),
|
||||
force: Type.Optional(Type.Boolean()),
|
||||
accountId: Type.Optional(Type.String()),
|
||||
currentQrDataUrl: Type.Optional(
|
||||
@@ -54,13 +58,11 @@ export function createWhatsAppLoginTool(): ChannelAgentTool {
|
||||
|
||||
const action = (args as { action?: string })?.action ?? "start";
|
||||
const accountId = readOptionalString((args as { accountId?: unknown }).accountId);
|
||||
const timeoutMs = readPositiveIntegerParam(args as Record<string, unknown>, "timeoutMs");
|
||||
if (action === "wait") {
|
||||
const result = await waitForWebLogin({
|
||||
accountId,
|
||||
timeoutMs:
|
||||
typeof (args as { timeoutMs?: unknown }).timeoutMs === "number"
|
||||
? (args as { timeoutMs?: number }).timeoutMs
|
||||
: undefined,
|
||||
timeoutMs,
|
||||
currentQrDataUrl: readOptionalString(
|
||||
(args as { currentQrDataUrl?: unknown }).currentQrDataUrl,
|
||||
),
|
||||
@@ -80,10 +82,7 @@ export function createWhatsAppLoginTool(): ChannelAgentTool {
|
||||
|
||||
const result = await startWebLoginWithQr({
|
||||
accountId,
|
||||
timeoutMs:
|
||||
typeof (args as { timeoutMs?: unknown }).timeoutMs === "number"
|
||||
? (args as { timeoutMs?: number }).timeoutMs
|
||||
: undefined,
|
||||
timeoutMs,
|
||||
force:
|
||||
typeof (args as { force?: unknown }).force === "boolean"
|
||||
? (args as { force?: boolean }).force
|
||||
|
||||
@@ -1404,6 +1404,7 @@
|
||||
"canvas:a2ui:bundle": "node scripts/bundle-a2ui.mjs",
|
||||
"changed:lanes": "node scripts/changed-lanes.mjs",
|
||||
"check": "node scripts/check.mjs",
|
||||
"check:dependency-guard-script-probe": "node -e \"process.exit(0)\"",
|
||||
"check:architecture": "pnpm check:import-cycles && pnpm check:madge-import-cycles && pnpm check:deprecated-api-usage && pnpm check:deprecated-jsdoc",
|
||||
"check:base-config-schema": "node --import tsx scripts/generate-base-config-schema.ts --check",
|
||||
"check:bundled-channel-config-metadata": "node --import tsx scripts/generate-bundled-channel-config-metadata.ts --check",
|
||||
@@ -1908,7 +1909,7 @@
|
||||
"tsdown": "0.22.0",
|
||||
"tsx": "4.22.3",
|
||||
"unrun": "0.3.0",
|
||||
"vitest": "4.1.8"
|
||||
"vitest": "4.1.7"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"sharp": "0.34.5",
|
||||
|
||||
1
pnpm-lock.yaml
generated
1
pnpm-lock.yaml
generated
@@ -1,4 +1,3 @@
|
||||
# Dependency guard probe: intentional throw-away devDependency lockfile diff.
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import fs from "node:fs";
|
||||
import process from "node:process";
|
||||
import { spawn } from "@lydell/node-pty";
|
||||
import { readPositiveIntEnv } from "./env-limits.mjs";
|
||||
|
||||
const [logPath, command, ...args] = process.argv.slice(2);
|
||||
|
||||
@@ -13,8 +14,8 @@ if (!logPath || !command) {
|
||||
const log = fs.createWriteStream(logPath, { flags: "w" });
|
||||
const pty = spawn(command, args, {
|
||||
name: process.env.TERM || "xterm-256color",
|
||||
cols: Number(process.env.COLUMNS || 120),
|
||||
rows: Number(process.env.LINES || 40),
|
||||
cols: readPositiveIntEnv("COLUMNS", 120),
|
||||
rows: readPositiveIntEnv("LINES", 40),
|
||||
cwd: process.cwd(),
|
||||
env: process.env,
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { appendFile, readFile } from "node:fs/promises";
|
||||
|
||||
export const dependencyChangeMarker = "<!-- openclaw:dependency-change-awareness -->";
|
||||
export const dependencyChangeMarker = "<!-- openclaw:dependency-guard -->";
|
||||
export const dependencyGraphGuardMarker = "<!-- openclaw:dependency-graph-guard -->";
|
||||
export const dependencyChangedLabel = "dependencies-changed";
|
||||
export const allowDependenciesCommand = "/allow-dependencies-change";
|
||||
@@ -316,7 +316,7 @@ function githubApi(token) {
|
||||
const baseHeaders = {
|
||||
accept: "application/vnd.github+json",
|
||||
authorization: `Bearer ${token}`,
|
||||
"user-agent": "openclaw-dependency-change-awareness",
|
||||
"user-agent": "openclaw-dependency-guard",
|
||||
"x-github-api-version": "2022-11-28",
|
||||
};
|
||||
const request = async (path, options = {}) => {
|
||||
@@ -521,7 +521,7 @@ async function main() {
|
||||
renderClearedDependencyGuardComment({ headSha: pullRequest.head?.sha }),
|
||||
);
|
||||
}
|
||||
await writeSummary("## Dependency Change Awareness\n\nNo dependency-related file changes detected.");
|
||||
await writeSummary("## Dependency Guard\n\nNo dependency-related file changes detected.");
|
||||
console.log("No dependency-related file changes detected.");
|
||||
return;
|
||||
}
|
||||
@@ -533,7 +533,7 @@ async function main() {
|
||||
);
|
||||
await writeSummary(
|
||||
[
|
||||
"## Dependency Change Awareness",
|
||||
"## Dependency Guard",
|
||||
"",
|
||||
`Detected ${dependencyGraphFiles.length} dependency-related file change(s).`,
|
||||
"",
|
||||
@@ -15,12 +15,12 @@ import {
|
||||
renderClearedDependencyGuardComment,
|
||||
sanitizeDisplayValue,
|
||||
securityApproverSet,
|
||||
} from "../../scripts/github/dependency-change-awareness.mjs";
|
||||
} from "../../scripts/github/dependency-guard.mjs";
|
||||
|
||||
const headSha = "a".repeat(40);
|
||||
const staleSha = "b".repeat(40);
|
||||
|
||||
describe("dependency change awareness script", () => {
|
||||
describe("dependency guard script", () => {
|
||||
it("detects dependency awareness file surfaces", () => {
|
||||
expect(isDependencyFile("pnpm-lock.yaml")).toBe(true);
|
||||
expect(isDependencyFile("package.json")).toBe(false);
|
||||
@@ -2,7 +2,7 @@ import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const WORKFLOW = ".github/workflows/dependency-change-awareness.yml";
|
||||
const WORKFLOW = ".github/workflows/dependency-guard.yml";
|
||||
const CODEOWNERS = ".github/CODEOWNERS";
|
||||
|
||||
type WorkflowStep = {
|
||||
@@ -13,11 +13,13 @@ type WorkflowStep = {
|
||||
};
|
||||
|
||||
type WorkflowJob = {
|
||||
name?: string;
|
||||
steps?: WorkflowStep[];
|
||||
};
|
||||
|
||||
type Workflow = {
|
||||
jobs?: Record<string, WorkflowJob>;
|
||||
name?: string;
|
||||
permissions?: Record<string, string>;
|
||||
};
|
||||
|
||||
@@ -25,7 +27,15 @@ function readWorkflow(): Workflow {
|
||||
return parse(readFileSync(WORKFLOW, "utf8")) as Workflow;
|
||||
}
|
||||
|
||||
describe("dependency change awareness workflow", () => {
|
||||
describe("dependency guard workflow", () => {
|
||||
it("uses the dependency guard check name", () => {
|
||||
const parsed = readWorkflow();
|
||||
|
||||
expect(parsed.name).toBe("Dependency Guard");
|
||||
expect(parsed.jobs).toHaveProperty("dependency-guard");
|
||||
expect(parsed.jobs?.["dependency-guard"]?.name).toBeUndefined();
|
||||
});
|
||||
|
||||
it("uses a metadata-only pull_request_target workflow with minimal write permissions", () => {
|
||||
const workflow = readFileSync(WORKFLOW, "utf8");
|
||||
const parsed = readWorkflow();
|
||||
@@ -57,29 +67,29 @@ describe("dependency change awareness workflow", () => {
|
||||
expect(workflow).not.toContain(snippet);
|
||||
}
|
||||
|
||||
const steps = readWorkflow().jobs?.["dependency-change-awareness"]?.steps ?? [];
|
||||
const steps = readWorkflow().jobs?.["dependency-guard"]?.steps ?? [];
|
||||
expect(steps).toHaveLength(2);
|
||||
expect(steps[0].uses).toBe("actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd");
|
||||
expect(steps[0].with?.ref).toBe("${{ github.event.pull_request.base.sha }}");
|
||||
expect(steps[0].with?.["persist-credentials"]).toBe(false);
|
||||
expect(steps[1].run).toBe("node scripts/github/dependency-change-awareness.mjs");
|
||||
expect(steps[1].run).toBe("node scripts/github/dependency-guard.mjs");
|
||||
});
|
||||
|
||||
it("uses a dedicated checked-in script and bounded sticky comments", () => {
|
||||
const workflow = readFileSync(WORKFLOW, "utf8");
|
||||
const steps = readWorkflow().jobs?.["dependency-change-awareness"]?.steps ?? [];
|
||||
const steps = readWorkflow().jobs?.["dependency-guard"]?.steps ?? [];
|
||||
const runStep = steps[1];
|
||||
const script = readFileSync("scripts/github/dependency-change-awareness.mjs", "utf8");
|
||||
const script = readFileSync("scripts/github/dependency-guard.mjs", "utf8");
|
||||
|
||||
expect(runStep.env?.OPENCLAW_SECURITY_TEAM_SLUG).toBe("openclaw-secops");
|
||||
expect(runStep.env?.OPENCLAW_SECURITY_APPROVERS).toBe("vincentkoc,steipete,joshavant");
|
||||
expect(workflow).toContain("scripts/github/dependency-change-awareness.mjs");
|
||||
expect(workflow).toContain("scripts/github/dependency-guard.mjs");
|
||||
expect(script).toContain('"dependencies-changed"');
|
||||
expect(script).not.toContain('"blocked: dependencies"');
|
||||
});
|
||||
|
||||
it("detects the intended dependency-related file surfaces", () => {
|
||||
const script = readFileSync("scripts/github/dependency-change-awareness.mjs", "utf8");
|
||||
const script = readFileSync("scripts/github/dependency-guard.mjs", "utf8");
|
||||
expect(script).toContain('filename.endsWith("package.json")');
|
||||
expect(script).toContain('filename.endsWith("package-lock.json")');
|
||||
expect(script).toContain('filename.endsWith("npm-shrinkwrap.json")');
|
||||
@@ -90,7 +100,7 @@ describe("dependency change awareness workflow", () => {
|
||||
});
|
||||
|
||||
it("blocks package lockfile and manifest graph changes unless secops approves the current head sha", () => {
|
||||
const script = readFileSync("scripts/github/dependency-change-awareness.mjs", "utf8");
|
||||
const script = readFileSync("scripts/github/dependency-guard.mjs", "utf8");
|
||||
expect(script).toContain('filename.endsWith("pnpm-lock.yaml")');
|
||||
expect(script).toContain('filename.endsWith("package-lock.json")');
|
||||
expect(script).toContain('filename.endsWith("npm-shrinkwrap.json")');
|
||||
@@ -110,13 +120,13 @@ describe("dependency change awareness workflow", () => {
|
||||
it("requires secops review for future workflow or guard changes", () => {
|
||||
const codeowners = readFileSync(CODEOWNERS, "utf8");
|
||||
expect(codeowners).toContain(
|
||||
"/.github/workflows/dependency-change-awareness.yml @openclaw/openclaw-secops",
|
||||
"/.github/workflows/dependency-guard.yml @openclaw/openclaw-secops",
|
||||
);
|
||||
expect(codeowners).toContain(
|
||||
"/test/scripts/dependency-change-awareness-workflow.test.ts @openclaw/openclaw-secops",
|
||||
"/test/scripts/dependency-guard-workflow.test.ts @openclaw/openclaw-secops",
|
||||
);
|
||||
expect(codeowners).toContain(
|
||||
"/scripts/github/dependency-change-awareness.mjs @openclaw/openclaw-secops",
|
||||
"/scripts/github/dependency-guard.mjs @openclaw/openclaw-secops",
|
||||
);
|
||||
expect(codeowners).toContain("/package-lock.json @openclaw/openclaw-secops");
|
||||
expect(codeowners).toContain("/npm-shrinkwrap.json @openclaw/openclaw-secops");
|
||||
@@ -8,7 +8,10 @@ import { describe, expect, it } from "vitest";
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const scriptPath = path.join(repoRoot, "scripts/e2e/lib/run-with-pty.mjs");
|
||||
|
||||
function runPtyProbe(logPath: string): Promise<{ code: number | null; stdout: string; stderr: string }> {
|
||||
function runPtyProbe(
|
||||
logPath: string,
|
||||
env: Record<string, string> = {},
|
||||
): Promise<{ code: number | null; stdout: string; stderr: string }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(
|
||||
process.execPath,
|
||||
@@ -19,7 +22,7 @@ function runPtyProbe(logPath: string): Promise<{ code: number | null; stdout: st
|
||||
"-lc",
|
||||
'printf "prompt\\n"; IFS= read -r value; printf "got:%s\\n" "$value"',
|
||||
],
|
||||
{ stdio: ["pipe", "pipe", "pipe"] },
|
||||
{ env: { ...process.env, ...env }, stdio: ["pipe", "pipe", "pipe"] },
|
||||
);
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
@@ -49,6 +52,19 @@ function runPtyProbe(logPath: string): Promise<{ code: number | null; stdout: st
|
||||
}
|
||||
|
||||
describe("run-with-pty", () => {
|
||||
it("rejects loose terminal dimension env values", async () => {
|
||||
const tempRoot = await mkdtemp(path.join(os.tmpdir(), "openclaw-run-with-pty-"));
|
||||
const logPath = path.join(tempRoot, "pty.log");
|
||||
try {
|
||||
const result = await runPtyProbe(logPath, { COLUMNS: "120cols" });
|
||||
|
||||
expect(result.code).not.toBe(0);
|
||||
expect(result.stderr).toContain("invalid COLUMNS: 120cols");
|
||||
} finally {
|
||||
await rm(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("forwards stdin through a PTY and writes the transcript log", async () => {
|
||||
const tempRoot = await mkdtemp(path.join(os.tmpdir(), "openclaw-run-with-pty-"));
|
||||
const logPath = path.join(tempRoot, "pty.log");
|
||||
|
||||
Reference in New Issue
Block a user