Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* Any copyright is dedicated to the Public Domain.
"use strict";
/**
* Unit tests for SecurityOrchestrator (JSON Policy System)
*
* Focus: Critical security boundaries and core functionality
* - Preference switch behavior (security on/off)
* - Policy execution (allow/deny with real policies)
* - Envelope validation (security boundary)
* - Error handling (fail-closed)
*/
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: initialization creates a session with ledger.
*
* Reason:
* SecurityOrchestrator.create() must initialize a functional session
* with an empty ledger ready for URL seeding. This is the entry point
* for all security layer operations.
*/
add_task(async function test_initialization_creates_session() {
setup();
orchestrator = await SecurityOrchestrator.create("test-session");
const ledger = orchestrator.getSessionLedger();
Assert.ok(ledger, "Should return session ledger");
Assert.equal(ledger.tabCount(), 0, "Should start with no tabs");
Assert.ok(
orchestrator.getSessionLedger(),
"Should be able to get session ledger"
);
teardown();
});
/**
* Test: preference switch disabled allows everything.
*
* Reason:
* When browser.ml.security.enabled=false, all policy enforcement is
* bypassed. This provides a debugging escape hatch and allows the
* feature to be disabled without code changes.
*/
add_task(async function test_pref_switch_disabled_allows_everything() {
setup();
Services.prefs.setBoolPref(PREF_SECURITY_ENABLED, false);
orchestrator = await SecurityOrchestrator.create("test-session");
const ledger = orchestrator.getSessionLedger();
ledger.forTab("tab-1"); // Empty ledger
const decision = await orchestrator.evaluate({
phase: "tool.execution",
action: {
type: "tool.call",
tool: "get_page_content",
urls: ["https://evil.com"], // Unseen URL
tabId: "tab-1",
},
context: {
currentTabId: "tab-1",
mentionedTabIds: [],
requestId: "test-123",
},
});
Assert.equal(
decision.effect,
"allow",
"Pref switch OFF: should allow everything (pass-through)"
);
teardown();
});
/**
* Test: preference switch enabled enforces policies.
*
* Reason:
* When browser.ml.security.enabled=true (the default), policies must
* be enforced. Unseen URLs should be denied. This is the expected
* production behavior.
*/
add_task(async function test_pref_switch_enabled_enforces_policies() {
setup();
Services.prefs.setBoolPref(PREF_SECURITY_ENABLED, true);
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: ["https://evil.com"],
tabId: "tab-1",
},
context: {
currentTabId: "tab-1",
mentionedTabIds: [],
requestId: "test-123",
},
});
Assert.equal(decision.effect, "deny", "Pref switch ON: should enforce");
Assert.equal(decision.code, "UNSEEN_LINK", "Should deny unseen links");
teardown();
});
/**
* Test: preference switch responds to runtime changes.
*
* Reason:
* The preference is checked on each evaluate() call, not cached at
* initialization. This allows toggling security on/off without
* restarting the browser or recreating the orchestrator.
*/
add_task(async function test_pref_switch_runtime_change() {
setup();
Services.prefs.setBoolPref(PREF_SECURITY_ENABLED, true);
orchestrator = await SecurityOrchestrator.create("test-session");
const ledger = orchestrator.getSessionLedger();
ledger.forTab("tab-1");
const envelope = {
phase: "tool.execution",
action: {
type: "tool.call",
tool: "get_page_content",
urls: ["https://evil.com"],
tabId: "tab-1",
},
context: {
currentTabId: "tab-1",
mentionedTabIds: [],
requestId: "req-1",
},
};
// Should deny when enabled
let decision = await orchestrator.evaluate(envelope);
Assert.equal(decision.effect, "deny", "Should deny when enabled");
// Disable at runtime
Services.prefs.setBoolPref(PREF_SECURITY_ENABLED, false);
// Should allow immediately
decision = await orchestrator.evaluate(envelope);
Assert.equal(
decision.effect,
"allow",
"Should allow immediately after runtime disable"
);
teardown();
});
/**
* Test: invalid envelope fails closed.
*
* Reason:
* Malformed envelopes (missing phase, action, or context) must be
* denied rather than allowed. Fail-closed behavior ensures that
* broken or malicious requests don't bypass security checks.
*/
add_task(async function test_invalid_envelope_fails_closed() {
setup();
Services.prefs.setBoolPref(PREF_SECURITY_ENABLED, true);
orchestrator = await SecurityOrchestrator.create("test-session");
const invalidEnvelopes = [
null,
{ action: { type: "test" }, context: {} }, // missing phase
{ phase: "test", context: {} }, // missing action
{ phase: "test", action: { type: "test" } }, // missing context
];
for (const envelope of invalidEnvelopes) {
const decision = await orchestrator.evaluate(envelope);
Assert.equal(
decision.effect,
"deny",
"Invalid envelope should fail closed (deny)"
);
Assert.equal(decision.code, "INVALID_REQUEST", "Should have correct code");
}
teardown();
});
/**
* Test: policy allows seeded URL.
*
* Reason:
* URLs added to the ledger represent user-visible, trusted content.
* Tool calls requesting these URLs should be allowed. This is the
* core functionality enabling legitimate AI-assisted browsing.
*/
add_task(async function test_policy_allows_seeded_url() {
setup();
Services.prefs.setBoolPref(PREF_SECURITY_ENABLED, true);
orchestrator = await SecurityOrchestrator.create("test-session");
const ledger = orchestrator.getSessionLedger();
ledger.forTab("tab-1").add("https://example.com");
const decision = await orchestrator.evaluate({
phase: "tool.execution",
action: {
type: "tool.call",
tool: "get_page_content",
urls: ["https://example.com"], // In ledger
tabId: "tab-1",
},
context: {
currentTabId: "tab-1",
mentionedTabIds: [],
requestId: "test-123",
},
});
Assert.equal(decision.effect, "allow", "Should allow seeded URL");
teardown();
});
/**
* Test: policy denies unseen URL.
*
* Reason:
* URLs not in the ledger are untrusted and potentially injected by
* malicious page content. They must be denied to prevent prompt
* injection attacks from directing tools to attacker-controlled URLs.
*/
add_task(async function test_policy_denies_unseen_url() {
setup();
Services.prefs.setBoolPref(PREF_SECURITY_ENABLED, true);
orchestrator = await SecurityOrchestrator.create("test-session");
const ledger = orchestrator.getSessionLedger();
ledger.forTab("tab-1"); // Empty ledger
const decision = await orchestrator.evaluate({
phase: "tool.execution",
action: {
type: "tool.call",
tool: "get_page_content",
urls: ["https://evil.com"], // Not in ledger
tabId: "tab-1",
},
context: {
currentTabId: "tab-1",
mentionedTabIds: [],
requestId: "test-123",
},
});
Assert.equal(decision.effect, "deny", "Should deny unseen URL");
Assert.equal(decision.code, "UNSEEN_LINK", "Should have UNSEEN_LINK code");
Assert.ok(decision.reason, "Should have reason");
Assert.equal(
decision.policyId,
"block-unseen-links",
"Should identify policy"
);
teardown();
});
/**
* Test: policy denies if any URL is unseen.
*
* Reason:
* All-or-nothing security: a request with multiple URLs must have
* all URLs in the ledger. If any URL is unseen, the entire request
* is denied. Partial trust is not acceptable.
*/
add_task(async function test_policy_denies_if_any_url_unseen() {
setup();
Services.prefs.setBoolPref(PREF_SECURITY_ENABLED, true);
orchestrator = await SecurityOrchestrator.create("test-session");
const ledger = orchestrator.getSessionLedger();
ledger.forTab("tab-1").add("https://example.com");
const decision = await orchestrator.evaluate({
phase: "tool.execution",
action: {
type: "tool.call",
tool: "get_page_content",
urls: [
"https://evil.com", // NOT OK
],
tabId: "tab-1",
},
context: {
currentTabId: "tab-1",
mentionedTabIds: [],
requestId: "test-123",
},
});
Assert.equal(
decision.effect,
"deny",
"Should deny if ANY URL unseen (all-or-nothing)"
);
teardown();
});
/**
* Test: malformed URL fails closed.
*
* Reason:
* URLs that cannot be parsed or normalized cannot be validated
* against the ledger. They must be treated as unseen and denied
* rather than allowed by default.
*/
add_task(async function test_malformed_url_fails_closed() {
setup();
Services.prefs.setBoolPref(PREF_SECURITY_ENABLED, true);
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-123",
},
});
Assert.equal(
decision.effect,
"deny",
"Malformed URL should fail closed (deny)"
);
// Malformed URLs are treated as unseen (not in ledger) rather than
// caught as specifically malformed
Assert.equal(decision.code, "UNSEEN_LINK", "Should have UNSEEN_LINK code");
teardown();
});