Files
hakorune/docs/reference/macro/capabilities.md

3.9 KiB
Raw Blame History

Macro Capabilities (Sandbox Policy)

Status: Design/PoC. This document defines the capability model for user macros executed via the PyVM sandbox.

Goals

  • Determinism by default: macro expansion should be reproducible and isolated.
  • Principle of least privilege: nothing is allowed unless explicitly requested.
  • Simple, declarative switches per macro file (future: nyash.toml), with safe defaults.

Capability Set (v0)

  • io: allow file I/O (readonly at first; write is out of scope for Phase 2)
  • net: allow network access (HTTP/Socket); default OFF
  • env: allow environment variable reads via a MacroCtx helper (getEnv); default OFF

Default: all OFF (io=false, net=false, env=false)

Stable Minimal Box API (macro sandbox)

User macros executed in the sandbox must not depend on external plugins. The following core Boxes and methods are guaranteed to exist and remain stable for macro authors (MVP scope):

  • ConsoleBox
    • print/println/log(string)
  • String (receiver is Python-style string in PyVM sandbox)
    • length() -> int
    • substring(start:int, end:int) -> string
    • lastIndexOf(substr:string) -> int
    • esc_json() -> string (escape for JSON embedding)
  • ArrayBox
    • size() -> int, get(i:int) -> any, set(i:int, v:any), push(v:any)
  • MapBox
    • size() -> int, has(key:string) -> bool, get(key:string) -> any, set(key:string, v:any), toString() -> string

Notes

  • These APIs are available only inside the macro sandbox child. Application execution (PyVM/LLVM) continues to use the normal plugin system.
  • The sandbox disables plugins by default (NYASH_DISABLE_PLUGINS=1) to ensure determinism; only the above minimal Boxes are relied upon by macros.
  • Built-in core normalization (for/foreach → Loop, match → If, Loop tail alignment) does not use Boxes and is not affected by plugin state.

Behavior per Capability

  • io=false
    • Disable FileBox and other I/O boxes in the macro sandbox.
    • No reads from the filesystem; macros operate purely on the AST JSON.
  • net=false
    • Disable Http/Socket boxes.
    • No external calls; prevents nondeterministic expansion due to remote content.
  • env=false
    • MacroCtx.getEnv is unavailable (returns an error / empty); child inherits a minimal, scrubbed environment.

Configuration (planned)

nyash.toml example (subject to refinement):

[macros]
paths = [
  "apps/macros/examples/echo_macro.hako",
  "apps/macros/examples/upper_string_macro.hako",
]

[macro_caps."apps/macros/examples/echo_macro.hako"]
io = false
net = false
env = false

[macro_caps."apps/macros/examples/upper_string_macro.hako"]
io = false
net = false
env = false

Phase2 PoC maps these to the child process environment/sandbox:

  • Always sets: NYASH_VM_USE_PY=1, NYASH_DISABLE_PLUGINS=1
  • Timeouts: NYASH_NY_COMPILER_TIMEOUT_MS (default 2000ms)
  • Strict execution: NYASH_MACRO_STRICT=1 (default) → child failure/timeout aborts the build
  • Future: map io/net/env to enabling specific safe Boxes inside the PyVM macro runtime

Execution Flow (recap)

  1. Parse → AST
  2. Expansion pass (fixedpoint with cycle/depth guards):
    • Builtin (Rust) macros first (e.g., derive Equals/ToString, publiconly)
    • User macros (Nyash) via Proxy → PyVM child
  3. Using/resolve → MIR → Backend (VM/LLVM/WASM/AOT)

Diagnostics/Observability

  • Trace JSONL: NYASH_MACRO_TRACE_JSONL=<file> produces one JSON record per pass with size/time/change flags.
  • Dump expanded AST: --dump-expanded-ast-json <file.hako> prints AST JSON v0 postexpansion for golden diffs.
  • Strict mode failures are surfaced with nonzero exit codes (2/124).

Recommendations

  • Keep macros pure (operate only on AST JSON v0) unless there is a strong case for capabilities.
  • Treat net=true as exceptional and subject to policy review, due to determinism concerns.
  • Prefer deterministic inputs (versioned data files) if io=true is deemed necessary in future.