Files
puaros/packages/ipuaro/src/infrastructure/tools/run/CommandSecurity.ts
imfozilbek f5f904a847 feat(ipuaro): add git and run tools (v0.9.0)
Git tools:
- GitStatusTool: repository status (branch, staged, modified, untracked)
- GitDiffTool: uncommitted changes with diff output
- GitCommitTool: create commits with confirmation

Run tools:
- CommandSecurity: blacklist/whitelist shell command validation
- RunCommandTool: execute shell commands with security checks
- RunTestsTool: auto-detect and run vitest/jest/mocha/npm test

All 18 planned tools now implemented.
Tests: 1086 (+233), Coverage: 98.08%
2025-12-01 02:54:29 +05:00

258 lines
5.6 KiB
TypeScript

/**
* Command security classification.
*/
export type CommandClassification = "allowed" | "blocked" | "requires_confirmation"
/**
* Result of command security check.
*/
export interface SecurityCheckResult {
/** Classification of the command */
classification: CommandClassification
/** Reason for the classification */
reason: string
}
/**
* Dangerous commands that are always blocked.
* These commands can cause data loss or security issues.
*/
export const DEFAULT_BLACKLIST: string[] = [
// Destructive file operations
"rm -rf",
"rm -r",
"rm -fr",
"rmdir",
// Dangerous git operations
"git push --force",
"git push -f",
"git reset --hard",
"git clean -fd",
"git clean -f",
// Publishing/deployment
"npm publish",
"yarn publish",
"pnpm publish",
// System commands
"sudo",
"su ",
"chmod",
"chown",
// Network/download commands that could be dangerous
"| sh",
"| bash",
// Environment manipulation
"export ",
"unset ",
// Process control
"kill -9",
"killall",
"pkill",
// Disk operations (require exact command start)
"mkfs",
"fdisk",
// Other dangerous
":(){ :|:& };:",
"eval ",
]
/**
* Safe commands that don't require confirmation.
* Matched by first word (command name).
*/
export const DEFAULT_WHITELIST: string[] = [
// Package managers
"npm",
"pnpm",
"yarn",
"npx",
"bun",
// Node.js
"node",
"tsx",
"ts-node",
// Git (read operations)
"git",
// Build tools
"tsc",
"tsup",
"esbuild",
"vite",
"webpack",
"rollup",
// Testing
"vitest",
"jest",
"mocha",
"playwright",
"cypress",
// Linting/formatting
"eslint",
"prettier",
"biome",
// Utilities
"echo",
"cat",
"ls",
"pwd",
"which",
"head",
"tail",
"grep",
"find",
"wc",
"sort",
"diff",
]
/**
* Git subcommands that are safe and don't need confirmation.
*/
const SAFE_GIT_SUBCOMMANDS: string[] = [
"status",
"log",
"diff",
"show",
"branch",
"remote",
"fetch",
"pull",
"stash",
"tag",
"blame",
"ls-files",
"ls-tree",
"rev-parse",
"describe",
]
/**
* Command security checker.
* Determines if a command is safe to execute, blocked, or requires confirmation.
*/
export class CommandSecurity {
private readonly blacklist: string[]
private readonly whitelist: string[]
constructor(blacklist: string[] = DEFAULT_BLACKLIST, whitelist: string[] = DEFAULT_WHITELIST) {
this.blacklist = blacklist.map((cmd) => cmd.toLowerCase())
this.whitelist = whitelist.map((cmd) => cmd.toLowerCase())
}
/**
* Check if a command is safe to execute.
*/
check(command: string): SecurityCheckResult {
const normalized = command.trim().toLowerCase()
const blacklistMatch = this.isBlacklisted(normalized)
if (blacklistMatch) {
return {
classification: "blocked",
reason: `Command contains blocked pattern: '${blacklistMatch}'`,
}
}
if (this.isWhitelisted(normalized)) {
return {
classification: "allowed",
reason: "Command is in the whitelist",
}
}
return {
classification: "requires_confirmation",
reason: "Command is not in the whitelist and requires user confirmation",
}
}
/**
* Check if command matches any blacklist pattern.
* Returns the matched pattern or null.
*/
private isBlacklisted(command: string): string | null {
for (const pattern of this.blacklist) {
if (command.includes(pattern)) {
return pattern
}
}
return null
}
/**
* Check if command's first word is in the whitelist.
*/
private isWhitelisted(command: string): boolean {
const firstWord = this.getFirstWord(command)
if (!this.whitelist.includes(firstWord)) {
return false
}
if (firstWord === "git") {
return this.isGitCommandSafe(command)
}
return true
}
/**
* Check if git command is safe (read-only operations).
*/
private isGitCommandSafe(command: string): boolean {
const parts = command.split(/\s+/)
if (parts.length < 2) {
return false
}
const subcommand = parts[1]
return SAFE_GIT_SUBCOMMANDS.includes(subcommand)
}
/**
* Get first word from command.
*/
private getFirstWord(command: string): string {
const match = /^(\S+)/.exec(command)
return match ? match[1] : ""
}
/**
* Add patterns to the blacklist.
*/
addToBlacklist(patterns: string[]): void {
for (const pattern of patterns) {
const normalized = pattern.toLowerCase()
if (!this.blacklist.includes(normalized)) {
this.blacklist.push(normalized)
}
}
}
/**
* Add commands to the whitelist.
*/
addToWhitelist(commands: string[]): void {
for (const cmd of commands) {
const normalized = cmd.toLowerCase()
if (!this.whitelist.includes(normalized)) {
this.whitelist.push(normalized)
}
}
}
/**
* Get current blacklist.
*/
getBlacklist(): string[] {
return [...this.blacklist]
}
/**
* Get current whitelist.
*/
getWhitelist(): string[] {
return [...this.whitelist]
}
}