MainSDK
Search⌘K
Interfaces

Platform, OS and network

Server facts and programs

system

Read facts about the server and run only the programs you declare. Every URL a program is given is checked against the network list, and each use is recorded.

Building for 0.3ships as a beta APIPhase 1 · Core, public betaSpec app-permissionsFree in your app, Apache-2.0

Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.

Stability The promise an interface ships with.

  • Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
  • Beta Usable in production; may change before it becomes stable.
  • Experimental May change or go away; needs an explicit opt-in in main.yaml.
Stability levels
Updated 2026-10-28

system lets a server-side app read a few facts about its machine and run the programs it needs, such as git, only when main.yaml declares them.

Like disk, it works through permissions: a fact or program that is not declared is refused. The app does not import node:os or node:child_process for this; mainsdk check fails on that (MAIN-PERM-0001). Programs run without a shell, so the arguments are never interpreted. system is server side only and free on every plan.

At a glance

permissions:
  mode: enforce
  system: [hostname, platform]
  processes: [git]

Calls

  • system.user()
  • system.hostname()
  • system.platform()
  • system.networkInterfaces()
  • system.run(cmd, args, opts)
  • createSystem(perm)
  • SYSTEM_FACTS(const)

Guarantees

Whatever the driver.

  • Only the facts and programs declared under permissions are readable or runnable
  • Programs run without a shell, so arguments are never interpreted
  • Every URL in a program's arguments is checked against network.allow
  • Every use is recorded with the calling file and line
  • Server side only

Drivers

1 drivers. Select one to see its line in main.yaml.

main.yamlBuilt in (default)
system:
  driver: builtin

Change this one line to swap providers; your calls stay the same.Built-in and local drivers integration

Codes

CodeWhere it appearsMeaning
MAIN-PERM-0001Checkmainsdk check, before deployRaw node:fs, node:os or node:child_process used outside a driver
MAIN-PERM-0003Checkmainsdk check, before deployA disk path, a system fact or a program found in the code isn't declared under permissions
MAIN-NET-0001Checkmainsdk check, before deployRaw socket, another HTTP client or direct fetch outside the SDK: new WebSocket, EventSource, node:http, node:https, node:dgram, ws, or git, curl or wget run as a child process
MAIN-PERM-0002RuntimeError responses and logsIn enforce mode, a disk path, a system fact or a program is used but not declared under permissions (403)
MAIN-PERM-0004RuntimeError responses and logsAn unknown name under permissions.system
MAIN-PERM-0005RuntimeError responses and logsA declared program could not start, for example because it is not installed on the server

Features

Nothing here has shipped yet. Each feature lands in its phase; the spec ticket builds it.

FeaturePhaseMilestoneSpec ticket
Declared operating system facts under permissions.systemPhase 10.3app-permissions
Declared programs under permissions.processesPhase 10.3app-permissions
URL arguments checked against network.allowPhase 10.3app-permissions
Undeclared facts and programs found by mainsdk checkPhase 10.3app-permissions

Quick start

main.yaml
permissions:
  mode: enforce
  system: [hostname, platform]
  processes: [git]
lib/build-info.ts
import { system } from "mainsdk";

export function buildInfo() {
  // allowed: platform is under permissions.system
  const { os, arch, node } = system.platform();
  return { host: system.hostname(), os, arch, node };
}

export async function currentCommit() {
  // allowed: git is under permissions.processes
  const { stdout } = await system.run("git", ["rev-parse", "HEAD"]);
  return stdout.trim();
}

Configuration

Every key below lives under permissions:.

main.yaml
permissions:
  mode: enforce                  # observe | enforce
  system: [user, hostname, platform, network-interfaces]
  processes: [git, curl]
KeyTypeWhat it does
modestringenforce refuses an undeclared fact or program. observe lets it run and records it
systemlistFacts the app may read: user, hostname, platform, network-interfaces. Any other name fails with MAIN-PERM-0004
processeslistProgram names the app may run, such as git. A program not listed is refused

What you get

  • Four facts, each declared before use: the running user, host name, platform and network addresses.
  • system.run for programs you declare under processes, run without a shell, with a timeout and capped output.
  • Every URL in a program's arguments checked against network.allow before it starts.
  • Every use recorded as a decision with the calling file and line.

Calls

CallWhat it doesNeeds
system.user()The user the server runs as: name, ids, home folder and shelluser
system.hostname()The machine's host namehostname
system.platform()Operating system, CPU architecture, OS release and Node versionplatform
system.networkInterfaces()The machine's network addresses, by interfacenetwork-interfaces
system.run(cmd, args, opts)Run a program without a shell and return { code, stdout, stderr }the program under processes

createSystem(perm) builds a system on a permission layer, and SYSTEM_FACTS (a constant array, not a function) lists the fact names a system permission may hold: user, hostname, platform, network-interfaces.

system.run takes opts.cwd (a folder the app may read under permissions.disk, or the folder inside a disk.temp call; default is the app folder) and opts.timeoutMs (default 60 seconds). A program that starts and exits non-zero returns its code; it does not throw. Output is limited to 16 MB per stream.

URLs in program arguments

A program that reaches the network, such as git clone https://…, git ls-remote git@github.com:… or curl https://…, has every URL in its arguments checked against network.allow before it starts. A host that is not allowed is refused with MAIN-NET-0002, so declaring git does not open every host to it.

Examples

Fetch a repository's default branch

scripts/default-branch.ts
import { system } from "mainsdk";

// allowed: git is under processes; github.com is under network.allow
const { stdout } = await system.run("git", ["ls-remote", "--symref", "https://github.com/acme/app.git", "HEAD"]);
main.yaml
network:
  allow: [github.com]
permissions:
  processes: [git]

Report which host the app runs on

app/api/health/route.ts
import { system } from "mainsdk";

export function GET() {
  return Response.json({ host: system.hostname(), platform: system.platform().os });
}
main.yaml
permissions:
  system: [hostname, platform]

Errors and checks

CodeWhenFix
MAIN-PERM-0001mainsdk check found node:os or node:child_process imported outside a driverUse system
MAIN-PERM-0002A fact or program that is not declared was used (403, enforce mode)Declare it under permissions, or remove the code that asks
MAIN-PERM-0003mainsdk check found a fact or program the code uses that permissions doesn't declareDeclare it; npx mainsdk discover prints the lines
MAIN-PERM-0004An unknown name under permissions.systemUse user, hostname, platform or network-interfaces
MAIN-PERM-0005A declared program could not start, for example because it is not installedInstall it on the server, or check its name
MAIN-NET-0001mainsdk check found git, curl or wget run outside system.runRun them through system.run
MAIN-NET-0002A URL in a program's arguments is not on network.allowAdd the host if the call is expected, or remove it

Swapping drivers

system has no driver. It reads the machine it runs on and always runs programs without a shell. What changes between environments is the permissions block: set permissions.system and processes per environment in main.yaml.

Testing

createTestApp() from @mainsdk/testing reads your permissions. Facts and programs are the real ones of the machine the test runs on, so assert on shape, not on a value.

lib/build-info.test.ts
import assert from "node:assert/strict";
import { it } from "node:test";
import { createTestApp } from "@mainsdk/testing";
import { system } from "mainsdk";

it("app-permissions AC2 refuses a fact that is not declared", () => {
  createTestApp({ yaml: "schema: 1\nproject: t\npermissions:\n  system: [hostname]\n" });
  assert.equal(typeof system.hostname(), "string");
  assert.throws(() => system.user(), { code: "MAIN-PERM-0002" });
});

permissions.decisions() on the test app lists each decision with its file and line. A fake for system.run is planned.

Security and privacy

  • Deny by default. Only the facts and programs under permissions can be used.
  • Programs run with execFile and no shell, so arguments are passed as they are.
  • URLs in arguments are checked against the network list before the program starts.
  • Every use is recorded as a decision with the calling file and line, and mainsdk info lists them.
  • The facts are about the server. They are not sent anywhere by this interface.

FAQ

Can I run sh -c through system.run? Declare sh under processes only if you accept that the shell can run anything it is given. The better route is a program with fixed arguments.

Why did a program work in my terminal but fail here? Check that it is installed on the server the app runs on, and that it is declared under processes. A missing program fails with MAIN-PERM-0005.

Does system read environment variables? No. Read configuration through config or secrets.

Next

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet