Skip to content

fix: restore critical security guardrails#675

Open
badMade wants to merge 2 commits into
mainfrom
badmade/create-patch-for-critical-security-fix
Open

fix: restore critical security guardrails#675
badMade wants to merge 2 commits into
mainfrom
badmade/create-patch-for-critical-security-fix

Conversation

@badMade
Copy link
Copy Markdown
Owner

@badMade badMade commented Jun 1, 2026

Motivation

  • Prevent privileged CI/PR checkout and secret exfiltration by treating untrusted PR content as data and keeping secrets/actions/Nix execution owned by the trusted base checkout.
  • Restore ACP file-read safety so editors/clients cannot inline arbitrary host files into model context.
  • Prevent model-controlled local-file exfiltration via the gateway by restricting model-emitted local paths to Hermes-managed cache directories.
  • Reinstate browser-eval safety checks to block token/metadata/private-network navigation and validate post-eval final URLs.

Description

  • Hardened the Nix lockfile fix workflow in .github/workflows/nix-lockfile-fix.yml to default contents permission to read, check out a trusted base-repo copy, check out PR code without credentials into a separate path, overlay only package manifests into the trusted tree, run the local action/Nix from the trusted checkout, and only push whitelisted generated Nix files back.
  • ACP: added _resolve_acp_resource_path and scoped ACP resource_link reads to the session cwd, enforced get_read_block_error checks and regular-file validation, redacted inlined text via redact_sensitive_text, and threaded cwd through _content_blocks_to_openai_user_content and prompt handling (changes in acp_adapter/server.py).
  • Gateway: introduced _trusted_gateway_media_roots, _is_trusted_gateway_media_path, and _filter_trusted_gateway_media_paths to only allow model-emitted MEDIA: tags and auto-detected bare local paths that resolve inside Hermes cache dirs to be sent natively, and applied filtering to both media_files and local_files before dispatch (changes in gateway/platforms/base.py).
  • Browser: restored and applied eval-time safety checks by invoking _browser_eval_security_error before eval, running _browser_eval_final_url_error after supervisor/subprocess eval side-effects, and enforcing the same checks on the Camofox eval path (changes in tools/browser_tool.py).
  • Tests: added regression tests that cover ACP-outside-cwd blocking and redaction, gateway media-path filtering, and browser-eval/Camofox-eval blocking (updated/added tests under tests/acp_adapter/, tests/gateway/, and tests/tools/).

Testing

  • python -m py_compile acp_adapter/server.py gateway/platforms/base.py tools/browser_tool.py — succeeded.
  • python -m compileall -q tests/acp_adapter/test_acp_images.py tests/gateway/test_platform_base.py tests/tools/test_browser_eval_supervisor_path.py — succeeded (syntax/compile checks).
  • YAML sanity check ruby -e 'require "yaml"; YAML.load_file(".github/workflows/nix-lockfile-fix.yml")' — succeeded.
  • Full test runs attempted with scripts/run_tests.sh and pytest but the environment could not fetch test dependencies (PyPI/network) and pytest runs failed due to missing runtime test deps (acp, yaml, requests, etc.), so end-to-end pytest in this environment did not complete; the added unit tests compile and were executed where dependency availability permitted.

Codex Task

Copilot AI review requested due to automatic review settings June 1, 2026 01:06
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces several security and validation enhancements, including resolving and validating ACP file resource paths under the active session directory, redacting sensitive text in resource links, restricting model-emitted media attachments to trusted Hermes-managed caches, and blocking unauthorized navigation or network requests during browser JavaScript evaluation. The review feedback suggests wrapping the cwd parameter in Path to prevent potential AttributeError crashes, expanding exception handling in _is_trusted_gateway_media_path to catch TypeError and ValueError, and caching the resolved media roots to avoid redundant I/O overhead.

Comment thread acp_adapter/server.py
Comment on lines +213 to +219
def _resolve_acp_resource_path(path: Path, cwd: Path | None) -> tuple[Path | None, str | None]:
"""Resolve an ACP file resource under the session cwd and read policy."""
if cwd is None:
return None, "ACP file resources require an active session cwd."

try:
root = cwd.expanduser().resolve()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The cwd parameter can be passed as a string (e.g., from the session manager or ACP client payloads). Calling cwd.expanduser() directly on a string will raise an AttributeError, crashing the resource resolution. Wrapping cwd in Path(cwd) ensures robust handling of both Path and str inputs.

Suggested change
def _resolve_acp_resource_path(path: Path, cwd: Path | None) -> tuple[Path | None, str | None]:
"""Resolve an ACP file resource under the session cwd and read policy."""
if cwd is None:
return None, "ACP file resources require an active session cwd."
try:
root = cwd.expanduser().resolve()
def _resolve_acp_resource_path(path: Path, cwd: Path | str | None) -> tuple[Path | None, str | None]:
"""Resolve an ACP file resource under the session cwd and read policy."""
if cwd is None:
return None, "ACP file resources require an active session cwd."
try:
root = Path(cwd).expanduser().resolve()

Comment thread gateway/platforms/base.py
Comment on lines +500 to +508
def _is_trusted_gateway_media_path(path: str) -> bool:
"""Return True when *path* is a regular file inside Hermes media caches."""
try:
candidate = Path(path).expanduser().resolve()
if not candidate.is_file():
return False
return any(candidate.is_relative_to(root) for root in _trusted_gateway_media_roots())
except OSError:
return False
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

If path is not a string or Path-like object, or if it contains null bytes (e.g., from malicious model output), Path(path) or resolve() can raise a TypeError or ValueError. Since the try block only catches OSError, these unhandled exceptions will crash the gateway process. Catching (OSError, TypeError, ValueError) ensures robust and safe execution.

Suggested change
def _is_trusted_gateway_media_path(path: str) -> bool:
"""Return True when *path* is a regular file inside Hermes media caches."""
try:
candidate = Path(path).expanduser().resolve()
if not candidate.is_file():
return False
return any(candidate.is_relative_to(root) for root in _trusted_gateway_media_roots())
except OSError:
return False
def _is_trusted_gateway_media_path(path: str) -> bool:
"""Return True when *path* is a regular file inside Hermes media caches."""
try:
candidate = Path(path).expanduser().resolve()
if not candidate.is_file():
return False
return any(candidate.is_relative_to(root) for root in _trusted_gateway_media_roots())
except (OSError, TypeError, ValueError):
return False

Comment thread gateway/platforms/base.py
Comment on lines +476 to +497
def _trusted_gateway_media_roots() -> tuple[Path, ...]:
"""Return Hermes-managed directories allowed for model-emitted attachments."""
home = get_hermes_home()
roots = [
get_image_cache_dir(),
get_audio_cache_dir(),
get_video_cache_dir(),
get_document_cache_dir(),
get_hermes_dir("cache/screenshots", "browser_screenshots"),
get_hermes_dir("cache/vision", "temp_vision_images"),
home / "cache" / "images",
home / "cache" / "audio",
home / "cache" / "videos",
home / "cache" / "documents",
]
resolved: list[Path] = []
for root in roots:
try:
resolved.append(Path(root).expanduser().resolve())
except OSError:
continue
return tuple(dict.fromkeys(resolved))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The _trusted_gateway_media_roots function is called on every invocation of _is_trusted_gateway_media_path during path filtering. This repeatedly executes multiple mkdir(parents=True, exist_ok=True) and resolve() system calls, which is highly inefficient. Caching the resolved roots at the module level avoids this redundant I/O overhead.

_TRUSTED_MEDIA_ROOTS_CACHE: tuple[Path, ...] | None = None


def _trusted_gateway_media_roots() -> tuple[Path, ...]:
    """Return Hermes-managed directories allowed for model-emitted attachments."""
    global _TRUSTED_MEDIA_ROOTS_CACHE
    if _TRUSTED_MEDIA_ROOTS_CACHE is not None:
        return _TRUSTED_MEDIA_ROOTS_CACHE
    home = get_hermes_home()
    roots = [
        get_image_cache_dir(),
        get_audio_cache_dir(),
        get_video_cache_dir(),
        get_document_cache_dir(),
        get_hermes_dir("cache/screenshots", "browser_screenshots"),
        get_hermes_dir("cache/vision", "temp_vision_images"),
        home / "cache" / "images",
        home / "cache" / "audio",
        home / "cache" / "videos",
        home / "cache" / "documents",
    ]
    resolved: list[Path] = []
    for root in roots:
        try:
            resolved.append(Path(root).expanduser().resolve())
        except OSError:
            continue
    _TRUSTED_MEDIA_ROOTS_CACHE = tuple(dict.fromkeys(resolved))
    return _TRUSTED_MEDIA_ROOTS_CACHE

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

🔎 Lint report: badmade/create-patch-for-critical-security-fix vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 8235 on HEAD, 8235 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 4352 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

Auto-merge: checks failing

The following checks did not pass:

  • test (failure)

Please fix the failing checks before this PR can be merged.

View workflow run

@badMade
Copy link
Copy Markdown
Owner Author

badMade commented Jun 1, 2026

@copilot, fix all failing checks.

Copy link
Copy Markdown

Copilot AI commented Jun 1, 2026

@copilot, fix all failing checks.

Implemented fixes for the failing checks in commit ff0a791. This addresses the failing ACP redaction assertion, gateway authorization NameError path, QQ unauthorized attachment processing behavior, webhook placeholder-secret signature validation, and updates media-routing tests to use trusted cache paths so they align with the security guardrails.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants