Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
44 changes: 44 additions & 0 deletions src/guardrails/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// List of dangerous command patterns that should be blocked
// Patterns are case-insensitive and stored in lowercase for efficiency
//
// NOTE: This is a basic substring-based approach that provides a first line of defense.
// Future improvements could include:
// - More comprehensive pattern list (sudo variants, different spacing, etc.)
// - Word boundary checking to avoid false positives on filenames
// - Command structure parsing to identify actual operations
// - Allowlist approach for additional security
const DENYLIST = [
"rm -rf /",
"rm -rf/*",
"rm -rf /.",
"shutdown",
"reboot",
":(){ :|:& };:",
"mkfs",
"dd if=",
];

// Pre-computed lowercase versions for efficient checking
const DENYLIST_LOWER = DENYLIST.map((token) => token.toLowerCase());

/**
* Enforces guardrails to prevent execution of dangerous shell commands
* @param command The command to check
* @param allowShell Whether shell commands are allowed
* @throws Error if the command is blocked by guardrails
*/
export function enforceGuardrails(command: string, allowShell: boolean): void {
if (!allowShell) {
throw new Error("Shell commands are disabled by configuration.");
}

const lower = command.toLowerCase();
const blockedIndex = DENYLIST_LOWER.findIndex((token) =>
lower.includes(token),
);
if (blockedIndex !== -1) {
throw new Error(
`Command blocked by guardrails: ${DENYLIST[blockedIndex]}`,
);
}
}
3 changes: 3 additions & 0 deletions src/loz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
requestApiKey,
} from "./config";
import { Git } from "./git";
import { enforceGuardrails } from "./guardrails";

// Get the path to the home directory
const HOME_PATH = os.homedir() || "";
Expand Down Expand Up @@ -563,6 +564,8 @@ export class Loz {
if (answer.toLowerCase() === "y") {
for (const cmd of commands) {
try {
// Enforce guardrails to prevent dangerous commands
enforceGuardrails(cmd, true);
await runCommand(cmd);
} catch (error: any) {
if (typeof error === "string" && error.indexOf("No output") === 0) {
Expand Down
84 changes: 84 additions & 0 deletions test/guardrails.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { expect } from "chai";
import "mocha";
import { enforceGuardrails } from "../src/guardrails";

describe("Guardrails Test", () => {
describe("enforceGuardrails", () => {
it("should throw error when shell is disabled", () => {
expect(() => enforceGuardrails("ls -la", false)).to.throw(
"Shell commands are disabled by configuration.",
);
});

it("should allow safe commands", () => {
expect(() => enforceGuardrails("ls -la", true)).to.not.throw();
expect(() => enforceGuardrails("cd /tmp", true)).to.not.throw();
expect(() => enforceGuardrails("echo hello", true)).to.not.throw();
expect(() => enforceGuardrails("cat file.txt", true)).to.not.throw();
});

it("should block rm -rf /", () => {
expect(() => enforceGuardrails("rm -rf /", true)).to.throw(
"Command blocked by guardrails: rm -rf /",
);
expect(() => enforceGuardrails("RM -RF /", true)).to.throw(
"Command blocked by guardrails: rm -rf /",
);
});

it("should block rm -rf / bypass attempts", () => {
expect(() => enforceGuardrails("rm -rf/*", true)).to.throw(
"Command blocked by guardrails",
);
expect(() => enforceGuardrails("rm -rf /.", true)).to.throw(
"Command blocked by guardrails",
);
});

it("should block shutdown", () => {
expect(() => enforceGuardrails("shutdown now", true)).to.throw(
"Command blocked by guardrails: shutdown",
);
expect(() => enforceGuardrails("SHUTDOWN", true)).to.throw(
"Command blocked by guardrails: shutdown",
);
});

it("should block reboot", () => {
expect(() => enforceGuardrails("reboot", true)).to.throw(
"Command blocked by guardrails: reboot",
);
expect(() => enforceGuardrails("sudo reboot", true)).to.throw(
"Command blocked by guardrails: reboot",
);
});

it("should block fork bomb", () => {
expect(() => enforceGuardrails(":(){ :|:& };:", true)).to.throw(
"Command blocked by guardrails: :(){ :|:& };:",
);
});

it("should block mkfs", () => {
expect(() => enforceGuardrails("mkfs /dev/sda", true)).to.throw(
"Command blocked by guardrails: mkfs",
);
expect(() => enforceGuardrails("mkfs.ext4 /dev/sda1", true)).to.throw(
"Command blocked by guardrails: mkfs",
);
});

it("should block dd if=", () => {
expect(() =>
enforceGuardrails("dd if=/dev/zero of=/dev/sda", true),
).to.throw("Command blocked by guardrails: dd if=");
});

it("should allow commands with similar but safe patterns", () => {
// These should be allowed as they don't match the blocked patterns
expect(() => enforceGuardrails("rm file.txt", true)).to.not.throw();
expect(() => enforceGuardrails("rm -r /tmp/test", true)).to.not.throw();
expect(() => enforceGuardrails("dd status=progress", true)).to.not.throw();
});
});
});
Loading