EXTENSION for TreeOS
shell
Gives the AI direct shell access to the land server. The execute-shell tool runs any command through Node.js child_process with a 30 second timeout and 8KB output cap. Three security layers. Layer 1: confined scope. Shell is inactive everywhere by default. Operators explicitly allow it at specific nodes with ext-allow shell. A DevOps branch gets shell. The rest of the tree never sees it. Layer 2: admin-only. The handler checks user.isAdmin before every execution and rejects all non-admin callers. Layer 3: energy metering. Each execution costs energy to prevent rapid-fire abuse. A regex blocklist catches the most destructive patterns (rm -rf, fork bombs, disk writes, firewall flushes, pipe-to-shell curls, password changes, service shutdowns, command substitution) even for admins. The blocklist prevents common accidents. It is not a sandbox. Shell access is real shell access. Confine it to positions where that is acceptable. Every command is logged with the user ID.
v1.0.1 by TreeOS Site 0 downloads 3 files 161 lines 6.4 KB published 38d ago
treeos ext install shell
View changelog

Manifest

Provides

  • tools
  • 1 energy actions

Requires

  • models: User

Optional

  • services: energy
SHA256: c3d836d45c34c66599c09ea95ff8fb147d9c1e009b06df9052262f6ccff1dbe3

Source Code

1import log from "../../seed/log.js";
2import getTools, { setEnergyService } from "./tools.js";
3
4export async function init(core) {
5  if (core.energy) setEnergyService(core.energy);
6
7  log.warn("Shell", "Shell extension loaded (confined). AI has system access where explicitly allowed.");
8
9  return {
10    tools: getTools(),
11  };
12}
13
1export default {
2  name: "shell",
3  version: "1.0.1",
4  builtFor: "TreeOS",
5  scope: "confined",
6  description:
7    "Gives the AI direct shell access to the land server. The execute-shell tool runs " +
8    "any command through Node.js child_process with a 30 second timeout and 8KB output " +
9    "cap. Three security layers. Layer 1: confined scope. Shell is inactive everywhere by " +
10    "default. Operators explicitly allow it at specific nodes with ext-allow shell. A DevOps " +
11    "branch gets shell. The rest of the tree never sees it. Layer 2: admin-only. The handler " +
12    "checks user.isAdmin before every execution and rejects all non-admin callers. Layer 3: " +
13    "energy metering. Each execution costs energy to prevent rapid-fire abuse. A regex " +
14    "blocklist catches the most destructive patterns (rm -rf, fork bombs, disk writes, " +
15    "firewall flushes, pipe-to-shell curls, password changes, service shutdowns, command " +
16    "substitution) even for admins. The blocklist prevents common accidents. It is not a " +
17    "sandbox. Shell access is real shell access. Confine it to positions where that is " +
18    "acceptable. Every command is logged with the user ID.",
19
20  needs: {
21    models: ["User"],
22  },
23
24  optional: {
25    services: ["energy"],
26  },
27
28  provides: {
29    models: {},
30    routes: false,
31    tools: true,
32    jobs: false,
33    orchestrator: false,
34    energyActions: {
35      shellExecute: { cost: 5 },
36    },
37    sessionTypes: {},
38    cli: [],
39  },
40};
41
1import { z } from "zod";
2import { execFile } from "child_process";
3import { promisify } from "util";
4import User from "../../seed/models/user.js";
5import log from "../../seed/log.js";
6
7const execFileAsync = promisify(execFile);
8const TIMEOUT_MS = 30000;
9const MAX_OUTPUT = 8000;
10
11let _useEnergy = async () => ({ energyUsed: 0 });
12
13export function setEnergyService(energy) {
14  if (energy?.useEnergy) _useEnergy = energy.useEnergy;
15}
16
17export default function getTools() {
18  return [
19    {
20      name: "execute-shell",
21      description: "Execute a shell command on the land server. Returns stdout and stderr. God-tier users only. 30 second timeout.",
22      schema: {
23        command: z.string().describe("The shell command to execute."),
24        userId: z.string().describe("Injected by server. Ignore."),
25      },
26      annotations: {
27        readOnlyHint: false,
28        destructiveHint: true,
29        idempotentHint: false,
30        openWorldHint: true,
31      },
32      async handler({ command, userId }) {
33        const user = await User.findById(userId).select("isAdmin").lean();
34        if (!user || !user.isAdmin) {
35          return { content: [{ type: "text", text: "Permission denied. Shell access requires admin." }] };
36        }
37        if (!command) {
38          return { content: [{ type: "text", text: "No command provided." }] };
39        }
40
41        // Block dangerous commands
42        const BLOCKED = [
43          /\brm\s+.*-[a-zA-Z]*r/i,        // rm -r, rm -rf, rm -fr
44          /\brm\s+.*\//,                   // rm with any path
45          /\brmdir\b/i,                    // rmdir
46          /\bmkfs\b/i,                     // format filesystem
47          /\bdd\s+/i,                      // disk destroy
48          /\b:\(\)\{.*\|.*\}/,             // fork bomb
49          /\bchmod\s+(777|000|\+s)\b/,     // dangerous permission changes
50          /\bchown\b/i,                    // any ownership change
51          />\s*\/dev\/sd/,                 // overwrite disk devices
52          /\bshutdown\b/i,                // shutdown
53          /\breboot\b/i,                  // reboot
54          /\binit\s+[06]\b/,              // init shutdown/reboot
55          /\bsystemctl\s+(stop|disable|mask|restart)\b/i, // stop/restart services
56          /\bkill\s+-9\s+1\b/,            // kill init
57          /\biptables\s+-F\b/i,           // flush firewall
58          /\bufw\s+(disable|reset)\b/i,   // disable firewall
59          /\bpasswd\b/i,                  // change passwords
60          /\busermod\b/i,                 // modify users
61          /\buserdel\b/i,                 // delete users
62          /\bcurl\b.*\|\s*(bash|sh)\b/i,  // pipe to shell
63          /\bwget\b.*\|\s*(bash|sh)\b/i,  // pipe to shell
64          /\beval\b/i,                    // eval execution
65          /`[^`]*`/,                      // backtick command substitution
66          /\$\([^)]*\)/,                  // $() command substitution
67        ];
68
69        const blocked = BLOCKED.find(re => re.test(command));
70        if (blocked) {
71          log.warn("Shell", `BLOCKED dangerous command from ${userId}: ${command.slice(0, 200)}`);
72          return { content: [{ type: "text", text: "Blocked: this command pattern is not allowed for safety. Use the server directly for destructive operations." }] };
73        }
74
75        // Energy metering
76        try {
77          await _useEnergy({ userId, action: "shellExecute" });
78        } catch {
79          return { content: [{ type: "text", text: "Insufficient energy for shell execution." }] };
80        }
81
82        log.warn("Shell", `${userId} executing: ${command.slice(0, 200)}`);
83
84        // Execute via /bin/sh -c but with execFile (no double shell interpretation).
85        // execFile with explicit shell path avoids the implicit shell spawning of exec().
86        // The command is passed as a single argument to sh -c, not interpreted by Node.
87        try {
88          const { stdout, stderr } = await execFileAsync("/bin/sh", ["-c", command], {
89            timeout: TIMEOUT_MS,
90            cwd: process.cwd(),
91            maxBuffer: 1024 * 1024,
92            env: { ...process.env, PATH: process.env.PATH },
93          });
94          const out = (stdout || "").slice(0, MAX_OUTPUT);
95          const err = (stderr || "").slice(0, MAX_OUTPUT);
96          let result = out || "(no output)";
97          if (err) result += "\n--- stderr ---\n" + err;
98          return { content: [{ type: "text", text: result }] };
99        } catch (err) {
100          const output = [err.stdout, err.stderr, err.message].filter(Boolean).join("\n").slice(0, MAX_OUTPUT);
101          return { content: [{ type: "text", text: `Command failed (exit ${err.code || "?"})\n${output}` }] };
102        }
103      },
104    },
105  ];
106}
107

Versions

Version Published Downloads
1.0.1 38d ago 0
1.0.0 48d ago 0
0 stars
0 flags
React from the CLI: treeos ext star shell

Comments

Loading comments...

Post comments from the CLI: treeos ext comment shell "your comment"
Max 3 comments per extension. One star and one flag per user.