fix: restore session/auth helpers lost in merge conflicts#688
Conversation
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`.
🔎 Lint report:
|
| 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.
There was a problem hiding this comment.
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.
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.
There was a problem hiding this comment.
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
ddgsfrom 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.
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.
Auto-merge: checks failingThe following checks did not pass:
Please fix the failing checks before this PR can be merged. |
… 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.
Summary
A series of merge resolutions dropped several helpers while keeping their call sites and tests, producing a broad cluster of
NameError/AttributeError/TypeErrorfailures 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 theteam_iddefinition in_is_user_authorized; the local was deleted in a prior merge but two call sites (pairing_check_idsandcheck_ids) still referenced it, causing every gateway auth path to raiseNameError.gateway/session_context.py: restoreget_terminal_cwd/set_terminal_cwd/reset_terminal_cwdhelpers (and the underlying_TERMINAL_CWDContextVar).run_agent.pyimportsget_terminal_cwdin several places and the missing symbol broke cron-codex and agent-init paths withImportError.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; dropddgsfrom 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_equalso unicode API keys are encoded to UTF-8 beforehmac.compare_digest, which otherwise raisesTypeErroron non-ASCII characters.Test plan
tests/gateway/test_session_env.py(12 tests) passtests/gateway/test_unauthorized_dm_behavior.py(26 tests) — allNameError: team_idfailures clearedtests/tools/test_web_providers_ddgs.py(19 tests) pass; existingtest_web_providers_brave_free.pystill passestests/gateway/test_webhook_adapter.py(55 tests) pass includingtest_validate_placeholder_secret_rejects_literal_hmactests/cron/test_codex_execution_paths.py(2 tests) pass —ImportError: get_terminal_cwdclearedtests/gateway/test_api_server.py::TestAuth(8 tests) pass including the two non-ASCII key testsGenerated by Claude Code