Skip to content

Commit 57f067f

Browse files
authored
Sanitize contract name when calling stellar contract init --name. (#2449)
1 parent 2848488 commit 57f067f

3 files changed

Lines changed: 73 additions & 4 deletions

File tree

cmd/crates/soroban-test/tests/it/config.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,20 @@ fn network_rm_rejects_path_traversal() {
688688
});
689689
}
690690

691+
#[test]
692+
fn contract_init_rejects_path_traversal() {
693+
TestEnv::with_default(|sandbox| {
694+
sandbox
695+
.new_assert_cmd("contract")
696+
.arg("init")
697+
.arg("my-project")
698+
.args(["--name", "../evil"])
699+
.assert()
700+
.failure()
701+
.stderr(predicate::str::contains("Invalid name"));
702+
});
703+
}
704+
691705
#[test]
692706
fn contract_alias_add_rejects_path_traversal() {
693707
TestEnv::with_default(|sandbox| {

cmd/soroban-cli/src/commands/contract/init.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::{
99
use clap::Parser;
1010
use rust_embed::RustEmbed;
1111

12-
use crate::{commands::global, print};
12+
use crate::{commands::global, config::address::ContractName, print};
1313

1414
#[derive(Parser, Debug, Clone)]
1515
#[group(skip)]
@@ -21,7 +21,7 @@ pub struct Cmd {
2121
default_value = "hello-world",
2222
long_help = "An optional flag to specify a new contract's name."
2323
)]
24-
pub name: String,
24+
pub name: ContractName,
2525

2626
#[arg(long, long_help = "Overwrite all existing files.")]
2727
pub overwrite: bool,
@@ -191,7 +191,7 @@ mod tests {
191191
let runner = Runner {
192192
args: Cmd {
193193
project_path: project_dir.to_string_lossy().to_string(),
194-
name: "hello_world".to_string(),
194+
name: "hello_world".parse().unwrap(),
195195
overwrite: false,
196196
},
197197
print: print::Print::new(false),
@@ -209,7 +209,7 @@ mod tests {
209209
let runner = Runner {
210210
args: Cmd {
211211
project_path: project_dir.to_string_lossy().to_string(),
212-
name: "contract2".to_string(),
212+
name: "contract2".parse().unwrap(),
213213
overwrite: false,
214214
},
215215
print: print::Print::new(false),

cmd/soroban-cli/src/config/address.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,36 @@ impl Display for AliasName {
218218
}
219219
}
220220

221+
#[derive(Clone, Debug)]
222+
pub struct ContractName(String);
223+
224+
impl std::ops::Deref for ContractName {
225+
type Target = str;
226+
fn deref(&self) -> &Self::Target {
227+
&self.0
228+
}
229+
}
230+
231+
impl std::str::FromStr for ContractName {
232+
type Err = Error;
233+
fn from_str(s: &str) -> Result<Self, Self::Err> {
234+
validate_name(s)?;
235+
Ok(ContractName(s.to_string()))
236+
}
237+
}
238+
239+
impl Display for ContractName {
240+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
241+
write!(f, "{}", self.0)
242+
}
243+
}
244+
245+
impl AsRef<std::path::Path> for ContractName {
246+
fn as_ref(&self) -> &std::path::Path {
247+
std::path::Path::new(&self.0)
248+
}
249+
}
250+
221251
#[cfg(test)]
222252
mod tests {
223253
use super::*;
@@ -272,4 +302,29 @@ mod tests {
272302
fn alias_name_rejects_empty() {
273303
assert!("".parse::<AliasName>().is_err());
274304
}
305+
306+
#[test]
307+
fn contract_name_valid() {
308+
assert!("hello-world".parse::<ContractName>().is_ok());
309+
assert!("my_contract_123".parse::<ContractName>().is_ok());
310+
}
311+
312+
#[test]
313+
fn contract_name_rejects_path_traversal() {
314+
assert!("../evil".parse::<ContractName>().is_err());
315+
assert!("../../etc/passwd".parse::<ContractName>().is_err());
316+
assert!("foo/bar".parse::<ContractName>().is_err());
317+
assert!("foo\\bar".parse::<ContractName>().is_err());
318+
}
319+
320+
#[test]
321+
fn contract_name_rejects_too_long() {
322+
assert!("a".repeat(251).parse::<ContractName>().is_err());
323+
assert!("a".repeat(250).parse::<ContractName>().is_ok());
324+
}
325+
326+
#[test]
327+
fn contract_name_rejects_empty() {
328+
assert!("".parse::<ContractName>().is_err());
329+
}
275330
}

0 commit comments

Comments
 (0)