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
24 changes: 24 additions & 0 deletions crates/config/src/towerfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ pub struct Parameter {

#[serde(default)]
pub default: String,

#[serde(default)]
pub hidden: bool,
}

#[derive(Deserialize, Serialize, Debug)]
Expand Down Expand Up @@ -122,6 +125,7 @@ impl Towerfile {
name,
description,
default,
hidden: false,
});
}
}
Expand Down Expand Up @@ -272,6 +276,26 @@ mod test {
assert_eq!(towerfile.parameters.len(), 2);
assert_eq!(towerfile.parameters[0].name, "my_first_param");
assert_eq!(towerfile.parameters[1].name, "my_second_param");
assert!(!towerfile.parameters[0].hidden);
}

#[test]
fn test_parses_secret_parameters() {
let toml = r#"
[app]
name = "my-app"
script = "./script.py"
source = ["*.py"]

[[parameters]]
name = "MY_PARAMETER"
hidden = true
"#;

let towerfile = crate::Towerfile::from_toml(toml).unwrap();
assert_eq!(towerfile.parameters.len(), 1);
assert_eq!(towerfile.parameters[0].name, "MY_PARAMETER");
assert!(towerfile.parameters[0].hidden);
}

#[test]
Expand Down
4 changes: 4 additions & 0 deletions crates/tower-api/src/models/parameter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ pub struct Parameter {
#[serde_as(as = "DefaultOnNull")]
#[serde(rename = "name")]
pub name: String,
#[serde(default)]
#[serde(rename = "hidden")]
pub hidden: bool,
}

impl Parameter {
Expand All @@ -31,6 +34,7 @@ impl Parameter {
default,
description,
name,
hidden: false,
}
}
}
9 changes: 8 additions & 1 deletion crates/tower-api/src/models/run_parameter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,17 @@ pub struct RunParameter {
#[serde_as(as = "DefaultOnNull")]
#[serde(rename = "value")]
pub value: String,
#[serde(default)]
#[serde(rename = "hidden")]
pub hidden: bool,
}

impl RunParameter {
pub fn new(name: String, value: String) -> RunParameter {
RunParameter { name, value }
RunParameter {
name,
value,
hidden: false,
}
}
}
12 changes: 10 additions & 2 deletions crates/tower-cmd/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -840,7 +840,11 @@ pub async fn create_schedule(
let run_parameters = parameters.map(|params| {
params
.into_iter()
.map(|(key, value)| RunParameter { name: key, value })
.map(|(key, value)| RunParameter {
name: key,
value,
hidden: false,
})
Comment on lines 843 to 847
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

secret is hard-coded to false for all schedule parameters.

At Line 843 and Line 881 mappings, this prevents creating/updating secret schedule parameters through this path and can downgrade secrecy semantics when parameters are re-sent. The request shape should carry secret metadata instead of forcing false.

Also applies to: 881-885

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/tower-cmd/src/api.rs` around lines 843 - 847, The mapping currently
constructs RunParameter with secret: false, which drops secret metadata; update
the map closures that produce RunParameter (the ones building RunParameter {
name: key, value, secret: false }) to read the secret flag from the incoming
request parameter (e.g., use the source tuple/struct's secret field such as
param.1.secret or param.secret) and set RunParameter.secret accordingly; make
the same change at both mapping sites that create RunParameter so secret
parameters are preserved when creating/updating schedules.

.collect()
});

Expand Down Expand Up @@ -874,7 +878,11 @@ pub async fn update_schedule(
let run_parameters = parameters.map(|params| {
params
.into_iter()
.map(|(key, value)| RunParameter { name: key, value })
.map(|(key, value)| RunParameter {
name: key,
value,
hidden: false,
})
.collect()
});

Expand Down
17 changes: 16 additions & 1 deletion crates/tower-cmd/src/mcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -889,7 +889,22 @@ IMPORTANT REMINDERS:
async fn tower_schedules_list(&self) -> Result<CallToolResult, McpError> {
match api::list_schedules(&self.config, None, None).await {
Ok(response) => {
Self::json_success(serde_json::json!({"schedules": response.schedules}))
let schedules = response
.schedules
.into_iter()
.map(|mut schedule| {
if let Some(parameters) = schedule.parameters.as_mut() {
for parameter in parameters {
if parameter.hidden {
parameter.value = "[hidden]".to_string();
}
}
}
schedule
})
.collect::<Vec<_>>();

Self::json_success(serde_json::json!({"schedules": schedules}))
}
Err(e) => Self::error_result("Failed to list schedules", e),
}
Expand Down
11 changes: 5 additions & 6 deletions crates/tower/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ mod bindings {

let spec = PackageSpec::from_towerfile(&towerfile);

let runtime = tokio::runtime::Runtime::new()
.map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
let runtime =
tokio::runtime::Runtime::new().map_err(|e| PyRuntimeError::new_err(e.to_string()))?;

let output = PathBuf::from(output);

Expand All @@ -40,8 +40,7 @@ mod bindings {
.as_ref()
.ok_or_else(|| PyRuntimeError::new_err("package build produced no output file"))?;

std::fs::copy(src, &output)
.map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
std::fs::copy(src, &output).map_err(|e| PyRuntimeError::new_err(e.to_string()))?;

Ok(())
})
Expand All @@ -53,8 +52,8 @@ mod bindings {
/// args: Command line arguments (typically sys.argv).
#[pyfunction]
fn _run_cli(args: Vec<String>) -> PyResult<()> {
let runtime = tokio::runtime::Runtime::new()
.map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
let runtime =
tokio::runtime::Runtime::new().map_err(|e| PyRuntimeError::new_err(e.to_string()))?;

// App::new_from_args() must run inside block_on because
// Session::from_config_dir() requires an active tokio reactor.
Expand Down