Skip to content
Merged
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
13 changes: 13 additions & 0 deletions docs/_docs/user-guide/eldritch.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,19 @@ for user_home_dir in file.list("/home/"):

---

## Agent

### agent.eval

`agent.eval(script: str) -> None`

The <b>agent.eval</b> method takes an arbitrary eldritch payload string and
executes it in the runtime environment of the executing tome. This means that
any `print`s or `eprint`s or output from the script will be merged with that
of the broader tome.

---

## Assets

### assets.copy
Expand Down
54 changes: 54 additions & 0 deletions implants/lib/eldritch/src/agent/eval_impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use std::collections::HashMap;

use crate::runtime::{Environment, Runtime};
use anyhow::Result;
use pb::eldritch::Tome;

pub fn eval(env: &Environment, script: String) -> Result<()> {
let tome = Tome {
eldritch: script,
parameters: HashMap::new(),
file_names: Vec::new(),
};
Runtime::run(env, &tome)
}

#[cfg(test)]
mod tests {
use std::{sync::mpsc::channel, time::Duration};

use crate::runtime::Message;

use super::*;
use anyhow::Result;

#[test]
fn test_eval() -> Result<()> {
let (tx, rx) = channel::<Message>();
let test_env = Environment::mock(1, tx);
eval(&test_env, String::from("print(\"hi\")"))?;
let m = rx.recv_timeout(Duration::from_secs(3))?;
match m {
Message::ReportText(r) => {
let expected_output = String::from("hi\n");
assert!(
r.text == expected_output,
"'{}' did not equal '{}'",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Should use want: got: style

r.text,
expected_output
);
Ok(())
}
_ => Err(anyhow::anyhow!("recieved nontext ouput from print")),
}?;
Ok(())
}

#[test]
fn test_eval_fail() -> Result<()> {
let (tx, _rx) = channel::<Message>();
let test_env = Environment::mock(1, tx);
assert!(eval(&test_env, String::from("blorp(\"hi\")")).is_err());
Ok(())
}
}
29 changes: 29 additions & 0 deletions implants/lib/eldritch/src/agent/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
mod eval_impl;

use starlark::{
environment::MethodsBuilder,
eval::Evaluator,
starlark_module,
values::{none::NoneType, starlark_value},
};

/*
* Define our library for this module.
*/
crate::eldritch_lib!(AgentLibrary, "agent_library");

/*
* Below, we define starlark wrappers for all of our library methods.
* The functions must be defined here to be present on our library.
*/
#[starlark_module]
#[rustfmt::skip]
#[allow(clippy::needless_lifetimes, clippy::type_complexity, clippy::too_many_arguments)]
fn methods(builder: &mut MethodsBuilder) {
#[allow(unused_variables)]
fn eval(this: &AgentLibrary, starlark_eval: &mut Evaluator<'v, '_>, script: String) -> anyhow::Result<NoneType> {
let env = crate::runtime::Environment::from_extra(starlark_eval.extra)?;
eval_impl::eval(env, script)?;
Ok(NoneType{})
}
}
1 change: 1 addition & 0 deletions implants/lib/eldritch/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![deny(warnings)]
#![allow(clippy::needless_lifetimes) /*Appears necessary for starlark macros*/]

pub mod agent;
pub mod assets;
pub mod crypto;
pub mod file;
Expand Down
5 changes: 5 additions & 0 deletions implants/lib/eldritch/src/runtime/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ impl Environment {
self.tx.send(msg.into())?;
Ok(())
}

#[cfg(test)]
pub fn mock(id: i64, tx: Sender<Message>) -> Self {
Self { id, tx }
}
}

/*
Expand Down
39 changes: 24 additions & 15 deletions implants/lib/eldritch/src/runtime/eval.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::drain::drain;
use crate::{
agent::AgentLibrary,
assets::AssetsLibrary,
crypto::CryptoLibrary,
file::FileLibrary,
Expand Down Expand Up @@ -62,7 +63,7 @@ pub async fn start(id: i64, tome: Tome) -> Runtime {
log::info!("evaluating tome (task_id={})", id);

// Run Tome
match run_impl(&env, &tome) {
match Runtime::run(&env, &tome) {
Ok(_) => {
#[cfg(debug_assertions)]
log::info!("tome evaluation successful (task_id={})", id);
Expand Down Expand Up @@ -118,20 +119,6 @@ pub async fn start(id: i64, tome: Tome) -> Runtime {
}
}

fn run_impl(env: &Environment, tome: &Tome) -> Result<()> {
let ast = Runtime::parse(tome).context("failed to parse tome")?;
let module = Runtime::alloc_module(tome).context("failed to allocate module")?;
let globals = Runtime::globals();
let mut eval: Evaluator = Evaluator::new(&module);
eval.extra = Some(env);
eval.set_print_handler(env);

match eval.eval_module(ast, &globals) {
Ok(_) => Ok(()),
Err(err) => Err(err.into_anyhow().context("failed to evaluate tome")),
}
}

/*
* Eldritch Runtime
*
Expand Down Expand Up @@ -173,6 +160,7 @@ impl Runtime {
const report: ReportLibrary = ReportLibrary;
const regex: RegexLibrary = RegexLibrary;
const http: HTTPLibrary = HTTPLibrary;
const agent: AgentLibrary = AgentLibrary;
}

GlobalsBuilder::extended_by(&[
Expand All @@ -195,6 +183,27 @@ impl Runtime {
.build()
}

/*
* Parse an Eldritch tome into a starlark Abstract Syntax Tree (AST) Module,
* then allocate a module for the tome, and finally use the passed Environment
* to evaluate the module+AST.
*/
pub fn run(env: &Environment, tome: &Tome) -> Result<()> {
let ast = Runtime::parse(tome).context("failed to parse tome")?;
let module = Runtime::alloc_module(tome).context("failed to allocate module")?;
let globals = Runtime::globals();
let mut eval: Evaluator = Evaluator::new(&module);
eval.extra = Some(env);
eval.set_print_handler(env);

match eval.eval_module(ast, &globals) {
Ok(_) => Ok(()),
Err(err) => Err(err
.into_anyhow()
.context("failed to evaluate eldritch script")),
}
}

/*
* Parse an Eldritch tome into a starlark Abstract Syntax Tree (AST) Module.
*/
Expand Down
10 changes: 10 additions & 0 deletions implants/lib/eldritch/src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,16 @@ mod tests {
want_text: format!("{}\n", r#"["download", "get", "post"]"#),
want_error: None,
},
agent_bindings: TestCase {
id: 123,
tome: Tome {
eldritch: String::from("print(dir(agent))"),
parameters: HashMap::new(),
file_names: Vec::new(),
},
want_text: format!("{}\n", r#"["eval"]"#),
want_error: None,
},
}

#[tokio::test(flavor = "multi_thread", worker_threads = 128)]
Expand Down
Loading