Skip to content
Merged

Golemv2 #1390

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions bin/embedded_files_test/exec_script/main.eldritch
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ def copy_script_and_execute():
print(shell_res)
print("\n")
elif sys.is_windows():
assets.copy("exec_script/hello_world.bat","C:\Windows\Temp\golem_cli_test-copy_script_and_execute")
shell_res = sys.shell("C:\Windows\Temp\golem_cli_test-copy_script_and_execute")
assets.copy("exec_script/hello_world.bat",r"C:\Windows\Temp\golem_cli_test-copy_script_and_execute.bat")
shell_res = sys.shell(r"cmd.exe /c C:\Windows\Temp\golem_cli_test-copy_script_and_execute.bat")
print(shell_res)
print("\n")

copy_script_and_execute()
if sys.hostname() == "eldritch-test-box":
print("we are in a mock env")
print(assets.read("exec_script/hello_world.sh")) # Print this so the test passes
#print("hello from an embedded shell script")
else:
copy_script_and_execute()
15 changes: 0 additions & 15 deletions bin/golem_cli_test/download_test.eldritch

This file was deleted.

15 changes: 15 additions & 0 deletions bin/golem_cli_test/download_test/main.eldritch
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
def download_stuff_test():
content = http.get("https://raw.githubusercontent.com/spellshift/realm/refs/heads/main/README.md")
# Allow this to work in both v1 and v2
if type(content) != "string":
content = str(content["body"])

# Mock is needed for tests
if not "# Realm" in content and not "Mock GET" in content:
print("failed to download readme")
return -1

print("OKAY!")
return 0

download_stuff_test()
1 change: 0 additions & 1 deletion bin/golem_cli_test/eldritch_test.eldritch

This file was deleted.

9 changes: 9 additions & 0 deletions bin/golem_cli_test/valid_tome/main.eldritch
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
def test():
res = ""
for i in ["H", "E", "L", "L", "O"]:
res = res + i
return res

print("hello from an asset directory")
print(test())
print(dir(file))
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
def test():
retorn "win"

print(test())
print(test())
2 changes: 2 additions & 0 deletions implants/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ members = [
"imix",
"imixv2",
"golem",
"golemv2",
"lib/eldritch",
"lib/transport",
"lib/pb",
Expand Down Expand Up @@ -75,6 +76,7 @@ criterion = "0.5"
netdev = "0.33.0"
derive_more = "=0.99.17"
eval = "0.4.3"
walkdir = "2.5.0"
flate2 = { version = "1.0.24", default-features = false }
gazebo = "0.8.1"
glob = "0.3.1"
Expand Down
26 changes: 9 additions & 17 deletions implants/golem/tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::io::prelude::*;
use std::process::{Command, Stdio}; // Run programs
use std::str;

const GOLEM_CLI_TEST_DIR: &str = "../../bin/golem_cli_test/";
const GOLEM_CLI_TEST_DIR: &str = "../../bin/golem_cli_test";
// Test running `./golem ./nonexistentdir/run.tome`
#[test]
fn test_golem_main_file_not_found() -> anyhow::Result<()> {
Expand All @@ -27,43 +27,35 @@ fn test_golem_main_file_not_found() -> anyhow::Result<()> {
fn test_golem_main_syntax_fail() -> anyhow::Result<()> {
let mut cmd = Command::new(cargo_bin!("golem"));

cmd.arg(format!("{GOLEM_CLI_TEST_DIR}syntax_fail.eldritch"));
cmd.arg(format!(
"{GOLEM_CLI_TEST_DIR}_shadow/syntax_fail/main.eldritch"
));
cmd.assert().failure().stderr(predicate::str::contains(
r#"Parse error: unexpected string literal"#.to_string(),
));

Ok(())
}
// Test running `./golem ../../bin/golem_cli_test/hello_world.tome`
// Test running `./golem ../../bin/golem_cli_test/valid_tome/main.eldritch`
#[test]
fn test_golem_main_basic_non_interactive() -> anyhow::Result<()> {
let mut cmd = Command::new(cargo_bin!("golem"));

cmd.arg(format!("{GOLEM_CLI_TEST_DIR}hello_world.eldritch"));
cmd.arg(format!("{GOLEM_CLI_TEST_DIR}/valid_tome/main.eldritch"));
cmd.assert()
.success()
.stdout(predicate::str::contains(r#"HELLO"#));
.stdout(predicate::str::contains(r#"HELLO"#))
.stdout(predicate::str::contains(r#""append", "compress""#));

Ok(())
}
// Test running `./golem ../../bin/golem_cli_test/eldritch_test.tome`
#[test]
fn test_golem_main_basic_eldritch_non_interactive() -> anyhow::Result<()> {
let mut cmd = Command::new(cargo_bin!("golem"));

cmd.arg(format!("{GOLEM_CLI_TEST_DIR}eldritch_test.eldritch"));
cmd.assert()
.success()
.stdout(predicate::str::contains(r#"["append", "compress""#));

Ok(())
}
// Test running `./golem ../../bin/golem_cli_test/eldritch_test.tome`
#[test]
fn test_golem_main_basic_async() -> anyhow::Result<()> {
let mut cmd = Command::new(cargo_bin!("golem"));

cmd.arg(format!("{GOLEM_CLI_TEST_DIR}download_test.eldritch"));
cmd.arg(format!("{GOLEM_CLI_TEST_DIR}/download_test/main.eldritch"));
cmd.assert()
.success()
.stdout(predicate::str::contains(r#"OKAY!"#));
Expand Down
30 changes: 30 additions & 0 deletions implants/golemv2/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "golemv2"
version = "0.1.0"
edition = "2024"

[dependencies]
clap = { workspace = true }
anyhow = { workspace = true }
pb = { workspace = true }
crossterm = { workspace = true }
walkdir = {workspace = true}
rust-embed = { workspace = true }
eldritch-core = { workspace = true }
eldritch-macros = { workspace = true }
eldritchv2 = { workspace = true, features = ["std", "stdlib"] }
eldritch-repl = { workspace = true }
eldritch-libagent = { workspace = true, features = ["fake_bindings"] }
eldritch-libassets = { workspace = true, features = ["stdlib"] }
eldritch-libpivot = { workspace = true, features = ["stdlib"] }
eldritch-libreport = { workspace = true, features = ["stdlib"] }
tokio.workspace = true
futures.workspace = true

[dev-dependencies]
assert_cmd = { workspace = true }
predicates = { workspace = true }
tempfile = { workspace = true }

[target.'cfg(target_os = "windows")'.build-dependencies]
static_vcruntime = { workspace = true }
Empty file.
94 changes: 94 additions & 0 deletions implants/golemv2/src/directorybackend.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// This file defines DirectoryAssetBackend which is compatible with StdAssetLibrary
use alloc::borrow::Cow;
use alloc::vec::Vec;
use anyhow::anyhow;
use core::fmt::Debug;
use eldritchv2::assets::std::AssetBackend;
use std::fs;
use std::path::PathBuf;

const MAX_RECURSION_DEPTH: usize = 64;

#[derive(Debug)]
pub struct DirectoryAssetBackend {
root: PathBuf,
}

// An asset backend that reads from the local filesystem
impl DirectoryAssetBackend {
/// Creates a new asset backend rooted at the given directory path.
///
/// # Errors
/// Returns a String error if the path does not exist or is not a directory.
pub fn new(directory_name: &str) -> anyhow::Result<Self> {
let root = PathBuf::from(directory_name);
if !root.exists() {
return Err(anyhow!("Directory not found: {}", directory_name));
}
if !root.is_dir() {
return Err(anyhow!("Path is not a directory: {}", directory_name));
}

// Canonicalize the path to resolve symlinks and '..' segments,
// which helps in later path checks.
let canonical_root = root
.canonicalize()
.map_err(|e| anyhow!("Failed to canonicalize path {}: {}", directory_name, e))?;

Ok(DirectoryAssetBackend {
root: canonical_root,
})
}

/// Safely constructs the full, canonical path to the requested file.
/// Returns `None` if the path attempts to leave the root directory.
fn get_safe_path(&self, file_path: &str) -> Option<PathBuf> {
// 1. Join the root and the relative file path.
let mut path = self.root.clone();
path.push(file_path);

let canonical_path = match path.canonicalize() {
Ok(p) => p,
// If canonicalize fails (e.g., file doesn't exist), we treat it as not found.
Err(_) => return None,
};

// Ensure the canonical path starts with the canonical root.
// If it doesn't, the path contained '..' segments that successfully escaped the root.
if !canonical_path.starts_with(&self.root) {
// Path traversal attempt detected
return None;
}

Some(canonical_path)
}
}

// Implementing AssetBackend for DirectoryAssetBackend
impl AssetBackend for DirectoryAssetBackend {
fn get(&self, file_path: &str) -> anyhow::Result<Vec<u8>> {
let safe_path = self
.get_safe_path(file_path)
.ok_or(anyhow::anyhow!("invalid file path"))?;
// The path is safe and exists. Read the contents.
let data = fs::read(&safe_path)?;
Ok(data)
}

fn assets(&self) -> Vec<Cow<'static, str>> {
walkdir::WalkDir::new(&self.root)
.max_depth(MAX_RECURSION_DEPTH)
.into_iter()
.filter_map(|e| e.ok())
.filter(|entry| entry.file_type().is_file())
.filter_map(|entry| {
entry
.path()
.strip_prefix(&self.root)
.ok() // Get Ok(RelPath), discard Err
.and_then(|rel_path| rel_path.to_str()) // Get Some(str), discard None
.map(|s| Cow::Owned(s.to_string()))
})
.collect()
}
}
Loading
Loading