Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: os == 'android'
- Manifest: toolkit/components/ml/tests/xpcshell/xpcshell.toml
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
/**
* Unit tests for ConditionEvaluator.sys.mjs
*
* Note: ConditionEvaluator is an internal module used by PolicyEvaluator.
* These tests verify it through SecurityOrchestrator (the public API) rather
* than testing internal implementation details.
*
* Focus: Testing condition evaluation behavior through policy execution
*/
const { SecurityOrchestrator } = ChromeUtils.importESModule(
"chrome://global/content/ml/security/SecurityOrchestrator.sys.mjs"
);
const PREF_SECURITY_ENABLED = "browser.ml.security.enabled";
/** @type {SecurityOrchestrator|null} */
let orchestrator = null;
function setup() {
Services.prefs.clearUserPref(PREF_SECURITY_ENABLED);
}
function teardown() {
Services.prefs.clearUserPref(PREF_SECURITY_ENABLED);
orchestrator = null;
}
/**
* Test: condition passes when all URLs are present in the ledger.
*
* Reason:
* The `allUrlsIn` condition should allow a tool call only when
* every URL in `action.urls` exists in the request-scoped ledger.
* This ensures that tool execution is restricted to trusted,
* user-visible URLs and prevents unseen-link tool calls.
*/
add_task(async function test_condition_passes_when_all_urls_in_ledger() {
setup();
orchestrator = await SecurityOrchestrator.create("test-session");
const ledger = orchestrator.getSessionLedger();
const tabLedger = ledger.forTab("tab-1");
const decision = await orchestrator.evaluate({
phase: "tool.execution",
action: {
type: "tool.call",
tool: "get_page_content",
tabId: "tab-1",
},
context: {
currentTabId: "tab-1",
mentionedTabIds: [],
requestId: "test",
},
});
Assert.equal(
decision.effect,
"allow",
"Should allow when all URLs in ledger (condition passes)"
);
teardown();
});
/**
* Test: condition fails when any URL is missing from the ledger.
*
* Reason:
* If even one URL in `action.urls` is not in the ledger, the condition
* must fail and deny the request. This enforces all-or-nothing security —
* partial trust is not acceptable for URL-based tool access.
*/
add_task(async function test_condition_fails_when_url_missing_from_ledger() {
setup();
orchestrator = await SecurityOrchestrator.create("test-session");
const ledger = orchestrator.getSessionLedger();
const decision = await orchestrator.evaluate({
phase: "tool.execution",
action: {
type: "tool.call",
tool: "get_page_content",
tabId: "tab-1",
},
context: {
currentTabId: "tab-1",
mentionedTabIds: [],
requestId: "test",
},
});
Assert.equal(
decision.effect,
"deny",
"Should deny when URL not in ledger (condition fails)"
);
Assert.equal(decision.code, "UNSEEN_LINK");
teardown();
});
/**
* Test: condition passes with an empty URLs array.
*
* Reason:
* When no URLs are requested, there's nothing to validate. The condition
* should pass (vacuous truth) since there are no untrusted URLs to block.
* This allows tools that don't require URL access to proceed.
*/
add_task(async function test_condition_passes_with_empty_urls_array() {
setup();
orchestrator = await SecurityOrchestrator.create("test-session");
const ledger = orchestrator.getSessionLedger();
ledger.forTab("tab-1");
const decision = await orchestrator.evaluate({
phase: "tool.execution",
action: {
type: "tool.call",
tool: "get_page_content",
urls: [], // Empty array
tabId: "tab-1",
},
context: {
currentTabId: "tab-1",
mentionedTabIds: [],
requestId: "test",
},
});
Assert.equal(
decision.effect,
"allow",
"Should allow with empty URLs (nothing to check)"
);
teardown();
});
/**
* Test: condition fails with a malformed URL.
*
* Reason:
* Malformed URLs cannot be normalized or matched against the ledger.
* The security layer must fail-closed: if a URL can't be validated,
* it's treated as unseen and denied rather than allowed.
*/
add_task(async function test_condition_fails_with_malformed_url() {
setup();
orchestrator = await SecurityOrchestrator.create("test-session");
const ledger = orchestrator.getSessionLedger();
ledger.forTab("tab-1");
const decision = await orchestrator.evaluate({
phase: "tool.execution",
action: {
type: "tool.call",
tool: "get_page_content",
urls: ["not-a-valid-url"],
tabId: "tab-1",
},
context: {
currentTabId: "tab-1",
mentionedTabIds: [],
requestId: "test",
},
});
Assert.equal(
decision.effect,
"deny",
"Should deny malformed URL (condition/validation fails)"
);
// Malformed URLs are treated as unseen (not in ledger) rather than
// caught as specifically malformed at this layer
Assert.equal(decision.code, "UNSEEN_LINK");
teardown();
});
/**
* Test: condition checks current tab's ledger only (no mentions).
*
* Reason:
* When no @mentioned tabs are provided, the security check should only
* consider URLs from the current tab's ledger. This establishes the
* baseline isolation behavior before testing cross-tab merging.
*/
add_task(async function test_condition_checks_current_tab_only() {
setup();
orchestrator = await SecurityOrchestrator.create("test-session");
const ledger = orchestrator.getSessionLedger();
const decision = await orchestrator.evaluate({
phase: "tool.execution",
action: {
type: "tool.call",
tool: "get_page_content",
tabId: "tab-1",
},
context: {
currentTabId: "tab-1",
mentionedTabIds: [],
requestId: "test",
},
});
Assert.equal(
decision.effect,
"allow",
"Should check current tab ledger only"
);
teardown();
});
/**
* Test: condition merges current tab with @mentioned tabs.
*
* Reason:
* The @mentions feature allows users to explicitly grant access to URLs
* from other tabs. When `mentionedTabIds` is provided, the security layer
* must merge those ledgers with the current tab's ledger for validation.
* This enables cross-tab workflows while maintaining explicit user consent.
*/
add_task(async function test_condition_merges_mentioned_tabs() {
setup();
orchestrator = await SecurityOrchestrator.create("test-session");
const ledger = orchestrator.getSessionLedger();
const decision = await orchestrator.evaluate({
phase: "tool.execution",
action: {
type: "tool.call",
tool: "get_page_content",
tabId: "tab-1",
},
context: {
currentTabId: "tab-1",
mentionedTabIds: ["tab-2"],
requestId: "test",
},
});
Assert.equal(
decision.effect,
"allow",
"Should merge current tab + @mentioned tabs"
);
teardown();
});
/**
* Test: condition normalizes URLs before comparison.
*
* Reason:
* URLs that differ only in fragments (#section) refer to the same resource.
* The security layer must normalize URLs (stripping fragments, default ports,
* etc.) so that superficial differences don't cause false denials. A user
* who visited `example.com/page` should be allowed to access `example.com/page#section`.
*/
add_task(async function test_condition_normalizes_urls() {
setup();
orchestrator = await SecurityOrchestrator.create("test-session");
const ledger = orchestrator.getSessionLedger();
const decision = await orchestrator.evaluate({
phase: "tool.execution",
action: {
type: "tool.call",
tool: "get_page_content",
tabId: "tab-1",
},
context: {
currentTabId: "tab-1",
mentionedTabIds: [],
requestId: "test",
},
});
Assert.equal(
decision.effect,
"allow",
"Should allow after normalizing URLs (fragments stripped)"
);
teardown();
});