Skip to main content
Comis integrates with the Node.js --permission flag to provide OS-level sandboxing for child processes spawned during skill execution. When enabled, child processes run with restricted file system and network access.
Source: packages/core/src/config/schema-security.tsPermissionConfigSchema Zod schema.

Overview

The Node.js permission model (available in Node.js 22+) restricts what a child process can access at the operating system level. This provides a defense layer beneath application-level controls like tool policies and content scanning. Key distinction: Node.js’s permission model is a process-level boundary. To enforce restrictions on child processes spawned during tool execution, the daemon process itself must be launched with --permission (children inherit via NODE_OPTIONS). Comis does not auto-inject these flags from config — they must be supplied on the daemon command line. See “How It Works” below for the operator-driven flow and the MCP exception that strips NODE_OPTIONS for compatibility.

Configuration

The PermissionConfigSchema has 3 fields:
FieldTypeDefaultDescription
enableNodePermissionsbooleanfalseEnable Node.js --permission flag enforcement for child processes
allowedFsPathsstring[][]Allowed filesystem read/write paths (passed as --allow-fs-read and --allow-fs-write)
allowedNetHostsstring[][]Allowed network hosts for outbound connections (passed as --allow-net)

YAML Configuration Example

security:
  permission:
    enableNodePermissions: true
    allowedFsPaths:
      - /home/comis/workspace
      - /tmp/comis
    allowedNetHosts:
      - api.openai.com
      - api.anthropic.com

How It Works

Operator-driven enforcement. Comis exposes the configuration surface (security.permission.*) but does not itself launch the daemon with --permission. Node.js applies the permission model only to the process that was launched with the --permission flag (and any children that inherit it via NODE_OPTIONS). To enforce these restrictions you must:
  1. Launch the daemon under Node.js 22+ with node --permission --allow-fs-read=... --allow-fs-write=... --allow-net=... packages/daemon/dist/daemon.js.
  2. Mirror the same paths/hosts in security.permission.allowedFsPaths and allowedNetHosts so audit/security checks (e.g. the SSRF surface check in packages/cli/src/security/checks/ssrf-surface.ts) report the surface accurately.
Setting enableNodePermissions: true alone does NOT pass --permission flags — it is a configuration declaration consumed today only by the security audit checker and admin UI. Always launch with the flags.Note: MCP stdio child processes are explicitly wrapped with /usr/bin/env -u NODE_OPTIONS … (see packages/skills/src/integrations/mcp-client.ts) to prevent NODE_OPTIONS-propagated permissions from breaking unrelated servers; design those MCP servers under the assumption they may run unsandboxed.
When the daemon process is launched with --permission:
  1. File system access: allowedFsPaths should mirror the --allow-fs-read / --allow-fs-write flags supplied to the daemon. Paths not in this list are inaccessible to the daemon and any child processes that inherit NODE_OPTIONS.
  2. Network access: allowedNetHosts should mirror --allow-net. Only connections to listed hosts are permitted.
  3. Child process spawning: Child Node processes inherit the daemon’s permission flags via NODE_OPTIONS unless explicitly stripped (as the MCP launcher does).

Permission Flag Mapping

Config FieldNode.js FlagEffect
allowedFsPaths--allow-fs-read, --allow-fs-writeRestricts file system access to listed paths (operator must pass the flags at daemon launch)
allowedNetHosts--allow-netRestricts outbound network to listed hosts (operator must pass the flag at daemon launch)
(implicit)--permissionMust be set on the daemon launch command; not auto-injected by Comis

Limitations

The Node.js permission model has several limitations to be aware of before enabling it.
  • Node.js 22+ required: The --permission flag is not available in older Node.js versions. Comis requires Node.js 22+, so this is satisfied by default.
  • Third-party module compatibility: Some npm packages that perform unrestricted file system or network operations will fail when permissions are enabled. Test thoroughly before enabling in production.
  • No per-tool granularity: Permissions apply to the entire child process. You cannot grant different file system paths to different tools within the same execution.
  • Path resolution: Paths in allowedFsPaths should be absolute. Relative paths may not resolve as expected in the child process context.
  • No wildcard support: The Node.js permission model does not support glob patterns in path specifications. Each directory must be listed explicitly.

Error Behavior

When a child process attempts an operation that violates its permissions, Node.js throws a ERR_ACCESS_DENIED error. This error propagates through the tool execution system and is reported to the agent as a tool failure. Example error when a child process attempts to read a file outside allowedFsPaths:
Error [ERR_ACCESS_DENIED]: Access to this API has been restricted.
The daemon logs these permission violations at WARN level with the restricted operation details for security audit.

Default Behavior

When enableNodePermissions is false (the default), child processes inherit the full permissions of the daemon process. This is the recommended setting for most deployments where the daemon runs as a dedicated system user with appropriate OS-level access controls. Enable Node.js permissions only when you need an additional sandboxing layer — for example, when running untrusted skills that may attempt to access files or network resources outside their intended scope. When enabling permissions, include at minimum:
PathPurpose
Data directory (~/.comis)Database, config, logs
Agent workspace directoryFile operations from tools
/tmp (or a subdirectory)Temporary files for media processing
Node.js installation directoryModule resolution
security:
  permission:
    enableNodePermissions: true
    allowedFsPaths:
      - /home/comis/.comis
      - /home/comis/workspace
      - /tmp/comis
      - /usr/lib/node_modules
    allowedNetHosts:
      - api.openai.com
      - api.anthropic.com
      - api.groq.com

Interaction with Other Security Layers

Node.js permissions are one layer in Comis’s defense-in-depth approach:
LayerScopeMechanism
Tool PolicyApplication-levelControls which tools an agent can use
Content ScannerSkill-levelDetects malicious patterns in skill bodies
SSRF GuardNetwork-levelBlocks requests to internal/metadata IPs
Safe PathFile-levelPrevents path traversal within allowed directories
Node PermissionsOS-levelRestricts child process file system and network access
Budget / Circuit BreakerResource-levelLimits token spend and failure cascades
Node.js permissions provide the lowest-level defense: even if all application-level checks are bypassed, the OS-level restrictions prevent access to unauthorized resources.

SecurityConfigSchema Context

The PermissionConfigSchema is nested within the broader SecurityConfigSchema:
security:
  logRedaction: true              # Structured log redaction (default: true)
  auditLog: true                  # Audit event logging (default: true)
  permission:                     # Node.js permission model
    enableNodePermissions: false
    allowedFsPaths: []
    allowedNetHosts: []
  actionConfirmation:             # Action confirmation gates
    requireForDestructive: true
    requireForSensitive: false
    autoApprove: []
  storage: encrypted              # Secrets store backend: encrypted (default) | file | env
The full SecurityConfigSchema is defined in packages/core/src/config/schema-security.ts and parsed via Zod with defaults for all fields.

Production fd-API Disablement (Linux)

When Comis runs under node --permission, Node.js categorically disables the file-descriptor-based fs API family at the V8/libuv level — this applies to the daemon process itself, not just child processes. This is distinct from the path-restriction behavior described above and affects how the daemon writes credential files and sets file permissions. Understanding this behavior matters if you enable security.permission.enableNodePermissions: true or if you run the daemon via the reference systemd unit (which passes --permission in ExecStart).

Disabled APIs

Under node --permission, the following APIs are refused with ERR_ACCESS_DENIED:
APIImpact
fs.fsyncSync / fs.fdatasyncSync / FileHandle.sync()Credential file writes (secrets, OAuth profiles) skip the fsync durability barrier
fs.fchmodSyncTrace files and session metadata cannot have permissions set after write
fs.fchownSyncOwnership changes on daemon-managed files are skipped
Each refusal throws: Error [ERR_ACCESS_DENIED]: ... is disabled when permission model is enabled

Historical production impact

Observed on a production VPS (2026-06-02): the data-dir lock’s fsync killed every boot attempt — every daemon start threw ERR_ACCESS_DENIED before the first log line. Later, fchmod refusals broke MCP OAuth discovery (no OAuth-protected MCP server could connect) and session-metadata writes. (Source: packages/shared/src/fsync-permission.ts:15-22) Both issues were root-caused to the fd-API categorical disablement rather than missing path grants.

How Comis guards against this

Comis guards all fd-API call sites via isFsyncDisabledByPermissionModel (defined in packages/shared/src/fsync-permission.ts:33). This function detects ERR_ACCESS_DENIED by error code or the categorical error message and returns true, allowing callers to skip the failing call gracefully rather than crashing. Guarded call sites:
  • packages/daemon/src/wiring/data-dir-lock.tsfsyncSync on the lock file and parent directory (boot-critical)
  • packages/core/src/oauth/oauth-credential-store-file.tsfsyncSync on OAuth profile atomic writes
  • packages/memory/src/file-secret-store.tsfsyncSync on the secrets file atomic writes
  • packages/cli/src/sync-tooling/atomic-write.tsfsyncSync
  • packages/observability/src/shared/fs-safe.tsfchmodSync on trace files and session metadata
  • packages/skills/src/tools/builtin/terminal-driver/terminal-worker-entry.ts — fd-based API

Operator implications

  • Best-effort durability: Under --permission, encrypted-mode credential file writes (secrets, OAuth profiles) are tmp → rename atomic but not fsync-durable. A kernel panic or power failure between rename and the next journal flush could corrupt the credential file. This risk is identical to writes on any filesystem without journaling guarantees — acceptable for most production deployments, but notable for high-availability setups with unreliable power.
  • Best-effort permissions: Trace files and session metadata may not have the expected 0o600 mode under --permission. Files are still written correctly; only the post-write chmod is skipped.
  • Linux-only: This behavior only occurs on production systemd deployments that pass --permission. macOS default setups and development installs (without --permission) are unaffected.
  • No action required by default: security.permission.enableNodePermissions defaults to false. You must explicitly opt in. If you do not enable it, none of this applies.
If you see ERR_ACCESS_DENIED errors in daemon logs that are NOT related to child-process file access, check whether the operation involves one of the fd-based APIs above. The isFsyncDisabledByPermissionModel guard handles the known call sites, but third-party Node.js libraries loaded by the daemon may also use fd-based APIs.

Security Model

Defense-in-depth security architecture

Sandbox

Skill sandboxing implementation

Safe Path

Path traversal prevention