refactor(security): refine safeBins hardening

This commit is contained in:
Peter Steinberger
2026-02-14 19:59:03 +01:00
parent eed6113359
commit 24d2c6292e
6 changed files with 173 additions and 121 deletions

View File

@@ -5,7 +5,7 @@ import { describe, expect, it, vi } from "vitest";
import {
analyzeArgvCommand,
analyzeShellCommand,
buildSafeShellCommand,
buildSafeBinsShellCommand,
evaluateExecAllowlist,
evaluateShellAllowlist,
isSafeBinUsage,
@@ -80,21 +80,30 @@ describe("exec approvals allowlist matching", () => {
});
describe("exec approvals safe shell command builder", () => {
it("single-quotes argv tokens while preserving pipes/chaining", () => {
it("quotes only safeBins segments (leaves other segments untouched)", () => {
if (process.platform === "win32") {
return;
}
const res = buildSafeShellCommand({
command: 'head $FOO | grep * && echo "a\'b" ; wc -l',
const analysis = analyzeShellCommand({
command: "rg foo src/*.ts | head -n 5 && echo ok",
cwd: "/tmp",
env: { PATH: "/usr/bin:/bin" },
platform: process.platform,
});
expect(analysis.ok).toBe(true);
const res = buildSafeBinsShellCommand({
command: "rg foo src/*.ts | head -n 5 && echo ok",
segments: analysis.segments,
segmentSatisfiedBy: [null, "safeBins", null],
platform: process.platform,
});
expect(res.ok).toBe(true);
expect(res.command).toContain("'$FOO'");
expect(res.command).toContain("'*'");
expect(res.command).toContain("&&");
expect(res.command).toContain(";");
expect(res.command).toContain("|");
expect(res.command).toContain("'a'\"'\"'b'");
// Preserve non-safeBins segment raw (glob stays unquoted)
expect(res.command).toContain("rg foo src/*.ts");
// SafeBins segment is fully quoted
expect(res.command).toContain("'head' '-n' '5'");
});
});
@@ -346,6 +355,9 @@ describe("exec approvals shell allowlist (chained commands)", () => {
describe("exec approvals safe bins", () => {
it("allows safe bins with non-path args", () => {
if (process.platform === "win32") {
return;
}
const dir = makeTempDir();
const binDir = path.join(dir, "bin");
fs.mkdirSync(binDir, { recursive: true });
@@ -370,6 +382,9 @@ describe("exec approvals safe bins", () => {
});
it("blocks safe bins with file args", () => {
if (process.platform === "win32") {
return;
}
const dir = makeTempDir();
const binDir = path.join(dir, "bin");
fs.mkdirSync(binDir, { recursive: true });