diff --git a/backend/parsers/windmill-parser-sql/src/lib.rs b/backend/parsers/windmill-parser-sql/src/lib.rs index f5ddb73151..5b9e1ddd6f 100644 --- a/backend/parsers/windmill-parser-sql/src/lib.rs +++ b/backend/parsers/windmill-parser-sql/src/lib.rs @@ -16,6 +16,9 @@ use std::{ }; pub use windmill_parser::{Arg, MainArgSignature, Typ}; +pub const SANITIZED_ENUM_STR: &str = "__sanitized_enum__"; +pub const SANITIZED_RAW_STRING_STR: &str = "__sanitized_raw_string__"; + pub fn parse_mysql_sig(code: &str) -> anyhow::Result { let parsed = parse_mysql_file(&code)?; if let Some(x) = parsed { @@ -147,7 +150,7 @@ lazy_static::lazy_static! { // -- $1 name (type) = default static ref RE_ARG_MYSQL: Regex = Regex::new(r#"(?m)^-- \? (\w+) \((\w+)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap(); - pub static ref RE_ARG_MYSQL_NAMED: Regex = Regex::new(r#"(?m)^-- :([a-z_][a-z0-9_]*) \((\w+)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap(); + pub static ref RE_ARG_MYSQL_NAMED: Regex = Regex::new(r#"(?m)^-- :([a-z_][a-z0-9_]*) \((\w+(?:\([\w, ]+\))?)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap(); static ref RE_ARG_PGSQL: Regex = Regex::new(r#"(?m)^-- \$(\d+) (\w+)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap(); @@ -159,6 +162,9 @@ lazy_static::lazy_static! { static ref RE_ARG_MSSQL: Regex = Regex::new(r#"(?m)^-- @(?:P|p)\d+ (\w+) \((\w+)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap(); + // used for `unsafe` sql interpolation + // -- %%name%% (type) = default + static ref RE_ARG_SQL_INTERPOLATION: Regex = Regex::new(r#"(?m)^--\s*%%([a-z_][a-z0-9_]*)%%\s*([\s\w\/]+)?(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap(); } fn parsed_default(parsed_typ: &Typ, default: String) -> Option { @@ -225,9 +231,34 @@ fn parse_oracledb_file(code: &str) -> anyhow::Result>> { } } + args.append(&mut parse_sql_sanitized_interpolation(code)); Ok(Some(args)) } +fn parse_sql_sanitized_interpolation(code: &str) -> Vec { + let mut args: Vec = vec![]; + + for cap in RE_ARG_SQL_INTERPOLATION.captures_iter(code) { + let name = cap.get(1).map(|x| x.as_str().to_string()).unwrap(); + let typ = cap.get(2).map(|x| x.as_str()); + let default = cap.get(3).map(|x| x.as_str().to_string()); + let has_default = default.is_some(); + let (parsed_typ, otyp) = parse_unsafe_typ(typ); + + let parsed_default = default.and_then(|x| parsed_default(&parsed_typ, x)); + args.push(Arg { + name, + typ: parsed_typ, + default: parsed_default, + otyp: Some(otyp.to_string()), + has_default, + oidx: None, + }); + } + + args +} + fn parse_mysql_file(code: &str) -> anyhow::Result>> { let mut args: Vec = vec![]; @@ -279,6 +310,7 @@ fn parse_mysql_file(code: &str) -> anyhow::Result>> { } } + args.append(&mut parse_sql_sanitized_interpolation(code)); Ok(Some(args)) } @@ -431,6 +463,7 @@ fn parse_pg_file(code: &str) -> anyhow::Result>> { } } + args.append(&mut parse_sql_sanitized_interpolation(code)); Ok(Some(args)) } @@ -485,6 +518,7 @@ fn parse_bigquery_file(code: &str) -> anyhow::Result>> { }); } + args.append(&mut parse_sql_sanitized_interpolation(code)); Ok(Some(args)) } @@ -513,6 +547,7 @@ fn parse_snowflake_file(code: &str) -> anyhow::Result>> { }); } + args.append(&mut parse_sql_sanitized_interpolation(code)); Ok(Some(args)) } @@ -541,9 +576,25 @@ fn parse_mssql_file(code: &str) -> anyhow::Result>> { }); } + args.append(&mut parse_sql_sanitized_interpolation(code)); Ok(Some(args)) } +fn parse_unsafe_typ(typ: Option<&str>) -> (Typ, &'static str) { + match typ { + Some(s) => { + let variants = s + .split("/") + .map(|x| x.trim().to_string()) + .filter(|x| !x.is_empty()) + .collect(); + + (Typ::Str(Some(variants)), SANITIZED_ENUM_STR) + } + None => (Typ::Str(None), SANITIZED_RAW_STRING_STR), + } +} + pub fn parse_mysql_typ(typ: &str) -> Typ { match typ { "varchar" | "char" | "binary" | "varbinary" | "blob" | "text" | "enum" | "set" => { @@ -1048,6 +1099,55 @@ SELECT @P2; } ); + Ok(()) + } + #[test] + fn test_parse_oracledb_sig() -> anyhow::Result<()> { + let code = r#" +-- :name1 (int) = 3 +-- :name2 (text) +-- :name4 (text) +SELECT :name, :name2; +SELECT * FROM table_name WHERE thing = :name4; +"#; + + println!("{:#?}", parse_oracledb_sig(code)?); + assert_eq!( + parse_oracledb_sig(code)?, + MainArgSignature { + star_args: false, + star_kwargs: false, + args: vec![ + Arg { + otyp: Some("int".to_string()), + name: "name1".to_string(), + typ: Typ::Int, + default: Some(json!(3)), + has_default: true, + oidx: None, + }, + Arg { + otyp: Some("text".to_string()), + name: "name2".to_string(), + typ: Typ::Str(None), + default: None, + has_default: false, + oidx: None, + }, + Arg { + otyp: Some("text".to_string()), + name: "name4".to_string(), + typ: Typ::Str(None), + default: None, + has_default: false, + oidx: None, + }, + ], + no_main_func: None, + has_preprocessor: None + } + ); + Ok(()) } } diff --git a/backend/windmill-worker/src/bigquery_executor.rs b/backend/windmill-worker/src/bigquery_executor.rs index 6c3b9337fd..b6367c8529 100644 --- a/backend/windmill-worker/src/bigquery_executor.rs +++ b/backend/windmill-worker/src/bigquery_executor.rs @@ -15,6 +15,7 @@ use serde::Deserialize; use crate::common::{build_http_client, OccupancyMetrics}; use crate::handle_child::run_future_with_polling_update_job_poller; +use crate::sanitized_sql_params::sanitize_and_interpolate_unsafe_sql_args; use crate::{ common::{build_args_values, resolve_job_timeout}, AuthedClientBackgroundTask, @@ -262,15 +263,21 @@ pub async fn do_bigquery( .await .map_err(|e| Error::ExecutionErr(e.to_string()))?; - let queries = parse_sql_blocks(query); - - let mut statement_values: HashMap = HashMap::new(); - let sig = parse_bigquery_sig(&query) .map_err(|x| Error::ExecutionErr(x.to_string()))? .args; + let (query, args_to_skip) = &sanitize_and_interpolate_unsafe_sql_args(query, &sig, &bigquery_args)?; + + let queries = parse_sql_blocks(query); + + let mut statement_values: HashMap = HashMap::new(); + + for arg in &sig { + if args_to_skip.contains(&arg.name) { + continue; + } let arg_t = arg.otyp.clone().unwrap_or_else(|| "string".to_string()); let arg_n = arg.clone().name; let arg_v = bigquery_args.get(&arg.name).cloned().unwrap_or(json!("")); diff --git a/backend/windmill-worker/src/lib.rs b/backend/windmill-worker/src/lib.rs index 4ae83309da..ba47fd81fc 100644 --- a/backend/windmill-worker/src/lib.rs +++ b/backend/windmill-worker/src/lib.rs @@ -43,6 +43,7 @@ mod rust_executor; mod worker; mod worker_flow; mod worker_lockfiles; +mod sanitized_sql_params; mod schema; pub use worker::*; diff --git a/backend/windmill-worker/src/mssql_executor.rs b/backend/windmill-worker/src/mssql_executor.rs index a033d1eff1..ef06c8f0bf 100644 --- a/backend/windmill-worker/src/mssql_executor.rs +++ b/backend/windmill-worker/src/mssql_executor.rs @@ -17,6 +17,7 @@ use windmill_queue::{append_logs, CanceledBy}; use crate::common::{build_args_values, OccupancyMetrics}; use crate::handle_child::run_future_with_polling_update_job_poller; +use crate::sanitized_sql_params::sanitize_and_interpolate_unsafe_sql_args; use crate::AuthedClientBackgroundTask; #[derive(Deserialize)] @@ -132,8 +133,13 @@ pub async fn do_mssql( .map_err(|x| Error::ExecutionErr(x.to_string()))? .args; + let (query, args_to_skip) = &sanitize_and_interpolate_unsafe_sql_args(query, &sig, &mssql_args)?; + let mut prepared_query = Query::new(query.to_owned()); for arg in &sig { + if args_to_skip.contains(&arg.name) { + continue; + } let arg_t = arg.otyp.clone().unwrap_or_else(|| "string".to_string()); let arg_v = mssql_args .get(&arg.name) diff --git a/backend/windmill-worker/src/mysql_executor.rs b/backend/windmill-worker/src/mysql_executor.rs index 80c41f21c3..ca8fa1a5c9 100644 --- a/backend/windmill-worker/src/mysql_executor.rs +++ b/backend/windmill-worker/src/mysql_executor.rs @@ -8,7 +8,6 @@ use mysql_async::{ }; use serde::{Deserialize, Serialize}; use serde_json::{json, value::RawValue, Value}; -use sqlx::types::Json; use tokio::sync::Mutex; use windmill_common::{ error::{to_anyhow, Error}, @@ -22,9 +21,7 @@ use windmill_queue::CanceledBy; use windmill_queue::MiniPulledJob; use crate::{ - common::{build_args_map, OccupancyMetrics}, - handle_child::run_future_with_polling_update_job_poller, - AuthedClientBackgroundTask, + common::{build_args_values, OccupancyMetrics}, handle_child::run_future_with_polling_update_job_poller, sanitized_sql_params::sanitize_and_interpolate_unsafe_sql_args, AuthedClientBackgroundTask }; #[derive(Deserialize)] @@ -113,36 +110,25 @@ pub async fn do_mysql( column_order: &mut Option>, occupancy_metrics: &mut OccupancyMetrics, ) -> windmill_common::error::Result> { - let args = build_args_map(job, client, db).await?.map(Json); - let job_args = if args.is_some() { - args.as_ref() - } else { - job.args.as_ref() - }; + let job_args = build_args_values(job, client, db).await?; let inline_db_res_path = parse_db_resource(&query); let db_arg = if let Some(inline_db_res_path) = inline_db_res_path { - let val = client + Some(client .get_authed() .await .get_resource_value_interpolated::( &inline_db_res_path, Some(job.id.to_string()), ) - .await?; - - let as_raw = serde_json::from_value(val).map_err(|e| { - Error::internal_err(format!("Error while parsing inline resource: {e:#}")) - })?; - - Some(as_raw) + .await?) } else { - job_args.and_then(|x| x.get("database").cloned()) + job_args.get("database").cloned() }; let database = if let Some(db) = db_arg { - serde_json::from_str::(db.get()) + serde_json::from_value::(db) .map_err(|e| Error::ExecutionErr(e.to_string()))? } else { return Err(Error::BadRequest("Missing database argument".to_string())); @@ -171,6 +157,8 @@ pub async fn do_mysql( .map_err(|x| Error::ExecutionErr(x.to_string()))? .args; + let (query, args_to_skip) = &sanitize_and_interpolate_unsafe_sql_args(query, &sig, &job_args)?; + let using_named_params = RE_ARG_MYSQL_NAMED.captures_iter(query).count() > 0; let mut statement_values: Params = match using_named_params { @@ -178,18 +166,16 @@ pub async fn do_mysql( false => Params::Positional(vec![]), }; for arg in &sig { + if args_to_skip.contains(&arg.name) { + continue; + } let arg_t = arg.otyp.clone().unwrap_or_else(|| "text".to_string()); let arg_n = arg.name.clone(); - let mysql_v = match job_args - .and_then(|x| { - x.get(arg.name.as_str()) - .map(|x| serde_json::from_str::(x.get()).ok()) - }) - .flatten() - .unwrap_or_else(|| json!(null)) + let mysql_v = match job_args.get(arg.name.as_str()) + .unwrap_or_else(|| &json!(null)) { Value::Null => mysql_async::Value::NULL, - Value::Bool(b) => mysql_async::Value::Int(if b { 1 } else { 0 }), + Value::Bool(b) => mysql_async::Value::Int(if *b { 1 } else { 0 }), Value::String(s) if arg_t == "timestamp" || arg_t == "datetime" diff --git a/backend/windmill-worker/src/oracledb_executor.rs b/backend/windmill-worker/src/oracledb_executor.rs index 3127659b02..7f340b8525 100644 --- a/backend/windmill-worker/src/oracledb_executor.rs +++ b/backend/windmill-worker/src/oracledb_executor.rs @@ -8,7 +8,6 @@ use itertools::Itertools; use oracle::sql_type::{InnerValue, OracleType, ToSql}; use serde::{Deserialize, Serialize}; use serde_json::{json, value::RawValue, Value}; -use sqlx::types::Json; use windmill_common::{ error::{to_anyhow, Error}, worker::to_raw_value, @@ -21,8 +20,9 @@ use windmill_parser_sql::{ use windmill_queue::CanceledBy; use crate::{ - common::{build_args_map, check_executor_binary_exists, OccupancyMetrics}, + common::{build_args_values, check_executor_binary_exists, OccupancyMetrics}, handle_child::run_future_with_polling_update_job_poller, + sanitized_sql_params::sanitize_and_interpolate_unsafe_sql_args, AuthedClientBackgroundTask, }; @@ -221,24 +221,23 @@ fn convert_oracledb_value_to_json(v: &oracle::SqlValue, c: &OracleType) -> serde fn get_statement_values( sig: Vec, - job_args: Option<&Json>>>, + job_args: &HashMap, + args_to_skip: &Vec, ) -> (Vec<(String, Box)>, Vec) { let mut statement_values = vec![]; let mut errors = vec![]; for arg in &sig { + if args_to_skip.contains(&arg.name) { + continue; + } let arg_t = arg.otyp.clone().unwrap_or_else(|| "text".to_string()); let arg_n = arg.name.clone(); - let oracle_v: Box = match job_args - .and_then(|x| { - x.get(arg.name.as_str()) - .map(|x| serde_json::from_str::(x.get()).ok()) - }) - .flatten() - .unwrap_or_else(|| json!(null)) + let oracle_v: Box = match job_args.get(arg.name.as_str()) + .unwrap_or_else(|| &json!(null)) { // Value::Null => todo!(), - Value::Bool(b) => Box::new(b), + Value::Bool(b) => Box::new(*b), Value::String(s) if arg_t == "timestamp" || arg_t == "datetime" @@ -248,10 +247,10 @@ fn get_statement_values( if let Ok(d) = chrono::DateTime::::from_str(s.as_str()) { Box::new(d) } else { - Box::new(s) + Box::new(s.clone()) } } - Value::String(s) => Box::new(s), + Value::String(s) => Box::new(s.clone()), Value::Number(n) if n.is_i64() && (arg_t == "int" @@ -309,36 +308,27 @@ pub async fn do_oracledb( "Oracle Database", )?; - let args = build_args_map(job, client, db).await?.map(Json); - let job_args = if args.is_some() { - args.as_ref() - } else { - job.args.as_ref() - }; + let job_args = build_args_values(job, client, db).await?; let inline_db_res_path = parse_db_resource(&query); let db_arg = if let Some(inline_db_res_path) = inline_db_res_path { - let val = client - .get_authed() - .await - .get_resource_value_interpolated::( - &inline_db_res_path, - Some(job.id.to_string()), - ) - .await?; - - let as_raw = serde_json::from_value(val).map_err(|e| { - Error::internal_err(format!("Error while parsing inline resource: {e:#}")) - })?; - - Some(as_raw) + Some( + client + .get_authed() + .await + .get_resource_value_interpolated::( + &inline_db_res_path, + Some(job.id.to_string()), + ) + .await?, + ) } else { - job_args.and_then(|x| x.get("database").cloned()) + job_args.get("database").cloned() }; let database = if let Some(db) = db_arg { - serde_json::from_str::(db.get()) + serde_json::from_value::(db) .map_err(|e| Error::ExecutionErr(e.to_string()))? } else { return Err(Error::BadRequest("Missing database argument".to_string())); @@ -350,7 +340,9 @@ pub async fn do_oracledb( .map_err(|x| Error::ExecutionErr(x.to_string()))? .args; - let (statement_values, errors) = get_statement_values(sig.clone(), job_args); + let (query, args_to_skip) = sanitize_and_interpolate_unsafe_sql_args(query, &sig, &job_args)?; + + let (statement_values, errors) = get_statement_values(sig.clone(), &job_args, &args_to_skip); if !errors.is_empty() { return Err(Error::ExecutionErr(errors.join("\n"))); @@ -372,13 +364,13 @@ pub async fn do_oracledb( let conn_a = Arc::new(std::sync::Mutex::new(conn)); - let queries = parse_sql_blocks(query); + let queries = parse_sql_blocks(&query); let result_f = if queries.len() > 1 { let f = async { let mut res: Vec> = vec![]; for (i, q) in queries.iter().enumerate() { - let (vals, _) = get_statement_values(sig.clone(), job_args); + let (vals, _) = get_statement_values(sig.clone(), &job_args, &args_to_skip); let r = do_oracledb_inner( q, vals, @@ -399,7 +391,7 @@ pub async fn do_oracledb( f.boxed() } else { - do_oracledb_inner(query, statement_values, conn_a, Some(column_order), false)? + do_oracledb_inner(&query, statement_values, conn_a, Some(column_order), false)? }; let result = run_future_with_polling_update_job_poller( diff --git a/backend/windmill-worker/src/pg_executor.rs b/backend/windmill-worker/src/pg_executor.rs index 645d7ff09f..2847389ca7 100644 --- a/backend/windmill-worker/src/pg_executor.rs +++ b/backend/windmill-worker/src/pg_executor.rs @@ -36,6 +36,7 @@ use windmill_queue::{CanceledBy, MiniPulledJob}; use crate::common::{build_args_values, sizeof_val, OccupancyMetrics}; use crate::handle_child::run_future_with_polling_update_job_poller; +use crate::sanitized_sql_params::sanitize_and_interpolate_unsafe_sql_args; use crate::{AuthedClientBackgroundTask, MAX_RESULT_SIZE}; use bytes::Buf; use lazy_static::lazy_static; @@ -290,6 +291,11 @@ pub async fn do_postgresql( (Some((client, handle)), None) }; + let sig = parse_pgsql_sig(&query).map_err(|x| Error::ExecutionErr(x.to_string()))?; + + let (query, _) = + &sanitize_and_interpolate_unsafe_sql_args(query, &sig.args, &pg_args)?; + let queries = parse_sql_blocks(query); let (client, handle) = if let Some((client, handle)) = new_client.as_ref() { @@ -299,7 +305,6 @@ pub async fn do_postgresql( (client, None) }; - let sig = parse_pgsql_sig(&query).map_err(|x| Error::ExecutionErr(x.to_string()))?; let param_idx_to_arg_and_value = sig .args .iter() diff --git a/backend/windmill-worker/src/sanitized_sql_params.rs b/backend/windmill-worker/src/sanitized_sql_params.rs new file mode 100644 index 0000000000..c7eb3b6ef4 --- /dev/null +++ b/backend/windmill-worker/src/sanitized_sql_params.rs @@ -0,0 +1,97 @@ +use anyhow::anyhow; +use std::collections::HashMap; + +use serde_json::Value; +use windmill_common::error; +use windmill_parser::Arg; +use windmill_parser_sql::{SANITIZED_ENUM_STR, SANITIZED_RAW_STRING_STR}; + +/// Identifier must be a continuous ASCII alphanumeric word, not starting with +/// a number, that can contain underscores +fn sanitize_identifier(arg: &Arg, input: &str) -> Result<(), error::Error> { + if input.is_empty() { + return Err(error::Error::BadRequest(format!("Interpolated argument `{}` cannot be empty", arg.name))); + } + if input + .chars() + .next() + .map(|c| c.is_ascii_alphabetic()) + .unwrap_or(false) + && input.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') + { + Ok(()) + } else { + Err(error::Error::BadRequest(format!("Interpolated argument `{}` contained forbidden characters. Received `{}` but should only contain alphanumerical characters and `_`.", arg.name, input))) + } +} + +pub fn sanitize_and_interpolate_unsafe_sql_args( + code: &str, + args: &Vec, + args_map: &HashMap, +) -> Result<(String, Vec), error::Error> { + let mut ret = code.to_string(); + let mut args_to_skip = vec![]; + + for arg in args { + if let Some(typ) = &arg.otyp { + let pattern = format!("%%{}%%", arg.name); + match typ.as_str() { + SANITIZED_ENUM_STR => { + let replace = + args_map + .get(&arg.name) + .and_then(|rv| rv.as_str()) + .ok_or(anyhow!( + "Sanitized enum `{}` needs to receive a string", + arg.name + ))?; + let windmill_parser::Typ::Str(Some(variants)) = &arg.typ else { + return Err(error::Error::ArgumentErr(format!( + "Wrong type of argument for sanitized enum `{}`", + arg.name + ))); + }; + if variants.iter().all(|v| v != replace) { + return Err(error::Error::ArgumentErr(format!( + "Sanitized enum argument `{}` expected one of `[{}]` but received `{}`", + arg.name, + variants + .iter() + .map(|s| format!("{s}")) + .collect::>() + .join(","), + replace, + ))); + } + + sanitize_identifier(&arg, replace)?; + ret = ret.replace(&pattern, replace); + args_to_skip.push(arg.name.to_string()); + } + SANITIZED_RAW_STRING_STR => { + let replace = + args_map + .get(&arg.name) + .and_then(|rv| rv.as_str()) + .ok_or(anyhow!( + "Sanitized raw string `{}` needs to receive a string", + arg.name + ))?; + let windmill_parser::Typ::Str(_) = &arg.typ else { + return Err(error::Error::ArgumentErr(format!( + "Wrong type of argument for sanitized raw string `{}`", + arg.name + ))); + }; + sanitize_identifier(&arg, replace)?; + ret = ret.replace(&pattern, &replace); + args_to_skip.push(arg.name.to_string()); + } + _ => continue, + } + } + } + + Ok((ret, args_to_skip)) +} diff --git a/backend/windmill-worker/src/snowflake_executor.rs b/backend/windmill-worker/src/snowflake_executor.rs index 7090c114e8..30f1188ce0 100644 --- a/backend/windmill-worker/src/snowflake_executor.rs +++ b/backend/windmill-worker/src/snowflake_executor.rs @@ -18,6 +18,7 @@ use serde::{Deserialize, Serialize}; use crate::common::{build_http_client, resolve_job_timeout, OccupancyMetrics}; use crate::handle_child::run_future_with_polling_update_job_poller; +use crate::sanitized_sql_params::sanitize_and_interpolate_unsafe_sql_args; use crate::{common::build_args_values, AuthedClientBackgroundTask}; #[derive(Serialize)] @@ -123,15 +124,21 @@ fn do_snowflake_inner<'a>( skip_collect: bool, http_client: &'a Client, ) -> windmill_common::error::Result>>> { - body.insert("statement".to_string(), json!(query)); - - let mut bindings = serde_json::Map::new(); let sig = parse_snowflake_sig(&query) .map_err(|x| Error::ExecutionErr(x.to_string()))? .args; + let (query, args_to_skip) = &sanitize_and_interpolate_unsafe_sql_args(query, &sig, &job_args)?; + + body.insert("statement".to_string(), json!(query)); + + let mut bindings = serde_json::Map::new(); + let mut i = 1; for arg in &sig { + if args_to_skip.contains(&arg.name) { + continue; + } let arg_t = arg.otyp.clone().unwrap_or_else(|| "string".to_string()); let arg_v = job_args.get(&arg.name).cloned().unwrap_or(json!("")); let snowflake_v = convert_typ_val(arg_t, arg_v); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index cece7518b1..819d7a117e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -72,7 +72,7 @@ "windmill-parser-wasm-nu": "^1.474.1", "windmill-parser-wasm-php": "^1.429.0", "windmill-parser-wasm-py": "^1.477.1", - "windmill-parser-wasm-regex": "^1.439.0", + "windmill-parser-wasm-regex": "^1.478.2", "windmill-parser-wasm-rust": "^1.429.0", "windmill-parser-wasm-ts": "^1.429.0", "windmill-parser-wasm-yaml": "^1.429.0", @@ -12605,9 +12605,9 @@ "integrity": "sha512-EY3mSMWpqFPzd7fsLg2/hSfQFU8HpW9nplFwm4JHHCDbcTpBzlvzjPJoHAAGO5kMzowAxjqi5ai/mXjeUWuiSg==" }, "node_modules/windmill-parser-wasm-regex": { - "version": "1.439.0", - "resolved": "https://registry.npmjs.org/windmill-parser-wasm-regex/-/windmill-parser-wasm-regex-1.439.0.tgz", - "integrity": "sha512-v7vcEOWurGbqvoTdtQ8wauyUYeuQExRCmr7phPtwUwD+1cNbqDrS11kM8p2Na3DXjL9RqGAGjd6uRjNSccZjjQ==" + "version": "1.478.2", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm-regex/-/windmill-parser-wasm-regex-1.478.2.tgz", + "integrity": "sha512-XXYOzCePuHBEdiTREF+UlRGDyUu5VVXD9RBb5JGiq9YkjKhKek0CkjSt8HhJ0yAu0i/gvWLOO0RboT2C04Qt1A==" }, "node_modules/windmill-parser-wasm-rust": { "version": "1.429.0", diff --git a/frontend/package.json b/frontend/package.json index d6135d7613..6356634a93 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -147,7 +147,7 @@ "windmill-parser-wasm-nu": "^1.474.1", "windmill-parser-wasm-php": "^1.429.0", "windmill-parser-wasm-py": "^1.477.1", - "windmill-parser-wasm-regex": "^1.439.0", + "windmill-parser-wasm-regex": "^1.478.2", "windmill-parser-wasm-rust": "^1.429.0", "windmill-parser-wasm-ts": "^1.429.0", "windmill-parser-wasm-yaml": "^1.429.0", @@ -515,4 +515,4 @@ "optionalDependencies": { "fsevents": "^2.3.3" } -} \ No newline at end of file +}