diff --git a/server/src/routes.rs b/server/src/routes.rs index 48df13b..50a3d9a 100644 --- a/server/src/routes.rs +++ b/server/src/routes.rs @@ -186,8 +186,19 @@ fn info_for(setup: crate::config::SetupConfig) -> SetupInfo { /// Note what is absent: nothing here names a program. Providers are found /// by asking the machine (`crate::setups`), never sent, so the enrolled /// token cannot introduce something to run. +/// Every request body below refuses fields it does not know. +/// +/// Silently ignoring one is the worst available answer: a caller that +/// misspells `permissionMode` gets a session in the default permission mode +/// and a 200 saying it worked, which is indistinguishable from success at +/// the place they are looking. It cost an hour of chasing a "race" that was +/// a snake_case key serde had dropped on the floor. A 400 naming the field +/// is the whole fix, and it belongs on all of them rather than on the one +/// that bit -- query strings are deliberately excluded, since a stale link +/// carrying an extra parameter is not a mistake worth failing. #[derive(Deserialize)] #[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] struct SshRequest { address: String, #[serde(default)] @@ -225,6 +236,7 @@ impl SshRequest { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] struct AddSetupRequest { name: String, /// Absent means this machine. @@ -239,6 +251,7 @@ struct AddSetupRequest { /// form that caused it, rather than at the first spawn. #[derive(Deserialize)] #[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] struct ProbeRequest { #[serde(default)] ssh: Option, @@ -320,6 +333,7 @@ async fn read_setup( #[derive(Deserialize)] #[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] struct UpdateSetupRequest { #[serde(default)] name: Option, @@ -365,6 +379,7 @@ async fn delete_setup( #[derive(Deserialize)] #[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] struct SpawnRequest { /// Which machine, and which of the things it offers. setup: String, @@ -576,6 +591,7 @@ async fn delete_session( #[derive(Deserialize)] #[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] struct MessageRequest { text: String, /// Ids from `POST /attachments`, uploaded before the message that @@ -599,6 +615,7 @@ async fn message( #[derive(Deserialize)] #[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] struct AnswerRequest { question_id: String, answer: String, @@ -658,6 +675,7 @@ async fn usage( } #[derive(Deserialize)] +#[serde(deny_unknown_fields)] struct ModelRequest { model: String, } @@ -675,6 +693,7 @@ async fn set_model( #[derive(Deserialize)] #[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] struct PermissionModeRequest { mode: String, }