forked from enderofwings/NexusOS
render_preview validates markup and hands it to the browser, which renders it in an opaque-origin sandboxed iframe. Nothing executes server-side. That model fits HTML/SVG/JSX and cannot fit C, Rust or Erlang, which need a real toolchain - so those get a second tool instead of a widened first one. The split is the feature: the model picks a track by picking a tool, rather than picking a `lang` value from an enum where half the entries run server-side and half do not. synapse/code_run.py compiles and runs one file in a throwaway directory and returns a ```nexus-run fence carrying the source and its captured output together, so a model cannot paste output without the code that produced it. Backticks in the source are re-encoded as ` - still valid JSON, and it cannot close the fence early. It is not a sandbox, and the module docstring says so up front. What it gives is containment by layers: consent (an action tool, gated by action_tool_policy, per-call Approve/Deny on "ask"), static screening, a scrubbed environment in a temp dir, wall-clock and POSIX rlimits, and a network namespace on Linux where unprivileged userns are available. Screening is a tripwire against a model reaching for `requests` out of habit, not a boundary against an adversary; layers 1 and 3-5 are the load-bearing ones. Backend RUN_LANGS and frontend run-langs.js are separate registries because the two sides need different things - one executes, one labels - and neither should depend on the other at runtime. tests/test_tools.py asserts the key sets and the fence tag stay equal, so drift fails the gate instead of rendering a run result under the wrong language. tests/snippet_probes/ is a data catalog rather than inlined cases, so adding a language is a data change and the meta-tests can assert every RUN_LANGS key has both a smoke probe and a screening probe. Probes skip cleanly on hosts without the toolchain. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
84 lines
3.2 KiB
JavaScript
84 lines
3.2 KiB
JavaScript
/*
|
|
* The run-result envelope: what parseRunResult will and won't accept.
|
|
*
|
|
* Everything this parses arrived as text a language model chose to paste into
|
|
* its reply, so the interesting cases are all the malformed ones. A bad envelope
|
|
* has to return null - the caller then shows the raw block as code, which is
|
|
* ugly but honest - rather than yield a half-built object that renders as a run
|
|
* that never happened.
|
|
*/
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import { RUN_LANGS, RUN_FENCE_LANG, parseRunResult } from "./run-langs.js";
|
|
|
|
const envelope = (over = {}) => JSON.stringify({
|
|
lang: "python",
|
|
source: "print(1)",
|
|
stdout: "1\n",
|
|
stderr: "",
|
|
exit_code: 0,
|
|
...over,
|
|
});
|
|
|
|
test("the fence tag is the one the backend emits", () => {
|
|
assert.equal(RUN_FENCE_LANG, "nexus-run");
|
|
});
|
|
|
|
test("every language has a display label", () => {
|
|
for (const [name, spec] of Object.entries(RUN_LANGS)) {
|
|
assert.equal(typeof spec.label, "string", name);
|
|
assert.ok(spec.label.length, name);
|
|
}
|
|
});
|
|
|
|
test("a well-formed envelope parses into display fields", () => {
|
|
const run = parseRunResult(envelope());
|
|
assert.equal(run.lang, "python");
|
|
assert.equal(run.label, "Python");
|
|
assert.equal(run.source, "print(1)");
|
|
assert.equal(run.stdout, "1\n");
|
|
assert.equal(run.exitCode, 0);
|
|
});
|
|
|
|
test("a backtick-escaped source round-trips through JSON", () => {
|
|
// run_snippet re-encodes ` as ` so the source cannot close the fence.
|
|
const run = parseRunResult('{"lang":"python","source":"x = \\u0060a\\u0060","exit_code":0}');
|
|
assert.equal(run.source, "x = `a`");
|
|
});
|
|
|
|
test("a non-zero exit code is preserved, not coerced away", () => {
|
|
// `exitCode || null` would turn 0 into null and hide a clean exit; a plain
|
|
// falsy check on the other side would call a failing program successful.
|
|
assert.equal(parseRunResult(envelope({ exit_code: 2 })).exitCode, 2);
|
|
assert.equal(parseRunResult(envelope({ exit_code: 0 })).exitCode, 0);
|
|
});
|
|
|
|
test("a missing exit code becomes null rather than a guess", () => {
|
|
assert.equal(parseRunResult(envelope({ exit_code: undefined })).exitCode, null);
|
|
assert.equal(parseRunResult(envelope({ exit_code: "0" })).exitCode, null);
|
|
});
|
|
|
|
test("absent streams read as empty, never undefined", () => {
|
|
const run = parseRunResult('{"lang":"c","source":"int main(){}","exit_code":0}');
|
|
assert.equal(run.stdout, "");
|
|
assert.equal(run.stderr, "");
|
|
});
|
|
|
|
test("malformed or foreign envelopes are rejected", () => {
|
|
assert.equal(parseRunResult("not json at all"), null);
|
|
assert.equal(parseRunResult("null"), null);
|
|
assert.equal(parseRunResult("[1,2,3]"), null);
|
|
assert.equal(parseRunResult('"a string"'), null);
|
|
assert.equal(parseRunResult(envelope({ lang: "haskell" })), null);
|
|
assert.equal(parseRunResult(envelope({ source: undefined })), null);
|
|
assert.equal(parseRunResult(envelope({ source: 42 })), null);
|
|
});
|
|
|
|
test("a prototype key is not mistaken for a supported language", () => {
|
|
// `data.lang in RUN_LANGS` would be true for "toString" and read the label
|
|
// off Object.prototype - a run panel titled with a function body.
|
|
assert.equal(parseRunResult(envelope({ lang: "toString" })), null);
|
|
assert.equal(parseRunResult(envelope({ lang: "constructor" })), null);
|
|
});
|