Skip to content

fix: Docker API hook manager crashes for all user-provided hooks (#1878)#1879

Closed
hafezparast wants to merge 2 commits intounclecode:developfrom
hafezparast:fix/hook-manager-import-crash-1878
Closed

fix: Docker API hook manager crashes for all user-provided hooks (#1878)#1879
hafezparast wants to merge 2 commits intounclecode:developfrom
hafezparast:fix/hook-manager-import-crash-1878

Conversation

@hafezparast
Copy link
Copy Markdown
Contributor

Summary

Changes

  • deploy/docker/hook_manager.py: Fix module injection + add exception classes to allowed builtins
  • tests/docker/test_hook_manager_unit.py: 36 unit tests covering:
    • Issue [Bug]: Docker API hook manager crashes for all kind of user-provided hooks #1878 exact repro + all 8 hook points
    • Security: import/__import__/exec/eval/open all blocked
    • Validation: empty code, sync func, missing params, syntax errors
    • Adversarial: infinite loop timeout, exception capture, 10k-line code, unicode, namespace isolation
    • Exception classes: raise/catch, isinstance checks, all 14 classes verified

Test plan

🤖 Generated with Claude Code

…lecode#1878)

PR unclecode#1712 removed `__import__` from the safe builtins for security, but
the hook compiler still used `exec("import asyncio", namespace)` which
requires `__import__`. This broke ALL user-provided hooks.

Fix: inject commonly-needed modules (asyncio, json, re, typing) directly
into the namespace instead of using exec-based imports. Also add standard
exception classes (ValueError, TypeError, KeyError, etc.) to the safe
builtins so hooks can use try/except properly.

Security: `__import__` remains blocked — users still cannot import
arbitrary modules at runtime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ntohidi
Copy link
Copy Markdown
Collaborator

ntohidi commented Mar 30, 2026

@hafezparast , thanks for the solid fix — the approach of replacing exec("import ...") with direct module injection is clean and correctly preserves the security intent of PR #1712. Tests are thorough.

A few things to address before the merge:

Must fix (critical):

  • Remove BaseException from allowed_builtins. Since asyncio.CancelledError inherits from BaseException (Python 3.9+), a hook with except BaseException: pass can defeat the asyncio.wait_for timeout in execute_hook_safely, creating a DoS vector. Users should only be able to catch Exception and its subclasses.

Should fix:

  • Inject the typing module itself into the namespace (namespace['typing'] = typing) alongside the individual names (Dict, List, Optional). Currently, if a user needs Union, Any, Tuple, etc., they have no way to access them since import is blocked.
  • Replace asyncio.get_event_loop().run_until_complete() in tests — it's deprecated since Python 3.10 and noisy on 3.12+. Use asyncio.run() or pytest-asyncio with @pytest.mark.asyncio.

@hafezparast
Copy link
Copy Markdown
Contributor Author

Docker verification ✅

Tested against a locally built image from this branch with CRAWL4AI_HOOKS_ENABLED=true:

Test Result
Simplest hook: async def hook(page, context, **kwargs): return page success: True
Hook using asyncio.sleep() success: True
Hook calling page.set_extra_http_headers() success: True

Docker logs show Hooks attachment status: success for all three — no __import__ not found errors. The module injection approach works cleanly.

@ntohidi
Copy link
Copy Markdown
Collaborator

ntohidi commented Apr 24, 2026

@hafezparast Thanks for the Docker verification — good to know it works end-to-end. However, the review items I listed still need to be addressed before we can merge:

  1. Remove BaseException from allowed_builtins — this is critical (DoS vector via except BaseException: pass defeating asyncio.wait_for timeout)
  2. Inject the typing module itself into the namespace (namespace['typing'] = typing) so users can access Union, Any, Tuple, etc.
  3. Replace asyncio.get_event_loop().run_until_complete() in tests with asyncio.run() or pytest-asyncio

Could you push those changes? Thanks!

- Remove BaseException from allowed_builtins: catching it defeats
  asyncio.wait_for timeout (asyncio.CancelledError inherits from
  BaseException in Python 3.9+), creating a DoS vector
- Inject typing module + Union/Any/Tuple/Set into hook namespace so
  users can access all common type hints without __import__
- Replace deprecated asyncio.get_event_loop().time() with
  asyncio.get_running_loop().time() (3x in execute_hook_safely)
- Replace deprecated asyncio.get_event_loop().run_until_complete()
  with asyncio.run() in test_hook_manager_unit.py (20 calls)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@hafezparast
Copy link
Copy Markdown
Contributor Author

All three review items addressed in the latest commit:

  1. BaseException removed — left a comment explaining why (defeats asyncio.wait_for timeout via CancelledError)
  2. typing module injected — plus Union, Any, Tuple, Set alongside the existing Dict, List, Optional
  3. get_event_loop() replacedasyncio.get_running_loop().time() in hook_manager.py (3 calls), asyncio.run() in test_hook_manager_unit.py (20 calls)

@ntohidi
Copy link
Copy Markdown
Collaborator

ntohidi commented Apr 24, 2026

@hafezparast Thanks for the work on this! The core bug has already been fixed on develop as part of the security remediation commit (e326da9) — exec("import ...") was replaced with direct module injection, which is the same approach your PR takes.

Since the fix is already in and this PR has merge conflicts with the current develop, we're closing it. If you'd like to contribute the test file (test_hook_manager_unit.py) separately, feel free to open a new PR rebased on current develop. The test coverage would be welcome!

@ntohidi ntohidi closed this Apr 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Docker API hook manager crashes for all kind of user-provided hooks

2 participants