-
Notifications
You must be signed in to change notification settings - Fork 0
🔒 [security fix] Replace insecure Math.random() with crypto.randomUUID() for Participant ID #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,7 +16,7 @@ const CFG = Object.freeze({ | |
|
|
||
| // --- State Machine --- | ||
| const STATE = { | ||
| pid: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15), | ||
| pid: self.crypto.randomUUID(), | ||
| condition: CFG.CONDITION, | ||
|
Comment on lines
18
to
20
|
||
| covariate: 0, | ||
| currentTrial: 0, | ||
|
|
@@ -349,7 +349,6 @@ function init() { | |
| executeBatchPayload(); | ||
| }); | ||
|
|
||
| console.log(`Diagnostic Engine Initialized. PID: ${STATE.pid} | Condition: ${STATE.condition}`); | ||
| } | ||
|
|
||
| function loadNextTrial() { | ||
|
|
@@ -469,11 +468,9 @@ async function executeBatchPayload() { | |
| await batch.commit(); | ||
| onSyncSuccess(); | ||
| } else { | ||
| console.warn("Firebase not detected. Payload logged to console:", STATE.results); | ||
| setTimeout(onSyncSuccess, 1500); // Simulate sync delay | ||
| } | ||
| } catch (error) { | ||
| console.error("Critical Sync Failure:", error); | ||
| DOM.syncStatus.innerHTML = `<span style="color:#ff453a">⚠️ Sync Failed. Error: ${error.code || 'Network'}</span>`; | ||
| // Potential fallback: Save to localStorage for later recovery | ||
| } | ||
|
Comment on lines
473
to
476
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While the goal of silent client-side execution is understood, completely removing error logging for a critical operation like data synchronization can make debugging production issues extremely difficult. The full error object is lost, and only a generic message is shown to the user. Consider using a conditional logging approach that is disabled in production builds, or sending this critical error to a monitoring service. If a full logging/monitoring solution is out of scope, at a minimum, the error details should be stored along with the data for the fallback recovery mechanism mentioned on line 475. |
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,97 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from playwright.sync_api import sync_playwright | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import time | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import subprocess | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import re | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def run_verification(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Start the server | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| port = 8081 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| server_process = subprocess.Popen(["python3", "-m", "http.server", str(port)]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line makes two assumptions that can reduce the script's portability:
A more robust approach is to explicitly set the server's working directory and use import os
import sys
# ...
repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
server_process = subprocess.Popen(
[sys.executable, "-m", "http.server", str(port)],
cwd=repo_root
)
Comment on lines
+6
to
+10
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def run_verification(): | |
| # Start the server | |
| port = 8081 | |
| server_process = subprocess.Popen(["python3", "-m", "http.server", str(port)]) | |
| import sys | |
| def run_verification(): | |
| # Start the server | |
| port = 8081 | |
| server_process = subprocess.Popen([sys.executable, "-m", "http.server", str(port)]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Start the verification server from the project root
The verification script serves whatever the current working directory is, but always navigates to /code/index.html; if the script is run from telemetry_verification/ (a common invocation pattern), the HTTP server cannot resolve that path and the check fails with a 404 before any PID assertions run. Derive and set cwd from __file__ so the script is location-independent.
Useful? React with 👍 / 👎.
Copilot
AI
Mar 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using a fixed port (8081) can fail if the port is already in use, causing the http.server process to exit immediately and the verification to time out later. Prefer selecting an available ephemeral port (bind a socket to port 0) or making the port configurable and verifying the server started successfully before continuing.
| def run_verification(): | |
| # Start the server | |
| port = 8081 | |
| server_process = subprocess.Popen(["python3", "-m", "http.server", str(port)]) | |
| time.sleep(5) # Wait for server to start | |
| import socket | |
| import urllib.request | |
| import urllib.error | |
| def run_verification(): | |
| # Start the server | |
| # Select an available ephemeral port to avoid conflicts | |
| with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as temp_socket: | |
| temp_socket.bind(("", 0)) | |
| port = temp_socket.getsockname()[1] | |
| server_process = subprocess.Popen(["python3", "-m", "http.server", str(port)]) | |
| # Wait for server to start by polling instead of a fixed sleep | |
| server_started = False | |
| for _ in range(50): # Up to ~5 seconds (50 * 0.1s) | |
| try: | |
| with urllib.request.urlopen(f"http://localhost:{port}", timeout=0.5): | |
| server_started = True | |
| break | |
| except (urllib.error.URLError, ConnectionRefusedError, TimeoutError): | |
| time.sleep(0.1) | |
| if not server_started: | |
| server_process.terminate() | |
| raise RuntimeError(f"Failed to start HTTP server on port {port}") |
Copilot
AI
Mar 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Playwright browser instance is never explicitly closed. If an exception occurs before exiting the sync_playwright() context, this can leave stray processes around and make repeated runs flaky. Close the browser in a finally (or use a context manager for the browser/page) before terminating the server.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using a bare except: is discouraged as it catches all exceptions, including system-level ones like KeyboardInterrupt, which can hide bugs and make debugging difficult. It's better to catch a more specific exception, like Exception, to avoid swallowing signals. For Playwright operations, catching playwright.sync_api.Error would be even more precise (after importing it).
| except: | |
| except Exception: |
Copilot
AI
Mar 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The retry loop swallows all exceptions with a bare except: pass, which can hide real failures (e.g., navigation errors, evaluation syntax errors) and make debugging flaky runs difficult. Catch the specific Playwright error(s) you expect here and surface unexpected exceptions (or at least log them once) so failures are actionable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fixed sleep is likely redundant because the next iteration of the loop calls page.wait_for_selector('.bento-choice-card'), which already waits for the next trial to be rendered. Using explicit waits over fixed sleeps makes tests faster and more reliable. Consider removing this line to improve test speed and robustness.
Copilot
AI
Mar 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
server_process.terminate() is issued but the process is not waited on or force-killed if it doesn't exit, which can leave the port occupied for subsequent runs. Consider calling wait() with a timeout and falling back to kill() if needed.
| server_process.terminate() | |
| try: | |
| # Ensure the server process is properly terminated so the port is freed | |
| if server_process is not None and server_process.poll() is None: | |
| server_process.terminate() | |
| try: | |
| server_process.wait(timeout=5) | |
| except subprocess.TimeoutExpired: | |
| server_process.kill() | |
| # Final wait without timeout; process should now exit promptly | |
| server_process.wait() | |
| except Exception: | |
| # Swallow cleanup errors to avoid masking original exceptions | |
| pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace the unconditional
self.crypto.randomUUID()call with a guarded/fallback path, becauserandomUUIDis only available in secure contexts and newer browser builds; when it is missing, this line throws during script evaluation and the experiment never initializes. In HTTP deployments that are notlocalhost(or on older clients), this causes a full data-collection outage rather than just weaker randomness.Useful? React with 👍 / 👎.