Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 149a042db9 |
@@ -377,78 +377,16 @@ fn call_neuron_mcp(tool_name: String, args: String) -> String {
|
|||||||
return json_safe(result)
|
return json_safe(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Agent workspace scope (defense-in-depth, NOT a hard security boundary).
|
|
||||||
//
|
|
||||||
// When a workspace root is configured (state key "agent_workspace_root", else
|
|
||||||
// env NEURON_AGENT_ROOT), the path-based tools (read_file, write_file,
|
|
||||||
// list_files, grep) are confined to that subtree by a lexical check, and
|
|
||||||
// run_command runs with its cwd set to the root. With no root set, behavior is
|
|
||||||
// unchanged (unscoped) for backward compatibility.
|
|
||||||
//
|
|
||||||
// LIMITATION — FLAGGED FOR WILL'S REVIEW: this is a lexical guard. It does not
|
|
||||||
// resolve symlinks and cannot stop an arbitrary shell command from cd-ing out
|
|
||||||
// of the root. Real confinement needs runtime support (cwd-locked exec /
|
|
||||||
// sandbox-exec / chroot) in el_runtime.c. This raises the floor; it is not a
|
|
||||||
// boundary. The default-allow-when-unset policy and the "cd <root> && (...)"
|
|
||||||
// wrapping are deliberate choices to confirm against the intended design.
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
fn agent_workspace_root() -> String {
|
|
||||||
let s: String = state_get("agent_workspace_root")
|
|
||||||
if !str_eq(s, "") {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return env("NEURON_AGENT_ROOT")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow if path stays under root. Empty root = no sandbox = allow. Rejects
|
|
||||||
// parent traversal and ~ expansion; absolute paths must live under root.
|
|
||||||
fn path_within_root(path: String, root: String) -> Bool {
|
|
||||||
if str_eq(root, "") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if str_contains(path, "..") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if str_starts_with(path, "~") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if str_starts_with(path, "/") {
|
|
||||||
return str_starts_with(path, root)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve a relative tool path against the root so it lands inside the subtree.
|
|
||||||
fn resolve_in_root(path: String, root: String) -> String {
|
|
||||||
if str_eq(root, "") {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
if str_starts_with(path, "/") {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
return root + "/" + path
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dispatch_tool(tool_name: String, tool_input: String) -> String {
|
fn dispatch_tool(tool_name: String, tool_input: String) -> String {
|
||||||
if str_eq(tool_name, "read_file") {
|
if str_eq(tool_name, "read_file") {
|
||||||
let path: String = json_get(tool_input, "path")
|
let path: String = json_get(tool_input, "path")
|
||||||
let root: String = agent_workspace_root()
|
let content: String = fs_read(path)
|
||||||
if !path_within_root(path, root) {
|
|
||||||
return json_safe("denied: path is outside the agent workspace root")
|
|
||||||
}
|
|
||||||
let content: String = fs_read(resolve_in_root(path, root))
|
|
||||||
return json_safe(content)
|
return json_safe(content)
|
||||||
}
|
}
|
||||||
if str_eq(tool_name, "write_file") {
|
if str_eq(tool_name, "write_file") {
|
||||||
let path: String = json_get(tool_input, "path")
|
let path: String = json_get(tool_input, "path")
|
||||||
let content: String = json_get(tool_input, "content")
|
let content: String = json_get(tool_input, "content")
|
||||||
let root: String = agent_workspace_root()
|
fs_write(path, content)
|
||||||
if !path_within_root(path, root) {
|
|
||||||
return json_safe("denied: path is outside the agent workspace root")
|
|
||||||
}
|
|
||||||
fs_write(resolve_in_root(path, root), content)
|
|
||||||
return json_safe("{\"ok\":true}")
|
return json_safe("{\"ok\":true}")
|
||||||
}
|
}
|
||||||
if str_eq(tool_name, "web_get") {
|
if str_eq(tool_name, "web_get") {
|
||||||
@@ -463,9 +401,7 @@ fn dispatch_tool(tool_name: String, tool_input: String) -> String {
|
|||||||
}
|
}
|
||||||
if str_eq(tool_name, "run_command") {
|
if str_eq(tool_name, "run_command") {
|
||||||
let cmd: String = json_get(tool_input, "command")
|
let cmd: String = json_get(tool_input, "command")
|
||||||
let root: String = agent_workspace_root()
|
let result: String = exec_capture(cmd)
|
||||||
let scoped: String = if str_eq(root, "") { cmd } else { "cd " + root + " && ( " + cmd + " )" }
|
|
||||||
let result: String = exec_capture(scoped)
|
|
||||||
return json_safe(result)
|
return json_safe(result)
|
||||||
}
|
}
|
||||||
// MCP connector tools (namespaced mcp__<server>__<tool>) are routed through
|
// MCP connector tools (namespaced mcp__<server>__<tool>) are routed through
|
||||||
@@ -485,21 +421,13 @@ fn dispatch_tool(tool_name: String, tool_input: String) -> String {
|
|||||||
}
|
}
|
||||||
if str_eq(tool_name, "list_files") {
|
if str_eq(tool_name, "list_files") {
|
||||||
let path: String = json_get(tool_input, "path")
|
let path: String = json_get(tool_input, "path")
|
||||||
let root: String = agent_workspace_root()
|
let result: String = exec_capture("ls -la " + path + " 2>&1")
|
||||||
if !path_within_root(path, root) {
|
|
||||||
return json_safe("denied: path is outside the agent workspace root")
|
|
||||||
}
|
|
||||||
let result: String = exec_capture("ls -la " + resolve_in_root(path, root) + " 2>&1")
|
|
||||||
return json_safe(result)
|
return json_safe(result)
|
||||||
}
|
}
|
||||||
if str_eq(tool_name, "grep") {
|
if str_eq(tool_name, "grep") {
|
||||||
let pattern: String = json_get(tool_input, "pattern")
|
let pattern: String = json_get(tool_input, "pattern")
|
||||||
let path: String = json_get(tool_input, "path")
|
let path: String = json_get(tool_input, "path")
|
||||||
let root: String = agent_workspace_root()
|
let result: String = exec_capture("grep -rn \"" + pattern + "\" " + path + " 2>&1 | head -50")
|
||||||
if !path_within_root(path, root) {
|
|
||||||
return json_safe("denied: path is outside the agent workspace root")
|
|
||||||
}
|
|
||||||
let result: String = exec_capture("grep -rn \"" + pattern + "\" " + resolve_in_root(path, root) + " 2>&1 | head -50")
|
|
||||||
return json_safe(result)
|
return json_safe(result)
|
||||||
}
|
}
|
||||||
if str_eq(tool_name, "edit_file") {
|
if str_eq(tool_name, "edit_file") {
|
||||||
|
|||||||
@@ -95,6 +95,24 @@ fn init_soul_edges() -> Void {
|
|||||||
engram_connect(val_hope, val_trust, el_from_float(0.7), "co-value")
|
engram_connect(val_hope, val_trust, el_from_float(0.7), "co-value")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensure_self_canonical_bridge — link the public self anchor (the graph API's
|
||||||
|
// traversal_root, kn-efeb4a5b, which carries only incidental tag edges) to the
|
||||||
|
// curated self node (015644f5, where the real identity / value / co-value edges
|
||||||
|
// live). Without this, public self-traversal (name=self / neuron) reaches tags
|
||||||
|
// instead of the curated identity. Idempotent: connects only if the edge is
|
||||||
|
// missing, so it is safe to run every boot — including on an already-populated
|
||||||
|
// graph where init_soul_edges() is skipped by the <100-edge gate.
|
||||||
|
fn ensure_self_canonical_bridge() -> Void {
|
||||||
|
let pub_self: String = "kn-efeb4a5b-5aff-4759-8a97-7233099be6ee"
|
||||||
|
let curated_self: String = "015644f5-8194-4af0-800d-dd4a0cd71396"
|
||||||
|
let nbrs: String = engram_neighbors_json(pub_self, 1, "out")
|
||||||
|
if !str_contains(nbrs, curated_self) {
|
||||||
|
engram_connect(pub_self, curated_self, el_from_float(0.95), "canonical-self")
|
||||||
|
engram_connect(curated_self, pub_self, el_from_float(0.95), "canonical-self")
|
||||||
|
println("[soul] canonical-self bridge built: kn-efeb4a5b <-> 015644f5")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// load_identity_context — pull key identity nodes from engram into working state.
|
// load_identity_context — pull key identity nodes from engram into working state.
|
||||||
// Called at boot after engram_load. These nodes contain values, intellectual-dna,
|
// Called at boot after engram_load. These nodes contain values, intellectual-dna,
|
||||||
// memory-philosophy — the graph-stored self that chat.el can include in prompts.
|
// memory-philosophy — the graph-stored self that chat.el can include in prompts.
|
||||||
@@ -398,6 +416,9 @@ if is_genesis && safe_to_seed {
|
|||||||
} else {
|
} else {
|
||||||
println("[soul] edges already present (" + int_to_str(edge_count_now) + ") - skipping init")
|
println("[soul] edges already present (" + int_to_str(edge_count_now) + ") - skipping init")
|
||||||
}
|
}
|
||||||
|
// Canonical-self bridge is idempotent — run it regardless of edge count so an
|
||||||
|
// already-populated graph still gets the public->curated self link.
|
||||||
|
ensure_self_canonical_bridge()
|
||||||
// Genesis saves to its local snapshot file (it manages its own Engram).
|
// Genesis saves to its local snapshot file (it manages its own Engram).
|
||||||
state_set("soul_snapshot_path", snapshot)
|
state_set("soul_snapshot_path", snapshot)
|
||||||
engram_save(snapshot)
|
engram_save(snapshot)
|
||||||
|
|||||||
Reference in New Issue
Block a user