Skip to content

Commit

Permalink
feat: add --allow-import flag (#25469)
Browse files Browse the repository at this point in the history
This replaces `--allow-net` for import permissions and makes the
security sandbox stricter by also checking permissions for statically
analyzable imports.

By default, this has a value of
`--allow-import=deno.land:443,jsr.io:443,esm.sh:443,raw.githubusercontent.com:443,gist.githubusercontent.com:443`,
but that can be overridden by providing a different set of hosts.

Additionally, when no value is provided, import permissions are inferred
from the CLI arguments so the following works because
`fresh.deno.dev:443` will be added to the list of allowed imports:

```ts
deno run -A -r https://fresh.deno.dev
```

---------

Co-authored-by: David Sherret <[email protected]>
  • Loading branch information
bartlomieju and dsherret authored Sep 26, 2024
1 parent 05415bb commit 5504ace
Show file tree
Hide file tree
Showing 507 changed files with 1,116 additions and 483 deletions.
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ deno_cache_dir = { workspace = true }
deno_config = { version = "=0.35.0", features = ["workspace", "sync"] }
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] }
deno_doc = { version = "0.148.0", features = ["html", "syntect"] }
deno_graph = { version = "=0.82.2" }
deno_graph = { version = "=0.82.3" }
deno_lint = { version = "=0.67.0", features = ["docs"] }
deno_lockfile.workspace = true
deno_npm = "=0.25.2"
Expand Down
253 changes: 203 additions & 50 deletions cli/args/flags.rs

Large diffs are not rendered by default.

102 changes: 67 additions & 35 deletions cli/args/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,7 @@ pub struct CliOptions {
// application need not concern itself with, so keep these private
flags: Arc<Flags>,
initial_cwd: PathBuf,
main_module_cell: std::sync::OnceLock<Result<ModuleSpecifier, AnyError>>,
maybe_node_modules_folder: Option<PathBuf>,
npmrc: Arc<ResolvedNpmRc>,
maybe_lockfile: Option<Arc<CliLockfile>>,
Expand Down Expand Up @@ -825,6 +826,7 @@ impl CliOptions {
npmrc,
maybe_node_modules_folder,
overrides: Default::default(),
main_module_cell: std::sync::OnceLock::new(),
start_dir,
deno_dir_provider,
})
Expand Down Expand Up @@ -1105,40 +1107,43 @@ impl CliOptions {
self.flags.env_file.as_ref()
}

pub fn resolve_main_module(&self) -> Result<ModuleSpecifier, AnyError> {
let main_module = match &self.flags.subcommand {
DenoSubcommand::Compile(compile_flags) => {
resolve_url_or_path(&compile_flags.source_file, self.initial_cwd())?
}
DenoSubcommand::Eval(_) => {
resolve_url_or_path("./$deno$eval.ts", self.initial_cwd())?
}
DenoSubcommand::Repl(_) => {
resolve_url_or_path("./$deno$repl.ts", self.initial_cwd())?
}
DenoSubcommand::Run(run_flags) => {
if run_flags.is_stdin() {
std::env::current_dir()
.context("Unable to get CWD")
.and_then(|cwd| {
resolve_url_or_path("./$deno$stdin.ts", &cwd)
.map_err(AnyError::from)
})?
} else if NpmPackageReqReference::from_str(&run_flags.script).is_ok() {
ModuleSpecifier::parse(&run_flags.script)?
} else {
resolve_url_or_path(&run_flags.script, self.initial_cwd())?
}
}
DenoSubcommand::Serve(run_flags) => {
resolve_url_or_path(&run_flags.script, self.initial_cwd())?
}
_ => {
bail!("No main module.")
}
};
pub fn resolve_main_module(&self) -> Result<&ModuleSpecifier, AnyError> {
self
.main_module_cell
.get_or_init(|| {
let main_module = match &self.flags.subcommand {
DenoSubcommand::Compile(compile_flags) => {
resolve_url_or_path(&compile_flags.source_file, self.initial_cwd())?
}
DenoSubcommand::Eval(_) => {
resolve_url_or_path("./$deno$eval.ts", self.initial_cwd())?
}
DenoSubcommand::Repl(_) => {
resolve_url_or_path("./$deno$repl.ts", self.initial_cwd())?
}
DenoSubcommand::Run(run_flags) => {
if run_flags.is_stdin() {
resolve_url_or_path("./$deno$stdin.ts", self.initial_cwd())?
} else if NpmPackageReqReference::from_str(&run_flags.script)
.is_ok()
{
ModuleSpecifier::parse(&run_flags.script)?
} else {
resolve_url_or_path(&run_flags.script, self.initial_cwd())?
}
}
DenoSubcommand::Serve(run_flags) => {
resolve_url_or_path(&run_flags.script, self.initial_cwd())?
}
_ => {
bail!("No main module.")
}
};

Ok(main_module)
Ok(main_module)
})
.as_ref()
.map_err(|err| deno_core::anyhow::anyhow!("{}", err))
}

pub fn resolve_file_header_overrides(
Expand All @@ -1159,7 +1164,7 @@ impl CliOptions {
(maybe_main_specifier, maybe_content_type)
{
HashMap::from([(
main_specifier,
main_specifier.clone(),
HashMap::from([("content-type".to_string(), content_type.to_string())]),
)])
} else {
Expand Down Expand Up @@ -1480,7 +1485,34 @@ impl CliOptions {
}

pub fn permissions_options(&self) -> PermissionsOptions {
self.flags.permissions.to_options()
fn files_to_urls(files: &[String]) -> Vec<Cow<'_, Url>> {
files
.iter()
.filter_map(|f| Url::parse(f).ok().map(Cow::Owned))
.collect()
}

// get a list of urls to imply for --allow-import
let cli_arg_urls = self
.resolve_main_module()
.ok()
.map(|url| vec![Cow::Borrowed(url)])
.or_else(|| match &self.flags.subcommand {
DenoSubcommand::Cache(cache_flags) => {
Some(files_to_urls(&cache_flags.files))
}
DenoSubcommand::Check(check_flags) => {
Some(files_to_urls(&check_flags.files))
}
DenoSubcommand::Install(InstallFlags {
kind: InstallKind::Global(flags),
}) => Url::parse(&flags.module_url)
.ok()
.map(|url| vec![Cow::Owned(url)]),
_ => None,
})
.unwrap_or_default();
self.flags.permissions.to_options(&cli_arg_urls)
}

pub fn reload_flag(&self) -> bool {
Expand Down
42 changes: 35 additions & 7 deletions cli/cache/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

use crate::args::jsr_url;
use crate::args::CacheSetting;
use crate::errors::get_error_class_name;
use crate::file_fetcher::FetchNoFollowOptions;
use crate::file_fetcher::FetchOptions;
use crate::file_fetcher::FetchPermissionsOption;
use crate::file_fetcher::FetchPermissionsOptionRef;
use crate::file_fetcher::FileFetcher;
use crate::file_fetcher::FileOrRedirect;
use crate::npm::CliNpmResolver;
Expand All @@ -19,6 +20,7 @@ use deno_graph::source::CacheInfo;
use deno_graph::source::LoadFuture;
use deno_graph::source::LoadResponse;
use deno_graph::source::Loader;
use deno_runtime::deno_permissions::PermissionsContainer;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
Expand Down Expand Up @@ -104,6 +106,13 @@ pub type LocalLspHttpCache =
deno_cache_dir::LocalLspHttpCache<RealDenoCacheEnv>;
pub use deno_cache_dir::HttpCache;

pub struct FetchCacherOptions {
pub file_header_overrides: HashMap<ModuleSpecifier, HashMap<String, String>>,
pub permissions: PermissionsContainer,
/// If we're publishing for `deno publish`.
pub is_deno_publish: bool,
}

/// A "wrapper" for the FileFetcher and DiskCache for the Deno CLI that provides
/// a concise interface to the DENO_DIR when building module graphs.
pub struct FetchCacher {
Expand All @@ -112,26 +121,27 @@ pub struct FetchCacher {
global_http_cache: Arc<GlobalHttpCache>,
npm_resolver: Arc<dyn CliNpmResolver>,
module_info_cache: Arc<ModuleInfoCache>,
permissions: FetchPermissionsOption,
permissions: PermissionsContainer,
cache_info_enabled: bool,
is_deno_publish: bool,
}

impl FetchCacher {
pub fn new(
file_fetcher: Arc<FileFetcher>,
file_header_overrides: HashMap<ModuleSpecifier, HashMap<String, String>>,
global_http_cache: Arc<GlobalHttpCache>,
npm_resolver: Arc<dyn CliNpmResolver>,
module_info_cache: Arc<ModuleInfoCache>,
permissions: FetchPermissionsOption,
options: FetchCacherOptions,
) -> Self {
Self {
file_fetcher,
file_header_overrides,
global_http_cache,
npm_resolver,
module_info_cache,
permissions,
file_header_overrides: options.file_header_overrides,
permissions: options.permissions,
is_deno_publish: options.is_deno_publish,
cache_info_enabled: false,
}
}
Expand Down Expand Up @@ -208,10 +218,24 @@ impl Loader for FetchCacher {
}
}

if self.is_deno_publish
&& matches!(specifier.scheme(), "http" | "https")
&& !specifier.as_str().starts_with(jsr_url().as_str())
{
// mark non-JSR remote modules as external so we don't need --allow-import
// permissions as these will error out later when publishing
return Box::pin(futures::future::ready(Ok(Some(
LoadResponse::External {
specifier: specifier.clone(),
},
))));
}

let file_fetcher = self.file_fetcher.clone();
let file_header_overrides = self.file_header_overrides.clone();
let permissions = self.permissions.clone();
let specifier = specifier.clone();
let is_statically_analyzable = !options.was_dynamic_root;

async move {
let maybe_cache_setting = match options.cache_setting {
Expand All @@ -230,7 +254,11 @@ impl Loader for FetchCacher {
.fetch_no_follow_with_options(FetchNoFollowOptions {
fetch_options: FetchOptions {
specifier: &specifier,
permissions: permissions.as_ref(),
permissions: if is_statically_analyzable {
FetchPermissionsOptionRef::StaticContainer(&permissions)
} else {
FetchPermissionsOptionRef::DynamicContainer(&permissions)
},
maybe_accept: None,
maybe_cache_setting: maybe_cache_setting.as_ref(),
},
Expand Down
25 changes: 17 additions & 8 deletions cli/factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ struct CliFactoryServices {
node_resolver: Deferred<Arc<NodeResolver>>,
npm_resolver: Deferred<Arc<dyn CliNpmResolver>>,
permission_desc_parser: Deferred<Arc<RuntimePermissionDescriptorParser>>,
root_permissions_container: Deferred<PermissionsContainer>,
sloppy_imports_resolver: Deferred<Option<Arc<SloppyImportsResolver>>>,
text_only_progress_bar: Deferred<ProgressBar>,
type_checker: Deferred<Arc<TypeChecker>>,
Expand Down Expand Up @@ -626,6 +627,7 @@ impl CliFactory {
self.maybe_file_watcher_reporter().clone(),
self.file_fetcher()?.clone(),
self.global_http_cache()?.clone(),
self.root_permissions_container()?.clone(),
)))
})
.await
Expand Down Expand Up @@ -659,6 +661,7 @@ impl CliFactory {
Ok(Arc::new(MainModuleGraphContainer::new(
self.cli_options()?.clone(),
self.module_load_preparer().await?.clone(),
self.root_permissions_container()?.clone(),
)))
})
.await
Expand Down Expand Up @@ -755,15 +758,20 @@ impl CliFactory {
))
}

pub fn create_permissions_container(
pub fn root_permissions_container(
&self,
) -> Result<PermissionsContainer, AnyError> {
let desc_parser = self.permission_desc_parser()?.clone();
let permissions = Permissions::from_options(
desc_parser.as_ref(),
&self.cli_options()?.permissions_options(),
)?;
Ok(PermissionsContainer::new(desc_parser, permissions))
) -> Result<&PermissionsContainer, AnyError> {
self
.services
.root_permissions_container
.get_or_try_init(|| {
let desc_parser = self.permission_desc_parser()?.clone();
let permissions = Permissions::from_options(
desc_parser.as_ref(),
&self.cli_options()?.permissions_options(),
)?;
Ok(PermissionsContainer::new(desc_parser, permissions))
})
}

pub async fn create_cli_main_worker_factory(
Expand Down Expand Up @@ -816,6 +824,7 @@ impl CliFactory {
npm_resolver.clone(),
self.permission_desc_parser()?.clone(),
self.root_cert_store_provider().clone(),
self.root_permissions_container()?.clone(),
StorageKeyResolver::from_options(cli_options),
cli_options.sub_command().clone(),
self.create_cli_main_worker_options()?,
Expand Down
Loading

0 comments on commit 5504ace

Please sign in to comment.