Phase 1 Track 1.2: Terminal allowlist + browser JS API restrictions#27
Phase 1 Track 1.2: Terminal allowlist + browser JS API restrictions#27richard-devbot wants to merge 15 commits intoCursorTouch:mainfrom
Conversation
Review Summary by QodoPhase 1 Security Hardening: Terminal Allowlist, Browser JS Restrictions, CI/CD Scanning
WalkthroughsDescription• Replace terminal blocklist with allowlist + block shell escapes (CWE-78) • Replace os.system() with subprocess.run() for safe process spawning (CWE-78) • Add browser JavaScript API restrictions + risk assessment (CWE-94) • Establish security guardrails framework with policies and content filters • Add comprehensive CI/CD security scanning (bandit, gitleaks, pip-audit, coverage) • Create security test infrastructure and AI principles documentation Diagramflowchart LR
A["Terminal Blocklist"] -->|Replace| B["Terminal Allowlist + Shell Escape Blocking"]
C["os.system Calls"] -->|Replace| D["subprocess.run Safe Spawning"]
E["Browser Script Action"] -->|Add| F["JS API Restrictions + Risk Assessment"]
B --> G["Security Guardrails Module"]
D --> G
F --> G
G --> H["Policy Engine + Content Filters"]
H --> I["CI/CD Security Scanning"]
I --> J["bandit + gitleaks + pip-audit + coverage"]
K["AI Principles Framework"] --> L["SECURITY_ROADMAP.md + AI_PRINCIPLES.md"]
L --> M["Security Test Suite"]
File Changes1. operator_use/agent/tools/builtin/terminal.py
|
Code Review by Qodo
1. browser script runs unconfirmed
|
| case "script": | ||
| if not script: | ||
| return ToolResult.error_result("script is required for script.") | ||
| _safety_err = _check_script_safety(script or "") | ||
| if _safety_err: | ||
| return ToolResult.error_result(_safety_err) | ||
| risk = _engine.assess("browser", {"action": "script"}) | ||
| logger.warning("Browser script execution -- risk=%s", risk.value) | ||
| result = await page.execute_script(script, truncate=True, repair=True) | ||
| return ToolResult.success_result(f"Script result: {result}") |
There was a problem hiding this comment.
1. browser script runs unconfirmed 📎 Requirement gap ⛨ Security
The browser tool executes the script action without any explicit human approval step, allowing LLM-supplied JavaScript to run immediately. This violates the requirement for human-in-the-loop confirmation before any browser JavaScript execution.
Agent Prompt
## Issue description
`browser` action `script` executes JavaScript immediately without an explicit human confirmation step.
## Issue Context
Compliance requires human-in-the-loop approval for any browser JavaScript execution to prevent CWE-94 style abuse.
## Fix Focus Areas
- operator_use/web/tools/browser.py[292-301]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| from operator_use.guardrails import PolicyEngine, DefaultPolicy | ||
| from pydantic import BaseModel, Field, model_validator | ||
| from markdownify import markdownify | ||
| from typing import Literal, Optional | ||
| from asyncio import sleep | ||
| from pathlib import Path | ||
| from os import getcwd | ||
| import logging | ||
| import httpx | ||
| import json | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| _engine = PolicyEngine([DefaultPolicy()]) | ||
|
|
||
| _BLOCKED_JS_PATTERNS = [ | ||
| "document.cookie", | ||
| "localStorage", | ||
| "sessionStorage", | ||
| "indexedDB", | ||
| "XMLHttpRequest", | ||
| "navigator.credentials", | ||
| "crypto.subtle", | ||
| ".getPassword", | ||
| "chrome.identity", | ||
| ] | ||
|
|
||
|
|
||
| def _check_script_safety(script_content: str) -> str | None: | ||
| """Returns an error message if script accesses sensitive APIs, None if safe.""" | ||
| script_lower = script_content.lower() | ||
| for pattern in _BLOCKED_JS_PATTERNS: | ||
| if pattern.lower() in script_lower: | ||
| return ( | ||
| f"Script blocked: accesses sensitive browser API {pattern!r}. " | ||
| "This is a security guardrail. If you need this capability, " | ||
| "request human confirmation first." | ||
| ) | ||
| return None | ||
|
|
||
|
|
||
| class BrowserTool(BaseModel): | ||
| action: Literal[ |
There was a problem hiding this comment.
4. No actionvalidator for browser.script 📎 Requirement gap ⚙ Maintainability
Browser script execution is not mediated by an ActionValidator guardrail and is instead implemented ad-hoc inside the browser tool. This prevents centralized, auditable enforcement and increases the risk of bypass/inconsistency.
Agent Prompt
## Issue description
Browser `script` enforcement is not centralized via an ActionValidator guardrail, and is implemented directly in the browser tool.
## Issue Context
Compliance requires consistent, auditable enforcement through a guardrails module component named ActionValidator.
## Fix Focus Areas
- operator_use/web/tools/browser.py[4-5]
- operator_use/web/tools/browser.py[17-18]
- operator_use/web/tools/browser.py[292-301]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| # Commands allowed by default. Deployments can extend via config: | ||
| # "terminal": {"extra_allowed_commands": ["make", "cargo"]} | ||
| _DEFAULT_ALLOWED = { | ||
| "git", "ls", "cat", "head", "tail", "grep", "find", "echo", "pwd", | ||
| "mkdir", "cp", "mv", "touch", "wc", "sort", "uniq", "cut", "tr", | ||
| "pip", "pip3", "uv", "npm", "node", "npx", "yarn", "bun", | ||
| "python", "python3", "pytest", "ruff", "mypy", | ||
| "cargo", "go", "rustc", | ||
| "curl", "wget", | ||
| "docker", "kubectl", | ||
| "env", "printenv", "which", "type", "man", | ||
| "tar", "gzip", "gunzip", "zip", "unzip", | ||
| "jq", "yq", "sed", "awk", | ||
| "ssh", "scp", "rsync", | ||
| } | ||
|
|
||
| # Patterns that indicate shell escape — always blocked regardless of base command | ||
| _SHELL_ESCAPE_PATTERNS = [ | ||
| "| bash", "| sh", "| zsh", "| fish", | ||
| "|bash", "|sh", "|zsh", | ||
| "$(", "`", | ||
| "&& bash", "&& sh", | ||
| ] | ||
|
|
||
| _SUBCOMMAND_BLOCKLIST = {"eval", "exec", "source"} | ||
|
|
||
|
|
||
| class Terminal(BaseModel): | ||
| cmd: str=Field(description="The shell command to run. On Windows uses cmd.exe, on Linux/macOS uses bash. Chain commands with && for sequential execution. Avoid interactive commands that wait for input.") | ||
| timeout: int = Field(ge=1,le=60,description="Timeout in seconds before the command is killed (1-60, default 10). Increase for slow operations like installs or builds.", default=10) | ||
|
|
||
| def _is_command_blocked(cmd: str) -> str | None: | ||
| """Return blocked pattern if cmd matches, else None.""" | ||
| normalized = " ".join(cmd.strip().split()) | ||
| for blocked in BLOCKED_COMMANDS: | ||
| if blocked in normalized: | ||
| return blocked | ||
| return None | ||
|
|
||
| def _get_base_command(cmd: str) -> str: | ||
| """Extract the base command name from a shell command string.""" | ||
| stripped = cmd.strip() | ||
| if not stripped: | ||
| return "" | ||
| # Handle /usr/bin/git -> git, ./script.sh -> script.sh | ||
| first_token = stripped.split()[0] | ||
| return Path(first_token).name | ||
|
|
||
|
|
||
| def _is_command_allowed(cmd: str, extra_allowed: set[str] | None = None) -> tuple[bool, str]: | ||
| """ | ||
| Returns (allowed: bool, reason: str). | ||
| Checks shell escape patterns first, then base command allowlist. | ||
| """ | ||
| # Check shell escape patterns | ||
| cmd_lower = cmd.lower() | ||
| for pattern in _SHELL_ESCAPE_PATTERNS: | ||
| if pattern in cmd_lower: | ||
| return False, f"Shell escape blocked: {pattern!r} in command" | ||
|
|
||
| # Check for dangerous subcommands | ||
| tokens = cmd_lower.split() | ||
| for token in tokens[1:]: # skip base command | ||
| clean = token.strip(";&|") | ||
| if clean in _SUBCOMMAND_BLOCKLIST: | ||
| return False, f"Subcommand blocked: {clean!r}" | ||
|
|
||
| # Check base command against allowlist | ||
| base = _get_base_command(cmd) | ||
| if not base: | ||
| return False, "Empty command" | ||
|
|
||
| allowed = _DEFAULT_ALLOWED | (extra_allowed or set()) | ||
| if base not in allowed: | ||
| return False, f"Command not in allowlist: {base!r}. Add to terminal.extra_allowed_commands in config if needed." | ||
|
|
||
| return True, "" | ||
|
|
||
|
|
||
| @Tool(name="terminal",description="Run a shell command and return stdout, stderr, and exit code. Use for git, package installs, running scripts, checking processes, or any CLI task. Commands run from the codebase root. Destructive commands (rm -rf /, format, shutdown, etc.) are blocked. For long outputs, results are truncated — pipe through head/tail if needed.", model=Terminal) | ||
| async def terminal(cmd: str, timeout: int = 10, **kwargs) -> str: | ||
| blocked = _is_command_blocked(cmd) | ||
| if blocked: | ||
| return ToolResult.error_result(f"Command blocked: contains forbidden pattern '{blocked}'") | ||
| allowed, reason = _is_command_allowed(cmd) | ||
| if not allowed: | ||
| return ToolResult.error_result(reason) |
There was a problem hiding this comment.
5. Terminal allowlist not config-driven 📎 Requirement gap ⛨ Security
Terminal allowlist customization is documented but not actually wired to config.json, since terminal() never reads config or passes extra_allowed_commands into _is_command_allowed. This fails the requirement for deployment-specific configurability.
Agent Prompt
## Issue description
Terminal allowlist is not actually configurable via `config.json`; `extra_allowed_commands` is never read/applied.
## Issue Context
Deployments need to extend the allowlist safely without code changes.
## Fix Focus Areas
- operator_use/agent/tools/builtin/terminal.py[9-11]
- operator_use/agent/tools/builtin/terminal.py[51-77]
- operator_use/agent/tools/builtin/terminal.py[81-85]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| # Patterns that indicate shell escape — always blocked regardless of base command | ||
| _SHELL_ESCAPE_PATTERNS = [ | ||
| "| bash", "| sh", "| zsh", "| fish", | ||
| "|bash", "|sh", "|zsh", | ||
| "$(", "`", | ||
| "&& bash", "&& sh", | ||
| ] | ||
|
|
||
| _SUBCOMMAND_BLOCKLIST = {"eval", "exec", "source"} | ||
|
|
||
|
|
||
| class Terminal(BaseModel): | ||
| cmd: str=Field(description="The shell command to run. On Windows uses cmd.exe, on Linux/macOS uses bash. Chain commands with && for sequential execution. Avoid interactive commands that wait for input.") | ||
| timeout: int = Field(ge=1,le=60,description="Timeout in seconds before the command is killed (1-60, default 10). Increase for slow operations like installs or builds.", default=10) | ||
|
|
||
| def _is_command_blocked(cmd: str) -> str | None: | ||
| """Return blocked pattern if cmd matches, else None.""" | ||
| normalized = " ".join(cmd.strip().split()) | ||
| for blocked in BLOCKED_COMMANDS: | ||
| if blocked in normalized: | ||
| return blocked | ||
| return None | ||
|
|
||
| def _get_base_command(cmd: str) -> str: | ||
| """Extract the base command name from a shell command string.""" | ||
| stripped = cmd.strip() | ||
| if not stripped: | ||
| return "" | ||
| # Handle /usr/bin/git -> git, ./script.sh -> script.sh | ||
| first_token = stripped.split()[0] | ||
| return Path(first_token).name | ||
|
|
||
|
|
||
| def _is_command_allowed(cmd: str, extra_allowed: set[str] | None = None) -> tuple[bool, str]: | ||
| """ | ||
| Returns (allowed: bool, reason: str). | ||
| Checks shell escape patterns first, then base command allowlist. | ||
| """ | ||
| # Check shell escape patterns | ||
| cmd_lower = cmd.lower() | ||
| for pattern in _SHELL_ESCAPE_PATTERNS: | ||
| if pattern in cmd_lower: | ||
| return False, f"Shell escape blocked: {pattern!r} in command" | ||
|
|
||
| # Check for dangerous subcommands | ||
| tokens = cmd_lower.split() | ||
| for token in tokens[1:]: # skip base command | ||
| clean = token.strip(";&|") | ||
| if clean in _SUBCOMMAND_BLOCKLIST: | ||
| return False, f"Subcommand blocked: {clean!r}" |
There was a problem hiding this comment.
6. Shell-escape checks trivially bypassed 📎 Requirement gap ⛨ Security
Terminal shell-escape blocking relies on simple substring checks and misses common formatting/evasion patterns (e.g., ; bash, | bash, |\nsh, or absolute-path shells like `| /bin/sh`). This violates the requirement to consistently block pipe-to-shell and command-injection patterns.
Agent Prompt
## Issue description
Shell-escape blocking can be bypassed with spacing/newlines/alternate shell invocation forms because checks are simple substring matches.
## Issue Context
The terminal tool runs `/bin/bash -c` (on non-Windows), so robust blocking is needed to prevent command injection and shell escapes.
## Fix Focus Areas
- operator_use/agent/tools/builtin/terminal.py[25-31]
- operator_use/agent/tools/builtin/terminal.py[56-67]
- operator_use/agent/tools/builtin/terminal.py[89-93]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| class TestTerminalSecurity: | ||
| def test_blocklist_blocks_rm_rf(self): | ||
| # These should be blocked even if rm were in the allowlist — shell escape | ||
| allowed, reason = _is_command_allowed("git status | bash -c 'rm -rf /'") | ||
| assert not allowed | ||
| assert "escape" in reason.lower() or "blocked" in reason.lower() | ||
|
|
||
| def test_blocklist_blocks_shell_escape(self): | ||
| for cmd in ["echo $(whoami)", "ls `id`", "git log | sh"]: | ||
| allowed, reason = _is_command_allowed(cmd) | ||
| assert not allowed, f"Should have blocked: {cmd!r}" | ||
|
|
||
| def test_allowlist_permits_safe_commands(self): | ||
| for cmd in ["git status", "ls -la", "pytest tests/", "python --version"]: | ||
| allowed, reason = _is_command_allowed(cmd) | ||
| assert allowed, f"Should have allowed: {cmd!r}, reason: {reason}" |
There was a problem hiding this comment.
7. Terminal evasion tests too narrow 📎 Requirement gap ⛯ Reliability
The added terminal security tests do not cover required evasion techniques (spacing tricks, nested shells, curl-pipe-to-bash variants) or broader allow/deny cases. This fails the requirement for regression-resistant tests of allowlist behavior and bypass attempts.
Agent Prompt
## Issue description
Terminal security tests do not cover the required evasion/bypass techniques and therefore won't prevent regressions.
## Issue Context
Compliance expects automated coverage for spacing tricks, nested shells, and curl-pipe-to-bash patterns, in addition to basic allow/deny cases.
## Fix Focus Areas
- tests/security/test_terminal_security.py[6-21]
- operator_use/agent/tools/builtin/terminal.py[25-77]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| """Clear the terminal in a way that works reliably across Windows shells.""" | ||
| if os.name == "nt": | ||
| os.system("cls") | ||
| subprocess.run(["cls"], check=False) |
There was a problem hiding this comment.
8. Windows tui clear crashes 🐞 Bug ✓ Correctness
cli.tui.clear_screen runs subprocess.run(["cls"]) on Windows, which can raise FileNotFoundError and crash the onboarding TUI when clearing the screen.
Agent Prompt
## Issue description
`clear_screen()` calls `subprocess.run(["cls"])` on Windows, which may raise `FileNotFoundError` because `cls` is not an executable.
## Issue Context
This affects the onboarding / configuration TUI on Windows.
## Fix Focus Areas
- operator_use/cli/tui.py[93-99]
## Suggested fix
- Replace with `subprocess.run(["cmd", "/c", "cls"], check=False)`.
- Optionally catch exceptions and fall back to printing ANSI clear sequences or no-op.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| # Patterns that indicate shell escape — always blocked regardless of base command | ||
| _SHELL_ESCAPE_PATTERNS = [ | ||
| "| bash", "| sh", "| zsh", "| fish", | ||
| "|bash", "|sh", "|zsh", | ||
| "$(", "`", | ||
| "&& bash", "&& sh", | ||
| ] | ||
|
|
||
| _SUBCOMMAND_BLOCKLIST = {"eval", "exec", "source"} | ||
|
|
||
|
|
||
| class Terminal(BaseModel): | ||
| cmd: str=Field(description="The shell command to run. On Windows uses cmd.exe, on Linux/macOS uses bash. Chain commands with && for sequential execution. Avoid interactive commands that wait for input.") | ||
| timeout: int = Field(ge=1,le=60,description="Timeout in seconds before the command is killed (1-60, default 10). Increase for slow operations like installs or builds.", default=10) | ||
|
|
||
| def _is_command_blocked(cmd: str) -> str | None: | ||
| """Return blocked pattern if cmd matches, else None.""" | ||
| normalized = " ".join(cmd.strip().split()) | ||
| for blocked in BLOCKED_COMMANDS: | ||
| if blocked in normalized: | ||
| return blocked | ||
| return None | ||
|
|
||
| def _get_base_command(cmd: str) -> str: | ||
| """Extract the base command name from a shell command string.""" | ||
| stripped = cmd.strip() | ||
| if not stripped: | ||
| return "" | ||
| # Handle /usr/bin/git -> git, ./script.sh -> script.sh | ||
| first_token = stripped.split()[0] | ||
| return Path(first_token).name | ||
|
|
||
|
|
||
| def _is_command_allowed(cmd: str, extra_allowed: set[str] | None = None) -> tuple[bool, str]: | ||
| """ | ||
| Returns (allowed: bool, reason: str). | ||
| Checks shell escape patterns first, then base command allowlist. | ||
| """ | ||
| # Check shell escape patterns | ||
| cmd_lower = cmd.lower() | ||
| for pattern in _SHELL_ESCAPE_PATTERNS: | ||
| if pattern in cmd_lower: | ||
| return False, f"Shell escape blocked: {pattern!r} in command" | ||
|
|
||
| # Check for dangerous subcommands | ||
| tokens = cmd_lower.split() | ||
| for token in tokens[1:]: # skip base command | ||
| clean = token.strip(";&|") | ||
| if clean in _SUBCOMMAND_BLOCKLIST: | ||
| return False, f"Subcommand blocked: {clean!r}" | ||
|
|
||
| # Check base command against allowlist | ||
| base = _get_base_command(cmd) | ||
| if not base: | ||
| return False, "Empty command" | ||
|
|
||
| allowed = _DEFAULT_ALLOWED | (extra_allowed or set()) | ||
| if base not in allowed: | ||
| return False, f"Command not in allowlist: {base!r}. Add to terminal.extra_allowed_commands in config if needed." | ||
|
|
||
| return True, "" |
There was a problem hiding this comment.
9. Allowlist bypass via chaining 🐞 Bug ⛨ Security
terminal._is_command_allowed only allowlists the first token and doesn’t block command separators like ; or general &&, so a command starting with an allowed base (e.g., git) can chain arbitrary non-allowlisted commands that will execute under bash -c/cmd /c.
Agent Prompt
## Issue description
The new terminal allowlist can be bypassed because it only validates the first command token and still executes through a shell. Shell separators (`;`, newline, `&&`, `||`, pipes/redirections, etc.) allow arbitrary follow-on commands.
## Issue Context
- `_is_command_allowed()` checks only a limited substring list and then allowlists the base command.
- `terminal()` executes via `/bin/bash -c` or `cmd /c`, so separators are honored.
## Fix Focus Areas
- operator_use/agent/tools/builtin/terminal.py[25-78]
- operator_use/agent/tools/builtin/terminal.py[81-103]
## Suggested fix
Choose one:
1) **Best**: Stop using a shell for execution. Parse with `shlex.split()` and run `create_subprocess_exec(base, *args, shell=False)`.
2) If shell execution is required:
- Explicitly reject any metacharacters/separators: `;`, `\n`, `&&`, `||`, `|`, `>`, `<`, `>>`, `2>`, `&`, etc.
- Optionally implement a strict parser that splits on separators and validates *each* command segment’s base command against the allowlist.
3) Add regression tests for examples like `git status; rm -rf /` and `git status && rm -rf /` to ensure they are blocked.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Response to Qodo Review — PR #27✅ Fixed
Missing browser script security tests
🗣️ Design question for Jeo (not blocking)Human-in-the-loop confirmation for script execution (Qodo finding #1) |
Comprehensive design document covering 5 phases (76 issues): - Phase 0: CI/CD, test infrastructure, AI principles framework - Phase 1: Critical security fixes (path traversal, JS injection, terminal, auth) - Phase 2: AI guardrails & responsible AI (prompt injection, content filtering, ethics) - Phase 3: Performance benchmarks & optimization - Phase 4: Comprehensive QA (unit, e2e, adversarial, fuzzing, CI hardening) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Addresses CWE-78 (OS Command Injection). Both occurrences in control_center.py and tui.py now use subprocess.run() with shell=True, check=False instead of os.system(). Closes CursorTouch#19 Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Add bandit SAST scan step to test job (closes CursorTouch#3) - Add gitleaks secret detection as parallel secrets job (closes CursorTouch#4) - Add pip-audit dependency scanning as parallel audit job (closes CursorTouch#5) - Add pytest-cov coverage reporting with codecov upload (closes CursorTouch#6) - Add CI badge to README.md (closes CursorTouch#2) - Add bandit, pip-audit, pytest-cov to dev dependencies Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Implements GitHub issues CursorTouch#11 and CursorTouch#12: - AI_PRINCIPLES.md: documents 6 core safety principles (least privilege, human oversight, transparency, containment, privacy by default, fail safe) with a development checklist for pre-merge security review. - operator_use/guardrails/: new module providing ActionPolicy, ContentFilter, PolicyEngine, and RiskLevel abstractions. Includes DefaultPolicy for built-in tool risk classification and CredentialFilter for masking API keys in logs and LLM context. Closes CursorTouch#11, closes CursorTouch#12 Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Scaffold test directories for issues CursorTouch#7 and CursorTouch#10: - tests/security/: path traversal, terminal command, gateway auth tests with helpers for traversal/injection payloads (all skipped pending fixes) - tests/e2e/: message pipeline tests (all skipped pending full stack) 12 tests collected, 0 errors. All skipped with tracking references. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…I cache - policies.py: Fix browser tool misclassification — tool is 'browser' with action arg, not 'browser_script'/'browser_navigate'. script/download => DANGEROUS, navigation/interaction => REVIEW (CWE-78 + CWE-22) - helpers.py + SECURITY_ROADMAP.md: Replace startswith() with is_relative_to() for path containment checks — startswith has prefix-collision vulnerability where /workspace_evil passes startswith(/workspace) - ci.yml: Add enable-cache: true to both test and audit setup-uv steps Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
bandit: - Remove shell=True from control_center.py and tui.py — pass ["cls"]/["clear"] as list args, no shell needed (resolves B602 HIGH) - Add [tool.bandit] config to pyproject.toml: skip B104 (0.0.0.0 is intentional LAN server binding), exclude generated vendored dirs - Add nosec B324 to restart.py MD5 (filename only, not security) - Add nosec B310 to fal/openai/together image providers (HTTPS API URLs only) - Pass -c pyproject.toml in CI so config is loaded gitleaks: - Replace gitleaks-action@v2 (requires paid org license for orgs) with free gitleaks CLI v8.24.3 downloaded at runtime pip-audit: - Upgrade cryptography → 46.0.6, pyasn1 → 0.6.3, requests → 2.33.1, tornado → 6.5.5 (all have CVE fixes available) - Add --ignore-vuln CVE-2026-4539 for pygments (ReDoS, no fix released yet) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
The control_center function was hardcoding graceful_fn=None when calling _do_restart(), ignoring the _graceful_restart_fn kwarg injected by start.py. This caused test_restart_calls_graceful_fn_not_os_exit to fail and meant graceful shutdown was never used even when wired. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…unknown commands [CursorTouch#17] Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ursorTouch#18] Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ouch#18] 9 tests: cookie, localStorage, sessionStorage, XHR, fetch, credentials, indexedDB, safe DOM scripts, case-insensitive matching. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
38b0200 to
42843bf
Compare
Ready to merge — rebased + all Qodo findings addressed ✅Hey @Jeomon, PR #27 is fully rebased onto latest main ( Fixes in this PR:
Rebase: This PR is independent of #26 and #28 — can merge in any order once #25 lands. Ready whenever you are! |
BrowserPlugin and ComputerPlugin no longer register hooks to the main agent — subagents manage their own state injection. Test assertions updated accordingly: - Remove stale XML-tag assertions from SYSTEM_PROMPT tests - Fix browser tool name: 'browser' -> 'browser_task' - Update hook tests: register_hooks() is now a no-op for main agent, so assertions verify hooks are NOT wired (not that they are)
Summary
Note: This branch is based on richardson/security-hardening and will rebase onto main after #25 merges.
Test plan
uv run pytest tests/security/test_terminal_security.py -v-- 3/3 passuv run pytest tests/ -q --tb=short-- 495 passed, 10 skipped, 0 failuresuv run ruff check operator_use/-- all checks passedGenerated with Claude Code