chore(salvage): strip duplicated/merge-corrupted blocks from PR #17664
Removes drive-by duplication that accumulated during the contributor branch's multiple rebases. All runtime-benign (dict last-wins, redefinition last-wins) but left dead source that would confuse reviewers and maintainers. Surgical in-place de-duplication (kept PR's intentional additions, removed only the doubled copy): * hermes_cli/auth.py: duplicate "gmi" + "azure-foundry" ProviderConfig * hermes_cli/models.py: duplicate "gmi" entry in _PROVIDER_MODELS * hermes_cli/config.py: duplicate NOTION/LINEAR/AIRTABLE/TENOR skill env block + duplicate get_custom_provider_context_length definition * hermes_cli/gateway.py: duplicate _setup_yuanbao * gateway/platforms/base.py: duplicate is_host_excluded_by_no_proxy * gateway/platforms/telegram.py: duplicate delete_message * gateway/stream_consumer.py: duplicate _should_send_fresh_final and _try_fresh_final * gateway/run.py: duplicate _parse_reasoning_command_args / _resolve_session_reasoning_config / _set_session_reasoning_override, duplicate "Drain silently when interrupted" interrupt check * run_agent.py: duplicate HERMES_AGENT_HELP_GUIDANCE append, duplicate codex_message_items capture, duplicate custom_providers resolution * tools/approval.py: duplicate HARDLINE_PATTERNS section and duplicate hardline call in check_dangerous_command * tools/mcp_tool.py: duplicate _orphan_stdio_pids module-level decl * cron/scheduler.py: duplicate "not configured/enabled" check — kept the new early-rejection, removed the stale late-path copy Full-file resets to origin/main (all PR additions were duplicates of content already on main): * ui-tui/packages/hermes-ink/index.d.ts * ui-tui/packages/hermes-ink/src/entry-exports.ts * ui-tui/packages/hermes-ink/src/ink/selection.ts * ui-tui/src/app/interfaces.ts * ui-tui/src/app/slash/commands/core.ts * ui-tui/src/components/thinking.tsx * ui-tui/src/lib/memoryMonitor.ts * ui-tui/src/types.ts * ui-tui/src/types/hermes-ink.d.ts * tests/hermes_cli/test_doctor.py * tests/hermes_cli/test_api_key_providers.py * tests/hermes_cli/test_model_validation.py * tests/plugins/memory/test_hindsight_provider.py * tests/run_agent/test_run_agent.py * tests/gateway/test_email.py * tests/tools/test_dockerfile_pid1_reaping.py * hermes_cli/commands.py (slack_native_slashes block — full duplicate)
This commit is contained in:
@@ -207,101 +207,6 @@ def _hardline_block_result(description: str) -> dict:
|
||||
}
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# Hardline (unconditional) blocklist
|
||||
# =========================================================================
|
||||
#
|
||||
# Commands so catastrophic they should NEVER run via the agent, regardless
|
||||
# of --yolo, /yolo, approvals.mode=off, or cron approve mode. This is a
|
||||
# floor below yolo: opting into yolo is the user trusting the agent with
|
||||
# their files and services, not trusting it to wipe the disk or power the
|
||||
# box off.
|
||||
#
|
||||
# Hardline only applies to environments that can actually damage the host
|
||||
# (local, ssh, container-host cron). Containerized backends (docker,
|
||||
# singularity, modal, daytona) already bypass the dangerous-command layer
|
||||
# because nothing they do can touch the host, so we leave that behavior
|
||||
# alone.
|
||||
#
|
||||
# The list is deliberately tiny — only things with no recovery path:
|
||||
# filesystem destruction rooted at /, raw block device overwrites, kernel
|
||||
# shutdown/reboot, and denial-of-service commands that take the host down.
|
||||
# Recoverable-but-costly operations (git reset --hard, rm -rf /tmp/x,
|
||||
# chmod -R 777, curl|sh) stay in DANGEROUS_PATTERNS where yolo can pass
|
||||
# them through — that's what yolo is for.
|
||||
#
|
||||
# Inspired by Mercury Agent's permission-hardened blocklist
|
||||
# (https://github.com/cosmicstack-labs/mercury-agent).
|
||||
|
||||
# Regex fragment matching the *start* of a command (i.e. positions where
|
||||
# a shell would begin parsing a new command). Used by shutdown/reboot
|
||||
# patterns so they don't fire on "echo reboot" or "grep 'shutdown' log".
|
||||
# Matches: start of string, after command separators (; && || | newline),
|
||||
# after subshell openers ( `$(` or backtick ), optionally consuming
|
||||
# leading wrapper commands (sudo, env VAR=VAL, exec, nohup, setsid).
|
||||
_CMDPOS = (
|
||||
r'(?:^|[;&|\n`]|\$\()' # start position
|
||||
r'\s*' # optional whitespace
|
||||
r'(?:sudo\s+(?:-[^\s]+\s+)*)?' # optional sudo with flags
|
||||
r'(?:env\s+(?:\w+=\S*\s+)*)?' # optional env with VAR=VAL pairs
|
||||
r'(?:(?:exec|nohup|setsid|time)\s+)*' # optional wrapper commands
|
||||
r'\s*'
|
||||
)
|
||||
|
||||
HARDLINE_PATTERNS = [
|
||||
# rm recursive targeting the root filesystem or protected roots
|
||||
(r'\brm\s+(-[^\s]*\s+)*(/|/\*|/ \*)(\s|$)', "recursive delete of root filesystem"),
|
||||
(r'\brm\s+(-[^\s]*\s+)*(/home|/home/\*|/root|/root/\*|/etc|/etc/\*|/usr|/usr/\*|/var|/var/\*|/bin|/bin/\*|/sbin|/sbin/\*|/boot|/boot/\*|/lib|/lib/\*)(\s|$)', "recursive delete of system directory"),
|
||||
(r'\brm\s+(-[^\s]*\s+)*(~|\$HOME)(/?|/\*)?(\s|$)', "recursive delete of home directory"),
|
||||
# Filesystem format
|
||||
(r'\bmkfs(\.[a-z0-9]+)?\b', "format filesystem (mkfs)"),
|
||||
# Raw block device overwrites (dd + redirection)
|
||||
(r'\bdd\b[^\n]*\bof=/dev/(sd|nvme|hd|mmcblk|vd|xvd)[a-z0-9]*', "dd to raw block device"),
|
||||
(r'>\s*/dev/(sd|nvme|hd|mmcblk|vd|xvd)[a-z0-9]*\b', "redirect to raw block device"),
|
||||
# Fork bomb (classic shell form)
|
||||
(r':\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:', "fork bomb"),
|
||||
# Kill every process on the system
|
||||
(r'\bkill\s+(-[^\s]+\s+)*-1\b', "kill all processes"),
|
||||
# System shutdown / reboot — anchor to command position (start of line,
|
||||
# after a command separator, or after sudo/env wrappers) so we don't
|
||||
# false-positive on "echo reboot" or "grep 'shutdown' logs".
|
||||
# _CMDPOS matches start-of-command positions.
|
||||
(_CMDPOS + r'(shutdown|reboot|halt|poweroff)\b', "system shutdown/reboot"),
|
||||
(_CMDPOS + r'init\s+[06]\b', "init 0/6 (shutdown/reboot)"),
|
||||
(_CMDPOS + r'systemctl\s+(poweroff|reboot|halt|kexec)\b', "systemctl poweroff/reboot"),
|
||||
(_CMDPOS + r'telinit\s+[06]\b', "telinit 0/6 (shutdown/reboot)"),
|
||||
]
|
||||
|
||||
|
||||
def detect_hardline_command(command: str) -> tuple:
|
||||
"""Check if a command matches the unconditional hardline blocklist.
|
||||
|
||||
Returns:
|
||||
(is_hardline, description) or (False, None)
|
||||
"""
|
||||
normalized = _normalize_command_for_detection(command).lower()
|
||||
for pattern, description in HARDLINE_PATTERNS:
|
||||
if re.search(pattern, normalized, re.IGNORECASE | re.DOTALL):
|
||||
return (True, description)
|
||||
return (False, None)
|
||||
|
||||
|
||||
def _hardline_block_result(description: str) -> dict:
|
||||
"""Build the standard block result for a hardline match."""
|
||||
return {
|
||||
"approved": False,
|
||||
"hardline": True,
|
||||
"message": (
|
||||
f"BLOCKED (hardline): {description}. "
|
||||
"This command is on the unconditional blocklist and cannot "
|
||||
"be executed via the agent — not even with --yolo, /yolo, "
|
||||
"approvals.mode=off, or cron approve mode. If you genuinely "
|
||||
"need to run it, run it yourself in a terminal outside the "
|
||||
"agent."
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# Dangerous command patterns
|
||||
# =========================================================================
|
||||
@@ -890,16 +795,6 @@ def check_dangerous_command(command: str, env_type: str,
|
||||
logger.warning("Hardline block: %s (command: %s)", hardline_desc, command[:200])
|
||||
return _hardline_block_result(hardline_desc)
|
||||
|
||||
# Hardline floor: commands with no recovery path (rm -rf /, mkfs, dd
|
||||
# to raw device, shutdown/reboot, fork bomb, kill -1) are blocked
|
||||
# unconditionally, BEFORE the yolo bypass. Opting into yolo is
|
||||
# trusting the agent with your files and services, not trusting it
|
||||
# to wipe the disk or power the box off.
|
||||
is_hardline, hardline_desc = detect_hardline_command(command)
|
||||
if is_hardline:
|
||||
logger.warning("Hardline block: %s (command: %s)", hardline_desc, command[:200])
|
||||
return _hardline_block_result(hardline_desc)
|
||||
|
||||
# --yolo: bypass all approval prompts. Gateway /yolo is session-scoped;
|
||||
# CLI --yolo remains process-scoped via the env var for local use.
|
||||
if os.getenv("HERMES_YOLO_MODE") or is_current_session_yolo_enabled():
|
||||
|
||||
@@ -1794,13 +1794,6 @@ _stdio_pids: Dict[int, str] = {} # pid -> server_name
|
||||
# sessions (e.g. concurrent cron jobs or live user chats).
|
||||
_orphan_stdio_pids: set = set()
|
||||
|
||||
# PIDs that survived their session context exit (SDK teardown failed to
|
||||
# terminate them). These are detected in _run_stdio's finally block and
|
||||
# can be cleaned up asynchronously by _kill_orphaned_mcp_children().
|
||||
# Separate from _stdio_pids so cleanup sweeps never race with active
|
||||
# sessions (e.g. concurrent cron jobs or live user chats).
|
||||
_orphan_stdio_pids: set = set()
|
||||
|
||||
|
||||
def _snapshot_child_pids() -> set:
|
||||
"""Return a set of current child process PIDs.
|
||||
|
||||
Reference in New Issue
Block a user