Skip to content

Sandboxed, permissioned execution for emitters#11102

Draft
timotheeguerin wants to merge 2 commits into
microsoft:mainfrom
timotheeguerin:secure-lib
Draft

Sandboxed, permissioned execution for emitters#11102
timotheeguerin wants to merge 2 commits into
microsoft:mainfrom
timotheeguerin:secure-lib

Conversation

@timotheeguerin

Copy link
Copy Markdown
Member

Summary

Introduces an opt-in OS-level sandbox for running TypeSpec emitters, where users authorize exactly which system capabilities each emitter may use. By default an emitter is granted no access to any system API.

Enabled via compile({ sandbox: true }). Each emitter runs in its own isolated child process with only the permissions the user approved in tspconfig.yaml.

How it works

  • Permission manifest — libraries/emitters declare needed capabilities in $lib:
    export const $lib = createTypeSpecLibrary({
      name: "@typespec/openapi3",
      diagnostics: {},
      permissions: [
        { permission: { kind: "fs-read", paths: ["./schemas"] }, reason: "Read shared JSON schemas" },
        { permission: { kind: "network", hosts: ["*.example.com"] }, reason: "Resolve remote refs" },
      ],
    });
  • User authorization — approved per emitter in tspconfig.yaml:
    permissions:
      "@typespec/openapi3":
        fs-read:
          - ./schemas
        network:
          - "*.example.com"
  • Effective permissions = requested ∩ granted, plus the emitter's always-writable output dir. Ungranted requests yield a permission-not-granted diagnostic with a pasteable tspconfig snippet.
  • OS isolation — child processes use Node's permission model (--allow-fs-read/write, --allow-child-process), a curated environment, and a privileged broker that re-validates every request. Each $onEmit runs in a child that recompiles to rebuild the live Program; emit diagnostics are serialized and re-targeted against the parent's source files.

Permission categories

fs-read, fs-write, network, env, exec — covering both emitters and (in a later phase) libraries.

Scope of this PR

  • ✅ Permission types, set algebra, manifest API
  • ✅ tspconfig grants + resolution + diagnostics
  • ✅ Sandbox runtime (child-process spawner, broker, bootstrap)
  • ✅ Per-emitter sandboxed execution (emitSandboxed)
  • ⏳ Follow-ups (not in this PR): full library/decorator sandboxing, --no-sandbox CLI escape hatch + default-rollout decision, security-model docs

Tests

Unit coverage for permission-set / resolve / permissioned-host / node-args, plus a runtime integration test and an end-to-end emit test (denied / granted / plain emitter). All 45 permissions tests pass.

Note

Draft — opening for early feedback on the design and the OS-isolation approach. sandbox defaults to false (opt-in), so existing behavior is unchanged.

Add an opt-in OS-level sandbox (`compile({ sandbox: true })`) that runs each
emitter in its own isolated child process with only the permissions the user
authorized in `tspconfig.yaml`. By default an emitter is granted no access to
any system API.

- Permission model: shared types, permission-set algebra (merge/intersect/
  diff), and a manifest API so libraries/emitters declare needed capabilities
  (`fs-read`, `fs-write`, `network`, `env`, `exec`).
- Config grants: `permissions` block in `tspconfig.yaml`, resolved into the
  effective set (requested ∩ granted, plus the emitter's always-writable
  output dir).
- Sandbox runtime: child-process spawner using Node's permission model
  (`--allow-fs-read/write`, `--allow-child-process`), curated env, and a
  privileged broker that re-validates each request.
- Per-emitter execution: `emitSandboxed` runs each `$onEmit` in a child that
  recompiles to rebuild the live `Program`; diagnostics are serialized and
  re-targeted against the parent's source files.
- Ungranted requests produce a `permission-not-granted` diagnostic with a
  pasteable tspconfig snippet.

Tests: unit coverage for permission-set/resolve/host/node-args plus runtime
integration and end-to-end emit (denied / granted / plain) scenarios.
@microsoft-github-policy-service microsoft-github-policy-service Bot added the compiler:core Issues for @typespec/compiler label Jun 26, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jun 26, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@typespec/compiler@11102

commit: 71a558e

@github-actions

Copy link
Copy Markdown
Contributor

All changed packages have been documented.

  • @typespec/compiler
Show changes

@typespec/compiler - feature ✏️

Add the foundation for sandboxed, permissioned execution of libraries and emitters. Libraries/emitters can declare the system capabilities they need via a permission manifest, and users approve them per emitter in tspconfig.yaml. By default an emitter/library is granted no access to any system API.,> ,> ts,> // In a library/emitter's `$lib`,> export const $lib = createTypeSpecLibrary({,> name: "@typespec/openapi3",,> diagnostics: {},,> permissions: [,> { permission: { kind: "fs-read", paths: ["./schemas"] }, reason: "Read shared JSON schemas" },,> { permission: { kind: "network", hosts: ["*.example.com"] }, reason: "Resolve remote refs" },,> ],,> });,> ,> ,> yaml,> # tspconfig.yaml — the user authorizes what the emitter requested,> permissions:,> "@typespec/openapi3":,> fs-read:,> - ./schemas,> network:,> - "*.example.com",>

Make the compiler host the single enforced choke point for sandboxed
emitters: after the child recompiles (which needs broad fs access for
module/spec/compiler resolution) the program host is narrowed to the
emitter's effective permissions, so an emitter can only touch the system
through the host within its grant. Emitters may always read their own
package directory, mirroring the always-writable output dir.

Also fix the browser bundle: keep node-only sandbox modules out of the
playground's static graph (direct pure-module imports in index.ts,
browser-field stubs for runtime/emit-runner, dynamic import in
emitSandboxed) and add the new `permissions` tspconfig key to the
completion tests.
@azure-sdk-automation

Copy link
Copy Markdown

You can try these changes here

🛝 Playground 🌐 Website 🛝 VSCode Extension

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

compiler:core Issues for @typespec/compiler

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant