@cofy-x/axern-sdk 0.5.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -68,6 +68,32 @@ try {
68
68
  }
69
69
  ```
70
70
 
71
+ ## Network Policies
72
+
73
+ Omitting `networkPolicy` keeps unrestricted v0.5 behavior. Strict policies are
74
+ fail-closed; `denyDns` only refuses matching traditional UDP/TCP DNS queries and
75
+ does not block direct IP traffic, DoH, DoT, or already-resolved addresses.
76
+
77
+ ```ts
78
+ import { NetworkPolicy, Sandbox } from "@cofy-x/axern-sdk";
79
+
80
+ const sandbox = await new Sandbox({
81
+ client,
82
+ image: "docker.io/library/python:3.12-slim",
83
+ networkPolicy: NetworkPolicy.denyDns(
84
+ "github.com",
85
+ "*.github.com",
86
+ "githubusercontent.com",
87
+ "*.githubusercontent.com",
88
+ ),
89
+ }).start();
90
+ ```
91
+
92
+ `NetworkPolicy.allowDomains("example.com", "*.example.com")` allows only
93
+ strict HTTP/HTTPS destinations validated by DNS plus HTTP Host or TLS SNI.
94
+ `NetworkPolicy.strict({ cidrRules: [...] })` adds explicit TCP/UDP CIDR and port
95
+ grants; `NetworkPolicy.denyAll()` allows no egress.
96
+
71
97
  Run a tool from a separate image with `execImage` or `processImage`. OCI and
72
98
  Nydus refs use the same image field. When `mounts` is omitted, the SDK requests
73
99
  `/workspace -> /workspace`; pass `mounts: []` for no shared paths. Use
@@ -6,6 +6,7 @@
6
6
  import * as grpc from "@grpc/grpc-js";
7
7
  import { NodeSandboxClient } from "../node/client.js";
8
8
  import type { ResourceQuantity } from "../resources.js";
9
+ import type { NetworkPolicy } from "../network-policy.js";
9
10
  import { TunnelControlClient } from "../tunnel/control.js";
10
11
  import type { VolumeMount } from "../types.js";
11
12
  import type { GatewayTransportOptions } from "../tunnel/relay.js";
@@ -33,6 +34,7 @@ export interface CreateServiceOptions {
33
34
  env?: Record<string, string>;
34
35
  cwd?: string;
35
36
  runtimeClass?: string;
37
+ networkPolicy?: NetworkPolicy;
36
38
  extensionCapabilities?: readonly ExtensionCapability[];
37
39
  volumes?: readonly VolumeMount[];
38
40
  requestCpu?: ResourceQuantity;
@@ -207,6 +207,9 @@ export class AxernClient {
207
207
  env: options.env ?? {},
208
208
  cwd: options.cwd ?? "",
209
209
  runtime_class: options.runtimeClass ?? "",
210
+ ...(options.networkPolicy === undefined
211
+ ? {}
212
+ : { network: { egress_policy: options.networkPolicy.toWire() } }),
210
213
  extension_capability_requirements: (options.extensionCapabilities ?? []).map((capability) => ({
211
214
  capability: { name: capability.name, value: capability.value ?? "" },
212
215
  })),
package/dist/index.d.ts CHANGED
@@ -14,6 +14,8 @@ export { SandboxProcess } from "./node/process.js";
14
14
  export type { ResourceQuantity } from "./resources.js";
15
15
  export { Sandbox } from "./sandbox/index.js";
16
16
  export type { SandboxMetadata, SandboxOptions, SandboxState } from "./sandbox/index.js";
17
+ export { NetworkPolicy, cidrRule, portRange } from "./network-policy.js";
18
+ export type { CIDRRule, PortRange, StrictNetworkPolicyOptions } from "./network-policy.js";
17
19
  export type { ChmodOptions, CapabilityDependencyStatus, CapabilityProviderStatus, CapabilityProviderSummary, CapabilityStatus, Command, ComputerUseDependencyStatus, ComputerUseDisplay, ComputerUseKeyboardOptions, ComputerUseMouseOptions, ComputerUseRegion, ComputerUseScreenshot, ComputerUseScreenshotOptions, ComputerUseStatus, CopyOptions, DownloadArchiveOptions, DownloadDirOptions, ExecOptions, ExecResult, ImageExecOptions, ImageProcessMount, ImageProcessOptions, MkdirOptions, MoveOptions, NodeCallOptions, ProcessEvent, ProcessOptions, ProcessResult, RemoveOptions, SandboxFileInfo, SandboxFileKind, TouchOptions, TunnelConnectorOptions, TunnelMetadata, TunnelOptions, UploadArchiveOptions, UploadDirOptions, VolumeMount, WriteFileOptions, } from "./types.js";
18
20
  export { workspaceMount } from "./types.js";
19
21
  export { AXERN_VERSION, platformName } from "./version.js";
package/dist/index.js CHANGED
@@ -9,5 +9,6 @@ export { AxernError, AxernRpcError, isNotFound, isCancelled, isUnavailable, erro
9
9
  export { NodeSandboxClient } from "./node/client.js";
10
10
  export { SandboxProcess } from "./node/process.js";
11
11
  export { Sandbox } from "./sandbox/index.js";
12
+ export { NetworkPolicy, cidrRule, portRange } from "./network-policy.js";
12
13
  export { workspaceMount } from "./types.js";
13
14
  export { AXERN_VERSION, platformName } from "./version.js";
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 cofy-x
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ export interface PortRange {
7
+ readonly start: number;
8
+ readonly end: number;
9
+ }
10
+ export interface CIDRRule {
11
+ readonly cidr: string;
12
+ readonly protocol: "tcp" | "udp";
13
+ readonly ports: readonly PortRange[];
14
+ }
15
+ export interface StrictNetworkPolicyOptions {
16
+ readonly domains?: readonly string[];
17
+ readonly cidrRules?: readonly CIDRRule[];
18
+ }
19
+ type NetworkPolicyWire = {
20
+ strict: {
21
+ allowed_domains: string[];
22
+ allowed_cidrs: Record<string, unknown>[];
23
+ };
24
+ } | {
25
+ dns_deny: {
26
+ denied_domains: string[];
27
+ };
28
+ };
29
+ export declare class NetworkPolicy {
30
+ private readonly wire;
31
+ private constructor();
32
+ static strict(options?: StrictNetworkPolicyOptions): NetworkPolicy;
33
+ static allowDomains(...domains: string[]): NetworkPolicy;
34
+ static denyDns(...domains: string[]): NetworkPolicy;
35
+ static denyAll(): NetworkPolicy;
36
+ /** @internal */
37
+ toWire(): NetworkPolicyWire;
38
+ }
39
+ export declare function portRange(start: number, end?: number): PortRange;
40
+ export declare function cidrRule(cidr: string, protocol: "tcp" | "udp", ...ports: PortRange[]): CIDRRule;
41
+ export {};
@@ -0,0 +1,132 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 cofy-x
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { isIP } from "node:net";
7
+ import { domainToASCII } from "node:url";
8
+ const maxRules = 256;
9
+ export class NetworkPolicy {
10
+ wire;
11
+ constructor(wire) {
12
+ this.wire = wire;
13
+ }
14
+ static strict(options = {}) {
15
+ const domains = normalizeDomains(options.domains ?? []);
16
+ const cidrRules = normalizeCIDRRules(options.cidrRules ?? []);
17
+ if (domains.length + cidrRules.length > maxRules) {
18
+ throw new RangeError(`network policy may contain at most ${maxRules} rules`);
19
+ }
20
+ return new NetworkPolicy({
21
+ strict: {
22
+ allowed_domains: domains,
23
+ allowed_cidrs: cidrRules.map((rule) => ({
24
+ cidr: rule.cidr,
25
+ protocol: rule.protocol === "tcp" ? 1 : 2,
26
+ ports: rule.ports.map((port) => ({ start: port.start, end: port.end })),
27
+ })),
28
+ },
29
+ });
30
+ }
31
+ static allowDomains(...domains) {
32
+ return NetworkPolicy.strict({ domains });
33
+ }
34
+ static denyDns(...domains) {
35
+ const normalized = normalizeDomains(domains);
36
+ if (normalized.length === 0)
37
+ throw new RangeError("denyDns requires at least one domain");
38
+ return new NetworkPolicy({ dns_deny: { denied_domains: normalized } });
39
+ }
40
+ static denyAll() {
41
+ return NetworkPolicy.strict();
42
+ }
43
+ /** @internal */
44
+ toWire() {
45
+ return structuredClone(this.wire);
46
+ }
47
+ }
48
+ export function portRange(start, end = start) {
49
+ if (!Number.isInteger(start) || !Number.isInteger(end) || start < 1 || end < start || end > 65535) {
50
+ throw new RangeError("ports must be an inclusive range within 1..65535");
51
+ }
52
+ return Object.freeze({ start, end });
53
+ }
54
+ export function cidrRule(cidr, protocol, ...ports) {
55
+ return normalizeCIDRRule({ cidr, protocol, ports });
56
+ }
57
+ function normalizeDomains(values) {
58
+ const result = [];
59
+ const seen = new Set();
60
+ for (const value of values) {
61
+ let raw = value.trim().toLowerCase();
62
+ const wildcard = raw.startsWith("*.");
63
+ if (wildcard)
64
+ raw = raw.slice(2);
65
+ raw = raw.replace(/\.+$/, "");
66
+ if (raw === "" || /[/:@?#*]/u.test(raw) || isIP(raw) !== 0) {
67
+ throw new TypeError(`invalid domain rule ${JSON.stringify(value)}`);
68
+ }
69
+ const ascii = domainToASCII(raw).toLowerCase();
70
+ const labels = ascii.split(".");
71
+ if (ascii === "" || Buffer.byteLength(ascii, "ascii") > 253 || labels.some((label) => label === "" || label.length > 63)) {
72
+ throw new TypeError(`invalid domain rule ${JSON.stringify(value)}`);
73
+ }
74
+ const normalized = wildcard ? `*.${ascii}` : ascii;
75
+ if (!seen.has(normalized)) {
76
+ seen.add(normalized);
77
+ result.push(normalized);
78
+ }
79
+ }
80
+ return result;
81
+ }
82
+ function normalizeCIDRRules(values) {
83
+ const result = [];
84
+ const seen = new Set();
85
+ for (const value of values) {
86
+ const rule = normalizeCIDRRule(value);
87
+ const key = JSON.stringify(rule);
88
+ if (!seen.has(key)) {
89
+ seen.add(key);
90
+ result.push(rule);
91
+ }
92
+ }
93
+ return result;
94
+ }
95
+ function normalizeCIDRRule(value) {
96
+ const cidr = value.cidr.trim();
97
+ const separator = cidr.lastIndexOf("/");
98
+ const address = separator < 0 ? "" : cidr.slice(0, separator);
99
+ const prefix = separator < 0 ? -1 : Number(cidr.slice(separator + 1));
100
+ const family = isIP(address);
101
+ if ((family !== 4 && family !== 6) || !Number.isInteger(prefix) || prefix < 0 || prefix > (family === 4 ? 32 : 128)) {
102
+ throw new TypeError(`invalid CIDR ${JSON.stringify(value.cidr)}`);
103
+ }
104
+ if (isProtectedCIDR(address, prefix, family)) {
105
+ throw new TypeError(`CIDR ${JSON.stringify(value.cidr)} targets a protected address range`);
106
+ }
107
+ if (value.protocol !== "tcp" && value.protocol !== "udp")
108
+ throw new TypeError("CIDR protocol must be tcp or udp");
109
+ if (value.ports.length === 0)
110
+ throw new RangeError("CIDR rule requires at least one port range");
111
+ const ports = value.ports.map((port) => portRange(port.start, port.end));
112
+ return Object.freeze({ cidr, protocol: value.protocol, ports: Object.freeze(ports) });
113
+ }
114
+ function isProtectedCIDR(address, prefix, family) {
115
+ if (family === 4) {
116
+ const value = address.split(".").reduce((result, octet) => (result << 8n) | BigInt(octet), 0n);
117
+ const protectedRanges = [
118
+ [0n, 32],
119
+ [1684301000n, 32], // 100.100.100.200
120
+ [2130706432n, 8], // 127.0.0.0
121
+ [2851995648n, 16], // 169.254.0.0
122
+ [3221225664n, 32], // 192.0.0.192
123
+ [3758096384n, 4], // 224.0.0.0
124
+ ];
125
+ return protectedRanges.some(([base, bits]) => prefix >= bits && (value >> BigInt(32 - bits)) === (base >> BigInt(32 - bits)));
126
+ }
127
+ const canonical = new URL(`http://[${address}]/`).hostname.slice(1, -1).toLowerCase();
128
+ if (prefix >= 128 && (canonical === "::" || canonical === "::1"))
129
+ return true;
130
+ const first = Number.parseInt(canonical.split(":", 1)[0] || "0", 16);
131
+ return (prefix >= 10 && first >= 0xfe80 && first <= 0xfebf) || (prefix >= 8 && first >= 0xff00 && first <= 0xffff);
132
+ }
@@ -28,6 +28,11 @@ enum PlatformCapability {
28
28
  PLATFORM_CAPABILITY_RUNSC_MEMORY_ENFORCEMENT_SELF_TEST = 13;
29
29
  PLATFORM_CAPABILITY_RUNC_EPHEMERAL_ENFORCEMENT_SELF_TEST = 14;
30
30
  PLATFORM_CAPABILITY_RUNSC_EPHEMERAL_ENFORCEMENT_SELF_TEST = 15;
31
+ PLATFORM_CAPABILITY_EGRESSD_DNS_POLICY_SELF_TEST = 16;
32
+ PLATFORM_CAPABILITY_EGRESSD_STRICT_EGRESS_SELF_TEST = 17;
33
+ // Workload-facing capabilities derived from the egressd self-test facts.
34
+ PLATFORM_CAPABILITY_DNS_POLICY_ENFORCEMENT = 18;
35
+ PLATFORM_CAPABILITY_STRICT_EGRESS_ENFORCEMENT = 19;
31
36
  }
32
37
 
33
38
  message ExtensionCapability {
@@ -49,6 +49,49 @@ enum NetworkMode {
49
49
 
50
50
  message NetworkSpec {
51
51
  NetworkMode mode = 1;
52
+ NetworkEgressPolicy egress_policy = 2;
53
+ }
54
+
55
+ // NetworkEgressPolicy deliberately separates strict enforcement from the
56
+ // DNS-only convenience policy. Callers must not treat dns_deny as an egress
57
+ // security boundary: direct IP traffic, encrypted DNS, and already-known
58
+ // addresses remain outside that policy.
59
+ message NetworkEgressPolicy {
60
+ oneof policy {
61
+ StrictEgressPolicy strict = 1;
62
+ DnsDenyPolicy dns_deny = 2;
63
+ }
64
+ }
65
+
66
+ // StrictEgressPolicy is default-deny. Domain grants authorize HTTP and HTTPS
67
+ // only, on TCP ports 80 and 443, and require controlled DNS plus matching HTTP
68
+ // Host or TLS SNI. Other protocols require an explicit CIDR rule.
69
+ message StrictEgressPolicy {
70
+ repeated string allowed_domains = 1;
71
+ repeated CIDREgressRule allowed_cidrs = 2;
72
+ }
73
+
74
+ // DnsDenyPolicy returns REFUSED for matching conventional UDP/TCP DNS queries.
75
+ // It does not block direct IP traffic, DoH, DoT, or previously resolved IPs.
76
+ message DnsDenyPolicy {
77
+ repeated string denied_domains = 1;
78
+ }
79
+
80
+ enum EgressProtocol {
81
+ EGRESS_PROTOCOL_UNSPECIFIED = 0;
82
+ EGRESS_PROTOCOL_TCP = 1;
83
+ EGRESS_PROTOCOL_UDP = 2;
84
+ }
85
+
86
+ message PortRange {
87
+ uint32 start = 1;
88
+ uint32 end = 2;
89
+ }
90
+
91
+ message CIDREgressRule {
92
+ string cidr = 1;
93
+ EgressProtocol protocol = 2;
94
+ repeated PortRange ports = 3;
52
95
  }
53
96
 
54
97
  message PlacementConstraints {
@@ -7,6 +7,7 @@ import { AxernClient } from "../client/index.js";
7
7
  import type { ExtensionCapability } from "../client/index.js";
8
8
  import type { SandboxProcess } from "../node/process.js";
9
9
  import type { ResourceQuantity } from "../resources.js";
10
+ import type { NetworkPolicy } from "../network-policy.js";
10
11
  import type { ChmodOptions, CapabilityStatus, Command, ComputerUseDisplay, ComputerUseKeyboardOptions, ComputerUseMouseOptions, ComputerUseScreenshot, ComputerUseScreenshotOptions, ComputerUseStatus, CopyOptions, DownloadDirOptions, ExecOptions, ExecResult, ImageExecOptions, ImageProcessOptions, MkdirOptions, MoveOptions, NodeCallOptions, ProcessOptions, RemoveOptions, SandboxFileInfo, TunnelMetadata, TunnelOptions, TouchOptions, UploadDirOptions, VolumeMount, WriteFileOptions } from "../types.js";
11
12
  export interface SandboxOptions {
12
13
  client: AxernClient;
@@ -18,6 +19,7 @@ export interface SandboxOptions {
18
19
  env?: Record<string, string>;
19
20
  cwd?: string;
20
21
  runtimeClass?: string;
22
+ networkPolicy?: NetworkPolicy;
21
23
  extensionCapabilities?: readonly ExtensionCapability[];
22
24
  volumes?: readonly VolumeMount[];
23
25
  requestCpu?: ResourceQuantity;
@@ -59,6 +59,7 @@ export class Sandbox {
59
59
  env: this.options.env,
60
60
  cwd: this.options.cwd,
61
61
  runtimeClass: this.options.runtimeClass,
62
+ networkPolicy: this.options.networkPolicy,
62
63
  extensionCapabilities: this.options.extensionCapabilities,
63
64
  volumes: this.options.volumes,
64
65
  requestCpu: this.options.requestCpu,
package/dist/version.d.ts CHANGED
@@ -3,5 +3,5 @@
3
3
  * Copyright 2026 cofy-x
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- export declare const AXERN_VERSION = "0.5.0";
6
+ export declare const AXERN_VERSION = "0.6.2";
7
7
  export declare function platformName(): string;
package/dist/version.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * Copyright 2026 cofy-x
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- export const AXERN_VERSION = "0.5.0";
6
+ export const AXERN_VERSION = "0.6.2";
7
7
  export function platformName() {
8
8
  return "axern";
9
9
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cofy-x/axern-sdk",
3
- "version": "0.5.0",
3
+ "version": "0.6.2",
4
4
  "description": "TypeScript SDK for Axern agentic infrastructure and isolated sandboxes",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {