UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

486 lines (485 loc) • 48.7 kB
export const READ_ONLY_FLAG_ARITIES = Object.freeze(["none", "number", "string", "char", "{}", "EOF"]); export const FLAG_VALUE_ACCEPTS = { none: () => false, number: (value) => /^\d+$/.test(value), string: () => true, char: (value) => value.length === 1, "{}": (value) => value === "{}", EOF: (value) => value === "EOF", }; const GIT_REFS = { "--all": "none", "--branches": "none", "--tags": "none", "--remotes": "none" }; const GIT_DATES = { "--since": "string", "--after": "string", "--until": "string", "--before": "string" }; const GIT_LOG_VIEW = { "--oneline": "none", "--graph": "none", "--decorate": "none", "--no-decorate": "none", "--date": "string", "--relative-date": "none" }; const GIT_COUNT = { "--max-count": "number", "-n": "number" }; const GIT_STAT = { "--stat": "none", "--numstat": "none", "--shortstat": "none", "--name-only": "none", "--name-status": "none" }; const GIT_COLOR = { "--color": "none", "--no-color": "none" }; const GIT_PATCH = { "--patch": "none", "-p": "none", "--no-patch": "none", "--no-ext-diff": "none", "-s": "none" }; const GIT_AUTHOR = { "--author": "string", "--committer": "string", "--grep": "string" }; const SIGNATURE_FORMAT = /%[-+ ]?G|%\(\*?signature/; const NAMED_FORMATS = new Set(["oneline", "short", "medium", "full", "fuller", "email", "raw"]); const unknownNamedFormat = (value) => value !== "" && !value.includes("%") && !value.startsWith("format:") && !value.startsWith("tformat:") && !NAMED_FORMATS.has(value); const formatOperandDanger = (args) => { for (let i = 0; i < args.length; i++) { const word = args[i]; if (SIGNATURE_FORMAT.test(word)) return true; for (const flag of ["--format", "--pretty", "--sort"]) { let value; if (word === flag && i + 1 < args.length) value = args[i + 1]; else if (word.startsWith(`${flag}=`)) value = word.slice(flag.length + 1); if (value === undefined) continue; if (SIGNATURE_FORMAT.test(value) || value.includes("signature")) return true; if (flag !== "--sort" && unknownNamedFormat(value)) return true; } } return false; }; const ghOperandDanger = (args) => { for (const word of args) { if (word === "") continue; let operand = word; if (word.startsWith("-")) { const eq = word.indexOf("="); if (eq === -1) continue; operand = word.slice(eq + 1); if (operand === "") continue; } if (!operand.includes("/") && !operand.includes("://") && !operand.includes("@")) continue; if (operand.includes("://") || operand.includes("@")) return true; if ((operand.match(/\//g) ?? []).length >= 2) return true; } return false; }; const DOCKER_RETARGET_FLAGS = ["-H", "-c", "-r", "--host", "--context", "--config", "--tlscacert", "--tlscert", "--tlskey", "--url", "--connection", "--identity", "--remote", "--module", "--out"]; const DOCKER_RETARGET_LETTERS = new Set(DOCKER_RETARGET_FLAGS.filter((f) => f.length === 2).map((f) => f[1])); export const dockerRetargetDanger = (args) => args.some((word) => { if (DOCKER_RETARGET_FLAGS.some((f) => word === f || word.startsWith(`${f}=`) || (f.length === 2 && word.length > 2 && word.startsWith(f)))) return true; const cluster = /^-([A-Za-z]+)/.exec(word)?.[1]; if (cluster !== undefined && cluster.length >= 2) for (const letter of cluster) if (DOCKER_RETARGET_LETTERS.has(letter)) return true; return false; }); const before = (s, sep) => s.split(sep)[0]; const GREP_FLAGS = { "-e": "string", "--regexp": "string", "-f": "string", "--file": "string", "-F": "none", "--fixed-strings": "none", "-G": "none", "--basic-regexp": "none", "-E": "none", "--extended-regexp": "none", "-P": "none", "--perl-regexp": "none", "-i": "none", "--ignore-case": "none", "--no-ignore-case": "none", "-v": "none", "--invert-match": "none", "-w": "none", "--word-regexp": "none", "-x": "none", "--line-regexp": "none", "-c": "none", "--count": "none", "--color": "string", "--colour": "string", "-L": "none", "--files-without-match": "none", "-l": "none", "--files-with-matches": "none", "-m": "number", "--max-count": "number", "-o": "none", "--only-matching": "none", "-q": "none", "--quiet": "none", "--silent": "none", "-s": "none", "--no-messages": "none", "-b": "none", "--byte-offset": "none", "-H": "none", "--with-filename": "none", "-h": "none", "--no-filename": "none", "--label": "string", "-n": "none", "--line-number": "none", "-T": "none", "--initial-tab": "none", "-u": "none", "--unix-byte-offsets": "none", "-Z": "none", "--null": "none", "-z": "none", "--null-data": "none", "-A": "number", "--after-context": "number", "-B": "number", "--before-context": "number", "-C": "number", "--context": "number", "--group-separator": "string", "--no-group-separator": "none", "-a": "none", "--text": "none", "--binary-files": "string", "-I": "none", "-D": "string", "--devices": "string", "-d": "string", "--directories": "string", "--exclude": "string", "--exclude-from": "string", "--exclude-dir": "string", "--include": "string", "-r": "none", "--recursive": "none", "-R": "none", "--dereference-recursive": "none", "--line-buffered": "none", "-U": "none", "--binary": "none", "--help": "none", "-V": "none", "--version": "none" }; const FD_FLAGS = { "-h": "none", "--help": "none", "-V": "none", "--version": "none", "-H": "none", "--hidden": "none", "-I": "none", "--no-ignore": "none", "--no-ignore-vcs": "none", "--no-ignore-parent": "none", "-s": "none", "--case-sensitive": "none", "-i": "none", "--ignore-case": "none", "-g": "none", "--glob": "none", "--regex": "none", "-F": "none", "--fixed-strings": "none", "-a": "none", "--absolute-path": "none", "-L": "none", "--follow": "none", "-p": "none", "--full-path": "none", "-0": "none", "--print0": "none", "-d": "number", "--max-depth": "number", "--min-depth": "number", "--exact-depth": "number", "-t": "string", "--type": "string", "-e": "string", "--extension": "string", "-S": "string", "--size": "string", "--changed-within": "string", "--changed-before": "string", "-o": "string", "--owner": "string", "-E": "string", "--exclude": "string", "--ignore-file": "string", "-c": "string", "--color": "string", "-j": "number", "--threads": "number", "--max-buffer-time": "string", "--max-results": "number", "-1": "none", "-q": "none", "--quiet": "none", "--show-errors": "none", "--strip-cwd-prefix": "none", "--one-file-system": "none", "--prune": "none", "--search-path": "string", "--base-directory": "string", "--path-separator": "string", "--batch-size": "number", "--no-require-git": "none", "--hyperlink": "string", "--and": "string", "--format": "string" }; const CHECKSUM_FLAGS = { "-b": "none", "--binary": "none", "-t": "none", "--text": "none", "-c": "none", "--check": "none", "--ignore-missing": "none", "--quiet": "none", "--status": "none", "--strict": "none", "-w": "none", "--warn": "none", "--tag": "none", "-z": "none", "--zero": "none", "--help": "none", "--version": "none" }; const TEST_INTEGER_OPERATORS = new Set(["-eq", "-ne", "-lt", "-le", "-gt", "-ge"]); const TEST_INTEGER = /^-?(0[xX][0-9a-fA-F]+|[0-9]+#[0-9a-zA-Z]+|[0-9]+)$/; export const READ_ONLY_COMMAND_TABLE = { xargs: { safeFlags: { "-I": "{}", "-n": "number", "-P": "number", "-L": "number", "-s": "number", "-E": "EOF", "-0": "none", "-t": "none", "-r": "none", "-x": "none", "-d": "char" } }, "git diff": { safeFlags: { ...GIT_STAT, ...GIT_COLOR, "--dirstat": "none", "--summary": "none", "--patch-with-stat": "none", "--word-diff": "none", "--word-diff-regex": "string", "--color-words": "none", "--no-renames": "none", "--no-ext-diff": "none", "--check": "none", "--ws-error-highlight": "string", "--full-index": "none", "--binary": "none", "--abbrev": "number", "--break-rewrites": "none", "--find-renames": "none", "--find-copies": "none", "--find-copies-harder": "none", "--irreversible-delete": "none", "--diff-algorithm": "string", "--histogram": "none", "--patience": "none", "--minimal": "none", "--ignore-space-at-eol": "none", "--ignore-space-change": "none", "--ignore-all-space": "none", "--ignore-blank-lines": "none", "--inter-hunk-context": "number", "--function-context": "none", "--exit-code": "none", "--quiet": "none", "--cached": "none", "--staged": "none", "--pickaxe-regex": "none", "--pickaxe-all": "none", "--no-index": "none", "--relative": "string", "--diff-filter": "string", "-p": "none", "-u": "none", "-s": "none", "-M": "none", "-C": "none", "-B": "none", "-D": "none", "-l": "none", "-S": "string", "-G": "string", "-O": "string", "-R": "none" }, }, "git log": { safeFlags: { ...GIT_LOG_VIEW, ...GIT_REFS, ...GIT_DATES, ...GIT_COUNT, ...GIT_STAT, ...GIT_COLOR, ...GIT_PATCH, ...GIT_AUTHOR, "--abbrev-commit": "none", "--full-history": "none", "--dense": "none", "--sparse": "none", "--simplify-merges": "none", "--ancestry-path": "none", "--source": "none", "--first-parent": "none", "--merges": "none", "--no-merges": "none", "--reverse": "none", "--walk-reflogs": "none", "--skip": "number", "--max-age": "number", "--min-age": "number", "--no-min-parents": "none", "--no-max-parents": "none", "--follow": "none", "--no-walk": "none", "--left-right": "none", "--cherry-mark": "none", "--cherry-pick": "none", "--boundary": "none", "--topo-order": "none", "--date-order": "none", "--author-date-order": "none", "--pretty": "string", "--format": "string", "--diff-filter": "string", "-S": "string", "-G": "string", "--pickaxe-regex": "none", "--pickaxe-all": "none" }, dangerous: formatOperandDanger, }, "git show": { safeFlags: { ...GIT_LOG_VIEW, ...GIT_STAT, ...GIT_COLOR, ...GIT_PATCH, "--abbrev-commit": "none", "--word-diff": "none", "--word-diff-regex": "string", "--color-words": "none", "--pretty": "string", "--format": "string", "--first-parent": "none", "--raw": "none", "--diff-filter": "string", "-m": "none", "--quiet": "none" }, dangerous: formatOperandDanger, }, "git shortlog": { safeFlags: { ...GIT_REFS, ...GIT_DATES, "-s": "none", "--summary": "none", "-n": "none", "--numbered": "none", "-e": "none", "--email": "none", "-c": "none", "--committer": "none", "--group": "string", "--format": "string", "--no-merges": "none", "--author": "string" }, dangerous: formatOperandDanger, }, "git reflog": { safeFlags: { ...GIT_LOG_VIEW, ...GIT_REFS, ...GIT_DATES, ...GIT_COUNT, ...GIT_AUTHOR }, dangerous: (args) => { const reading = new Set(["show", "list"]); const writing = new Set(["expire", "delete", "exists", "drop", "write"]); const first = args[0]; if (first !== undefined && first !== "" && !first.startsWith("-") && !reading.has(first)) return true; return args.some((w) => writing.has(w)); }, }, "git stash list": { safeFlags: { ...GIT_LOG_VIEW, ...GIT_REFS, ...GIT_COUNT } }, "git ls-remote": { safeFlags: { "--branches": "none", "-b": "none", "--tags": "none", "-t": "none", "--heads": "none", "-h": "none", "--refs": "none", "--quiet": "none", "-q": "none", "--exit-code": "none", "--get-url": "none", "--symref": "none", "--sort": "string" }, dangerous: (args) => { if (args.some((w) => w === "-o" || w === "--server-option" || w.startsWith("--server-option="))) return true; let pastDashes = false; for (let i = 0; i < args.length; i++) { const w = args[i]; if (w === "") continue; if (!pastDashes && w === "--") { pastDashes = true; continue; } if (!pastDashes && w.startsWith("-") && w !== "-") { if (w === "--sort") i++; continue; } return true; } return formatOperandDanger(args); }, }, "git status": { safeFlags: { "--short": "none", "-s": "none", "--branch": "none", "-b": "none", "--porcelain": "none", "--long": "none", "--verbose": "none", "-v": "none", "--untracked-files": "string", "-u": "string", "--ignored": "none", "--ignore-submodules": "string", "--column": "none", "--no-column": "none", "--ahead-behind": "none", "--no-ahead-behind": "none", "--renames": "none", "--no-renames": "none", "--find-renames": "string", "-M": "string" } }, "git blame": { safeFlags: { ...GIT_COLOR, "-L": "string", "--porcelain": "none", "-p": "none", "--line-porcelain": "none", "--incremental": "none", "--root": "none", "--show-stats": "none", "--show-name": "none", "--show-number": "none", "-n": "none", "--show-email": "none", "-e": "none", "-f": "none", "--date": "string", "-w": "none", "--ignore-rev": "string", "--ignore-revs-file": "string", "-M": "none", "-C": "none", "--score-debug": "none", "--abbrev": "number", "-s": "none", "-l": "none", "-t": "none" } }, "git ls-files": { safeFlags: { "--cached": "none", "-c": "none", "--deleted": "none", "-d": "none", "--modified": "none", "-m": "none", "--others": "none", "-o": "none", "--ignored": "none", "-i": "none", "--stage": "none", "-s": "none", "--killed": "none", "-k": "none", "--unmerged": "none", "-u": "none", "--directory": "none", "--no-empty-directory": "none", "--eol": "none", "--full-name": "none", "--abbrev": "number", "--debug": "none", "-z": "none", "-t": "none", "-v": "none", "-f": "none", "--exclude": "string", "-x": "string", "--exclude-from": "string", "-X": "string", "--exclude-per-directory": "string", "--exclude-standard": "none", "--error-unmatch": "none", "--recurse-submodules": "none" } }, "git config --get": { safeFlags: { "--local": "none", "--global": "none", "--system": "none", "--worktree": "none", "--default": "string", "--type": "string", "--bool": "none", "--int": "none", "--bool-or-int": "none", "--path": "none", "--expiry-date": "none", "-z": "none", "--null": "none", "--name-only": "none", "--show-origin": "none", "--show-scope": "none" } }, "git remote show": { safeFlags: { "-n": "none" }, dangerous: (args) => { const dd = args.indexOf("--"); const flags = dd === -1 ? args : args.slice(0, dd); const operands = dd === -1 ? [] : args.slice(dd + 1); const names = flags.filter((w) => w !== "-n").concat(operands); if (names.length !== 1) return true; if (!flags.includes("-n")) return true; return !/^[a-zA-Z0-9_][a-zA-Z0-9_-]*$/.test(names[0]); }, }, "git remote": { safeFlags: { "-v": "none", "--verbose": "none" }, dangerous: (args) => args.some((w) => w !== "-v" && w !== "--verbose") }, "git merge-base": { safeFlags: { "--is-ancestor": "none", "--fork-point": "none", "--octopus": "none", "--independent": "none", "--all": "none" } }, "git rev-parse": { safeFlags: { "--verify": "none", "--short": "string", "--abbrev-ref": "none", "--symbolic": "none", "--symbolic-full-name": "none", "--show-toplevel": "none", "--show-cdup": "none", "--show-prefix": "none", "--git-dir": "none", "--git-common-dir": "none", "--absolute-git-dir": "none", "--show-superproject-working-tree": "none", "--is-inside-work-tree": "none", "--is-inside-git-dir": "none", "--is-bare-repository": "none", "--is-shallow-repository": "none", "--is-shallow-update": "none", "--path-prefix": "none" } }, "git rev-list": { safeFlags: { ...GIT_REFS, ...GIT_DATES, ...GIT_COUNT, ...GIT_AUTHOR, "--count": "none", "--reverse": "none", "--first-parent": "none", "--ancestry-path": "none", "--merges": "none", "--no-merges": "none", "--min-parents": "number", "--max-parents": "number", "--no-min-parents": "none", "--no-max-parents": "none", "--skip": "number", "--max-age": "number", "--min-age": "number", "--walk-reflogs": "none", "--oneline": "none", "--abbrev-commit": "none", "--pretty": "string", "--format": "string", "--abbrev": "number", "--full-history": "none", "--dense": "none", "--sparse": "none", "--source": "none", "--graph": "none" }, dangerous: formatOperandDanger, }, "git describe": { safeFlags: { "--tags": "none", "--match": "string", "--exclude": "string", "--long": "none", "--abbrev": "number", "--always": "none", "--contains": "none", "--first-match": "none", "--exact-match": "none", "--candidates": "number", "--dirty": "none", "--broken": "none" } }, "git cat-file": { safeFlags: { "-t": "none", "-s": "none", "-p": "none", "-e": "none", "--batch-check": "none", "--allow-undetermined-type": "none" } }, "git for-each-ref": { safeFlags: { "--format": "string", "--sort": "string", "--count": "number", "--contains": "string", "--no-contains": "string", "--merged": "string", "--no-merged": "string", "--points-at": "string" }, dangerous: formatOperandDanger, }, "git grep": { safeFlags: { "-e": "string", "-E": "none", "--extended-regexp": "none", "-G": "none", "--basic-regexp": "none", "-F": "none", "--fixed-strings": "none", "-P": "none", "--perl-regexp": "none", "-i": "none", "--ignore-case": "none", "-v": "none", "--invert-match": "none", "-w": "none", "--word-regexp": "none", "-n": "none", "--line-number": "none", "-c": "none", "--count": "none", "-l": "none", "--files-with-matches": "none", "-L": "none", "--files-without-match": "none", "-h": "none", "-H": "none", "--heading": "none", "--break": "none", "--full-name": "none", "--color": "none", "--no-color": "none", "-o": "none", "--only-matching": "none", "-A": "number", "--after-context": "number", "-B": "number", "--before-context": "number", "-C": "number", "--context": "number", "--and": "none", "--or": "none", "--not": "none", "--max-depth": "number", "--untracked": "none", "--no-index": "none", "--recurse-submodules": "none", "--cached": "none", "--threads": "number", "-q": "none", "--quiet": "none" } }, "git stash show": { safeFlags: { ...GIT_STAT, ...GIT_COLOR, ...GIT_PATCH, "--word-diff": "none", "--word-diff-regex": "string", "--diff-filter": "string", "--abbrev": "number" } }, "git worktree list": { safeFlags: { "--porcelain": "none", "-v": "none", "--verbose": "none", "--expire": "string" } }, "git tag": { safeFlags: { "-l": "none", "--list": "none", "-n": "number", "--contains": "string", "--no-contains": "string", "--merged": "string", "--no-merged": "string", "--sort": "string", "--format": "string", "--points-at": "string", "--column": "none", "--no-column": "none", "-i": "none", "--ignore-case": "none" }, dangerous: (args) => { if (formatOperandDanger(args)) return true; const valued = new Set(["--contains", "--no-contains", "--merged", "--no-merged", "--points-at", "--sort", "--format", "-n"]); let i = 0; let listing = false; let pastDashes = false; while (i < args.length) { const w = args[i]; if (w === "") { i++; continue; } if (w === "--" && !pastDashes) { pastDashes = true; i++; continue; } if (!pastDashes && w.startsWith("-")) { if (w === "--list" || w === "-l") listing = true; else if (w[0] === "-" && w[1] !== "-" && w.length > 2 && !w.includes("=") && w.slice(1).includes("l")) listing = true; if (w.includes("=")) i++; else if (valued.has(w)) i += 2; else i++; } else { if (!listing) return true; i++; } } return false; }, }, "git branch": { safeFlags: { "-l": "none", "--list": "none", "-a": "none", "--all": "none", "-r": "none", "--remotes": "none", "-v": "none", "-vv": "none", "--verbose": "none", "--color": "none", "--no-color": "none", "--column": "none", "--no-column": "none", "--abbrev": "number", "--no-abbrev": "none", "--contains": "string", "--no-contains": "string", "--merged": "none", "--no-merged": "none", "--points-at": "string", "--sort": "string", "--show-current": "none", "-i": "none", "--ignore-case": "none" }, dangerous: (args) => { if (formatOperandDanger(args)) return true; const valued = new Set(["--contains", "--no-contains", "--points-at", "--sort"]); const optionalOperand = new Set(["--merged", "--no-merged"]); let i = 0; let lastFlag = ""; let listing = false; let pastDashes = false; while (i < args.length) { const w = args[i]; if (w === "") { i++; continue; } if (w === "--" && !pastDashes) { pastDashes = true; lastFlag = ""; i++; continue; } if (!pastDashes && w.startsWith("-")) { if (w === "--list" || w === "-l") listing = true; else if (w[0] === "-" && w[1] !== "-" && w.length > 2 && !w.includes("=") && w.slice(1).includes("l")) listing = true; if (w.includes("=")) { lastFlag = before(w, "="); i++; } else if (valued.has(w)) { lastFlag = w; i += 2; } else { lastFlag = w; i++; } } else { if (!listing && !optionalOperand.has(lastFlag)) return true; i++; } } return false; }, }, "gh pr view": { safeFlags: { "--json": "string", "--comments": "none", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger }, "gh pr list": { safeFlags: { "--state": "string", "-s": "string", "--author": "string", "--assignee": "string", "--label": "string", "--limit": "number", "-L": "number", "--base": "string", "--head": "string", "--search": "string", "--json": "string", "--draft": "none", "--app": "string", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger }, "gh pr diff": { safeFlags: { "--color": "string", "--name-only": "none", "--patch": "none", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger }, "gh pr checks": { safeFlags: { "--watch": "none", "--required": "none", "--fail-fast": "none", "--json": "string", "--interval": "number", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger }, "gh issue view": { safeFlags: { "--json": "string", "--comments": "none", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger }, "gh issue list": { safeFlags: { "--state": "string", "-s": "string", "--assignee": "string", "--author": "string", "--label": "string", "--limit": "number", "-L": "number", "--milestone": "string", "--search": "string", "--json": "string", "--app": "string", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger }, "gh repo view": { safeFlags: { "--json": "string" }, dangerous: ghOperandDanger }, "gh run list": { safeFlags: { "--branch": "string", "-b": "string", "--status": "string", "-s": "string", "--workflow": "string", "-w": "string", "--limit": "number", "-L": "number", "--json": "string", "--repo": "string", "-R": "string", "--event": "string", "-e": "string", "--user": "string", "-u": "string", "--created": "string", "--commit": "string", "-c": "string" }, dangerous: ghOperandDanger }, "gh run view": { safeFlags: { "--log": "none", "--log-failed": "none", "--exit-status": "none", "--verbose": "none", "-v": "none", "--json": "string", "--repo": "string", "-R": "string", "--job": "string", "-j": "string", "--attempt": "number", "-a": "number" }, dangerous: ghOperandDanger }, "gh auth status": { safeFlags: { "--active": "none", "-a": "none", "--hostname": "string", "-h": "string", "--json": "string" }, dangerous: ghOperandDanger }, "gh pr status": { safeFlags: { "--conflict-status": "none", "-c": "none", "--json": "string", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger }, "gh issue status": { safeFlags: { "--json": "string", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger }, "gh release list": { safeFlags: { "--exclude-drafts": "none", "--exclude-pre-releases": "none", "--json": "string", "--limit": "number", "-L": "number", "--order": "string", "-O": "string", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger }, "gh release view": { safeFlags: { "--json": "string", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger }, "gh workflow list": { safeFlags: { "--all": "none", "-a": "none", "--json": "string", "--limit": "number", "-L": "number", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger }, "gh workflow view": { safeFlags: { "--ref": "string", "-r": "string", "--yaml": "none", "-y": "none", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger }, "gh label list": { safeFlags: { "--json": "string", "--limit": "number", "-L": "number", "--order": "string", "--search": "string", "-S": "string", "--sort": "string", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger }, "gh search repos": { safeFlags: { "--archived": "none", "--created": "string", "--followers": "string", "--forks": "string", "--good-first-issues": "string", "--help-wanted-issues": "string", "--include-forks": "string", "--json": "string", "--language": "string", "--license": "string", "--limit": "number", "-L": "number", "--match": "string", "--number-topics": "string", "--order": "string", "--owner": "string", "--size": "string", "--sort": "string", "--stars": "string", "--topic": "string", "--updated": "string", "--visibility": "string" } }, "gh search issues": { safeFlags: { "--app": "string", "--assignee": "string", "--author": "string", "--closed": "string", "--commenter": "string", "--comments": "string", "--created": "string", "--include-prs": "none", "--interactions": "string", "--involves": "string", "--json": "string", "--label": "string", "--language": "string", "--limit": "number", "-L": "number", "--locked": "none", "--match": "string", "--mentions": "string", "--milestone": "string", "--no-assignee": "none", "--no-label": "none", "--no-milestone": "none", "--no-project": "none", "--order": "string", "--owner": "string", "--project": "string", "--reactions": "string", "--repo": "string", "-R": "string", "--sort": "string", "--state": "string", "--team-mentions": "string", "--updated": "string", "--visibility": "string" } }, "gh search prs": { safeFlags: { "--app": "string", "--assignee": "string", "--author": "string", "--base": "string", "-B": "string", "--checks": "string", "--closed": "string", "--commenter": "string", "--comments": "string", "--created": "string", "--draft": "none", "--head": "string", "-H": "string", "--interactions": "string", "--involves": "string", "--json": "string", "--label": "string", "--language": "string", "--limit": "number", "-L": "number", "--locked": "none", "--match": "string", "--mentions": "string", "--merged": "none", "--merged-at": "string", "--milestone": "string", "--no-assignee": "none", "--no-label": "none", "--no-milestone": "none", "--no-project": "none", "--order": "string", "--owner": "string", "--project": "string", "--reactions": "string", "--repo": "string", "-R": "string", "--review": "string", "--review-requested": "string", "--reviewed-by": "string", "--sort": "string", "--state": "string", "--team-mentions": "string", "--updated": "string", "--visibility": "string" } }, "gh search commits": { safeFlags: { "--author": "string", "--author-date": "string", "--author-email": "string", "--author-name": "string", "--committer": "string", "--committer-date": "string", "--committer-email": "string", "--committer-name": "string", "--hash": "string", "--json": "string", "--limit": "number", "-L": "number", "--merge": "none", "--order": "string", "--owner": "string", "--parent": "string", "--repo": "string", "-R": "string", "--sort": "string", "--tree": "string", "--visibility": "string" } }, "gh search code": { safeFlags: { "--extension": "string", "--filename": "string", "--json": "string", "--language": "string", "--limit": "number", "-L": "number", "--match": "string", "--owner": "string", "--repo": "string", "-R": "string", "--size": "string" } }, "docker logs": { safeFlags: { "--follow": "none", "-f": "none", "--tail": "string", "-n": "string", "--timestamps": "none", "-t": "none", "--since": "string", "--until": "string", "--details": "none" }, dangerous: dockerRetargetDanger }, "docker inspect": { safeFlags: { "--format": "string", "-f": "string", "--type": "string", "--size": "none", "-s": "none" }, dangerous: dockerRetargetDanger }, file: { safeFlags: { "--brief": "none", "-b": "none", "--mime": "none", "-i": "none", "--mime-type": "none", "--mime-encoding": "none", "--apple": "none", "--check-encoding": "none", "-c": "none", "--exclude": "string", "--exclude-quiet": "string", "--print0": "none", "-0": "none", "-F": "string", "--separator": "string", "--help": "none", "--version": "none", "-v": "none", "--no-dereference": "none", "-h": "none", "--dereference": "none", "-L": "none", "--keep-going": "none", "-k": "none", "--list": "none", "-l": "none", "--no-buffer": "none", "-n": "none", "--preserve-date": "none", "-p": "none", "--raw": "none", "-r": "none", "-s": "none", "--special-files": "none" } }, sort: { safeFlags: { "--ignore-leading-blanks": "none", "-b": "none", "--dictionary-order": "none", "-d": "none", "--ignore-case": "none", "-f": "none", "--general-numeric-sort": "none", "-g": "none", "--human-numeric-sort": "none", "-h": "none", "--ignore-nonprinting": "none", "-i": "none", "--month-sort": "none", "-M": "none", "--numeric-sort": "none", "-n": "none", "--random-sort": "none", "-R": "none", "--reverse": "none", "-r": "none", "--sort": "string", "--stable": "none", "-s": "none", "--unique": "none", "-u": "none", "--version-sort": "none", "-V": "none", "--zero-terminated": "none", "-z": "none", "--key": "string", "-k": "string", "--field-separator": "string", "-t": "string", "--check": "none", "-c": "none", "--check-char-order": "none", "-C": "none", "--merge": "none", "-m": "none", "--buffer-size": "string", "-S": "string", "--parallel": "number", "--batch-size": "number", "--help": "none", "--version": "none" } }, man: { safeFlags: { "-a": "none", "--all": "none", "-d": "none", "-f": "none", "--whatis": "none", "-h": "none", "-k": "none", "--apropos": "none", "-w": "none", "-S": "string", "-s": "string" }, dangerous: (args) => { const searchFlags = new Set(["-k", "-f", "--apropos", "--whatis"]); const valuedFlags = new Set(["-S", "-s"]); let searching = false; for (const w of args) { if (w === "--") break; if (!w.startsWith("-") || w === "-") continue; if (w.startsWith("--")) { if (searchFlags.has(w)) searching = true; } else if (/[kf]/.test(w.slice(1))) searching = true; } let searchSeen = false; let pastDashes = false; for (let i = 0; i < args.length; i++) { const w = args[i]; if (!pastDashes && w === "--") { pastDashes = true; continue; } if (!(pastDashes || !w.startsWith("-") || w === "-")) { if (searchFlags.has(w)) searchSeen = true; else if (valuedFlags.has(w)) i++; continue; } pastDashes = true; if (searching && w.startsWith("-")) return true; if (!searchSeen && (w.includes("/") || w.includes("\\") || w.includes("~"))) return true; } return false; }, }, help: { safeFlags: { "-d": "none" }, dangerous: (args) => args.some((w) => w.includes("/") || w.includes("\\") || w.includes("~")) }, netstat: { safeFlags: { "-a": "none", "-L": "none", "-l": "none", "-n": "none", "-f": "string", "-g": "none", "-i": "none", "-I": "string", "-s": "none", "-r": "none", "-m": "none", "-v": "none" } }, ps: { safeFlags: { "-e": "none", "-A": "none", "-a": "none", "-d": "none", "-N": "none", "--deselect": "none", "-f": "none", "-F": "none", "-l": "none", "-j": "none", "-y": "none", "-w": "none", "-ww": "none", "--width": "number", "-c": "none", "-H": "none", "--forest": "none", "--headers": "none", "--no-headers": "none", "-n": "string", "--sort": "string", "-L": "none", "-T": "none", "-m": "none", "-C": "string", "-G": "string", "-g": "string", "-p": "string", "--pid": "string", "-q": "string", "--quick-pid": "string", "-s": "string", "--sid": "string", "-t": "string", "--tty": "string", "-U": "string", "-u": "string", "--user": "string", "--help": "none", "--info": "none", "-V": "none", "--version": "none" }, dangerous: (args) => args.some((w) => !w.startsWith("-") && /^[a-zA-Z]*e[a-zA-Z]*$/.test(w)), }, base64: { respectsDoubleDash: false, safeFlags: { "-d": "none", "-D": "none", "--decode": "none", "-b": "number", "--break": "number", "-w": "number", "--wrap": "number", "-i": "string", "--input": "string", "--ignore-garbage": "none", "-h": "none", "--help": "none", "--version": "none" } }, grep: { safeFlags: GREP_FLAGS }, egrep: { safeFlags: GREP_FLAGS }, fgrep: { safeFlags: GREP_FLAGS }, rg: { safeFlags: { "-e": "string", "--regexp": "string", "-f": "string", "-i": "none", "--ignore-case": "none", "-S": "none", "--smart-case": "none", "-F": "none", "--fixed-strings": "none", "-w": "none", "--word-regexp": "none", "-v": "none", "--invert-match": "none", "-c": "none", "--count": "none", "-l": "none", "--files-with-matches": "none", "--files-without-match": "none", "-n": "none", "--line-number": "none", "-o": "none", "--only-matching": "none", "-A": "number", "--after-context": "number", "-B": "number", "--before-context": "number", "-C": "number", "--context": "number", "-H": "none", "-h": "none", "--heading": "none", "--no-heading": "none", "-q": "none", "--quiet": "none", "--column": "none", "-g": "string", "--glob": "string", "-t": "string", "--type": "string", "-T": "string", "--type-not": "string", "--type-list": "none", "--hidden": "none", "--no-ignore": "none", "-u": "none", "-m": "number", "--max-count": "number", "-d": "number", "--max-depth": "number", "-a": "none", "--text": "none", "-L": "none", "--follow": "none", "--color": "string", "--json": "none", "--stats": "none", "--help": "none", "--version": "none", "--debug": "none", "--": "none" } }, sha256sum: { safeFlags: CHECKSUM_FLAGS }, sha1sum: { safeFlags: CHECKSUM_FLAGS }, md5sum: { safeFlags: CHECKSUM_FLAGS }, tree: { safeFlags: { "-a": "none", "-d": "none", "-l": "none", "-f": "none", "-x": "none", "-L": "number", "-P": "string", "-I": "string", "--gitignore": "none", "--gitfile": "string", "--ignore-case": "none", "--matchdirs": "none", "--metafirst": "none", "--prune": "none", "--info": "none", "--infofile": "string", "--noreport": "none", "--charset": "string", "--filelimit": "number", "-q": "none", "-N": "none", "-Q": "none", "-p": "none", "-u": "none", "-g": "none", "-s": "none", "-h": "none", "--si": "none", "--du": "none", "-D": "none", "--timefmt": "string", "-F": "none", "--inodes": "none", "--device": "none", "-v": "none", "-t": "none", "-c": "none", "-U": "none", "-r": "none", "--dirsfirst": "none", "--filesfirst": "none", "--sort": "string", "-i": "none", "-A": "none", "-S": "none", "-n": "none", "-C": "none", "-X": "none", "-J": "none", "-H": "string", "--nolinks": "none", "--hintro": "string", "--houtro": "string", "-T": "string", "--hyperlink": "none", "--scheme": "string", "--authority": "string", "--fromfile": "none", "--fromtabfile": "none", "--fflinks": "none", "--help": "none", "--version": "none" } }, date: { safeFlags: { "-d": "string", "--date": "string", "-r": "string", "--reference": "string", "-u": "none", "--utc": "none", "--universal": "none", "-I": "none", "--iso-8601": "string", "-R": "none", "--rfc-email": "none", "--rfc-3339": "string", "--debug": "none", "--help": "none", "--version": "none" }, dangerous: (args) => { const valued = new Set(["-d", "--date", "-r", "--reference", "--rfc-3339"]); let i = 0; while (i < args.length) { const w = args[i]; if (w.startsWith("--") && w.includes("=")) i++; else if (w.startsWith("-")) i += valued.has(w) ? 2 : 1; else { if (!w.startsWith("+")) return true; i++; } } return false; }, }, hostname: { safeFlags: { "-f": "none", "--fqdn": "none", "--long": "none", "-s": "none", "--short": "none", "-i": "none", "--ip-address": "none", "-I": "none", "--all-ip-addresses": "none", "-a": "none", "--alias": "none", "-d": "none", "--domain": "none", "-A": "none", "--all-fqdns": "none", "-v": "none", "--verbose": "none", "-h": "none", "--help": "none", "-V": "none", "--version": "none" }, wordsShape: (args) => args.every((w) => /^-[a-zA-Z]$/.test(w) || /^--[a-zA-Z-]+$/.test(w)), }, lsof: { safeFlags: { "-?": "none", "-h": "none", "-v": "none", "-a": "none", "-b": "none", "-C": "none", "-l": "none", "-n": "none", "-N": "none", "-O": "none", "-P": "none", "-Q": "none", "-R": "none", "-t": "none", "-U": "none", "-V": "none", "-X": "none", "-H": "none", "-E": "none", "-F": "none", "-g": "none", "-i": "none", "-K": "none", "-L": "none", "-o": "none", "-r": "none", "-s": "none", "-S": "none", "-T": "none", "-x": "none", "-A": "string", "-c": "string", "-d": "string", "-e": "string", "-k": "string", "-p": "string", "-u": "string" }, dangerous: (args) => { for (let i = 0; i < args.length; i++) { const w = args[i]; if (w === "+m" || w.startsWith("+m")) return true; if (/^-[a-zA-Z]*i\S*@/.test(w) && /[a-zA-Z]/.test(before(w.slice(w.indexOf("@") + 1), ":"))) return true; if (/^-[a-zA-Z]*i$/.test(w)) { const next = args[i + 1] ?? ""; if (next.includes("@") && /[a-zA-Z]/.test(before(next.slice(next.indexOf("@") + 1), ":"))) return true; } } return false; }, }, pgrep: { safeFlags: { "-d": "string", "--delimiter": "string", "-l": "none", "--list-name": "none", "-a": "none", "--list-full": "none", "-v": "none", "--inverse": "none", "-w": "none", "--lightweight": "none", "-c": "none", "--count": "none", "-f": "none", "--full": "none", "-g": "string", "--pgroup": "string", "-G": "string", "--group": "string", "-i": "none", "--ignore-case": "none", "-n": "none", "--newest": "none", "-o": "none", "--oldest": "none", "-O": "string", "--older": "string", "-P": "string", "--parent": "string", "-s": "string", "--session": "string", "-t": "string", "--terminal": "string", "-u": "string", "--euid": "string", "-U": "string", "--uid": "string", "-x": "none", "--exact": "none", "-F": "string", "--pidfile": "string", "-L": "none", "--logpidfile": "none", "-r": "string", "--runstates": "string", "--ns": "string", "--nslist": "string", "--help": "none", "-V": "none", "--version": "none" } }, tput: { safeFlags: { "-T": "string", "-V": "none", "-x": "none" }, dangerous: (args) => { const terminalWriting = new Set(["init", "reset", "rs1", "rs2", "rs3", "is1", "is2", "is3", "iprog", "if", "rf", "clear", "flash", "mc0", "mc4", "mc5", "mc5i", "mc5p", "pfkey", "pfloc", "pfx", "pfxl", "smcup", "rmcup"]); let i = 0; let pastDashes = false; while (i < args.length) { const w = args[i]; if (w === "--") { pastDashes = true; i++; } else if (!pastDashes && w.startsWith("-")) { if (w === "-S") return true; if (!w.startsWith("--") && w.length > 2 && w.includes("S")) return true; i += w === "-T" ? 2 : 1; } else { if (terminalWriting.has(w)) return true; i++; } } return false; }, }, ss: { safeFlags: { "-h": "none", "--help": "none", "-V": "none", "--version": "none", "-n": "none", "--numeric": "none", "-a": "none", "--all": "none", "-l": "none", "--listening": "none", "-o": "none", "--options": "none", "-e": "none", "--extended": "none", "-m": "none", "--memory": "none", "-p": "none", "--processes": "none", "-i": "none", "--info": "none", "-s": "none", "--summary": "none", "-4": "none", "--ipv4": "none", "-6": "none", "--ipv6": "none", "-0": "none", "--packet": "none", "-t": "none", "--tcp": "none", "-M": "none", "--mptcp": "none", "-S": "none", "--sctp": "none", "-u": "none", "--udp": "none", "-d": "none", "--dccp": "none", "-w": "none", "--raw": "none", "-x": "none", "--unix": "none", "--tipc": "none", "--vsock": "none", "-f": "string", "--family": "string", "-A": "string", "--query": "string", "--socket": "string", "-Z": "none", "--context": "none", "-z": "none", "--contexts": "none", "-b": "none", "--bpf": "none", "-E": "none", "--events": "none", "-H": "none", "--no-header": "none", "-O": "none", "--oneline": "none", "--tipcinfo": "none", "--tos": "none", "--cgroup": "none", "--inet-sockopt": "none" }, dangerous: (args) => { const keyword = /^(dst|src|dport|sport|and|or|not|eq|ne|ge|le|gt|lt|autobound|state|exclude|dev|fwmark|cgroup)$/; const takesOperand = /^(state|exclude|dport|sport|dev|fwmark|cgroup)$/; const valuedFlag = /^(-f|--family|-A|--query|--socket)$/; const filter = []; let pastDashes = false; for (let i = 0; i < args.length; i++) { const w = args[i]; if (!pastDashes && w === "--") { pastDashes = true; continue; } if (!pastDashes && w.startsWith("-")) { if (valuedFlag.test(w)) i++; continue; } filter.push(w); } const tokens = filter.join(" ").split(/[\s()=!<>&|,]+/).filter(Boolean); let skipOperand = false; for (const token of tokens) { if (skipOperand) { skipOperand = false; continue; } if (keyword.test(token)) { skipOperand = takesOperand.test(token); continue; } if (/[g-zG-Z]/.test(token) || (/[a-fA-F]/.test(token) && (token.includes(".") || !token.includes(":")))) return true; } return false; }, }, fd: { safeFlags: FD_FLAGS }, fdfind: { safeFlags: FD_FLAGS }, pyright: { respectsDoubleDash: false, safeFlags: { "--outputjson": "none", "--pythonversion": "string", "--pythonplatform": "string", "--level": "string", "--stats": "none", "--verbose": "none", "--version": "none", "--dependencies": "none", "--warnings": "none" }, dangerous: (args) => args.some((w) => w === "--watch" || w === "-w") }, test: { respectsDoubleDash: false, safeFlags: { "-b": "string", "-c": "string", "-d": "string", "-e": "string", "-f": "string", "-g": "string", "-G": "string", "-h": "string", "-k": "string", "-L": "string", "-N": "string", "-O": "string", "-p": "string", "-r": "string", "-s": "string", "-S": "string", "-t": "string", "-u": "string", "-w": "string", "-x": "string", "-z": "string", "-n": "string", "-eq": "string", "-ne": "string", "-lt": "string", "-le": "string", "-gt": "string", "-ge": "string", "-nt": "string", "-ot": "string", "-ef": "string" }, dangerous: (args) => { if (args.some((w) => w === "-v" || w === "-R" || w === "-a" || w === "-o" || /\[/.test(w))) return true; for (let i = 0; i < args.length; i++) { const w = args[i]; if (TEST_INTEGER_OPERATORS.has(w)) for (const side of [args[i - 1], args[i + 1]]) if (side !== undefined && !TEST_INTEGER.test(side)) return true; if (w === "-t") { const fd = args[i + 1]; if (fd !== undefined && !TEST_INTEGER.test(fd)) return true; } } return false; }, }, }; export const READ_ONLY_BARE_PROGRAMS = Object.freeze(["docker ps", "docker images", "cal", "uptime", "cat", "head", "tail", "wc", "stat", "strings", "hexdump", "od", "nl", "id", "uname", "free", "df", "du", "locale", "groups", "nproc", "basename", "dirname", "realpath", "cut", "paste", "tr", "column", "tac", "rev", "fold", "expand", "unexpand", "fmt", "comm", "cmp", "numfmt", "readlink", "diff", "true", "false", "sleep", "which", "type", "expr", "seq", "tsort", "pr"]); export const READ_ONLY_GLOB_PROGRAMS = Object.freeze(["ls", "cat", "head", "tail", "wc", "stat", "grep", "egrep", "fgrep", "diff", "du", "df", "echo", "strings", "hexdump", "od", "nl", "cut", "column", "tr", "tac", "rev", "cmp", "basename", "dirname", "realpath", "readlink", "sha256sum", "sha1sum", "md5sum", "cd"]); export const READ_ONLY_EXACT_FORMS = [["claude", "-h"], ["claude", "--help"], ["node", "-v"], ["node", "--version"], ["python", "--version"], ["python3", "--version"], ["ip", "addr"]]; export const READ_ONLY_BARE_ONLY = Object.freeze(["pwd", "whoami", "alias"]); export const FIND_ACTION_PRIMARIES = Object.freeze(["-delete", "-exec", "-execdir", "-ok", "-okdir", "-fprint", "-fprint0", "-fls", "-fprintf", "-files0-from"]); export const FIND_VALUE_PRIMARIES = Object.freeze(["-name", "-iname", "-path", "-ipath", "-lname", "-ilname", "-regex", "-iregex", "-wholename", "-iwholename", "-samefile", "-newer", "-anewer", "-cnewer", "-mnewer", "-perm", "-user", "-group", "-uid", "-gid", "-size", "-type", "-xtype", "-fstype", "-inum", "-links", "-used", "-context", "-amin", "-cmin", "-mmin", "-atime", "-ctime", "-mtime", "-mindepth", "-maxdepth", "-printf", "-regextype", "-D", "-f", "-flags", "-Bnewer", "-Btime", "-Bmin", "-files0-from", "-xattrname"]); export const FIND_NEWER_PRIMARY = /^-newer[aBcm][aBcmt]$/; export const READ_ONLY_ENV_NAMES = Object.freeze(["GOEXPERIMENT", "GOOS", "GOARCH", "CGO_ENABLED", "GO111MODULE", "RUST_BACKTRACE", "RUST_LOG", "NODE_ENV", "PYTHONUNBUFFERED", "PYTHONDONTWRITEBYTECODE", "PYTEST_DISABLE_PLUGIN_AUTOLOAD", "PYTEST_DEBUG", "ANTHROPIC_API_KEY", "LANG", "LANGUAGE", "LC_ALL", "LC_CTYPE", "LC_TIME", "CHARSET", "TERM", "COLORTERM", "NO_COLOR", "FORCE_COLOR", "TZ", "LS_COLORS", "LSCOLORS", "GREP_COLOR", "GREP_COLORS", "GCC_COLORS", "TIME_STYLE", "BLOCK_SIZE", "BLOCKSIZE", "COLUMNS", "LINES", "CLICOLOR", "CLICOLOR_FORCE", "CI", "DEBIAN_FRONTEND", "GIT_TERMINAL_PROMPT"]); export const XARGS_READ_ONLY_TARGETS = Object.freeze(["echo", "printf", "wc", "grep", "egrep", "fgrep", "head", "tail"]);