Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
172 lines
5.6 KiB
JavaScript
172 lines
5.6 KiB
JavaScript
"use strict";
|
|
|
|
/**
|
|
* Shell-aware command blocklist helpers shared by the main-process AI exec
|
|
* paths (in-app bridge handlers, MCP TCP bridge handlers, terminal worker).
|
|
*
|
|
* The default table in lib/commandBlocklist.json is grouped into
|
|
* common / posix / powershell patterns. Callers pass the best shell kind they
|
|
* can resolve for the target session:
|
|
* - live session objects: resolveSessionBlocklistShellKind(session) mirrors
|
|
* the inputs the AI PTY wrapper uses (confirmed kind, live idle prompt,
|
|
* remote login-shell hint)
|
|
* - metadata-only paths: meta.shellType (often empty; callers that know a
|
|
* downstream authoritative check re-runs the defaults should fall back to
|
|
* checkBlocklistCommonOnly instead of the strict full table, so
|
|
* POSIX-only patterns never block PowerShell-native commands)
|
|
*
|
|
* User-added patterns (settings list entries that are not part of the default
|
|
* table) always apply, on every shell.
|
|
*/
|
|
|
|
const {
|
|
DEFAULT_COMMAND_BLOCKLIST,
|
|
COMMON_PATTERNS,
|
|
isDefaultBlocklistPattern,
|
|
selectDefaultBlocklistPatterns,
|
|
} = require("../../../lib/commandBlocklist.cjs");
|
|
const { resolveEffectiveShellKind } = require("./ptyExecHelpers.cjs");
|
|
const {
|
|
getFreshIdlePrompt,
|
|
isDefaultPowerShellPromptLine,
|
|
isDefaultCmdPromptLine,
|
|
isDefaultPosixPromptLine,
|
|
stripAnsi,
|
|
} = require("./shellUtils.cjs");
|
|
|
|
function compilePatterns(patterns) {
|
|
return patterns
|
|
.map((pattern) => {
|
|
try {
|
|
return { pattern, regex: new RegExp(pattern, "i") };
|
|
} catch {
|
|
return null;
|
|
}
|
|
})
|
|
.filter(Boolean);
|
|
}
|
|
|
|
const compiledDefaultCache = new Map();
|
|
|
|
function compiledDefaultPatternsFor(shellKind) {
|
|
const key = String(shellKind || "").toLowerCase();
|
|
let compiled = compiledDefaultCache.get(key);
|
|
if (!compiled) {
|
|
compiled = compilePatterns(selectDefaultBlocklistPatterns(key));
|
|
compiledDefaultCache.set(key, compiled);
|
|
}
|
|
return compiled;
|
|
}
|
|
|
|
const compiledCommonPatterns = compilePatterns(COMMON_PATTERNS);
|
|
// User blocklists are stable between settings updates; compile each list once.
|
|
const compiledUserCache = new WeakMap();
|
|
|
|
function compiledUserPatterns(blocklist) {
|
|
let compiled = compiledUserCache.get(blocklist);
|
|
if (!compiled) {
|
|
const userPatterns = blocklist.filter(
|
|
(pattern) => !isDefaultBlocklistPattern(pattern),
|
|
);
|
|
compiled = compilePatterns(userPatterns);
|
|
compiledUserCache.set(blocklist, compiled);
|
|
}
|
|
return compiled;
|
|
}
|
|
|
|
function firstMatch(command, compiled) {
|
|
for (const { pattern, regex } of compiled) {
|
|
if (regex.test(command)) {
|
|
return { blocked: true, matchedPattern: pattern };
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function firstEnabledDefaultMatch(command, compiled, enabledPatterns) {
|
|
for (const { pattern, regex } of compiled) {
|
|
if (enabledPatterns.has(pattern) && regex.test(command)) {
|
|
return { blocked: true, matchedPattern: pattern };
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function normalizeConfiguredBlocklist(blocklist) {
|
|
return Array.isArray(blocklist) ? blocklist : DEFAULT_COMMAND_BLOCKLIST;
|
|
}
|
|
|
|
/**
|
|
* User additions + default patterns selected for shellKind.
|
|
* Unknown / empty shell kinds keep the strict full default table.
|
|
*/
|
|
function checkBlocklistForShell(command, shellKind, configuredBlocklist = DEFAULT_COMMAND_BLOCKLIST) {
|
|
const blocklist = normalizeConfiguredBlocklist(configuredBlocklist);
|
|
const userMatch = firstMatch(command, compiledUserPatterns(blocklist));
|
|
if (userMatch) return userMatch;
|
|
return firstEnabledDefaultMatch(
|
|
command,
|
|
compiledDefaultPatternsFor(shellKind),
|
|
new Set(blocklist),
|
|
) || { blocked: false };
|
|
}
|
|
|
|
/**
|
|
* User additions + shell-independent (common) default patterns only.
|
|
* For metadata-only call sites that know a downstream authoritative check
|
|
* re-runs the full shell-selected defaults on the live session.
|
|
*/
|
|
function checkBlocklistCommonOnly(command, configuredBlocklist = DEFAULT_COMMAND_BLOCKLIST) {
|
|
const blocklist = normalizeConfiguredBlocklist(configuredBlocklist);
|
|
const userMatch = firstMatch(command, compiledUserPatterns(blocklist));
|
|
if (userMatch) return userMatch;
|
|
return firstEnabledDefaultMatch(command, compiledCommonPatterns, new Set(blocklist)) || { blocked: false };
|
|
}
|
|
|
|
/**
|
|
* Best-effort shell kind for a live session, mirroring the inputs the AI PTY
|
|
* wrapper uses (ptyExecHelpers.resolveEffectiveShellKind): confirmed shell
|
|
* kind, live idle prompt, and the remote login-shell probe hint.
|
|
*/
|
|
function resolveSessionBlocklistShellKind(session) {
|
|
if (!session || typeof session !== "object") return "";
|
|
let prompt = null;
|
|
try {
|
|
prompt = getFreshIdlePrompt(session);
|
|
} catch {
|
|
prompt = null;
|
|
}
|
|
try {
|
|
const baseKind = session.shellKind === "unknown" ? "" : (session.shellKind || "");
|
|
const loginShellHint = session._loginShellKind || "";
|
|
const resolved = resolveEffectiveShellKind(baseKind, prompt, { loginShellHint }) || "";
|
|
if (baseKind || loginShellHint) return resolved;
|
|
|
|
const lastPromptLine = stripAnsi(String(prompt || ""))
|
|
.replace(/\r/g, "\n")
|
|
.split("\n")
|
|
.pop()
|
|
.replace(/\s+$/, "");
|
|
if (
|
|
isDefaultPowerShellPromptLine(lastPromptLine)
|
|
|| isDefaultCmdPromptLine(lastPromptLine)
|
|
|| isDefaultPosixPromptLine(lastPromptLine)
|
|
) {
|
|
return resolved;
|
|
}
|
|
|
|
// Wrapper selection defaults an unclassified remote session to POSIX.
|
|
// Safety keeps it unknown so failed probes retain the strict all-groups
|
|
// fallback instead of silently omitting PowerShell rules.
|
|
return "";
|
|
} catch {
|
|
return session.shellKind || session._loginShellKind || "";
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
checkBlocklistForShell,
|
|
checkBlocklistCommonOnly,
|
|
resolveSessionBlocklistShellKind,
|
|
};
|