Skip to main content

ADR-015 — Extension UX modes & lexicon refactor

1. Context

Two coupled problems with current extension UX:

Problem A — Wrong terminology: 163 source files reference "PII" in labels, popup HTML, badge tooltips, onboarding copy, i18n keys, CSS classnames. But Noxys actually detects far more: secrets (API keys, JWT, tokens), proprietary code/IP, business confidentials (M&A, layoff plans via intent classifier), multi-modal risks (images via OCR per ADR-011), shadow AI usage signals, and EU AI Act compliance events. "PII" understates the product by ~70%.

Problem B — Single-mode UX: current UX assumes a self-service consumer install — visible onboarding, rich popup, settings panel, easy disable button. Inappropriate for enterprise deployments where IT pushes the extension via policy and end-users must not be able to disable, configure, or even fully see what the extension is doing. We also need automatic enrollment so end-users never type a tenant ID.

The 3 deployment methods rule (memory feedback_three_deployment_methods.md) keeps customer-facing surface at 3. This ADR adds modes within the existing extension surface — not a 4th method.

2. Decision summary

  • Two operational modes: Self-Service (Mode A, default) and Managed (Mode B). Detected at boot, not user-toggled.
  • Mode B auto-provisioning: tenant_id + enrollment JWT pushed via chrome.storage.managed (Chrome ADMX / Edge / Firefox policies.json / macOS configuration profile). Extension auto-enrolls on first run, no user interaction.
  • Mode B tamper resistance: heartbeat to /api/v1/extension/heartbeat every 5 min; integrity hash check at boot; console alerts admin on silence >30 min or hash mismatch; OS-level disable lock via ExtensionInstallForcelist.
  • Lexicon migration: "PII" demoted from top-level term to one sub-category. Top-level becomes "Risk Signals" with categories pii / secret / code / ip / confidential / shadow-ai.
  • Collection invariant, display variable: Mode A and Mode B capture identical data; only the UI surface differs. Privacy notice + DPIA cover this.
  • Tier gating: Mode B only available to Pro+ (Free tier locked to self-service to prevent local-bypass to Pro features). Enterprise = managed mandatory.

3. Mode detection at boot

// src/background/mode-detect.ts (new)
export async function detectMode(): Promise<"managed" | "self-service"> {
const managed = await chrome.storage.managed.get(["tenant_id", "enrollment_jwt"]);
if (managed.tenant_id && managed.enrollment_jwt) return "managed";
return "self-service";
}

chrome.storage.managed is read-only and signed by the OS policy mechanism — cannot be spoofed by user-land code.

4. UX matrix

SurfaceMode A (self-service)Mode B (managed)
Onboarding5-step wizardNone — silent enroll
Popup bodyStats + detection feed + tipsLogo + green dot + 1 line "Protected by Noxys"
Settings pageFullReturns 404 / hidden route
Disable toggleVisibleRemoved; OS lock via ExtensionInstallForcelist
Coaching overlayVerboseSilent (admin-toggleable)
Badge counterVisibleHidden (counter-productive — "I did something wrong" feel)
Devtools warningNoneLogs devtools_open event (non-blocking)
Telemetry payloadIdenticalIdentical

Principle: collection invariant, display variable.

5. Auto-enrollment flow (Mode B)

  1. IT pushes managed config via OS policy:
    {
    "tenant_id": "acme-corp",
    "enrollment_jwt": "<short-lived JWT signed by noxys-api>",
    "api_url": "https://api.noxys.cloud",
    "channel": "stable"
    }
  2. Extension reads via chrome.storage.managed.get at boot.
  3. POST /api/v1/extension/enroll with enrollment_jwt → API validates tier (must be Pro+), returns long-lived extension_token.
  4. Stored in chrome.storage.local, wrapped via WebCrypto + (when available) DPAPI/Keychain via native messaging.
  5. Heartbeat starts; first sync of policies + recognizers.

If enrollment_jwt rejected (tier mismatch, expired, revoked): extension stays in dormant Mode B — UI invisible, no detection, retries every 24h. Admin sees "Enrollment failed" in Console.

6. Tamper resistance (Mode B only)

MechanismImplementation
Disable lockExtensionInstallForcelist (Chrome/Edge enterprise built-in)
HeartbeatPOST /api/v1/extension/heartbeat every 5 min; payload includes version, channel, build, last_event_ts
Silence alertConsole raises P2 alert if no heartbeat for >30 min on any enrolled user
Integrity checkAt boot, hash CRX manifest + key files (background/index.js, content-scripts/prompt-interceptor.js); compare to expected hash from /api/v1/extension/integrity?version=X.Y.Z; mismatch → log + dormant
Devtools detectionContent-script monitors window.__noxys_devtools_open heuristic; logs event (non-blocking)
Incognito coverageMode B sets incognito: split — events captured even in incognito (Mode A respects incognito boundary)

7. Lexicon migration plan

Replacement table (binding for all UI surfaces, i18n keys, code identifiers):

OldNew
PII detectedRisk signal blocked
PII alertsRisk events
PII this sessionEvents this session
Auto-mask PII before sendingAuto-redact sensitive content before sending
pii.alert.label (i18n key)risk.event.label
.pii-banner (CSS).risk-banner
.pii-item (CSS).risk-item
pii_count (storage key)risk_event_count (with migration shim for 1 minor version)

Categories visible in Pro+ UI:

  • pii — names, emails, phone, ID numbers (existing Presidio recognizers)
  • secret — API keys, JWT, tokens, certs
  • code — proprietary code snippets, repo paths
  • ip — design docs, internal architecture, patents-in-progress
  • confidential — financials, M&A, layoff plans (via intent classifier per AI-PLACE-4)
  • shadow-ai — usage of unsanctioned LLM service

Storage migration: extension v0.x.0+1 reads pii_count, writes risk_event_count, keeps both for one minor; v0.x.0+2 drops pii_count.

8. Tier matrix

ModeFreeProEnterprise
A self-serviceyes (default)yes
B managedoptionalmandatory

Free tier locked to Mode A — prevents local-machine bypass to unlock Pro-only AI capabilities (per ADR-014).

9. Sprint plan

8 sprints, 17 days total. Distributed W23-24 → W27-28.

IDTitleRepoWindowEffortPriority
EXT-UX-1Lexicon refactor — PII → Risk Signals (163 files)noxys-extensionW23-242dP1
EXT-UX-2Mode detection at boot (managed vs self-service)noxys-extensionW23-241dP0
EXT-UX-3Managed-mode UI — minimal popup, hidden options routenoxys-extensionW23-242dP1
EXT-UX-4Auto-enrollment — chrome.storage.managed + JWT exchangenoxys-extension + noxys-apiW23-243dP0
EXT-UX-5ADMX / policies.json / macOS config profile templatesnoxys-infraW25-262dP1
EXT-UX-6Heartbeat + integrity hash + console silence alertsnoxys-extension + noxys-apiW25-263dP1
EXT-UX-7Risk Signal categorization UI (multi-cat replaces mono-PII)noxys-extensionW25-262dP2
EXT-UX-8Console — managed deployment dashboard (heartbeat status, tamper alerts)noxys-consoleW27-282dP2

Dependencies:

  • EXT-UX-3 → EXT-UX-2
  • EXT-UX-4 → EXT-UX-2; depends on /api/v1/extension/enroll (already in ENROLL-1)
  • EXT-UX-6 → EXT-UX-4
  • EXT-UX-8 → EXT-UX-6

Mode B = workplace monitoring tool. Legal binding requirements:

  1. CNIL Art 88 RGPD: employer must declare to works council (CSE) before deployment in France. Provide template notice (FR/EN/DE) in noxys-console/Compliance page (covered by AI-PLACE-6).
  2. Extension store listings: "Used as managed enterprise extension" disclosure in Chrome Web Store + AMO descriptions.
  3. DPIA template: AI-PLACE-6 generates per-tenant DPIA referencing Mode B specifically.
  4. Privacy notice: Mode B end-users see one-time toast on first detection: "This device is protected by Noxys, deployed by your organization. [Learn more]" — link to tenant-customizable notice page.

11. Non-goals

  • Native Windows app / driver-level capture (out of scope; that's the endpoint-agent surface).
  • Mode B for self-installed users (would defeat tier gating).
  • Dynamic mode switching mid-session (mode = boot-locked).
  • Forcing Mode B onto consumer Chrome without enterprise policy (impossible by design + would violate store policies).

12. Open questions

  • Firefox managed install maturity: confirm 3rdparty.<extension-id> policy carries enrollment payload reliably across Firefox 115+ ESR. Mitigation: degrade to in-extension first-run pairing screen if managed storage unavailable.
  • Integrity hash registry: per-version hash served by API needs CDN cache + stale-while-revalidate to avoid false-positive on rolling auto-update windows.
  • macOS native messaging for keychain wrap: optional or required for v1? Default to optional (WebCrypto-only) — keychain via separate sprint if needed.

13. Refs

  • ADR-013 Extension versioning (channels + telemetry headers reused for heartbeat)
  • ADR-014 AI placement (Mode B forces SLM ON without toggle)
  • Memory: feedback_three_deployment_methods.md
  • Memory: project_pricing_tiers.md
  • Existing: ENROLL-1 (extension enrollment endpoint backend)
  • Existing: 163 PII references inventoried via grep -r "PII\|pii" src/