Skip to content

Commit

Permalink
feat!: force users to explicitly enable code execution (#276)
Browse files Browse the repository at this point in the history
This changes the code snippet execution so that it is disabled by
default and users need to opt in by either:
* Running presenterm with the `-x` flag.
* Set the `snippet.exec.enable` property to true in their config files.

This aims to make running untrusted presentations more secure. You can
always set the property in your config file if you only ever run your
own presentations, but by default this prevents opening a presentation
someone else wrote and accidentally executing code in one of the slides
that does something malicious.
  • Loading branch information
mfontanini authored Jul 3, 2024
2 parents 759a6ca + b1ae243 commit 041a83f
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 3 deletions.
30 changes: 30 additions & 0 deletions config-file-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
"options": {
"$ref": "#/definitions/OptionsConfig"
},
"snippet": {
"$ref": "#/definitions/SnippetConfig"
},
"typst": {
"$ref": "#/definitions/TypstConfig"
}
Expand Down Expand Up @@ -262,6 +265,33 @@
},
"additionalProperties": false
},
"SnippetConfig": {
"type": "object",
"properties": {
"exec": {
"description": "The properties for snippet execution.",
"allOf": [
{
"$ref": "#/definitions/SnippetExecConfig"
}
]
}
},
"additionalProperties": false
},
"SnippetExecConfig": {
"type": "object",
"required": [
"enable"
],
"properties": {
"enable": {
"description": "Whether to enable snippet execution.",
"type": "boolean"
}
},
"additionalProperties": false
},
"TypstConfig": {
"type": "object",
"properties": {
Expand Down
18 changes: 18 additions & 0 deletions src/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ pub struct Config {

#[serde(default)]
pub bindings: KeyBindingsConfig,

#[serde(default)]
pub snippet: SnippetConfig,
}

impl Config {
Expand Down Expand Up @@ -114,6 +117,21 @@ pub struct OptionsConfig {
pub strict_front_matter_parsing: Option<bool>,
}

#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SnippetConfig {
/// The properties for snippet execution.
#[serde(default)]
pub exec: SnippetExecConfig,
}

#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SnippetExecConfig {
/// Whether to enable snippet execution.
pub enable: bool,
}

#[derive(Clone, Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct TypstConfig {
Expand Down
8 changes: 8 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ struct Cli {
#[clap(long)]
validate_overflows: bool,

/// Enable code snippet execution.
#[clap(short = 'x', long)]
enable_snippet_execution: bool,

/// The path to the configuration file.
#[clap(short, long)]
config_file: Option<String>,
Expand Down Expand Up @@ -135,6 +139,7 @@ fn make_builder_options(config: &Config, mode: &PresentMode, force_default_theme
end_slide_shorthand: config.options.end_slide_shorthand.unwrap_or_default(),
print_modal_background: false,
strict_front_matter_parsing: config.options.strict_front_matter_parsing.unwrap_or(true),
enable_snippet_execution: config.snippet.exec.enable,
}
}

Expand Down Expand Up @@ -208,6 +213,9 @@ fn run(mut cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
let validate_overflows = overflow_validation(&mode, &config.defaults.validate_overflows) || cli.validate_overflows;
let resources_path = path.parent().unwrap_or(Path::new("/"));
let mut options = make_builder_options(&config, &mode, force_default_theme);
if cli.enable_snippet_execution {
options.enable_snippet_execution = true;
}
let graphics_mode = select_graphics_mode(&cli, &config);
let printer = Arc::new(ImagePrinter::new(graphics_mode.clone())?);
let registry = ImageRegistry(printer.clone());
Expand Down
35 changes: 32 additions & 3 deletions src/processing/builder.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::modals::KeyBindingsModalBuilder;
use crate::{
custom::{KeyBindingsConfig, OptionsConfig},
execute::CodeExecutor,
Expand Down Expand Up @@ -33,8 +34,6 @@ use serde::Deserialize;
use std::{borrow::Cow, cell::RefCell, fmt::Display, iter, mem, path::PathBuf, rc::Rc, str::FromStr};
use unicode_width::UnicodeWidthStr;

use super::modals::KeyBindingsModalBuilder;

// TODO: move to a theme config.
static DEFAULT_BOTTOM_SLIDE_MARGIN: u16 = 3;
pub(crate) static DEFAULT_IMAGE_Z_INDEX: i32 = -2;
Expand All @@ -55,6 +54,7 @@ pub struct PresentationBuilderOptions {
pub end_slide_shorthand: bool,
pub print_modal_background: bool,
pub strict_front_matter_parsing: bool,
pub enable_snippet_execution: bool,
}

impl PresentationBuilderOptions {
Expand All @@ -81,6 +81,7 @@ impl Default for PresentationBuilderOptions {
end_slide_shorthand: false,
print_modal_background: false,
strict_front_matter_parsing: true,
enable_snippet_execution: false,
}
}
}
Expand Down Expand Up @@ -687,7 +688,7 @@ impl<'a> PresentationBuilder<'a> {
if self.options.allow_mutations && context.borrow().groups.len() > 1 {
self.chunk_mutators.push(Box::new(HighlightMutator::new(context)));
}
if code.attributes.execute {
if code.attributes.execute && self.options.enable_snippet_execution {
self.push_code_execution(code)?;
}
Ok(())
Expand Down Expand Up @@ -1047,6 +1048,7 @@ impl From<StrictPresentationMetadata> for PresentationMetadata {
#[cfg(test)]
mod test {
use super::*;
use crate::markdown::elements::CodeAttributes;
use rstest::rstest;

fn build_presentation(elements: Vec<MarkdownElement>) -> Presentation {
Expand Down Expand Up @@ -1464,4 +1466,31 @@ mod test {
let result = try_build_presentation_with_options(elements, options);
assert!(result.is_ok());
}

#[rstest]
#[case::enabled(true)]
#[case::disabled(false)]
fn snippet_execution(#[case] enabled: bool) {
let element = MarkdownElement::Code(Code {
contents: "".into(),
language: CodeLanguage::Rust,
attributes: CodeAttributes { execute: true, ..Default::default() },
});
let options = PresentationBuilderOptions { enable_snippet_execution: enabled, ..Default::default() };
let presentation = build_presentation_with_options(vec![element], options);
let slide = presentation.iter_slides().next().unwrap();
let mut found_render_async = false;
for operation in slide.iter_operations() {
let is_render_async = matches!(operation, RenderOperation::RenderAsync(_));
if is_render_async {
assert!(enabled);
found_render_async = true;
}
}
if found_render_async {
assert!(enabled, "code execution block found but not enabled");
} else {
assert!(!enabled, "code execution enabled but not found");
}
}
}

0 comments on commit 041a83f

Please sign in to comment.