fix: production safety audit — 4 critical bugs across backend, server, and electron#1290
Open
eren-karakus0 wants to merge 2 commits intoeigent-ai:mainfrom
Open
Conversation
…, and electron 1. fix(electron): enforce context isolation in child windows The `open-win` IPC handler created child BrowserWindows with `nodeIntegration: true` and `contextIsolation: false`, while the main window correctly uses `contextIsolation: true`. Any XSS in a child window would gain full Node.js access. Fixed by setting `nodeIntegration: false`, `contextIsolation: true`, and `sandbox: true` to match the main window's security posture. 2. fix(server): separate ExpiredSignatureError from InvalidTokenError in JWT decoding `jwt.decode()` raises `ExpiredSignatureError` (a subclass of `InvalidTokenError`) for expired tokens before the manual `exp` check can execute. The `except InvalidTokenError` block catches both cases and raises `token_invalid`, making it impossible for clients to distinguish expired tokens from corrupted ones. This breaks automatic token refresh flows. Fixed by catching `ExpiredSignatureError` explicitly before `InvalidTokenError`. 3. fix(backend): replace assert statements with proper runtime guards in ListenChatAgent `assert message is not None` and `assert res is not None` in both `step()` and `astep()` are stripped when Python runs with `-O` (optimized mode). This causes silent `None` propagation into the SSE event stream and downstream consumers. Replaced with explicit `if`/`raise RuntimeError` guards that survive optimization and provide clear error messages. 4. fix(backend): cancel background tasks before clearing the set `background_tasks.clear()` in the `improve()` handler only removes references from the set — it does not cancel running asyncio.Task objects. Ghost tasks continue executing, potentially writing stale SSE events into a new conversation's stream. Fixed by iterating and calling `.cancel()` on each non-done task before clearing. Tests: - 4 new tests for assert guard replacements (step/astep, sync/async) - 2 new tests for background task cancellation behavior
bytecii
reviewed
Feb 19, 2026
| ) | ||
|
|
||
| assert message is not None | ||
| if message is None: |
Collaborator
There was a problem hiding this comment.
Why we change to warning and let the step continue?
Author
There was a problem hiding this comment.
Good point — changed to raise RuntimeError instead, keeping fail-fast behavior consistent with the res=None guard below. Fixed in latest push.
|
|
||
|
|
||
| @pytest.mark.unit | ||
| class TestAssertReplacedWithProperGuards: |
Collaborator
There was a problem hiding this comment.
Just use def test_xxx instead of creating class for now
Author
There was a problem hiding this comment.
Done — replaced class with plain def test_xxx functions.
Address review feedback: - message=None now raises RuntimeError instead of defaulting to empty string, keeping fail-fast behavior consistent with res=None handling - Replace test class with plain def test_xxx functions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A cross-stack production safety audit uncovering 4 confirmed bugs across the Python backend, server auth, and Electron main process. Each bug is independently verified and includes test coverage where applicable.
Bug 1: Electron Child Windows Missing Context Isolation (CRITICAL)
File:
electron/main/index.ts—open-winIPC handlerThe main window correctly sets
contextIsolation: true, but theopen-winhandler creates childBrowserWindowinstances withnodeIntegration: trueandcontextIsolation: false. With context isolation disabled, any JavaScript running in a child renderer window shares the same context as Electron's built-in modules — meaning any XSS payload or injected script gains fullrequire('child_process').exec(...)access with no sandbox barrier.Before:
After:
Bug 2: JWT Token Expiry Returns Wrong Error Code (HIGH)
File:
server/app/component/auth.py—Auth.decode_token()PyJWT's
jwt.decode()already validates theexpclaim and raisesExpiredSignatureError(a subclass ofInvalidTokenError) for expired tokens. The manualpayload["exp"] < datetime.now()check never executes because expired tokens don't reach that line. Theexcept InvalidTokenErrorblock catches both expired and invalid tokens, raisingtoken_invalidfor both cases.This makes it impossible for clients to distinguish "token expired, please refresh" from "token is corrupted, please re-login", breaking any automatic token refresh logic.
Before:
After:
Bug 3:
assertStatements Used for Runtime Control Flow (HIGH)File:
backend/app/agent/listen_chat_agent.py—step()andastep()Both
step()andastep()useassert message is not Noneandassert res is not Nonefor critical runtime checks. Python'sassertstatements are completely removed when running with-O(optimized mode), which is common in production deployments.When stripped:
Nonemessage propagates into the SSEActionDeactivateAgentDataevent, corrupting the frontend data streamNoneresponse returns to callers, causing downstreamAttributeErrorcrashesBefore:
After:
Bug 4:
background_tasks.clear()Doesn't Cancel Running Tasks (HIGH)File:
backend/app/controller/chat_controller.py—improve()handlerWhen a user sends a follow-up message after task completion,
improve()callstask_lock.background_tasks.clear(). This only removes references from the Python set — it does not cancel the underlyingasyncio.Taskobjects. Ghost tasks continue running in the event loop, holding resources, and potentially writing stale SSE events into the new conversation's stream.Before:
After:
Test Plan
Added 6 new unit tests:
Assert Guard Tests (
test_listen_chat_agent.py):test_step_handles_none_message_without_assert— verifies step() doesn't crash with AssertionErrortest_step_raises_runtime_error_when_res_is_none— verifies RuntimeError instead of AssertionErrortest_astep_handles_none_message_without_assert— async version of message guardtest_astep_raises_runtime_error_when_res_is_none— async version of res guardBackground Task Tests (
test_chat_controller.py):test_improve_cancels_background_tasks_when_done— verifies running tasks are cancelled, done tasks are nottest_improve_handles_missing_background_tasks_attr— verifies graceful handling without the attributeFiles Changed
electron/main/index.tsnodeIntegration: false,contextIsolation: true,sandbox: truein child windowsserver/app/component/auth.pyExpiredSignatureErrorseparately; remove dead manual exp checkbackend/app/agent/listen_chat_agent.pyassertstatements withif/raiseguardsbackend/app/controller/chat_controller.pybackend/tests/app/agent/test_listen_chat_agent.pybackend/tests/app/controller/test_chat_controller.py