Skip to content

fix(security): harden V4A Move guard, ACP resource reads, API health auth, Discord mentions, webhook idempotency#41754

Open
cruzlxyz wants to merge 1 commit into
NousResearch:mainfrom
cruzlxyz:fix/security-hardening-audit-findings
Open

fix(security): harden V4A Move guard, ACP resource reads, API health auth, Discord mentions, webhook idempotency#41754
cruzlxyz wants to merge 1 commit into
NousResearch:mainfrom
cruzlxyz:fix/security-hardening-audit-findings

Conversation

@cruzlxyz

@cruzlxyz cruzlxyz commented Jun 8, 2026

Copy link
Copy Markdown

Summary

Security hardening from audit findings addressing 5 vulnerability classes across file tools, ACP adapter, API server, webhooks, and Discord platform.

Changes

  • tools/file_tools.py: Extended V4A traversal guard to cover *** Move File: headers (regex + loop all paths)
  • acp_adapter/server.py: ACP resource links now go through get_read_block_error() — blocks .env, SSH keys, credentials, config files; added CRLF→LF normalization for cross-platform text consistency
  • gateway/platforms/api_server.py:
    • SSRF check (is_safe_url()) for image URLs in chat completions
    • /health/detailed now requires API auth when API_SERVER_KEY configured
    • Reject chat completions whose last non-system message is not user (role alternation guard)
  • gateway/platforms/webhook.py: Idempotency key scoped to route:delivery_id:body_sha256 (prevents replay with different body)
  • plugins/platforms/discord/adapter.py: allowed_mentions added to all REST sends (forum starter, media, text) — new _build_allowed_mentions_payload() mirrors gateway bot defaults (deny @everyone/@roles by default)

Tests Added

  • test_v4a_move_file_header_rejects_traversal
  • test_acp_resource_link_blocks_secret_env_files
  • test_health_detailed_requires_auth_when_key_configured
  • test_rejects_assistant_final_message
  • test_rejects_private_image_url

All new tests pass (5/5).

@cruzlxyz cruzlxyz requested a review from a team June 8, 2026 03:28
@cruzlxyz cruzlxyz force-pushed the fix/security-hardening-audit-findings branch from 7d7fb13 to 76a4fe4 Compare June 8, 2026 03:43
@alt-glitch alt-glitch added type/security Security vulnerability or hardening P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery comp/acp Agent Communication Protocol adapter tool/file File tools (read, write, patch, search) platform/discord Discord bot adapter platform/webhook Webhook / API server labels Jun 8, 2026
@liuhao1024

Copy link
Copy Markdown
Contributor

Two issues found during review:

1. Broken test: test_acp_resource_link_blocks_secret_env_files

def test_acp_resource_link_blocks_secret_env_files(tmp_path):
    secret = tmp_path / ".env"
    secret.write_text("API_KEY=*** encoding=\"utf-8")

    # ...

    assert "Resource blocked" in content
    assert "super-secret" not in content

The file content is "API_KEY=***" — the string "super-secret" is never written. The second assertion passes vacuously and does not validate that actual secrets are suppressed. Either the file content should include a realistic secret value (e.g. "API_KEY=super-secret-key-12345") or the assertion should check against the actual written content.

Also, the write_text call appears to have a quoting issue — encoding="utf-8" looks like it's being passed as a keyword argument, but the first argument string isn't properly closed. This may cause a SyntaxError at collection time.

2. pytest config change removes safety guards

remove_plugins = ["timeout"]
# Removed: addopts = "-m 'not integration' --timeout=30 --timeout-method=signal"

This removes the 30-second per-test timeout and the not integration marker filter. Impact:

  • A hung test will block CI indefinitely (no timeout fallback)
  • Integration tests that require external services (API keys, Modal, etc.) may now run in CI and fail

If this is intentional (e.g. timeouts are now handled at the per-file subprocess level), the rationale should be documented in the PR body.

@austinpickett austinpickett left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed by Hermes Agent — Request changes (one scope issue; the security work itself is solid) 🟢🔴

I checked this out locally and ran the new tests against current main. All five hardening changes verified passing:

  • test_v4a_move_file_header_rejects_traversal
  • test_acp_resource_link_blocks_secret_env_files
  • test_health_detailed_requires_auth_when_key_configured
  • test_rejects_assistant_final_message
  • test_rejects_private_image_url

(plus the related auth-guard tests in test_api_server.py — 17 passed total.)

The security changes are well done:

  • V4A Move guard (file_tools.py) — regex now captures *** Move File: src -> dst with both paths in separate groups, filters empties, and loops all paths through has_traversal_component. Correct and complete.
  • ACP resource reads (acp_adapter/server.py) — routes reads through get_read_block_error() (verified the helper exists in agent/file_safety.py), with graceful OSError handling and a sensible Windows path fix. Blocks .env/SSH keys/creds as claimed.
  • API SSRF + auth (api_server.py) — is_safe_url() guard on image URLs (helper confirmed in tools/url_safety.py), /health/detailed now gated behind _check_auth, and the role-alternation guard rejecting a non-user final message is a genuinely good catch (prevents client-supplied assistant text becoming fresh instructions).
  • Discord allowed_mentions — added to all three REST send paths via _build_allowed_mentions_payload(), denying @everyone/@roles by default.
  • Webhook idempotency scoped to route:delivery_id:body_sha256 — sound.

The one blocker — an unrelated change that needs to come out:

pyproject.toml flips the global pytest config:

-addopts = "-m 'not integration' --timeout=30 --timeout-method=signal"
+addopts = "-m 'not integration' --timeout=60 --timeout-method=thread"

This isn't security hardening — it's a repo-wide test-infra change (doubles the per-test timeout and switches signalthread) buried in a security PR, and it also deletes the explanatory comment block documenting why signal is used inside the per-file subprocess runner (scripts/run_tests_parallel.py). The PR description doesn't mention it at all.

Please revert the pyproject.toml hunk so this PR is purely the security hardening. If the timeout/method change is intentional and needed, it deserves its own PR with rationale (why thread over signal, why 60s) so it can be evaluated on its own merits.

Drop that one hunk and I'm happy to approve — the actual hardening is clean, well-scoped per file, and properly tested.

@cruzlxyz

Copy link
Copy Markdown
Author

Thanks for the review! I reverted the pyproject.toml timeout/method change so this PR stays focused on the security hardening only.

I also fixed the .env resource-link test to use an actual secret value (super-secret) so the suppression assertion is meaningful.

Targeted tests passed locally:

5 passed, 3 warnings in 1.40s

I'll keep any pytest timeout/method discussion for a separate PR if needed.

@cruzlxyz

Copy link
Copy Markdown
Author

Follow-up verification:

  • pyproject.toml is back to the original pytest config.
  • The ACP .env resource-link test uses a real super-secret value.
  • Targeted tests passed locally:
5 passed, 3 warnings in 2.64s

Ready for re-review, thanks!

@egilewski egilewski left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Needs rework.

I stopped at the early gates for the current branch state. Current GitHub main is d1383a6b1450c6c139720b1b01f8b99cc130453f, PR base is b5a457c033035e8dcd203745e68be16b50c2390e, and PR head is d17e155741301c9844ab0bfcac5d29f831ea756a.

Validation:

  • git rev-list --left-right --count upstream/main...refs/remotes/upstream/pr/41754 returned 202 175.
  • git merge-tree --write-tree upstream/main refs/remotes/upstream/pr/41754 exited non-zero and reported conflicts across many unrelated areas, including desktop files, gateway platform files, hermes_cli/*, plugin files, tests, tools/approval.py, tui_gateway/server.py, and docs.
  • git diff --check upstream/main...refs/remotes/upstream/pr/41754 fails with tests/hermes_cli/test_gui_uninstall.py:348: new blank line at EOF.
  • git diff --stat upstream/main...refs/remotes/upstream/pr/41754 now spans 439 files with about 28k insertions, including desktop UI/i18n, plugins, docs, CLI, gateway, and many unrelated test files outside the advertised V4A/ACP/API/webhook/Discord hardening scope.

The individual security ideas may still be valuable, but this branch is not in a reviewable or mergeable shape against current main. Please rebase/rescope it to the listed hardening changes only and remove the unrelated repo-wide drift before requesting another review.

Signed: GPT-5.5-xhigh in Codex

@cruzlxyz cruzlxyz force-pushed the fix/security-hardening-audit-findings branch from d17e155 to 6f70a0f Compare June 11, 2026 05:00
@cruzlxyz

Copy link
Copy Markdown
Author

Rescoped and force-pushed this PR onto current main.

What changed:

  • Removed the unrelated branch drift; PR is now focused on the advertised security hardening.
  • Diff is now 8 files / 139 additions / 18 deletions.
  • git diff --check is clean.

Targeted verification passed locally:

uv run --extra dev --extra acp --extra messaging pytest \
  tests/tools/test_file_tools.py::TestSensitivePathCheck::test_v4a_move_file_header_rejects_traversal \
  tests/acp_adapter/test_acp_images.py::test_acp_resource_link_blocks_secret_env_files \
  tests/gateway/test_api_server.py::TestHealthDetailedEndpoint::test_health_detailed_requires_auth_when_key_configured \
  tests/gateway/test_api_server.py::TestChatCompletionsEndpoint::test_rejects_assistant_final_message \
  tests/gateway/test_api_server.py::TestChatCompletionsEndpoint::test_rejects_private_image_url

5 passed, 3 warnings in 2.93s

Ready for re-review. Thanks!

@egilewski egilewski left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Recommendation: request changes

I reviewed this against current GitHub main fa7f24e8980367c2ca849eb99e1eb2331c7d3699, PR base 3e74f75e41ecd5a3b937d692ba7dcffbf77304f6, and PR head 6f70a0f7efbf95afe7dd12495d6318496e44806c.

Validation:

  • git merge-tree --write-tree upstream/main 6f70a0f7efbf95afe7dd12495d6318496e44806c: passed, produced tree 7a4dab0a6719ad6a605479e9e91b6fe3ae5c679a.
  • git diff --check upstream/main...6f70a0f7efbf95afe7dd12495d6318496e44806c: passed.
  • pytest -q tests/tools/test_file_tools.py::TestSensitivePathCheck::test_v4a_move_file_header_rejects_traversal tests/acp_adapter/test_acp_images.py::test_acp_resource_link_blocks_secret_env_files tests/gateway/test_api_server.py::TestHealthDetailedEndpoint::test_health_detailed_requires_auth_when_key_configured tests/gateway/test_api_server.py::TestChatCompletionsEndpoint::test_rejects_assistant_final_message tests/gateway/test_api_server.py::TestChatCompletionsEndpoint::test_rejects_private_image_url tests/gateway/test_discord_allowed_mentions.py -p no:cacheprovider: passed, 24 passed and 3 aiohttp app-key warnings.
  • pytest -q tests/gateway/test_webhook_adapter.py::TestIdempotency -p no:cacheprovider: failed, 1 failed and 2 passed.

Finding:
The webhook idempotency test suite is broken by the new scoped _seen_deliveries key. TestIdempotency.test_expired_delivery_id_allows_reprocess still backdates adapter._seen_deliveries["delivery-456"], but the implementation now stores keys as route:delivery_id:body_sha256. The actual fresh scoped key remains in the cache, so the second request is still treated as a duplicate and the test fails with AssertionError: assert 200 == 202.

Please update the idempotency test and any helper code so TTL expiry is validated against the new key shape.

Signed: GPT-5.5-xhigh in Codex

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

Labels

comp/acp Agent Communication Protocol adapter comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/discord Discord bot adapter platform/webhook Webhook / API server tool/file File tools (read, write, patch, search) type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants