Runtime

Plugin Permissions, Sandboxing, and Approval Policy on Windows

Report summary

Platform limitation. A C desktop host cannot honestly claim that optional plugin code is “sandboxed” merely because the plugin declares permissions, is loaded in a separate AssemblyLoadContext, or is gated by a user-consent dialog. On modern .NET, AssemblyLoadContext is for dependency loading and un

Status
Research archive item
Category
Runtime
Length
5,934 words
Reading time
27 minutes
Report type
research-note

Key topics

  • Runtime
  • Agentic Web
  • .NET
  • Python
  • Privacy
  • Semantic Systems
  • Research Archive
  • Strategy

Research provenance

Archive status
Research archive item
Content identity
sha256:3f3fbfad7d61b9ffbbea6b77192903752bdd8449125744178e23a07b54bb3e04

For citation, use the report title and canonical URL. Archival presence does not establish authorship or promote report statements into portfolio evidence.

Source availability: 45 citation markers in the source export have no recoverable source links. Those markers are omitted from this reader; any supplied bibliography and ordinary links remain. Check the original sources before relying on the cited claims.

This page renders the archived Markdown as safe, formatted HTML. It is background research and does not become a portfolio claim without evidence review.

Full report

On this page

Executive recommendation

Platform limitation. A C# desktop host cannot honestly claim that optional plugin code is “sandboxed” merely because the plugin declares permissions, is loaded in a separate AssemblyLoadContext, or is gated by a user-consent dialog. On modern .NET, AssemblyLoadContext is for dependency loading and unloading, not security; all code in the process has the process’s full permissions. .NET Code Access Security and “security transparency” are no longer supported as security boundaries, and Microsoft’s guidance is to use operating-system boundaries such as separate processes, user accounts, containers, or virtualization instead. For packaged desktop apps running at Medium IL, many operations are already available because the app is running as the user; capability declarations there are often signal and privacy plumbing, not confinement.

Platform guarantee. On current Windows, the strongest practical isolation choices for untrusted Windows desktop plugins are AppContainer-based isolation for process/resource confinement and Windows Sandbox or a VM for virtualization-based isolation. Microsoft explicitly describes AppContainer as an isolation boundary with default-deny access to most resources, network isolation, file isolation, credential isolation, process isolation, and window isolation. Microsoft also describes Win32 app isolation/AppContainer as a Windows security boundary, while Windows Sandbox provides a disposable Hyper-V-backed environment whose contents are deleted when it closes.

Platform limitation. Windows Sandbox is not safe by default for this use case if you simply launch it with defaults: networking is enabled by default, clipboard redirection is enabled by default, audio input is enabled by default, and mapped host folders can expose host data. Microsoft specifically warns that enabled networking can expose untrusted applications to the internal network, and that mapped folders can let malicious software affect the system or steal data.

Security synthesis. The recommended architecture is therefore a brokered capability system with three explicit trust lanes. First-party plugins may be allowed outside an OS sandbox only when the product labels them as trusted code with full user authority and still routes high-consequence actions through a broker. Signed partner plugins should run out of process by default, use brokered operations instead of ambient authority, and be eligible for narrowly scoped durable approvals only where the target, action class, and data consequence are stable. Untrusted third-party plugins should never run in-process; they should run in a separate sandboxed process or disposable environment, receive only opaque references or narrow broker calls, and be ineligible for durable grants for browser, email, credential, purchase, destructive, administrative, or broad desktop-automation actions. This aligns with OS realities, least-privilege guidance, deny-by-default guidance, and the CISA principle that the burden of security should not be shifted to the user.

Policy proposal. Use the following defaults:

Trust tierDefault host modelDurable approvalsHigh-consequence capabilities
First-partyOut-of-proc preferred; full-trust only when necessary and clearly labeledExact-operation durable grants possible for low/medium risk; category/app-level only for low-risk readsRuntime prompt or workflow-specific confirmation
Signed partnerOut-of-proc mandatory; AppContainer preferred where compatibleExact-operation durable grants only; no category-level durable grants for high-risk actionsPrompt every time or session-only
Untrusted third-partyOut-of-proc mandatory; AppContainer or Windows Sandbox/VMNo durable grants for browser, email, messaging send/publish, credentials, purchase, admin, destructive, unknown targetsOne-shot only, or disallowed

Scope and assumptions declaration. This report treats the owner-supplied bullet list as unverified requirements context only. It does not assume any present implementation satisfies them. It also respects the stated zero-access boundary: no probing of localhost, no private-network interaction, no source or binary review, no policy inspection, no credentials, no logs, and no operational attack instructions. Everything below is generic, architectural, and nonoperational.

Source and threat-model method. This report prioritizes Microsoft Windows/.NET/Python primary documentation, NIST authorization and zero-trust guidance, OWASP authorization/logging/secrets guidance, CISA Secure by Design guidance, and peer-reviewed or well-established usable-security research on warning fatigue, contextual permissions, and notice design. Where the platform offers a true boundary, the report says so; where it offers only hardening, orchestration, or policy convention, the report says so explicitly.

Capability taxonomy

Policy proposal. Use a capability identifier that never conflates resource class, operation, and scope. A stable form is:

cap.<family>.<resource>.<operation>

Examples:

cap.fs.path.read
cap.fs.path.write
cap.net.http.connect
cap.net.localhost.connect
cap.net.private.connect
cap.process.spawn.exec
cap.ui.clipboard.read
cap.ui.automation.invoke
cap.device.camera.read
cap.device.microphone.read
cap.comm.email.read
cap.comm.email.send
cap.comm.browser.navigate
cap.comm.browser.act
cap.credential.use
cap.commerce.purchase.confirm
cap.system.settings.modify
cap.model.invoke
cap.memory.session.read
cap.remote.session.control

The reason to split these dimensions is that ABAC works best when subject, object, operation, and environment facts are evaluated separately, and because wildcarded permission bundles tend to hide authority expansion. NIST defines ABAC as authorization based on subject, object, operation, and environment attributes, and OWASP recommends attribute-based or relationship-based authorization with deny-by-default and per-request validation.

Policy proposal. Represent each request as a canonical tuple, not a loose permission string:

{
  "plugin": {
    "id": "plugin.example",
    "version": "2.4.0",
    "publisher": "Example Co",
    "signature": "thumbprint-or-signer-id",
    "trustTier": "partner"
  },
  "capability": "cap.comm.email.send",
  "resource": {
    "kind": "mailbox-message",
    "targetApp": "outlook.exe",
    "targetIdentity": "mailbox:primary",
    "resolvedPath": null,
    "resolvedHost": null,
    "resolvedIp": null
  },
  "operation": {
    "verb": "send",
    "destructive": false,
    "publishesExternally": true
  },
  "dataClass": "communications-sensitive",
  "destination": {
    "kind": "email-recipient",
    "scope": "example.com"
  },
  "origin": {
    "localOrRemote": "remote",
    "remoteSessionId": "opaque-session-id",
    "ownerAuthLevel": "phishing-resistant-or-none"
  },
  "interaction": {
    "requiresUserPresence": true,
    "requiresForeground": true,
    "requiresPerActionConfirmation": true
  },
  "timing": {
    "requestedAtUtc": "2026-07-11T00:00:00Z",
    "maxAgeSeconds": 30,
    "frequencyClass": "single"
  },
  "approval": {
    "grantKind": "once",
    "approvalBindingHash": "canonical-request-hash"
  }
}

This is the minimum shape needed to bind policy, consent, and execution to the same concrete request and avoid “allow email” or “allow browser” as ambiguous ambient authority. The environment fields follow NIST’s ABAC model, and the per-request validation follows OWASP authorization guidance.

Policy proposal. Do not allow implicit capability composition. The following should remain separate and never imply each other:

Capability familySensitivityDurable grant defaultRemote eligibleAuto-approval eligibleRequired audit minimum
cap.fs.path.readMediumExact path / subtree onlySometimesLow-risk exact scopes onlyFinal resolved path, path owner class, data class
cap.fs.path.writeHighExact path onlyRareNoFinal resolved path, overwrite/create/delete flags
cap.net.http.connectMediumExact scheme+host+port onlySometimesLow-risk destinations onlyResolved host/IP, scheme, port
cap.net.localhost.connectHighSession only or noneRareNoResolved IP/port/process if known
cap.net.private.connectHighSession only or noneRareNoResolved RFC1918 target, subnet class
cap.process.spawn.execHighExact executable+arg template onlyRareNoExecutable identity, args template, child policy
cap.ui.clipboard.readHighNone or session onlyNoNoRead/write, size class, foreground state
cap.ui.automation.invokeVery highNoneNoNoTarget app identity, action verb, user presence
cap.device.camera.readHighSession onlyRareNoDevice identity, duration
cap.device.microphone.readHighSession onlyRareNoDevice identity, duration
cap.notify.postLowSession or durable exact typeYesSometimesNotification channel, payload class
cap.model.invokeMediumExact model/profile onlyYesSometimesModel ID, input/output size classes
cap.memory.session.readHighSession onlyRareNoSource buffer class, byte-count band
cap.credential.useVery highNone or workflow-boundRareNoSecret alias only, never raw secret
cap.comm.browser.navigateHighExact site/session onlyRareNoSite/origin, target profile class
cap.comm.browser.actVery highNoneNoNoTarget origin, semantic action class
cap.comm.email.readVery highExact mailbox/folder session onlyRareNoMailbox/folder identity only
cap.comm.email.sendVery highNoneRareNoMailbox alias, recipient-domain count band
cap.comm.message.sendVery highNoneRareNoChannel/service, recipient class
cap.commerce.purchase.confirmCriticalNoneNoNoMerchant/app identity, amount band, confirmation proof
cap.system.settings.modifyCriticalNoneNoNoSetting family, target state class

Security synthesis. Microsoft’s own capability model for AppContainer distinguishes internet, internet+server, and private-network access; it separately models privacy-sensitive stores such as contacts and appointments; it separately models devices such as microphone and webcam; and it requires explicit capability declarations for many privacy-sensitive resources. That is a useful signal for the host taxonomy, but not a sufficient desktop-host policy by itself because Medium IL/full-trust desktop apps already run as the user.

Policy proposal. Resource scope must be explicit along these axes:

  1. Resource scope: exact file, subtree, exact host, exact domain suffix, exact app binary identity, exact mailbox/folder, exact notification channel, exact model ID.
  2. Operation: read, write, append, delete, connect, listen, spawn, navigate, act, send, purchase, modify.
  3. Data class: public, user-private, communications-sensitive, credential-adjacent, financial, admin/security.
  4. Destination: none, exact host, exact recipient domain, exact app identity.
  5. Duration: once, foreground action, session, durable exact operation.
  6. Frequency: one shot, bounded repeated, unbounded.
  7. User presence: foreground visible, local user confirmed, local user absent.
  8. Origin: local, locally queued, remote owner session.
  9. Remote binding: remote session ID, assurance level, issued-at, expiry, nonce, endpoint fingerprint.

That structure mirrors NIST’s subject/object/operation/environment model and makes privilege escalation through vague “category” approvals much harder.

Policy proposal. Grant classes should be constrained as follows:

  • Install-time declarations: all capabilities the plugin may ever request, with conservative maxima for scope classes.
  • Runtime requests: every concrete request that touches user data, external side effects, cross-application automation, network destinations, or state-changing operations.
  • One-shot grants: the default for browser actions, email send, purchase, destructive writes, admin changes, localhost/private-network access, credentials, and unknown targets.
  • Session grants: acceptable for repeated low/medium-risk exact-scope reads or model invocations during an active local session.
  • Durable grants: only for low-risk exact operation+scope tuples with stable target identities.
  • Never durable: credentials, purchase, send/publish, destructive, administrative, security-setting, password-manager, browser automation, email send/read, UI automation, localhost/private-network, and unknown custom applications.

This matches the research finding that “ask on first use” is often a poor proxy for later contexts, and that permission decisions are strongly context dependent; it also limits the kinds of grants that can silently outlive the conditions under which they were originally understandable.

Policy proposal. Detect permission expansion on update by computing a normalized manifest diff over:

  • capability IDs
  • broadened resource selectors
  • broadened destination selectors
  • broader duration/frequency
  • broader origin eligibility
  • changed target-app classes
  • newly remote-eligible actions
  • reduced user-presence requirements
  • signer/publisher changes.

Any broadening should invalidate relevant durable grants and require re-consent before first post-update use. Signer change, plugin ID change, or permission broadening should invalidate all prior durable grants for that plugin. This is a policy convention, not a platform guarantee.

Sample. Generic valid manifest:

{
  "pluginId": "partner.report-export",
  "version": "1.2.0",
  "publisher": "Example Partner",
  "requestedCapabilities": [
    {
      "id": "cap.fs.path.read",
      "scope": { "kind": "subtree", "path": "%USERPROFILE%\\Documents\\Reports" },
      "grantEligibility": ["once", "session", "durable-exact"]
    },
    {
      "id": "cap.net.http.connect",
      "scope": { "scheme": "https", "host": "api.example.com", "port": 443 },
      "grantEligibility": ["once", "session"]
    },
    {
      "id": "cap.notify.post",
      "scope": { "channel": "export-status" },
      "grantEligibility": ["session", "durable-exact"]
    }
  ]
}

Sample. Generic invalid manifest patterns:

{
  "requestedCapabilities": [
    { "id": "cap.fs.path.read", "scope": { "path": "C:\\" } },
    { "id": "cap.net.http.connect", "scope": { "host": "*" } },
    { "id": "cap.net.localhost.connect", "grantEligibility": ["durable-exact"] },
    { "id": "cap.comm.email.send", "grantEligibility": ["durable-exact", "durable-app"] },
    { "id": "cap.comm.browser.act", "scope": { "targetApp": "*" } }
  ]
}

Platform limitation. Path and handle semantics must be resolved at execution time, not string-validated once up front. Windows symbolic links and reparse points are transparent to many file APIs; Microsoft recommends FILE_FLAG_OPEN_REPARSE_POINT when the application needs to treat a reparse point as such, and GetFinalPathNameByHandle returns the final path from a handle. That makes final-path validation part of policy enforcement, not just a manifest concern.

Windows enforcement option matrix

Security synthesis. The table below scores mechanisms comparatively for this plugin-host problem on a 0–5 scale, where 5 means “strong / favorable for this dimension” and 0 means “absent / unusable for this dimension.” These are architectural scores, not CVSS-style risk numbers.

MechanismActual security strength.NET compatCPython compatBroker supportChild-process controlNetwork controlFilesystem controlUI automation compatibilityResource governanceDeployment complexityOperational burdenPrecise limitation
In-process full trust05310005012Plugin has the host’s full token and memory space
AssemblyLoadContext05010005012Isolation for loading/unloading only; Microsoft says it has no security features
Out-of-proc full trust, same user15542115223Separate crash/memory domain, but still same user authority unless sandboxed
Restricted token24441123233Reduces token privileges/SIDs, but not a modern, comprehensive app sandbox
Low integrity24431022233Write-up protection is useful, but it is not broad least-privilege confinement
AppContainer43352441344Best practical OS process sandbox here; compatibility cost is real, especially for classic desktop integration
Windows Sandbox54435552445Strongest isolation, but significantly heavier and defaults need hardening
Service broker45553443344Not a sandbox alone; secure only if plugins cannot bypass it
Job Objects15534005522Great for lifecycle/governance, not principal containment
ACLs / DACLs25530042033Strong for named objects you own; weak against ambient user authority elsewhere
Windows Firewall / AppID tags25530405033Network-only, and still separate from file/process/UI authority
VBS / HVCI / platform hardening15500005032Hardens the platform and kernel trust, not plugin resource access semantics

Platform limitation. AssemblyLoadContext does not provide security features, and Microsoft explicitly recommends process boundaries plus IPC for true isolation problems. Likewise, .NET CAS is unsupported as a security boundary, and Microsoft recommends OS boundaries for least privilege.

Platform guarantee. Restricted tokens, integrity levels, and AppContainer each constrain different parts of the Windows authority model. Restricted tokens reduce SIDs/privileges on an access token. Mandatory Integrity Control ensures a process runs at no higher integrity than its executable and supports low-integrity execution. AppContainer adds broader least-privilege process, credential, file, network, and window isolation. These mechanisms are not interchangeable; AppContainer is the most complete app-level isolation primitive in this set.

Platform guarantee. Job Objects are excellent for containment support, especially JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, active-process limits, and per-job/per-process memory limits. Those are strong lifecycle and governance controls for plugin runners and their descendants. They do not, by themselves, restrict data access.

Platform guarantee. Windows can additionally harden plugin-runner processes with mitigation policies such as disabling dynamic code generation, restricting system calls at the NTUser/GDI layer, restricting image loading from remote or low-integrity locations, and requiring signed images. These mitigations are valuable defense in depth and should be applied before or during process initialization, but they do not replace a resource sandbox.

Platform limitation. UI Automation and elevated desktop interaction are not a generic plugin entitlement. Microsoft’s UIAccess documentation says such apps must be signed, installed in secure locations, and marked in the manifest, and it explicitly says UIAccess should not be used by applications that are not assistive technologies. That means UI automation that can cross normal boundaries should be treated as a highly sensitive, narrowly brokered capability, not as a routine plugin privilege.

Platform guarantee. App control is a practical adjunct to sandboxing. Microsoft positions App Control for Business as a security feature that changes Windows from “all code runs unless blocked” to “code runs only if policy says so”; it also covers scripts, installers, and interactive PowerShell sessions via Constrained Language Mode. That makes it suitable for controlling which plugins, helper executables, Python runtimes, and script entry points the host will tolerate on the machine. AppLocker remains useful administratively, but Microsoft does not treat it as a security feature under the same servicing criteria.

Security synthesis. Practical hosting guidance for current Windows:

  • C# first-party plugin: out-of-proc runner, same user, broker-enforced, job object, process mitigations, app control; in-process only if explicitly labeled trusted/full-trust.
  • C# signed partner plugin: out-of-proc runner with broker; AppContainer where compatibility permits; otherwise restricted token + low integrity + mitigations + firewall/AppID tags + ACL-scoped work directories.
  • Python untrusted plugin: never embedded in the main process. Host a separate Python runner or disposable environment; use CPython audit hooks only for telemetry and policy hints, never as the primary sandbox. Python’s own docs say audit hooks are not suitable for implementing a sandbox and that malicious code can bypass hooks added in Python.
  • High-risk third-party or remote-capable plugins: prefer AppContainer plus broker, or Windows Sandbox/VM when compatibility is poor or when browser/email/automation features create too much consequence concentration.

Policy proposal. The host should have four major trust zones:

[Local UI / Consent Surface]
          |
          v
[Policy Decision Point] <---- [Policy Store] <---- [Emergency Stop Latch]
          |
          v
[Broker / Policy Enforcement Point] ---- [Audit Receipt Service]
    |            |             |
    |            |             +---- [Secret Vault Adapter]
    |            |
    |            +---- [OS Capability Adapters: fs, net, mail, browser, model, process]
    |
    +---- [Plugin Runner: First-party]
    +---- [Plugin Runner: Signed Partner]
    +---- [Plugin Runner: Untrusted Third-party Sandbox]

This follows NIST’s PDP/PEP separation: the decision point evaluates policy using request-specific attributes, and the enforcement point performs the action only after it has revalidated the concrete resource.

Policy proposal. Broker responsibilities:

  1. verify plugin identity, signature, version, trust tier, and update state;
  2. resolve concrete resources before decision and again before execution;
  3. translate abstract capabilities into narrow operations, not raw handles;
  4. mediate all durable grants and bind them to exact tuples;
  5. enforce emergency stop and session state before every action;
  6. collect privacy-safe receipts;
  7. expose revocation and audit UX.

Policy proposal. Plugin responsibilities:

  1. declare maximum needed capabilities at install/update time;
  2. request concrete operations at runtime;
  3. operate on broker-returned value objects only;
  4. never receive ambient access tokens, unbounded file handles, or unrestricted automation objects;
  5. tolerate denial and re-prompt outcomes.

Security synthesis. The broker should expose operations like these, rather than OS handles or arbitrary COM objects:

  • ReadText(pathRef, maxBytes, encodingPolicy)
  • WriteFile(pathRef, createMode, hashExpected)
  • HttpFetch(destRef, method=GET|POST, bodyClass, timeoutClass)
  • SpawnAllowlisted(exeRef, argTemplateId, cwdRef, envProfileId)
  • ShowNotification(channelId, payloadClass)
  • InvokeModel(modelId, inputClass, tokenBudgetBand)
  • ComposeDraft(mailboxAlias, recipientScope, bodyClass)
  • SendComposedDraft(draftId)
  • BrowserNavigate(profileClass, siteRef)
  • BrowserAct(profileClass, siteRef, semanticAction)
  • UseSecret(alias, operation=sign|fetch-token|smtp-send, neverReturnRaw=true)

That shape directly reflects the least-privilege and per-request-authorization guidance from NIST and OWASP.

Policy proposal. Preventing broker bypass requires removing ambient user authority from the plugin runner as far as Windows allows:

  • No plugin DLLs in the main host process.
  • No inherited handles except an explicit allowlist. Windows documents that inheritable handles flow to child processes if bInheritHandles=TRUE; use PROC_THREAD_ATTRIBUTE_HANDLE_LIST to provide only specific inheritable handles.
  • Child-process creation restricted where possible, and all allowed descendants assigned to the same job object with kill-on-close. Microsoft documents child-process policy attributes and job assignment support at process creation time.
  • Work directories created per runner with ACLs and, where applicable, AppContainer BFS/path grants instead of broad host-folder exposure.
  • Firewall rules scoped to the plugin runner or AppID tags for allowlisted network use. Microsoft’s firewall guidance supports App Control AppID tags and explicit precedence where block wins over allow.
  • App Control/WDAC for the plugin runner, helper binaries, and script engines, so a plugin cannot simply pivot to arbitrary child tools.

Platform limitation. Browser and email capabilities are never just “network” or “UI” capabilities. Browsers can hold saved passwords, payment methods, SSO state, cookies, and authenticated sessions; email clients can expose mailbox contents and trigger send operations with external side effects. Microsoft Edge documentation describes saved payment info and autofill behavior, and Microsoft Outlook displays a security prompt when a program tries to send email on the user’s behalf. Because these processes concentrate many identities and side effects, a plugin that can drive them is effectively being trusted with more than one narrow act.

Policy proposal. Label the following honestly in product UX:

  • Trusted plugin: “Runs with your user authority outside the Windows sandbox boundary.”
  • Brokered plugin: “Can act only through the broker for approved operations; some OS-level isolation is applied.”
  • Sandboxed plugin: “Runs in a separate Windows sandboxed process or disposable environment, with brokered capabilities only.”

This wording matters because false reassurance undermines informed consent and contradicts the Secure by Design principle that the burden should not be shifted to the user through vague abstractions.

Policy evaluation and approval model

Security synthesis. The policy model should be ABAC with deny-overrides. Inputs should include: plugin identity, signer, trust tier, capability ID, exact resource after canonical resolution, exact destination after hostname/IP resolution, local vs remote origin, remote-session facts, target app identity, session state, user presence state, current safety state, prior grants, plugin update state, and emergency-stop state. NIST defines ABAC around subject, object, operation, and environment attributes, and NIST’s zero-trust guidance emphasizes that resource requests should not rely on implied trust from an earlier authentication event. OWASP recommends deny by default and validating authorization on every request.

Policy proposal. Recommended evaluation order:

  1. emergency stop latch
  2. host safety state and session availability
  3. plugin identity/signer/trust tier validity
  4. plugin update or signer drift invalidation
  5. capability declaration admissibility
  6. concrete resource resolution and target validation
  7. exact deny rules
  8. exact allow rules
  9. wildcard/subtree allow rules
  10. whether user presence is required now
  11. whether remote origin is permitted at all
  12. whether an existing grant is still fresh and still bound
  13. whether runtime approval is required
  14. execution-time revalidation
  15. receipt creation.

Policy proposal. Conflict resolution rules:

  • Deny beats allow at every specificity level.
  • Exact resource/target beats wildcard when both are allows.
  • Stale grant loses to current policy version or current signer state.
  • Clock uncertainty: if local clock skew or expired session evidence makes freshness ambiguous, fail closed for high-risk capabilities and require fresh approval.
  • Unknown target identity: treat as higher risk and withhold durable options.
  • Permission broadening on update: invalidate all grants that depend on the broadened dimension.

This is consistent with Windows Firewall’s documented precedence, where explicit block rules beat conflicting allows and more-specific rules beat less-specific ones absent an explicit block.

Policy proposal. Every grant should be bound to a canonical request hash:

approvalBindingHash = H(
  pluginId,
  pluginVersion,
  signerId,
  capabilityId,
  concreteResource,
  concreteDestination,
  targetAppIdentity,
  originClass,
  remoteSessionId-or-null,
  userPresenceRequirement,
  policyVersion,
  grantKind,
  expiry
)

That hash should be stored with the approval receipt and rechecked at execution. The grant is not “allow email” or “allow browser”; it is “allow this plugin version from this signer to perform this exact semantic action against this exact scope under these conditions.” This is a policy convention designed to avoid TOCTOU and replay.

Sample. Deterministic evaluation pseudocode:

function evaluate(request, state):
    if state.emergencyStopLatched:
        return Deny("EMERGENCY_STOP")

    if !verifyPluginIdentity(request.plugin, state.trustStore):
        return Deny("PLUGIN_IDENTITY_INVALID")

    if pluginUpdateChangedSignerOrId(request.plugin, state.previousPluginRecord):
        invalidatePluginGrants(request.plugin.id)

    if !declaredInManifest(request.plugin, request.capability):
        return Deny("UNDECLARED_CAPABILITY")

    resolved = resolveConcreteTargets(request)
    if !resolved.ok:
        return Deny("UNRESOLVABLE_TARGET")

    if matchesAnyExactDeny(request, resolved, state.policy):
        return Deny("EXACT_DENY")

    if request.origin.localOrRemote == "remote":
        if !state.remoteSession.active:
            return Deny("REMOTE_SESSION_INACTIVE")
        if !bindsToRemoteSession(request, state.remoteSession):
            return Deny("REMOTE_BINDING_INVALID")

    if requiresUserPresence(request.capability, resolved):
        if !state.localUser.presentOrConfirmed:
            return RequireApproval("USER_PRESENCE_REQUIRED")

    grant = lookupGrant(request, resolved, state.grants)
    if grant.exists:
        if !grantFresh(grant, state.policyVersion, request.plugin):
            return RequireApproval("STALE_GRANT")
        if !grant.bindingHashMatches(request, resolved, state):
            return Deny("GRANT_BINDING_MISMATCH")
        if !executionTimeRevalidate(request, resolved, state):
            return Deny("EXECUTION_REVALIDATION_FAILED")
        return Allow("BOUND_GRANT")

    risk = classifyRisk(request, resolved, state)
    if risk == "critical":
        return RequireApproval("CRITICAL_RUNTIME_APPROVAL")
    if risk == "high":
        return RequireApproval("HIGH_RUNTIME_APPROVAL")
    if risk == "medium" and state.autoApprovalEligible(request, resolved):
        return Allow("AUTO_APPROVED_EXACT_LOW_CONSEQUENCE")
    return RequireApproval("RUNTIME_APPROVAL")

Sample. Deterministic test corpus:

TestInputExpected
Deny precedenceExact deny on cap.fs.path.write to ...\Finance plus subtree allow on ...\DocumentsDeny
Symlink escapePath string inside allowed subtree, final handle resolves outside subtreeDeny
DNS rebindingApproved host name resolves to different IP class at execution timeRevalidate; deny if outside approved class
Update broadeningPlugin v1 had read only; v2 adds writePrior durable grants invalidated; re-consent required
Signer changeSame plugin ID, new signerAll durable grants invalidated
Remote binding lossCommand arrives after remote session expiryDeny
Emergency stop raceQueued work with prior approval, stop latch set before executeDeny/cancel
Unknown target appCustom app binary not recognizedOne-shot only or deny, no durable options
Category durable withholdingEmail send requested with “always allow app”UI must not offer it
Stale policy versionGrant bound to older policy versionRequire re-evaluation

Policy proposal. Approval options by risk class:

Risk classAllow onceDenyAllow for sessionAlways allow exact operationAlways allow appAlways allow category
LowYesYesYesYesSometimesRarely
MediumYesYesYesSometimesRarelyNo
HighYesYesSometimesRarelyNoNo
Very high / CriticalYesYesRarelyNoNoNo

Policy proposal. Withhold durable options entirely for:

  • credentials and password managers
  • purchases or payment confirmation
  • email/message send or publish
  • destructive writes/deletes
  • admin/security setting changes
  • security tool exceptions
  • browser actions with authenticated or unknown state
  • any unknown target application
  • UI automation and accessibility automation
  • localhost/private-network/loopback access
  • remote-origin requests unless the local user explicitly creates a bounded remote session.

Approval-risk matrix. The following durable-grant policy is recommended:

Target classReadWriteSend / PublishPurchaseCredentialDestructiveAdministrative
BrowserSession exact-site onlyOne-shot exact-site onlyOne-shot onlyNeverNeverNeverNever
Email clientSession exact-folder read onlyDraft-only session perhapsOne-shot onlyN/ANeverNeverNever
Messaging appSession exact-channel read onlyOne-shot exact-channel onlyOne-shot onlyN/ANeverNeverNever
File managerExact-path/sessionExact-path/onceN/AN/AN/AOne-shot onlyNever
TerminalNo durableNo durableN/AN/ANeverNo durableNo durable
Developer tool / IDESession exact workspaceExact workspace onlyNo durableN/ANeverOne-shot destructive onlyNever
Password managerNeverNeverNeverN/ANeverNeverNever
Finance appSession exact-view onlyOne-shot onlyOne-shot onlyOne-shot with extra confirmationNeverNeverNever
System settingsRead perhapsOne-shot onlyN/AN/ANeverN/ANever durable
Accessibility automationNo durableNo durableN/AN/ANeverNo durableNever
Camera / microphoneSession onlyN/AN/AN/AN/AN/AN/A
Custom unknown appOne-shot onlyOne-shot onlyOne-shot onlyNeverNeverNeverNever

External guidance. Repeated warnings and poorly differentiated prompts produce habituation. Research has shown that repeated security warnings are tuned out over time, that habituation can generalize from non-security notifications to security warnings if they share look and feel, and that permissions are strongly context dependent rather than well handled by a single “ask on first use” decision. This is the research basis for preferring high-specificity prompts, exact-scope durable rules only for lower-risk operations, and withholding broad durable grants for high-consequence actions.

Consent and warning guidance. “Simple” and “Advanced” views should expose the same authority boundary. The simple view should summarize actor, action, target, and consequence in plain language. The advanced view should reveal the exact plugin ID, signer, capability ID, resource path/final path, destination host/IP, target app identity, grant duration, local/remote origin, and revocation consequences. The difference is detail level, not hidden authority. This follows best-practice notice design and the principle that good security warnings should be necessary, explained, actionable, and tested.

Sample. Suggested information structures:

  • Install permission summary: plugin identity, signer, trust tier, max requested capabilities, “full trust” label if applicable, remote eligibility, ineligible durable categories.
  • Runtime approval: who, what, exact target, whether local or remote, whether user presence is required, grant options, and a plain-English side-effect statement.
  • Durable-rule creation: exact tuple being remembered, expiry/revocation conditions, what updates invalidate it.
  • New permissions on update: old vs new capabilities, why it matters, what prior grants are revoked.
  • Browser warning: authenticated sites, saved passwords/payment methods/session state may exist in the browser context.
  • Email-send warning: external side effect, recipient-domain class, mailbox alias, and no durable approval option.
  • Credential warning: plugin will not receive raw secret; the broker will perform a bounded secret-backed action.
  • Purchase warning: amount band, merchant/app identity, mandatory foreground confirmation.
  • Remote-origin warning: identifies the remote owner session, assurance level, endpoint, and local override availability.
  • Revocation and emergency stop: what stops immediately, what stays denied, and how to resume.

Remote authority, secrets, and privacy-safe audit

Platform limitation. Remote origin is a materially different environment condition from local origin. NIST’s zero-trust guidance says the request itself must be validated, not just the subject once at session start; NIST 800-63 guidance also calls for session monitoring and action when fraud or anomalies are detected during a session. For this design, that means remote control must be bound to an explicit remote session and re-evaluated continually, not piggybacked on a prior approval dialog or a generic “logged in” state.

Policy proposal. Each remote-capable request should bind at minimum to:

  • remote session ID
  • remote owner identity and assurance level
  • issuing endpoint fingerprint
  • issued-at and expiry
  • nonce / monotonic request number
  • local desktop opt-in state
  • current local user presence state
  • plugin identity and approval-binding hash
  • intended target app/resource/destination.

Any mismatch at execution should fail closed.

Policy proposal. Local controls must always override remote authority:

  • local emergency stop
  • local revocation of durable rules
  • local pause/deny of current remote session
  • local requirement for foreground confirmation on high-consequence actions
  • local visible indicator that remote-origin requests are active
  • local audit viewer that cannot be hidden behind generic reassurance.

Policy proposal. Emergency stop semantics should be latched and fail closed:

  1. new requests denied immediately;
  2. queued work invalidated;
  3. session grants suspended or revoked;
  4. active plugin runners instructed to stop;
  5. child processes killed via job object where applicable;
  6. durable rules for remote-origin actions disabled until explicit local reset;
  7. receipts record the stop reason and affected operations.

Job Objects directly support kill-on-close behavior for process trees, which is useful for implementing the stop boundary.

Policy proposal. Restart, network loss, service outage, and stale command behavior:

  • Restart: local safety defaults reloaded first; no queued privileged action resumes without full re-evaluation.
  • Network loss: remote commands fail closed; no optimistic replay.
  • Service outage: broker continues enforcing local-deny logic; lack of remote confirmation never expands privilege.
  • Stale commands: expire by issued-at + max-age and by remote session continuity.

Secrets model. Plugins should receive references or brokered secret-backed operations, never raw secrets, passwords, SMTP credentials, OAuth refresh tokens, browser cookies, or API keys. The broker should resolve a secret alias to a bounded action such as “sign this challenge,” “obtain short-lived access token for host X,” or “send mail through mailbox Y,” and return only the operation result. OWASP’s Secrets Management guidance emphasizes centralization, access control, rotation, and auditing; Windows DPAPI can protect local secret material at rest using the current user or machine context, which is useful for broker-side encrypted storage if an external vault is unavailable.

Privacy-safe audit. Audit should prove identity, decision, scope, policy, execution, and revocation without storing prompts, private files, email content, credentials, or screenshots. Recommended fields:

  • audit event ID and UTC timestamp
  • local monotonic sequence number
  • plugin ID, version, publisher, signer thumbprint
  • trust tier
  • local user/session ID
  • origin class local/remote
  • remote session ID and endpoint fingerprint if applicable
  • capability ID and semantic action class
  • final resolved path / host / IP / target app identity
  • data-class band and size band
  • matched rule ID and policy version
  • decision: allow / deny / prompt / revoked / stopped
  • approval-binding hash
  • execution result and error class
  • receipt hash / correlation ID
  • emergency-stop or revocation cause if applicable.

External guidance. OWASP and NIST both recommend logging security decisions while avoiding unneeded sensitive data; NIST specifically recommends append-only privileges where feasible, avoiding unneeded sensitive data such as passwords, and protecting archived logs against tampering.

Threat model and residual risk

Threat model. The host should explicitly model at least the following threats:

ThreatMain failure modeRequired controlResidual risk
Malicious pluginUses granted capability for abuseExact-scope broker permissions, deny-by-default, no ambient authorityUser may still approve harmful but specific action
Compromised publisherSigned update broadens behaviorUpdate diffing, signer pinning, durable-grant invalidationTrusted publisher compromise remains serious
Confused deputyBroker uses its own authority on behalf of wrong actorApproval binding, exact target binding, broker-side revalidationBroker bugs can still misapply authority
Permission launderingPlugin pivots through browser/email/terminalHigh-consequence capability classes with no durable broad grantsLocal user can still approve one-shot misuse
Ambient authority bypassSame-user process touches host resources directlyAppContainer or reduced runner, ACLs, app control, firewall, child-controlFull-trust runners remain trusted code
Symlink / path escapeApproved path resolves elsewhereHandle-based final-path validation, reparse-point handlingFilesystem race bugs are still possible
DNS rebindingApproved hostname resolves to local/other target laterResolve and validate at execution; bind host+IP class; short max-ageDNS and cache edge cases remain tricky
Localhost abusePlugin or web content reaches local privileged servicesSeparate localhost capability; default deny; browser local-network checks not relied on aloneSame-device software is highly privileged by nature
Child-process escapePlugin launches unrestricted helperChild-process policy, job object, app control, allowlisted spawns onlySome tools may still be needed operationally
Handle inheritancePlugin child inherits broker handlesExplicit handle list only; noninheritable by defaultCoding errors can reintroduce inheritance
UI spoofingMalicious content imitates consent UISecure/trusted prompt surface, signed trusted UI, stable visualsAdvanced social engineering remains possible
Prompt injectionExternal content manipulates plugin/agent decision pathTreat model output as untrusted; capability mediation; high-risk actions confirmed separatelyPrompt injection is not fully solved in agentic systems
Stale grant / replayOld approval reused in new contextBinding hash, expiry, policy version, remote nonce, per-exec revalidationClock and persistence bugs can weaken this
Policy downgradePlugin or update seeks broader defaultsSigned policy store, version monotonicity, migration testsAdministrative misconfiguration still matters
Audit tamperingEvents hidden or alteredAppend-only / remote append / digests / encryptionFull host compromise can still damage local logs
Emergency-stop raceApproved work executes after stopStop latch checked before execute, job kill-on-close, queue invalidationIn-flight external side effects may already have happened

External guidance. DNS rebinding is a real class of risk for local services and must be checked on the actual connection target; the Local Network Access specification says checks must be performed for each new connection, and OWASP’s SSRF guidance warns against URL consistency and TOCTOU issues such as DNS rebinding. The same specification also notes that loopback and local-network mitigations do not fully solve local-service attacks by themselves.

Platform limitation. You should not rely on browser-side local-network controls alone. Chromium/WebView2 local-network protections are a useful defense-in-depth measure, but WebView2 documents local-network checks as a feature that must be enabled by the app for configuration, and the web-platform specification itself documents rollout limitations and incomplete mitigation scope. For a desktop host, localhost/private-network policy belongs in the broker, not only in the browser engine.

Platform limitation. CPython audit hooks improve visibility but are explicitly not a sandbox, and Python’s docs warn that malicious code can trivially disable or bypass hooks added from Python. Therefore, “sandboxed Python plugin” is only honest when the Python runtime itself is outside the trust boundary and constrained by the OS or virtualization layer.

Verification, open questions, and sources

Verification strategy. Automated verification should include:

  • unit tests for capability parsing, canonicalization, matching, deny precedence, expiry, signer drift, remote-session binding, emergency-stop evaluation, and update-diff invalidation;
  • schema tests for valid/invalid manifests and grant serialization;
  • property-based tests for path/domain/subtree matching and wildcard restrictions;
  • negative broker tests proving that raw secret retrieval, unrestricted process spawn, broad browser/email grants, and unknown-target durable grants are refused;
  • containment integration tests that validate actual filesystem, registry, network, child-process, and handle-inheritance behavior for each runner type;
  • remote-origin tests covering replay, stale requests, loss of local opt-in, and service outages;
  • approval-binding tests proving that modified destination/resource/target-app details break the grant;
  • restart/revocation tests proving that queued work is canceled and grants are invalidated as designed;
  • adversarial fixture plugins that attempt path escapes, localhost access, child-process breakout, handle inheritance, UI spoofing, and deceptive consent text.

OWASP explicitly recommends creating unit and integration test cases for authorization logic, and NIST’s decision/enforcement separation supports testing those functions independently.

Security synthesis. Several claims require specialist Windows security assessment rather than ordinary QA:

  • whether a chosen AppContainer or CreateProcessInSandbox profile truly blocks the intended filesystem, network, registry, and UI interactions for your exact plugin workload;
  • whether the broker leaks authority through COM, inherited handles, shared folders, or helper processes;
  • whether browser/email automation surfaces can be mediated without semantic confused-deputy failures;
  • whether emergency-stop and queue cancellation are race-free for externally visible side effects;
  • whether remote-session binding and local override controls are strong against replay and stale-state bugs.

Open implementation and policy questions.

  • Unknown. Whether to standardize on packaged AppContainer/Win32 app isolation, newer sandbox APIs, or a VM-backed lane for the third-party tier depends on compatibility tolerance and minimum OS baseline. Microsoft’s newer sandbox API documentation is promising, but its schema is still explicitly versioned, and it notes that nested sandbox composition is not yet finalized.
  • Policy proposal. Decide whether third-party browser/email automation should exist at all, or only for first-party/signed-partner plugins with one-shot approvals.
  • Policy proposal. Decide whether category-level durable grants should be forbidden globally, or allowed only for strictly low-risk read-only classes.
  • Policy proposal. Decide whether remote control should require locally visible foreground indication at all times, even for read-only operations.
  • Policy proposal. Decide whether the “no account for local core use” requirement also implies purely local policy storage and local-only durable grant management.

Limitations and residual risk. Even a well-designed system cannot eliminate several truths: a full-trust plugin remains trusted code; browser/email/desktop-automation capabilities concentrate many identities and side effects that Windows does not turn into semantically safe “single actions”; per-request consent can still be socially engineered; and local software on the same machine can remain highly privileged relative to loopback and user data. NIST’s zero-trust guidance is therefore the right mindset: verify every request, keep trust zones small, and avoid assuming that a previously authenticated or previously approved entity remains safe for later resource requests.

Sources. The most load-bearing sources for this report are Microsoft Learn documentation on AssemblyLoadContext, unsupported .NET security sandboxing mechanisms, AppContainer isolation, Win32 app isolation, Windows Sandbox, restricted tokens, Mandatory Integrity Control, process mitigation policies, Job Objects, UIAccess, Windows Firewall/AppID rules, and App Control for Business; Python documentation and PEPs on audit hooks and their sandbox limitations; NIST SP 800-162 on ABAC, NIST SP 800-207 on zero trust, and NIST guidance on session monitoring and log management; OWASP cheat sheets for authorization, logging, SSRF, and secrets management; CISA Secure by Design guidance; and usable-security research on notice design, warning habituation, and contextual permissions.