Skip to content

Network permission rule sets never match; explicit permission objects also bypass the documented merge-over-default #1884

Description

@ragnorc

Problem

A network permission rule set ({ default: 'deny', rules: [...] }) never allows anything — every outbound connection fails with EACCES even when a rule explicitly allows the host, while the plain scope string 'allow' works. This makes the documented per-host egress allowlist (the recommended posture for agent VMs) unusable:

ERR TypeError: fetch failed Error: EACCES: EACCES: permission denied,
tcp://api.openai.com:443: blocked by network.http policy

Reproduced on 0.2.15 and 0.2.16-rc.1, macOS arm64, Node 25, via the Core path (AgentOs.create(), no actor layer involved).

Reproduction

Exit codes: 42 = HTTP reached (any status), 43 = blocked.

import { AgentOs } from '@rivet-dev/agentos';

const PROBE = `fetch('https://api.openai.com/v1/models')
  .then(() => process.exit(42)).catch(() => process.exit(43));`;

const CASES: [string, unknown][] = [
  ['scope-allow', 'allow'],
  ['rules-exact-host', { default: 'deny', rules: [{ mode: 'allow', patterns: ['api.openai.com'] }] }],
  ['deny-control', 'deny'],
];

for (const [name, network] of CASES) {
  const vm = await AgentOs.create({
    permissions: { fs: 'allow', childProcess: 'allow', process: 'allow', env: 'allow', binding: 'allow', network } as never,
  });
  await vm.filesystem.writeFile('/tmp/t.js', PROBE);
  const r = await vm.process.exec('node /tmp/t.js', { captureStdio: true });
  console.log(name, '→', r.exitCode);
  await vm.dispose();
}

Observed:

scope-allow        → 42   (reached)
rules-exact-host   → 43   (blocked — should be 42)
deny-control       → 43   (blocked, correct)

Also tried: patterns: ['api.openai.com:443'], patterns: ['*'], patterns: ['**'], explicit operations: ['fetch','http','dns','connect'] — all blocked identically.

Related: explicit permission objects skip the documented merge and the binding auto-grant

The permissions docs say a partial policy is "merged over a secure default" and that binding is auto-granted when bindings are registered. In agentos-core/dist/agent-os.js the actual behavior is wholesale replacement:

const hostPermissions = options?.permissions ?? { ...allowAll, binding: "allow" };

Any explicit permissions object replaces the default entirely — so { network: 'allow' } alone silently denies fs/childProcess/process/env, and registering bindings while passing any explicit policy leaves binding undefined→denied. Either the merge should be implemented as documented, or the docs should state replacement semantics.

Expected behavior

  • A matching mode: 'allow' rule permits the connection (at minimum for a bare-host pattern, per the documented patterns: ["api.example.com"] example).
  • Partial permission objects merge over the documented secure default, or the docs are corrected.
  • binding auto-grant behavior is consistent between the no-permissions and explicit-permissions paths.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions