How to Configure app.json Manifest for Permissions, Windows, and Security Policies in Native SDK

The Native SDK uses an app.zon manifest file (Zig Object Notation) to declare global capabilities, window-specific security policies, and bridge command restrictions, which are parsed by src/tooling/manifest.zig and enforced at runtime by the security layer in src/runtime/builtin_bridge.zig.

The vercel-labs/native repository provides a Native SDK that requires explicit capability declarations through a manifest file. While developers often search for app.json configuration patterns, this SDK implements a strict schema using app.zon (Zig Object Notation) to define permissions, window properties, and security policies that the runtime validates before executing privileged operations.

Understanding the Manifest Format and Location

Unlike traditional Electron or React Native apps that use app.json, the Native SDK expects a root-level app.zon file. This file is parsed during the build process by src/tooling/manifest.zig, specifically through the readMetadata function, which validates the schema defined in src/tooling/raw_manifest.zig.

The manifest structure consists of three primary security layers:

  • Global permissions – Capabilities granted to the entire application
  • Window configurations – Individual window properties with optional security overrides
  • Bridge commands – API endpoints with explicit permission requirements and origin whitelists

Configuring Global Permissions

Global permissions are defined in the root permissions array. According to src/primitives/app_manifest/types.zig (around lines 231-239), valid permission strings include "window", "filesystem", "network", "clipboard", and "credentials".

The parsePermissions function in src/tooling/manifest.zig processes these entries, using duplicateStringList to allocate the permission strings. Invalid entries trigger diagnostics via manifest.printDiagnostic, while the validatePermissions function in src/primitives/app_manifest/validation.zig ensures only recognized capability tokens are accepted.

{
  name = "my-app",
  version = "1.0.0",
  
  // Global permissions required by the app
  permissions = .{ "window", "filesystem", "network" },
}

Defining Window Configurations and Security Policies

Window definitions reside in the windows array, where each object can optionally include a security block to override global permissions or restrict origins.

Window-Specific Permission Overrides

When a window defines a security.permissions array, the runtime uses these instead of the global set. As implemented in src/runtime/builtin_bridge.zig, the security check logic evaluates:

if (self.options.security.permissions.len > 0) {
    // Use the provided list
    policy.permissions = self.options.security.permissions;
} else {
    // Fallback to global permissions
    policy.permissions = metadata.permissions;
}

If the security block is omitted, the window inherits all global permissions defined in the root permissions array.

Origin Whitelisting

The security.origins array restricts which contexts can execute bridge commands within a specific window. This prevents unauthorized code from accessing privileged APIs even if the global permissions would otherwise allow it.

windows = .{
  {
    name = "main",
    width = 800,
    height = 600,
    security = {
      permissions = .{ "window", "clipboard" },
      origins = .{ "zero://app", "https://example.com" },
    },
  },
},

Bridge Commands and Security Enforcement

Bridge commands declared in the bridge_commands array define explicit permission requirements via the permissions field. The runtime enforces these through security.hasPermission in src/runtime/builtin_bridge.zig, checking both the permission token and the origin against the whitelist before executing the command.

bridge_commands = .{
  {
    name = "native.ping",
    permissions = .{ "filesystem" },
    origins = .{ "zero://app" },
  },
},

If a command originates from a context not listed in origins, or if the required permission is missing from either the window-specific or global permission set, the bridge denies the call and logs a security error.

Complete Configuration Examples

Minimal Manifest with Global Permissions

{
  name = "hello",
  version = "0.1.0",
  permissions = .{ "window", "network" },
}

Window-Specific Security Overrides

{
  name = "notes",
  version = "2.0.0",
  permissions = .{ "window", "filesystem", "network" },

  windows = .{
    {
      name = "viewer",
      width = 400,
      height = 800,
      security = {
        permissions = .{ "window" },
      },
    },
    {
      name = "editor",
      width = 800,
      height = 600,
    },
  },
}

Strict Origin and Permission Restrictions

{
  name = "secure-app",
  version = "1.0.0",
  permissions = .{ "window", "network" },

  windows = .{
    {
      name = "main",
      width = 1024,
      height = 768,
      security = {
        permissions = .{ "window" },
        origins = .{ "zero://app", "https://trusted.example.com" },
      },
    },
  },

  bridge_commands = .{
    {
      name = "native.secure.fetch",
      permissions = .{ "network" },
      origins = .{ "https://trusted.example.com" },
    },
  },
}

Summary

  • The Native SDK uses app.zon (Zig Object Notation) rather than JSON, parsed by src/tooling/manifest.zig via the readMetadata function.
  • Global permissions are defined in the root permissions array and validated through src/primitives/app_manifest/validation.zig.
  • Windows can override global permissions through a security block containing a permissions array and optional origins whitelist.
  • Bridge commands enforce runtime security checks using security.hasPermission in src/runtime/builtin_bridge.zig, requiring both the permission token and origin validation.
  • The concrete implementation example is available in tools/guest-mac/app.zon within the vercel-labs/native repository.

Frequently Asked Questions

Is the manifest file JSON or ZON format?

The Native SDK uses app.zon (Zig Object Notation), not JSON. While the syntax resembles JSON with structural types, it uses Zig-specific constructs like anonymous lists (.{ "item" }) and assignment syntax (= instead of :). The tooling in src/tooling/manifest.zig parses this format natively.

How do window-specific permissions interact with global permissions?

Window-specific permissions defined in windows[*].security.permissions completely replace the global permission set for that window. If the security block is absent, the window inherits all global permissions. This is handled by the conditional logic in src/runtime/builtin_bridge.zig that checks self.options.security.permissions.len before falling back to metadata.permissions.

What happens if a bridge command lacks the required permission?

The runtime denies the command execution. When a bridge command is invoked, src/runtime/builtin_bridge.zig validates that the command's required permissions exist in the current security context (either window-specific or global). If validation fails, the SDK logs a security error and rejects the operation without executing the native code.

Where is the manifest validation logic located?

Validation occurs across three files: src/tooling/manifest.zig handles parsing and structural validation, src/primitives/app_manifest/types.zig defines the permission constants and structures, and src/primitives/app_manifest/validation.zig contains the validatePermissions function that verifies permission strings against the allowed set.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →