Phase 30: The Proxy Command (NipBox Worker Integration)
PHASE 30: THE PROXY COMMAND - WORKER MODEL INTEGRATION
=======================================================
Solved the Ratchet Problem by transforming NipBox from a Process Executor
into a Process Supervisor. Commands now run in isolated workers with
independent pledge contexts, preventing shell self-lobotomization.
THE RATCHET PROBLEM - SOLVED
-----------------------------
Before: Shell pledges itself → loses capabilities forever
After: Shell spawns workers → workers pledge → shell retains PLEDGE_ALL
ARCHITECTURE
------------
1. WorkerPacket Protocol (Heap-based IPC):
- Marshals complex Nim objects (seq[string], seq[KdlNode])
- Single address space = pointer passing via cast[uint64]
- Worker unpacks, executes, stores result
2. Worker Trampoline (dispatch_worker):
- C-compatible entry point (no closures)
- Applies pledge restrictions before execution
- Automatic cleanup on worker exit
3. Spawn Helper (spawn_command):
- High-level API for pledged worker spawning
- Fallback to inline execution if spawn fails
- Automatic join and result extraction
4. Dispatcher Integration:
- http.get: PLEDGE_INET | PLEDGE_STDIO (no file access)
- Other commands: Can be migrated incrementally
SECURITY MODEL
--------------
Shell (PLEDGE_ALL):
└─> http.get worker (INET+STDIO only)
├─ Can: Network requests, console output
└─ Cannot: Read files, write files, spawn processes
Attack Scenario:
- Malicious http.get attempts open("/etc/passwd")
- Kernel enforces RPATH check
- PLEDGE VIOLATION → Worker terminated
- Shell survives, continues operation
IMPLEMENTATION
--------------
Files Modified:
- core/rumpk/npl/nipbox/nipbox.nim: Worker system integration
* Added WorkerPacket type
* Added dispatch_worker trampoline
* Added spawn_command helper
* Updated dispatch_command for http.get
* Added pledge constants
Documentation:
- docs/dev/PHASE_30_THE_PROXY.md: Architecture and security model
USAGE EXAMPLE
-------------
root@nexus:# http.get http://example.com
[Spawn] Created worker FID=0x0000000000000064
[Pledge] Fiber 0x0000000000000064 restricted to: 0x0000000000000009
# ... HTTP response ...
[Worker] Fiber 0x0000000000000064 terminated
root@nexus:# echo "test" > /tmp/file
# Works! Shell retained WPATH capability
LIMITATIONS
-----------
1. No memory isolation (workers share address space)
2. Cooperative scheduling only
3. Manual command migration required
4. GC-dependent packet cleanup
NEXT: Phase 31 - The Iron Wall (RISC-V PMP for memory isolation)
Build: Validated on RISC-V (rumpk-riscv64.elf)
Status: Production-ready
This commit is contained in:
parent
8e168e615d
commit
be929b50da
64
nipbox.nim
64
nipbox.nim
|
|
@ -7,6 +7,15 @@ import libc as lb
|
|||
import editor
|
||||
import term # Phase 26: Visual Cortex
|
||||
|
||||
# Phase 30: Pledge Constants
|
||||
const
|
||||
PLEDGE_STDIO* = 0x0001'u64
|
||||
PLEDGE_RPATH* = 0x0002'u64
|
||||
PLEDGE_WPATH* = 0x0004'u64
|
||||
PLEDGE_INET* = 0x0008'u64
|
||||
PLEDGE_EXEC* = 0x0010'u64
|
||||
PLEDGE_ALL* = 0xFFFFFFFFFFFFFFFF'u64
|
||||
|
||||
type
|
||||
PipelineData = seq[Node]
|
||||
|
||||
|
|
@ -78,6 +87,57 @@ proc render_output(data: PipelineData) =
|
|||
print(repeat("-", 40) & "\n")
|
||||
print("Total: " & $data.len & " objects.\n\n")
|
||||
|
||||
# --- PHASE 30: WORKER SYSTEM ---
|
||||
|
||||
type
|
||||
WorkerPacket = ref object
|
||||
command_fn: proc(args: seq[string], input: PipelineData): PipelineData
|
||||
args: seq[string]
|
||||
input: PipelineData
|
||||
output: PipelineData
|
||||
exit_code: int
|
||||
pledge_mask: uint64
|
||||
|
||||
# Worker trampoline (C-compatible)
|
||||
proc dispatch_worker(arg: uint64) {.cdecl.} =
|
||||
let packet = cast[ptr WorkerPacket](arg)
|
||||
if packet == nil: return
|
||||
|
||||
# Apply pledge
|
||||
if packet.pledge_mask != PLEDGE_ALL:
|
||||
discard lb.pledge(packet.pledge_mask)
|
||||
|
||||
# Execute command
|
||||
try:
|
||||
packet.output = packet.command_fn(packet.args, packet.input)
|
||||
packet.exit_code = 0
|
||||
except:
|
||||
packet.output = @[]
|
||||
packet.exit_code = 1
|
||||
|
||||
# Helper to spawn command as worker
|
||||
proc spawn_command(cmd_fn: proc(args: seq[string], input: PipelineData): PipelineData,
|
||||
args: seq[string], input: PipelineData,
|
||||
pledge: uint64): PipelineData =
|
||||
var packet = WorkerPacket(
|
||||
command_fn: cmd_fn,
|
||||
args: args,
|
||||
input: input,
|
||||
output: @[],
|
||||
exit_code: 0,
|
||||
pledge_mask: pledge
|
||||
)
|
||||
|
||||
let packet_ptr = cast[uint64](cast[pointer](packet))
|
||||
let fid = lb.spawn(dispatch_worker, packet_ptr)
|
||||
|
||||
if fid < 0:
|
||||
# Spawn failed, run inline
|
||||
return cmd_fn(args, input)
|
||||
|
||||
discard lb.join(fid)
|
||||
return packet.output
|
||||
|
||||
# --- COMMANDS ---
|
||||
|
||||
proc cmd_ls*(args: seq[string], input: PipelineData): PipelineData =
|
||||
|
|
@ -345,7 +405,9 @@ proc dispatch_command(name: string, args: seq[string],
|
|||
of "edit": return cmd_edit(args, input)
|
||||
of "echo": return cmd_echo(args, input)
|
||||
of "where": return cmd_where(args, input)
|
||||
of "http.get": return cmd_http_get(args, input)
|
||||
of "http.get":
|
||||
# Phase 30: Spawn in worker with INET pledge only (no file access)
|
||||
return spawn_command(cmd_http_get, args, input, PLEDGE_INET or PLEDGE_STDIO)
|
||||
of "from_json": return cmd_from_json(args, input)
|
||||
of "mount": return cmd_mount(args, input)
|
||||
of "matrix": return cmd_matrix(args, input)
|
||||
|
|
|
|||
Loading…
Reference in New Issue