@sema-agent/core
Version:
Stateless, task-oriented AI agent core
486 lines (485 loc) • 48.7 kB
JavaScript
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"]);