Server facts and programs
systemRead 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.
app-permissionsFree in your app, Apache-2.0Status 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.
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]import { system } from "mainsdk";
// allowed only if git is under permissions.processes and the URL is in network.allow
const out = await system.run("git", ["ls-remote", "https://github.com/acme/app.git"]);
const os = system.platform();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" });
});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.
system:
driver: builtinChange this one line to swap providers; your calls stay the same.Built-in and local drivers integration
Codes
| Code | Where it appears | Meaning |
|---|---|---|
MAIN-PERM-0001 | Checkmainsdk check, before deploy | Raw node:fs, node:os or node:child_process used outside a driver |
MAIN-PERM-0003 | Checkmainsdk check, before deploy | A disk path, a system fact or a program found in the code isn't declared under permissions |
MAIN-NET-0001 | Checkmainsdk check, before deploy | 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 | RuntimeError responses and logs | In enforce mode, a disk path, a system fact or a program is used but not declared under permissions (403) |
MAIN-PERM-0004 | RuntimeError responses and logs | An unknown name under permissions.system |
MAIN-PERM-0005 | RuntimeError responses and logs | A 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.
| Feature | Phase | Milestone | Spec ticket |
|---|---|---|---|
| Declared operating system facts under permissions.system | Phase 1 | 0.3 | app-permissions |
| Declared programs under permissions.processes | Phase 1 | 0.3 | app-permissions |
| URL arguments checked against network.allow | Phase 1 | 0.3 | app-permissions |
| Undeclared facts and programs found by mainsdk check | Phase 1 | 0.3 | app-permissions |
Quick start
permissions:
mode: enforce
system: [hostname, platform]
processes: [git]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:.
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.runfor programs you declare underprocesses, run without a shell, with a timeout and capped output.- Every URL in a program's arguments checked against
network.allowbefore 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 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
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"]);network:
allow: [github.com]
permissions:
processes: [git]Report which host the app runs on
import { system } from "mainsdk";
export function GET() {
return Response.json({ host: system.hostname(), platform: system.platform().os });
}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 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.
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
permissionscan be used. - Programs run with
execFileand 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 infolists 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
- Local files: the folders the app may read and write.
- Network permissions: the hosts a program may reach.
- Declare what your app uses: run discovery and write the permissions block.
Was this page helpful?