From 7e59810df0bcc6fae15e537aeede8673432cea47 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 19:55:55 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=92=20[security=20fix]=20Replace=20ins?= =?UTF-8?q?ecure=20Math.random()=20with=20crypto.randomUUID()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit addresses a security vulnerability where participant IDs were generated using the predictable Math.random() function. Changes: - Replaced insecure PID generation with self.crypto.randomUUID() in code/experiment.js. - Removed all console.log, console.warn, and console.error statements from code/experiment.js to ensure silent client-side execution as per repository directives. - Added an automated Playwright verification script in telemetry_verification/verify_pid.py to validate UUID format and end-to-end experiment flow. The fix ensures cryptographic unpredictability for session identifiers while maintaining the strict behavioral and privacy requirements of the telemetry engine. Co-authored-by: hashexplaindata <221828969+hashexplaindata@users.noreply.github.com> --- code/experiment.js | 5 +- telemetry_verification/verify_pid.py | 97 ++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 telemetry_verification/verify_pid.py diff --git a/code/experiment.js b/code/experiment.js index 55bce07..2b887e3 100644 --- a/code/experiment.js +++ b/code/experiment.js @@ -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, 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 = `⚠️ Sync Failed. Error: ${error.code || 'Network'}`; // Potential fallback: Save to localStorage for later recovery } diff --git a/telemetry_verification/verify_pid.py b/telemetry_verification/verify_pid.py new file mode 100644 index 0000000..2070f01 --- /dev/null +++ b/telemetry_verification/verify_pid.py @@ -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)]) + time.sleep(5) # Wait for server to start + + try: + with sync_playwright() as p: + browser = p.chromium.launch(headless=True) + page = browser.new_page() + + # Listen for console messages + page.on("console", lambda msg: print(f"PAGE CONSOLE: {msg.text}")) + + # Navigate to the experiment + print(f"Navigating to http://localhost:{port}/code/index.html") + page.goto(f"http://localhost:{port}/code/index.html") + + # 1. Capture initial PID + print("Waiting for STATE.pid...") + # Instead of wait_for_function which seems to hang if there are load issues, + # let's try a retry loop in evaluate + pid = None + for _ in range(30): + try: + pid = page.evaluate("typeof STATE !== 'undefined' ? STATE.pid : null") + if pid: + break + except: + pass + time.sleep(1) + + print(f"Initial PID: {pid}") + if not pid: + # Fallback: check if script is even there + scripts = page.evaluate("Array.from(document.scripts).map(s => s.src)") + print(f"Loaded scripts: {scripts}") + raise Exception("PID not found in STATE after timeout") + + # 2. Click Consent + page.wait_for_selector("#btn-consent") + page.click("#btn-consent") + print("Clicked consent") + + # 3. Select Familiarity + page.wait_for_selector(".btn-familiarity") + page.click(".btn-familiarity") + print("Selected familiarity") + + # 4. Complete 6 trials + for i in range(6): + print(f"Trial {i+1}") + page.wait_for_selector(".bento-choice-card") + page.click(".bento-choice-card") + time.sleep(0.5) # Transition time + + # 5. Justification + page.wait_for_selector("#semantic-justification") + page.fill("#semantic-justification", "This is a security verification test justification.") + print("Filled justification") + + # 6. Finalize + page.wait_for_function("!document.getElementById('btn-finalize').disabled") + page.click("#btn-finalize") + print("Clicked finalize") + + # 7. Final Screen + page.wait_for_selector("#display-pid", timeout=10000) + displayed_pid = page.inner_text("#display-pid") + print(f"Displayed PID: {displayed_pid}") + + if pid != displayed_pid: + raise Exception(f"PID mismatch! Initial: {pid}, Displayed: {displayed_pid}") + + # Check UUID format + uuid_regex = r"^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + if not re.match(uuid_regex, pid, re.IGNORECASE): + raise Exception(f"PID {pid} is not a valid UUID v4") + print("Verified PID is a valid UUID v4") + + # Take a screenshot + screenshot_dir = os.path.join(os.path.dirname(__file__), "screenshots") + os.makedirs(screenshot_dir, exist_ok=True) + page.screenshot(path=os.path.join(screenshot_dir, "verification.png")) + print("Verification successful, screenshot saved.") + + finally: + server_process.terminate() + +if __name__ == "__main__": + run_verification()