---
title: "Server facts and programs"
description: "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."
section: "interfaces"
status: "Building for 0.3 · ships as a beta API"
spec: "app-permissions"
canonical: "https://mainsdk.dev/docs/interfaces/system"
updated: "2026-10-28"
---

# Server facts and programs

> 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.

Status: Building for 0.3 · ships as a beta API · Canonical: https://mainsdk.dev/docs/interfaces/system · 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

- Status: Building for 0.3 · ships as a beta API
- Phase: 1 (Core, public beta)
- Milestone: 0.3 Every interface
- Plan: free in your app (Apache-2.0)
- Spec: https://github.com/hubxero/mainsdk/blob/develop/docs/specs/app-permissions.md
- Category: Platform, OS and network

Calls:

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

In main.yaml:

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

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

Pick one in main.yaml with `system.driver`.

| Driver | Id in main.yaml | Default | Note |
| --- | --- | --- | --- |
| Built in | `builtin` | yes |  |

## Codes

| Code | Where | Meaning |
| --- | --- | --- |
| `MAIN-PERM-0001` | check | Raw `node:fs`, `node:os` or `node:child_process` used outside a driver |
| `MAIN-PERM-0003` | check | A `disk` path, a `system` fact or a program found in the code isn't declared under `permissions` |
| `MAIN-NET-0001` | check | Raw 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-0002` | runtime | In `enforce` mode, a `disk` path, a `system` fact or a program is used but not declared under `permissions` (403) |
| `MAIN-PERM-0004` | runtime | An unknown name under `permissions.system` |
| `MAIN-PERM-0005` | runtime | A declared program could not start, for example because it is not installed on the server |

## Features

| Feature | Phase | Milestone | Spec ticket |
| --- | --- | --- | --- |
| Declared operating system facts under permissions.system | 1 | 0.3 | `app-permissions` |
| Declared programs under permissions.processes | 1 | 0.3 | `app-permissions` |
| URL arguments checked against network.allow | 1 | 0.3 | `app-permissions` |
| Undeclared facts and programs found by mainsdk check | 1 | 0.3 | `app-permissions` |

## MainSDK Cloud

Everything on this page is free in your app.


## Quick start

```yaml title="main.yaml"
permissions:
  mode: enforce
  system: [hostname, platform]
  processes: [git]
```

```ts title="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:`.

```yaml title="main.yaml"
permissions:
  mode: enforce                  # observe | enforce
  system: [user, hostname, platform, network-interfaces]
  processes: [git, curl]
```

| Key | Type | What it does |
| --- | --- | --- |
| `mode` | string | `enforce` refuses an undeclared fact or program. `observe` lets it run and records it |
| `system` | list | Facts the app may read: `user`, `hostname`, `platform`, `network-interfaces`. Any other name fails with `MAIN-PERM-0004` |
| `processes` | list | Program 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

| Call | What it does | Needs |
| --- | --- | --- |
| `system.user()` | The user the server runs as: name, ids, home folder and shell | `user` |
| `system.hostname()` | The machine's host name | `hostname` |
| `system.platform()` | Operating system, CPU architecture, OS release and Node version | `platform` |
| `system.networkInterfaces()` | The machine's network addresses, by interface | `network-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`](https://mainsdk.dev/docs/interfaces/network) 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

```ts title="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"]);
```

```yaml title="main.yaml"
network:
  allow: [github.com]
permissions:
  processes: [git]
```

### Report which host the app runs on

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

export function GET() {
  return Response.json({ host: system.hostname(), platform: system.platform().os });
}
```

```yaml title="main.yaml"
permissions:
  system: [hostname, platform]
```

## Errors and checks

| Code | When | Fix |
| --- | --- | --- |
| `MAIN-PERM-0001` | `mainsdk check` found `node:os` or `node:child_process` imported outside a driver | Use `system` |
| `MAIN-PERM-0002` | A fact or program that is not declared was used (403, enforce mode) | Declare it under `permissions`, or remove the code that asks |
| `MAIN-PERM-0003` | `mainsdk check` found a fact or program the code uses that `permissions` doesn't declare | Declare it; `npx mainsdk discover` prints the lines |
| `MAIN-PERM-0004` | An unknown name under `permissions.system` | Use `user`, `hostname`, `platform` or `network-interfaces` |
| `MAIN-PERM-0005` | A declared program could not start, for example because it is not installed | Install it on the server, or check its name |
| `MAIN-NET-0001` | `mainsdk check` found `git`, `curl` or `wget` run outside `system.run` | Run them through `system.run` |
| `MAIN-NET-0002` | A URL in a program's arguments is not on `network.allow` | Add 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`](https://mainsdk.dev/docs/interfaces/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.

```ts title="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`](https://mainsdk.dev/docs/interfaces/config) or [`secrets`](https://mainsdk.dev/docs/interfaces/secrets).

## Next

- [Local files](https://mainsdk.dev/docs/interfaces/disk): the folders the app may read and write.
- [Network permissions](https://mainsdk.dev/docs/interfaces/network): the hosts a program may reach.
- [Declare what your app uses](https://mainsdk.dev/docs/guides/declare-what-your-app-uses): run discovery and write the permissions block.
