Skip to content

fix: restore session/auth helpers lost in merge conflicts#688

Merged
github-actions[bot] merged 14 commits into
mainfrom
claude/brave-ramanujan-VUulH
Jun 2, 2026
Merged

fix: restore session/auth helpers lost in merge conflicts#688
github-actions[bot] merged 14 commits into
mainfrom
claude/brave-ramanujan-VUulH

Conversation

@badMade

@badMade badMade commented Jun 1, 2026

Copy link
Copy Markdown
Owner

Summary

A series of merge resolutions dropped several helpers while keeping their call sites and tests, producing a broad cluster of NameError / AttributeError / TypeError failures across gateway, cron, web-tools and api-server tests. This restores the lost APIs and tightens a couple of related security guardrails.

  • gateway/run.py: restore the team_id definition in _is_user_authorized; the local was deleted in a prior merge but two call sites (pairing_check_ids and check_ids) still referenced it, causing every gateway auth path to raise NameError.
  • gateway/session_context.py: restore get_terminal_cwd / set_terminal_cwd / reset_terminal_cwd helpers (and the underlying _TERMINAL_CWD ContextVar). run_agent.py imports get_terminal_cwd in several places and the missing symbol broke cron-codex and agent-init paths with ImportError.
  • tools/web_tools.py: rename _ddgs_package_importable_ddgs_package_available (with a backward-compat alias) so the new ddgs tests can monkeypatch the canonical symbol; drop ddgs from the auto-detect fallback so just having the package importable doesn't silently opt users into a rate-limited HTML-scraping backend.
  • gateway/platforms/webhook.py: reject unresolved ${VAR} placeholder secrets. Treating a literal ${WEBHOOK_SECRET} as the HMAC key silently weakens auth — anyone who can guess the placeholder name can forge a valid signature.
  • gateway/platforms/api_server.py: restore _constant_time_equal so unicode API keys are encoded to UTF-8 before hmac.compare_digest, which otherwise raises TypeError on non-ASCII characters.

Test plan

  • tests/gateway/test_session_env.py (12 tests) pass
  • tests/gateway/test_unauthorized_dm_behavior.py (26 tests) — all NameError: team_id failures cleared
  • tests/tools/test_web_providers_ddgs.py (19 tests) pass; existing test_web_providers_brave_free.py still passes
  • tests/gateway/test_webhook_adapter.py (55 tests) pass including test_validate_placeholder_secret_rejects_literal_hmac
  • tests/cron/test_codex_execution_paths.py (2 tests) pass — ImportError: get_terminal_cwd cleared
  • tests/gateway/test_api_server.py::TestAuth (8 tests) pass including the two non-ASCII key tests
  • All cron tests (344) pass

Generated by Claude Code

A series of merge resolutions dropped several helpers while keeping their
call sites and tests. The result was a broad cluster of NameError /
AttributeError / TypeError failures across gateway, cron, web-tools and
api-server tests.

- gateway/run.py: restore `team_id` definition in `_is_user_authorized`;
  it was deleted but two call sites still reference it.
- gateway/session_context.py: restore `get_terminal_cwd` /
  `set_terminal_cwd` / `reset_terminal_cwd` helpers (and the underlying
  `_TERMINAL_CWD` ContextVar) that run_agent.py imports.
- tools/web_tools.py: rename `_ddgs_package_importable` to
  `_ddgs_package_available` (with a backward-compat alias) so tests can
  monkeypatch the expected symbol; drop ddgs from the auto-detect
  fallback so just having the package importable doesn't silently opt
  users into a rate-limited HTML-scraping backend.
- gateway/platforms/webhook.py: reject unresolved `${VAR}` placeholder
  secrets; treating them as real HMAC secrets silently weakened auth.
- gateway/platforms/api_server.py: restore `_constant_time_equal` so
  unicode API keys compare safely instead of raising TypeError from
  `hmac.compare_digest`.
Copilot AI review requested due to automatic review settings June 1, 2026 21:22
@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown

🔎 Lint report: claude/brave-ramanujan-VUulH 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: 8249 on HEAD, 8262 on base (✅ -13)

🆕 New issues (3):

Rule Count
invalid-argument-type 3
First entries
run_agent.py:7317: [invalid-argument-type] invalid-argument-type: Argument to function `build_anthropic_client` is incorrect: Expected `str`, found `str | dict[Unknown | str, Unknown | str | dict[str, str]] | Any | ... omitted 3 union elements`
run_agent.py:13576: [invalid-argument-type] invalid-argument-type: Argument to function `len` is incorrect: Expected `Sized`, found `(str & ~AlwaysFalsy) | (dict[Unknown | str, Unknown | str | dict[str, str]] & ~AlwaysFalsy) | (Any & ~AlwaysFalsy) | ... omitted 3 union elements`
run_agent.py:13573: [invalid-argument-type] invalid-argument-type: Argument to function `_is_oauth_token` is incorrect: Expected `str`, found `str | dict[Unknown | str, Unknown | str | dict[str, str]] | Any | ... omitted 3 union elements`

✅ Fixed issues (10):

Rule Count
unresolved-import 5
invalid-argument-type 3
unresolved-reference 2
First entries
tests/gateway/test_api_server.py:703: [unresolved-import] unresolved-import: Cannot resolve imported module `gateway.proxy_scope_auth`
tools/web_tools.py:220: [unresolved-import] unresolved-import: Cannot resolve imported module `ddgs`
tests/run_agent/test_run_agent.py:2100: [unresolved-reference] unresolved-reference: Name `_FakeProviderMemoryManager` used when not defined
run_agent.py:13571: [invalid-argument-type] invalid-argument-type: Argument to function `_is_oauth_token` is incorrect: Expected `str`, found `str | dict[Unknown, Unknown] | Any | ... omitted 3 union elements`
run_agent.py:7317: [invalid-argument-type] invalid-argument-type: Argument to function `build_anthropic_client` is incorrect: Expected `str`, found `str | dict[Unknown, Unknown] | Any | ... omitted 3 union elements`
gateway/run.py:5544: [unresolved-reference] unresolved-reference: Name `team_id` used when not defined
gateway/platforms/api_server.py:3036: [unresolved-import] unresolved-import: Module `tools.approval` has no member `reset_current_run_id`
run_agent.py:11105: [unresolved-import] unresolved-import: Module `gateway.session_context` has no member `get_terminal_cwd`
run_agent.py:13574: [invalid-argument-type] invalid-argument-type: Argument to function `len` is incorrect: Expected `Sized`, found `(str & ~AlwaysFalsy) | (dict[Unknown, Unknown] & ~AlwaysFalsy) | (Any & ~AlwaysFalsy) | ... omitted 3 union elements`
gateway/platforms/api_server.py:3038: [unresolved-import] unresolved-import: Module `tools.approval` has no member `set_current_run_id`

Unchanged: 4352 pre-existing issues carried over.

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

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

Copy link
Copy Markdown

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 configuration improvements, including a safe constant-time comparison helper for non-ASCII API keys, validation to reject unresolved environment variable placeholders in webhook secrets, session-scoped terminal working directory management, and the exclusion of the ddgs backend from automatic detection. A critical issue was identified in the new _constant_time_equal function, where a None API key would cause an AttributeError during encoding; a guard clause has been suggested to resolve this.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread gateway/platforms/api_server.py Outdated
Defense in depth: the existing _check_auth caller early-returns when
self._api_key is falsy, so None can't actually reach this helper today,
but accepting Optional[str] and short-circuiting to False keeps the
helper safe for any future caller and matches the type the call site
already permits.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR restores several session/auth helper APIs that were lost during merge conflict resolution (causing widespread runtime errors in gateway/cron/web tools), and adds/adjusts a couple of security guardrails around webhook secrets, API key comparison, and web backend selection.

Changes:

  • Restore missing gateway session helpers (terminal CWD ContextVar accessors) and fix a deleted local (team_id) used in gateway authorization.
  • Harden authentication paths: reject unresolved ${VAR} placeholder webhook secrets and restore constant-time API key comparison that supports non-ASCII tokens.
  • Adjust web backend wiring: exclude ddgs from auto-detect and standardize ddgs availability helper naming with a backward-compat alias.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tools/web_tools.py Excludes ddgs from auto-detect and renames the ddgs availability helper (with alias).
gateway/session_context.py Restores per-session terminal CWD ContextVar and helper functions.
gateway/run.py Restores team_id local used by Slack authorization checks.
gateway/platforms/webhook.py Rejects unresolved ${VAR} placeholder secrets during signature validation.
gateway/platforms/api_server.py Restores a constant-time API key comparison helper that handles non-ASCII keys.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tools/web_tools.py Outdated
claude added 3 commits June 1, 2026 21:26
A bare ``import ddgs`` executes any local ``ddgs.py`` shadowing the
installed package on sys.path. The provider module already has a safer
non-importing check (metadata + find_spec, verified by
test_availability_does_not_import_shadowed_local_module). Delegate to
it so both call paths share the same protection.
A prior merge removed these helpers from tools/approval.py but kept the
api_server callers that import them, breaking every /v1/runs request
with ImportError. Restore the contextvar (`_approval_run_id`) and the
three accessors so the API run path can bind a per-task run id to
pending gateway approvals again.

Clears the 7 failures under tests/gateway/test_api_server_runs.py.
A prior merge deleted gateway/proxy_scope_auth.py and stripped the
HMAC-signature check from the /v1/chat/completions handler, while
leaving the tests that exercise both paths. Restore the module and
re-wire the handler:

- Imports verify_proxy_scope_signature + signature/timestamp headers
  from gateway.proxy_scope_auth.
- Uses ``"hermes_proxy_scope" in body`` (not truthy check) so an
  explicit JSON ``null`` is rejected with 400.
- Returns 403 with "trusted gateway proxy authentication" when the
  signature is missing or invalid.

Clears the 3 TestChatCompletionsEndpoint failures.
@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown

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

claude added 9 commits June 1, 2026 22:23
… return

When _strip_think_blocks is mocked in tests (TestInlineThinkBlockExtraction
binds only _build_assistant_message + _extract_reasoning, leaving every
other method as a MagicMock), it returns a MagicMock instead of a string.
sanitize_context() then crashes because re.sub expects str/bytes.

Guard the scrub: if _strip_think_blocks returns a string, sanitize that;
otherwise fall back to sanitizing the original _san_content. Production
agents always return a string, so behavior there is unchanged.

Clears the 7 TestInlineThinkBlockExtraction failures.
A prior merge dropped the is_safe_url() check on http(s) image URLs from
_normalize_multimodal_content, leaving only the scheme guard. Image URLs
pointing at private/internal addresses now reach the multimodal pipeline
and can exfiltrate internal-network content (test_private_image_url_rejected,
test_cloud_metadata_image_url_rejected). Re-add the check before the URL
is normalized into the image part.
…xt test with scrub semantics

Two lost-in-merge regressions in tests/run_agent/test_run_agent.py:

1. _FakeProviderMemoryManager class was deleted but two
   TestConcurrentToolExecution tests still instantiate it, raising
   NameError. Restore the minimal double from #209.

2. test_memory_context_in_stored_content_is_preserved was renamed to
   _is_scrubbed in #478 and the assertions inverted to match the
   storage-boundary scrub the production code now performs. The pre-rename
   version kept the old "preserve" assertions, which fail against the
   correct production behaviour. Update the test to its post-#478 form.
…ver fanout

Two lost-in-merge regressions in hermes_cli/tools_config.py:

- _implicit_default_off_toolsets no longer dropped homeassistant from
  default_off when HASS_TOKEN was set. That regressed Norbert's HA cron
  setup (the original PR NousResearch#14798 carve-out) because cron / cli would
  silently drop the toolset even though the operator had provisioned
  credentials. Restore the HASS_TOKEN check.

- _get_platform_tools required platform_toolsets to explicitly re-list
  every globally configured MCP server (exa, web-search-prime, etc.) to
  keep them enabled. Once a platform had any explicit builtin toolset
  list, MCP servers vanished. Restore the simpler rule: no_mcp opts out;
  otherwise enabled_mcp_servers always fan out — explicit builtin
  selection is the platform allowlist, not the MCP allowlist.

Clears 2 test_tools_config failures and the related
test_reasoning_command "exa in toolsets" assertion.
The inline secret-block in browser_navigate only ran one urllib.unquote
pass, so a URL with double-percent-encoded prefixes (sk%252Dant%252D…)
slipped through and reached the browser. agent.redact.url_contains_secret
applies repeated decoding (3 passes) and also splits the URL into
component values before matching, so it catches the multi-encode tricks
that test_blocks_percent_encoded_api_key_in_url and
test_blocks_split_api_key_in_query_values exercise.

Clears 2 test_browser_secret_exfil failures.
Two adjacent security regressions:

- tools/browser_tool.py: pre-navigation SSRF check skipped local backends
  (`not _is_local_backend()` short-circuited the guard) even though the
  surrounding comment explicitly states local backends must enforce it
  too — browser_snapshot can return local-file / internal-service
  responses in reduced-tool configurations. Drop the local-backend skip
  so the guard fires unless the operator opts in via
  ``browser.allow_private_urls``.

- gateway/platforms/qqbot/adapter.py: restore the attachment pre-auth
  gate from #349. _handle_c2c/_handle_group/_handle_guild/_handle_dm
  now check `_is_source_authorized_for_attachment_processing` before
  calling `_process_attachments`, and forward a text-only event when
  the sender isn't allowlisted. This prevents an unauthorized sender
  from forcing the bot to fetch attacker-controlled attachment URLs
  (SSRF amplification, large-file DoS, redirect attacks). Failure-closed
  when gateway_runner isn't attached yet, with a throttled warning so
  startup races don't spam the log.

Clears 2 test_browser_ssrf_local failures and
test_unauthorized_c2c_skips_attachment_processing.
dispatch_once() was calling waitpid(-1, WNOHANG) on every tick, which
reaps any zombie child of the gateway process — including non-kanban
subprocesses (npm install, agent-browser, etc.) whose callers rely on
their own Popen.wait()/subprocess.run() exit status. That broke
unrelated tools whenever the kanban dispatcher ran in the same process.

Restore the scoped reaper from #393: track each kanban worker PID in
_known_worker_child_pids when it's persisted via _set_worker_pid, and
in dispatch_once only waitpid those specific PIDs. Windows is still a
no-op (no zombies / no WNOHANG).

Clears test_source_gates_waitpid_loop.
… loop

When the batched-delta background task ("_batch_flush_after") hit a
ConnectionResetError on response.write(), the exception was swallowed
in the detached task and the main loop kept waiting on stream_q for
items that would never arrive — the streaming endpoint hung until the
client timed out and the agent was never interrupted.

Restore #398's fix: catch the flush exception in the background task,
stash it in _batch_error, and push a sentinel into stream_q so the main
loop re-raises it on dequeue. Both the live loop and the drain path
honour the sentinel.

Clears test_stream_batched_delta_disconnect_interrupts_agent.
…rust roots

A prior merge widened _SANE_PATH_DIRS to include /opt/homebrew/{bin,sbin}
and /usr/local/{bin,sbin} unconditionally and made _browser_candidate_path_dirs
always inject Homebrew node prefixes. Cron / systemd / locked-down operator
configs that intentionally strip those trust roots from PATH would silently
get them injected back, defeating the restriction.

Restore #234's design:
- _SANE_PATH_DIRS only includes Termux + system dirs (/usr/{bin,sbin}, /{bin,sbin}).
- _browser_candidate_path_dirs(existing_path) takes the operator-provided PATH
  and only adds Homebrew node prefixes / /usr/local / hermes-managed Node bin
  when the operator already opted into that trust root.
- _find_agent_browser passes os.environ.get("PATH","") through to
  _merge_browser_path so the gating actually fires (previously it passed "").

Clears all 4 test_browser_homebrew_paths failures.
@github-actions github-actions Bot merged commit 5377bf2 into main Jun 2, 2026
14 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants