Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ air-gapped signing, extensions

# Unreleased

* feat: `--untrusted`, on `build`, `deploy` and `sync`, holds a project someone else handed over to what it may run: each build step is a pre-built module, each sync step is a plugin, and the file each names is a regular file within the project once every link is followed. Anything else is refused before the command acts on it, naming what was found. Reading is not restricted: argument files, manifests and recipes are read from wherever the manifest points, and the steps a recipe renders to are held to the same list.
* chore: macOS on Intel (`x86_64-apple-darwin`) is no longer a release target. Releases ship no Intel macOS archive, the shell installer refuses that platform, and npm no longer publishes `@icp-sdk/icp-cli-darwin-x64`, so installing `@icp-sdk/icp-cli` on an Intel Mac warns of an unsupported platform. Homebrew dropped Intel macOS in September 2026, and GitHub is retiring its Intel macOS runners. v1.8.0 is the last release with an Intel macOS build.

## Experimental
Expand Down
12 changes: 9 additions & 3 deletions crates/icp-app/src/context/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use icp_project::store_artifact::ArtifactStore;
use std::time::Duration;

use crate::{agent, identity, identity::PasswordFunc};
use icp_project::{Lazy, ProjectLoadImpl, host::Host, manifest, store_id};
use icp_project::{Lazy, ProjectLoad, ProjectLoadImpl, host::Host, manifest, store_id, untrusted};

#[derive(Debug, Snafu)]
pub enum ContextInitError {
Expand All @@ -38,11 +38,14 @@ pub enum ContextInitError {
},
}

/// With `untrusted`, the loaded project is vetted by [`icp_project::untrusted`]
/// before any command acts on it.
pub fn initialize(
project_root_override: Option<PathBuf>,
debug: bool,
password_func: PasswordFunc,
pem_session_duration: Option<Duration>,
untrusted: bool,
) -> Result<Context, ContextInitError> {
// Setup global directory structure
let dirs = Arc::new(Directories::new().context(DirectoriesSnafu)?);
Expand Down Expand Up @@ -125,8 +128,11 @@ pub fn initialize(
files: files.clone(),
};

let pload = Lazy::new(pload);
let pload = Arc::new(pload);
let pload: Arc<dyn ProjectLoad> = if untrusted {
Arc::new(Lazy::new(untrusted::Vetted::new(pload, files.clone())))
} else {
Arc::new(Lazy::new(pload))
};

// Telemetry data bag (written by subsystems, read at session finish)
let telemetry_data = Arc::new(crate::telemetry_data::TelemetryData::default());
Expand Down
5 changes: 4 additions & 1 deletion crates/icp-cli/src/commands/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use tracing::info;
use icp_project::operations::build::build_many;

use crate::{
options::{EnvironmentOpt, arg_struct_change_help},
options::{EnvironmentOpt, UntrustedOpt, arg_struct_change_help},
render::rendered,
};

Expand All @@ -22,6 +22,9 @@ pub(crate) struct BuildArgs {

#[command(flatten)]
pub(crate) environment: BuildEnvironmentOpt,

#[command(flatten)]
pub(crate) untrusted: UntrustedOpt,
}

arg_struct_change_help!(
Expand Down
5 changes: 4 additions & 1 deletion crates/icp-cli/src/commands/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use tracing::info;
use crate::options::EnvironmentOpt;
use crate::{
commands::{args::ArgsOpt, canister::create},
options::{IdentityOpt, arg_struct_change_help},
options::{IdentityOpt, UntrustedOpt, arg_struct_change_help},
render::rendered,
};

Expand Down Expand Up @@ -97,6 +97,9 @@ pub(crate) struct DeployArgs {
/// `upgrade_args` in the manifest.
#[command(flatten)]
pub(crate) args_opt: ArgsOpt,

#[command(flatten)]
pub(crate) untrusted: UntrustedOpt,
}

arg_struct_change_help!(
Expand Down
5 changes: 4 additions & 1 deletion crates/icp-cli/src/commands/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use tracing::info;
use icp_project::operations::{readiness::wait_until_serving_queries, sync::sync_many};

use crate::{
options::{EnvironmentOpt, IdentityOpt},
options::{EnvironmentOpt, IdentityOpt, UntrustedOpt},
render::rendered,
};

Expand All @@ -32,6 +32,9 @@ pub(crate) struct SyncArgs {

#[command(flatten)]
pub(crate) identity: IdentityOpt,

#[command(flatten)]
pub(crate) untrusted: UntrustedOpt,
}

pub(crate) async fn exec(ctx: &Context, args: &SyncArgs) -> Result<(), anyhow::Error> {
Expand Down
1 change: 1 addition & 0 deletions crates/icp-cli/src/complete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ fn context() -> Option<&'static Context> {
false,
Arc::new(|| Err("cannot prompt while completing".to_string())),
None,
false,
Comment thread
raymondk marked this conversation as resolved.
Resolved
)
.ok()
})
Expand Down
16 changes: 16 additions & 0 deletions crates/icp-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ async fn run() -> Result<(), Error> {
cli.debug,
password_func,
pem_session_duration,
command.untrusted(),
)?;

// Extension commands are left out of telemetry: telemetry parses against
Expand Down Expand Up @@ -266,6 +267,21 @@ enum Invocation {
Extension(icp_app::extension::Installed, ArgMatches),
}

impl Invocation {
/// Whether the command was asked to treat the project as untrusted. The
/// flag belongs to the commands that act on what a project says — build,
/// deploy, sync — but it is the context every command runs in that
/// applies it, so it is read off the command before that is composed.
fn untrusted(&self) -> bool {
match self {
Invocation::BuiltIn(Command::Build(args)) => args.untrusted.untrusted,
Invocation::BuiltIn(Command::Deploy(args)) => args.untrusted.untrusted,
Invocation::BuiltIn(Command::Sync(args)) => args.untrusted.untrusted,
Invocation::BuiltIn(_) | Invocation::Extension(..) => false,
}
}
}

/// Dispatch the command to its handler.
async fn dispatch(ctx: &icp_app::context::Context, command: Command) -> Result<(), Error> {
match command {
Expand Down
13 changes: 13 additions & 0 deletions crates/icp-cli/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,19 @@ impl From<IdentityOpt> for IdentitySelection {
}
}

/// `--untrusted`, for a command that acts on what a project says: build,
/// deploy, sync. A deploy service handed a project by an agent is what asks
/// for it, and those are the commands it runs.
#[derive(Args, Clone, Debug, Default)]
pub(crate) struct UntrustedOpt {
/// Hold the project to what one someone else handed over may run: each
/// build step a pre-built module, each sync step a plugin, and what each
/// names a regular file within the project once links are followed.
/// Anything else is refused. Reading is not restricted
#[arg(long)]
pub(crate) untrusted: bool,
}

#[derive(Args, Clone, Debug, Default)]
#[clap(group(ArgGroup::new("environment-select").multiple(false)))]
pub(crate) struct EnvironmentOpt {
Expand Down
Loading
Loading