tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 3bb4ee67577f68107b11272216a9fd9c6cbb28a5
parent 183d774937c14e9b3f36ba298c296111747744aa
Author: Randy Concepcion <rconcepcion@mozilla.com>
Date:   Wed, 19 Nov 2025 16:04:05 +0000

Bug 1997986 - Add ML security layer initially as a pass-through component for LLM calls and tool dispatch. r=tarek,ai-ondevice-reviewers

- Moved JSActor pattern from security-specific JSActor back to parent
  MLEngine JSActor
- Move validation logic for both request/response from child to parent
  to avoid IPC round trip (improve performance)

Differential Revision: https://phabricator.services.mozilla.com/D271495

Diffstat:
Mtoolkit/components/ml/actors/MLEngineParent.sys.mjs | 59+++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mtoolkit/components/ml/moz.build | 6+++++-
Mtoolkit/components/ml/tests/browser/browser.toml | 2++
Atoolkit/components/ml/tests/browser/browser_ml_engine_security.js | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atoolkit/components/ml/tests/xpcshell/test_ml_engine_security.js | 43+++++++++++++++++++++++++++++++++++++++++++
Atoolkit/components/ml/tests/xpcshell/xpcshell.toml | 5+++++
6 files changed, 173 insertions(+), 9 deletions(-)

diff --git a/toolkit/components/ml/actors/MLEngineParent.sys.mjs b/toolkit/components/ml/actors/MLEngineParent.sys.mjs @@ -1040,6 +1040,30 @@ export class MLEngine { } /** + * Validates an inference request before sending to child process. + * + * @param {object} request - The request to validate + * @returns {object|null} The validated request, or null if blocked + * @private + */ + #validateRequest(request) { + lazy.console.debug("[MLSecurity] Validating request:", request); + return request; + } + + /** + * Validates an inference response after receiving from child process. + * + * @param {object} response - The response to validate + * @returns {object|null} The validated response, or null if blocked + * @private + */ + #validateResponse(response) { + lazy.console.debug("[MLSecurity] Validating response:", response); + return response; + } + + /** * Observes shutdown events from the child process. * * When the inference process is shutdown, we want to set the port to null and throw an error. @@ -1308,12 +1332,19 @@ export class MLEngine { }); } if (response) { - const totalTime = - response.metrics.tokenizingTime + response.metrics.inferenceTime; - Glean.firefoxAiRuntime.runInferenceSuccess[ - this.getGleanLabel() - ].accumulateSingleSample(totalTime); - request.resolve(response); + // Validate response before returning to caller + const validatedResponse = this.#validateResponse(response); + if (!validatedResponse) { + request.reject(new Error("Response failed security validation")); + } else { + const totalTime = + validatedResponse.metrics.tokenizingTime + + validatedResponse.metrics.inferenceTime; + Glean.firefoxAiRuntime.runInferenceSuccess[ + this.getGleanLabel() + ].accumulateSingleSample(totalTime); + request.resolve(validatedResponse); + } } else { request.reject(error); } @@ -1477,6 +1508,12 @@ export class MLEngine { throw new Error("Port does not exist"); } + // Validate request before sending to child process + const validatedRequest = this.#validateRequest(request); + if (!validatedRequest) { + throw new Error("Request failed security validation"); + } + const resourcesPromise = this.getInferenceResources(); const beforeRun = ChromeUtils.now(); @@ -1484,7 +1521,7 @@ export class MLEngine { { type: "EnginePort:Run", requestId, - request, + request: validatedRequest, engineRunOptions: { enableInferenceProgress: false }, }, transferables @@ -1572,12 +1609,18 @@ export class MLEngine { throw new Error("The port is null"); } + // Validate request before sending to child process + const validatedRequest = this.#validateRequest(request); + if (!validatedRequest) { + throw new Error("Request failed security validation"); + } + // Send the request to the engine via postMessage with optional transferables this.#port.postMessage( { type: "EnginePort:Run", requestId, - request, + request: validatedRequest, engineRunOptions: { enableInferenceProgress: true }, }, transferables diff --git a/toolkit/components/ml/moz.build b/toolkit/components/ml/moz.build @@ -12,11 +12,15 @@ JAR_MANIFESTS += ["jar.mn"] with Files("**"): BUG_COMPONENT = ("Core", "Machine Learning: On Device") -DIRS += ["actors"] +DIRS += [ + "actors", +] if CONFIG["MOZ_WIDGET_TOOLKIT"] != "android": DIRS += ["backends/llama"] +XPCSHELL_TESTS_MANIFESTS += ["tests/xpcshell/xpcshell.toml"] + BROWSER_CHROME_MANIFESTS += [ "tests/browser/browser.toml", "tests/browser/perftest.toml", diff --git a/toolkit/components/ml/tests/browser/browser.toml b/toolkit/components/ml/tests/browser/browser.toml @@ -22,6 +22,8 @@ support-files = [ ["browser_ml_engine_rs_hub.js"] +["browser_ml_engine_security.js"] + ["browser_ml_native.js"] skip-if = [ "os == 'android'", diff --git a/toolkit/components/ml/tests/browser/browser_ml_engine_security.js b/toolkit/components/ml/tests/browser/browser_ml_engine_security.js @@ -0,0 +1,67 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Browser integration tests for ML security layer scaffolding. + * These tests verify that the security layer infrastructure is correctly + * integrated into MLEngine. + */ + +/** + * Test that MLEngine can be instantiated with security layer integrated. + */ +add_task(async function test_mlengine_instantiation() { + info("Testing MLEngine instantiation with security layer"); + + await SpecialPowers.pushPrefEnv({ + set: [["browser.ml.enable", true]], + }); + + const tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "data:text/html,<meta charset=utf-8>MLEngine Test" + ); + + try { + const { MLEngine } = ChromeUtils.importESModule( + "resource://gre/actors/MLEngineParent.sys.mjs" + ); + + // Create engine instance + const engine = new MLEngine({ + mlEngineParent: {}, + pipelineOptions: { + engineId: "browser-test-instantiation", + featureId: "test-feature", + taskName: "test-task", + }, + notificationsCallback: null, + }); + + Assert.ok(engine, "MLEngine instantiates successfully"); + Assert.equal( + engine.engineId, + "browser-test-instantiation", + "Engine ID is set correctly" + ); + Assert.equal( + engine.engineStatus, + "uninitialized", + "Initial status is uninitialized" + ); + + // Verify engine is tracked + const retrieved = MLEngine.getInstance("browser-test-instantiation"); + Assert.equal(retrieved, engine, "Engine is tracked in instances map"); + + // Clean up + await MLEngine.removeInstance("browser-test-instantiation", false, false); + + info("MLEngine instantiation works with security layer"); + } finally { + BrowserTestUtils.removeTab(tab); + await SpecialPowers.popPrefEnv(); + } +}); diff --git a/toolkit/components/ml/tests/xpcshell/test_ml_engine_security.js b/toolkit/components/ml/tests/xpcshell/test_ml_engine_security.js @@ -0,0 +1,43 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { MLEngine } = ChromeUtils.importESModule( + "resource://gre/actors/MLEngineParent.sys.mjs" +); + +/** + * Test that MLEngine properly manages request lifecycle. + * This ensures security layer integration doesn't break request tracking. + */ +add_task(async function test_request_lifecycle_management() { + info("Testing request lifecycle management"); + + const engine = new MLEngine({ + mlEngineParent: {}, + pipelineOptions: { engineId: "test-request-lifecycle" }, + notificationsCallback: null, + }); + + // Verify engine starts with no pending requests + // The #requests map is private, but we can test behavior + + // Without a port, run() should throw before creating a request + let errorThrown = false; + try { + await engine.run({ args: ["test"] }); + } catch (error) { + errorThrown = true; + Assert.ok( + error.message.includes("Port does not exist"), + "Should fail on port check" + ); + } + + Assert.ok(errorThrown, "run() without port should throw"); + + await MLEngine.removeInstance("test-request-lifecycle", false, false); + + info("Request lifecycle management works correctly"); +}); diff --git a/toolkit/components/ml/tests/xpcshell/xpcshell.toml b/toolkit/components/ml/tests/xpcshell/xpcshell.toml @@ -0,0 +1,5 @@ +[DEFAULT] +head = "" +skip-if = ["os == 'android'"] + +["test_ml_engine_security.js"]