mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 03:13:56 +08:00
feat(security): add XSS and sqli validation for dashboard metadata fields (#9104)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
171
Cargo.lock
generated
171
Cargo.lock
generated
@ -339,6 +339,19 @@ version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "ammonia"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64e6d1c7838db705c9b756557ee27c384ce695a1c51a6fe528784cb1c6840170"
|
||||
dependencies = [
|
||||
"html5ever",
|
||||
"maplit",
|
||||
"once_cell",
|
||||
"tendril",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "analytics"
|
||||
version = "0.1.0"
|
||||
@ -1737,7 +1750,7 @@ checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz-build",
|
||||
"phf",
|
||||
"phf 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1747,8 +1760,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1"
|
||||
dependencies = [
|
||||
"parse-zoneinfo",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
"phf 0.11.2",
|
||||
"phf_codegen 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1878,6 +1891,7 @@ dependencies = [
|
||||
name = "common_utils"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"base64-serde",
|
||||
@ -1921,6 +1935,7 @@ dependencies = [
|
||||
"time",
|
||||
"tokio 1.45.1",
|
||||
"url",
|
||||
"urlencoding",
|
||||
"utoipa",
|
||||
"uuid",
|
||||
]
|
||||
@ -3282,6 +3297,16 @@ version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||
|
||||
[[package]]
|
||||
name = "futf"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843"
|
||||
dependencies = [
|
||||
"mac",
|
||||
"new_debug_unreachable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.1.31"
|
||||
@ -3784,6 +3809,20 @@ dependencies = [
|
||||
"utf8-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "html5ever"
|
||||
version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7"
|
||||
dependencies = [
|
||||
"log",
|
||||
"mac",
|
||||
"markup5ever",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.12"
|
||||
@ -4845,6 +4884,32 @@ dependencies = [
|
||||
"twox-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mac"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
||||
|
||||
[[package]]
|
||||
name = "maplit"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
||||
|
||||
[[package]]
|
||||
name = "markup5ever"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016"
|
||||
dependencies = [
|
||||
"log",
|
||||
"phf 0.10.1",
|
||||
"phf_codegen 0.10.0",
|
||||
"string_cache",
|
||||
"string_cache_codegen",
|
||||
"tendril",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "masking"
|
||||
version = "0.1.0"
|
||||
@ -5159,6 +5224,12 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "new_debug_unreachable"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
@ -5838,13 +5909,32 @@ dependencies = [
|
||||
"indexmap 2.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
|
||||
dependencies = [
|
||||
"phf_shared 0.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"phf_shared 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
|
||||
dependencies = [
|
||||
"phf_generator 0.10.0",
|
||||
"phf_shared 0.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5853,8 +5943,18 @@ version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
"phf_generator 0.11.2",
|
||||
"phf_shared 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
|
||||
dependencies = [
|
||||
"phf_shared 0.10.0",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5863,10 +5963,19 @@ version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"phf_shared 0.11.2",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
|
||||
dependencies = [
|
||||
"siphasher 0.3.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.2"
|
||||
@ -6049,6 +6158,12 @@ dependencies = [
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "precomputed-hash"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.20"
|
||||
@ -8238,6 +8353,31 @@ dependencies = [
|
||||
"tokio 1.45.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f"
|
||||
dependencies = [
|
||||
"new_debug_unreachable",
|
||||
"parking_lot 0.12.3",
|
||||
"phf_shared 0.11.2",
|
||||
"precomputed-hash",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "string_cache_codegen"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0"
|
||||
dependencies = [
|
||||
"phf_generator 0.11.2",
|
||||
"phf_shared 0.11.2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stringmatch"
|
||||
version = "0.4.0"
|
||||
@ -8459,6 +8599,17 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tendril"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0"
|
||||
dependencies = [
|
||||
"futf",
|
||||
"mac",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tera"
|
||||
version = "1.20.0"
|
||||
@ -9634,6 +9785,12 @@ version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "110352d4e9076c67839003c7788d8604e24dcded13e0b375af3efaa8cf468517"
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "utf16_iter"
|
||||
version = "1.0.5"
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use common_enums::{CountryAlpha2, MerchantProductType};
|
||||
use common_types::primitive_wrappers::SafeString;
|
||||
use common_utils::{id_type, pii};
|
||||
use masking::Secret;
|
||||
use strum::EnumString;
|
||||
@ -50,16 +51,16 @@ pub struct ProcessorConnected {
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct OnboardingSurvey {
|
||||
pub designation: Option<String>,
|
||||
pub about_business: Option<String>,
|
||||
pub business_website: Option<String>,
|
||||
pub hyperswitch_req: Option<String>,
|
||||
pub major_markets: Option<Vec<String>>,
|
||||
pub business_size: Option<String>,
|
||||
pub required_features: Option<Vec<String>>,
|
||||
pub required_processors: Option<Vec<String>>,
|
||||
pub planned_live_date: Option<String>,
|
||||
pub miscellaneous: Option<String>,
|
||||
pub designation: Option<SafeString>,
|
||||
pub about_business: Option<SafeString>,
|
||||
pub business_website: Option<SafeString>,
|
||||
pub hyperswitch_req: Option<SafeString>,
|
||||
pub major_markets: Option<Vec<SafeString>>,
|
||||
pub business_size: Option<SafeString>,
|
||||
pub required_features: Option<Vec<SafeString>>,
|
||||
pub required_processors: Option<Vec<SafeString>>,
|
||||
pub planned_live_date: Option<SafeString>,
|
||||
pub miscellaneous: Option<SafeString>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
@ -85,27 +86,27 @@ pub enum ConfigurationType {
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct Feedback {
|
||||
pub email: pii::Email,
|
||||
pub description: Option<String>,
|
||||
pub description: Option<SafeString>,
|
||||
pub rating: Option<i32>,
|
||||
pub category: Option<String>,
|
||||
pub category: Option<SafeString>,
|
||||
}
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct ProdIntent {
|
||||
pub legal_business_name: Option<String>,
|
||||
pub business_label: Option<String>,
|
||||
pub legal_business_name: Option<SafeString>,
|
||||
pub business_label: Option<SafeString>,
|
||||
pub business_location: Option<CountryAlpha2>,
|
||||
pub display_name: Option<String>,
|
||||
pub poc_email: Option<Secret<String>>,
|
||||
pub business_type: Option<String>,
|
||||
pub business_identifier: Option<String>,
|
||||
pub business_website: Option<String>,
|
||||
pub poc_name: Option<Secret<String>>,
|
||||
pub poc_contact: Option<Secret<String>>,
|
||||
pub comments: Option<String>,
|
||||
pub display_name: Option<SafeString>,
|
||||
pub poc_email: Option<pii::Email>,
|
||||
pub business_type: Option<SafeString>,
|
||||
pub business_identifier: Option<SafeString>,
|
||||
pub business_website: Option<SafeString>,
|
||||
pub poc_name: Option<Secret<SafeString>>,
|
||||
pub poc_contact: Option<Secret<SafeString>>,
|
||||
pub comments: Option<SafeString>,
|
||||
pub is_completed: bool,
|
||||
#[serde(default)]
|
||||
pub product_type: MerchantProductType,
|
||||
pub business_country_name: Option<String>,
|
||||
pub business_country_name: Option<SafeString>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
pub use bool_wrappers::*;
|
||||
pub use safe_string::*;
|
||||
pub use u32_wrappers::*;
|
||||
mod bool_wrappers {
|
||||
use std::ops::Deref;
|
||||
@ -433,3 +434,106 @@ mod u32_wrappers {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe string wrapper that validates input against XSS attacks
|
||||
mod safe_string {
|
||||
use std::ops::Deref;
|
||||
|
||||
use common_utils::validation::contains_potential_xss_or_sqli;
|
||||
use masking::SerializableSecret;
|
||||
use serde::{de::Error, Deserialize, Serialize};
|
||||
|
||||
/// String wrapper that prevents XSS and SQLi attacks
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Default)]
|
||||
pub struct SafeString(String);
|
||||
|
||||
impl SafeString {
|
||||
/// Creates a new SafeString after XSS and SQL injection validation
|
||||
pub fn new(value: String) -> Result<Self, String> {
|
||||
if contains_potential_xss_or_sqli(&value) {
|
||||
return Err("Input contains potentially malicious content".into());
|
||||
}
|
||||
Ok(Self(value))
|
||||
}
|
||||
|
||||
/// Creates a SafeString from a string slice
|
||||
pub fn from_string_slice(value: &str) -> Result<Self, String> {
|
||||
Self::new(value.to_string())
|
||||
}
|
||||
|
||||
/// Returns the inner string as a string slice
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Consumes self and returns the inner String
|
||||
pub fn into_inner(self) -> String {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns true if the string is empty
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
/// Returns the length of the string
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for SafeString {
|
||||
type Target = String;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
// Custom serialization and deserialization
|
||||
impl Serialize for SafeString {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
self.0.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for SafeString {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let value = String::deserialize(deserializer)?;
|
||||
Self::new(value).map_err(D::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
// Implement SerializableSecret for SafeString to work with Secret<SafeString>
|
||||
impl SerializableSecret for SafeString {}
|
||||
|
||||
// Diesel implementations for database operations
|
||||
impl<DB> diesel::serialize::ToSql<diesel::sql_types::Text, DB> for SafeString
|
||||
where
|
||||
DB: diesel::backend::Backend,
|
||||
String: diesel::serialize::ToSql<diesel::sql_types::Text, DB>,
|
||||
{
|
||||
fn to_sql<'b>(
|
||||
&'b self,
|
||||
out: &mut diesel::serialize::Output<'b, '_, DB>,
|
||||
) -> diesel::serialize::Result {
|
||||
self.0.to_sql(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB> diesel::deserialize::FromSql<diesel::sql_types::Text, DB> for SafeString
|
||||
where
|
||||
DB: diesel::backend::Backend,
|
||||
String: diesel::deserialize::FromSql<diesel::sql_types::Text, DB>,
|
||||
{
|
||||
fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result<Self> {
|
||||
String::from_sql(value).map(Self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,8 +60,10 @@ thiserror = "1.0.69"
|
||||
time = { version = "0.3.41", features = ["serde", "serde-well-known", "std"] }
|
||||
tokio = { version = "1.45.1", features = ["macros", "rt-multi-thread"], optional = true }
|
||||
url = { version = "2.5.4", features = ["serde"] }
|
||||
urlencoding = "2.1.3"
|
||||
utoipa = { version = "4.2.3", features = ["preserve_order", "preserve_path_order"] }
|
||||
uuid = { version = "1.17.0", features = ["v7"] }
|
||||
ammonia = "3.3"
|
||||
|
||||
# First party crates
|
||||
common_enums = { version = "0.1.0", path = "../common_enums" }
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
//! Custom validations for some shared types.
|
||||
|
||||
#![deny(clippy::invalid_regex)]
|
||||
|
||||
use std::{collections::HashSet, sync::LazyLock};
|
||||
|
||||
use error_stack::report;
|
||||
@ -82,6 +84,78 @@ pub fn validate_domain_against_allowed_domains(
|
||||
})
|
||||
}
|
||||
|
||||
/// checks whether the input string contains potential XSS or SQL injection attack vectors
|
||||
pub fn contains_potential_xss_or_sqli(input: &str) -> bool {
|
||||
let decoded = urlencoding::decode(input).unwrap_or_else(|_| input.into());
|
||||
|
||||
// Check for suspicious percent-encoded patterns
|
||||
static PERCENT_ENCODED: LazyLock<Option<Regex>> = LazyLock::new(|| {
|
||||
Regex::new(r"%[0-9A-Fa-f]{2}")
|
||||
.map_err(|_err| {
|
||||
#[cfg(feature = "logs")]
|
||||
logger::error!(?_err);
|
||||
})
|
||||
.ok()
|
||||
});
|
||||
|
||||
if decoded.contains('%') {
|
||||
match PERCENT_ENCODED.as_ref() {
|
||||
Some(regex) => {
|
||||
if regex.is_match(&decoded) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
None => return true,
|
||||
}
|
||||
}
|
||||
|
||||
if ammonia::is_html(&decoded) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static XSS: LazyLock<Option<Regex>> = LazyLock::new(|| {
|
||||
Regex::new(
|
||||
r"(?is)\bon[a-z]+\s*=|\bjavascript\s*:|\bdata\s*:\s*text/html|\b(alert|prompt|confirm|eval)\s*\(",
|
||||
)
|
||||
.map_err(|_err| {
|
||||
#[cfg(feature = "logs")]
|
||||
logger::error!(?_err);
|
||||
})
|
||||
.ok()
|
||||
});
|
||||
|
||||
static SQLI: LazyLock<Option<Regex>> = LazyLock::new(|| {
|
||||
Regex::new(
|
||||
r"(?is)(?:')\s*or\s*'?\d+'?=?\d*|union\s+select|insert\s+into|drop\s+table|information_schema|sleep\s*\(|--|;",
|
||||
)
|
||||
.map_err(|_err| {
|
||||
#[cfg(feature = "logs")]
|
||||
logger::error!(?_err);
|
||||
})
|
||||
.ok()
|
||||
});
|
||||
|
||||
match XSS.as_ref() {
|
||||
Some(regex) => {
|
||||
if regex.is_match(&decoded) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
None => return true,
|
||||
}
|
||||
|
||||
match SQLI.as_ref() {
|
||||
Some(regex) => {
|
||||
if regex.is_match(&decoded) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
None => return true,
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use fake::{faker::internet::en::SafeEmail, Fake};
|
||||
@ -150,4 +224,103 @@ mod tests {
|
||||
prop_assert!(validate_email(&email).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_basic_script_tags() {
|
||||
assert!(contains_potential_xss_or_sqli(
|
||||
"<script>alert('xss')</script>"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_event_handlers() {
|
||||
assert!(contains_potential_xss_or_sqli(
|
||||
"onload=alert('xss') onclick=alert('xss') onmouseover=alert('xss')",
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_data_url_payload() {
|
||||
assert!(contains_potential_xss_or_sqli(
|
||||
"data:text/html,<script>alert('xss')</script>",
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_iframe_javascript_src() {
|
||||
assert!(contains_potential_xss_or_sqli(
|
||||
"<iframe src=javascript:alert('xss')></iframe>",
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_svg_with_script() {
|
||||
assert!(contains_potential_xss_or_sqli(
|
||||
"<svg><script>alert('xss')</script></svg>",
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_object_with_js() {
|
||||
assert!(contains_potential_xss_or_sqli(
|
||||
"<object data=javascript:alert('xss')></object>",
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_mixed_case_tags() {
|
||||
assert!(contains_potential_xss_or_sqli(
|
||||
"<ScRiPt>alert('xss')</ScRiPt>"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_embedded_script_in_text() {
|
||||
assert!(contains_potential_xss_or_sqli(
|
||||
"Test<script>alert('xss')</script>Company",
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_math_with_script() {
|
||||
assert!(contains_potential_xss_or_sqli(
|
||||
"<math><script>alert('xss')</script></math>",
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_basic_sql_tautology() {
|
||||
assert!(contains_potential_xss_or_sqli("' OR '1'='1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_time_based_sqli() {
|
||||
assert!(contains_potential_xss_or_sqli("' OR SLEEP(5) --"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_percent_encoded_sqli() {
|
||||
// %27 OR %271%27=%271 is a typical encoded variant
|
||||
assert!(contains_potential_xss_or_sqli("%27%20OR%20%271%27%3D%271"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_benign_html_as_suspicious() {
|
||||
assert!(contains_potential_xss_or_sqli("<b>Hello</b>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allows_legitimate_plain_text() {
|
||||
assert!(!contains_potential_xss_or_sqli("My Test Company Ltd."));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allows_normal_url() {
|
||||
assert!(!contains_potential_xss_or_sqli("https://example.com"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allows_percent_char_without_encoding() {
|
||||
assert!(!contains_potential_xss_or_sqli("Get 50% off today"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use api_models::user::dashboard_metadata::{self as api, GetMultipleMetaDataPayload};
|
||||
#[cfg(feature = "email")]
|
||||
use common_enums::EntityType;
|
||||
use common_utils::pii;
|
||||
use diesel_models::{
|
||||
enums::DashboardMetadata as DBEnum, user::dashboard_metadata::DashboardMetadata,
|
||||
};
|
||||
@ -11,7 +8,7 @@ use error_stack::{report, ResultExt};
|
||||
use hyperswitch_interfaces::crm::CrmPayload;
|
||||
#[cfg(feature = "email")]
|
||||
use masking::ExposeInterface;
|
||||
use masking::PeekInterface;
|
||||
use masking::{PeekInterface, Secret};
|
||||
use router_env::logger;
|
||||
|
||||
use crate::{
|
||||
@ -456,11 +453,6 @@ async fn insert_metadata(
|
||||
metadata
|
||||
}
|
||||
types::MetaData::ProdIntent(data) => {
|
||||
if let Some(poc_email) = &data.poc_email {
|
||||
let inner_poc_email = poc_email.peek().as_str();
|
||||
pii::Email::from_str(inner_poc_email)
|
||||
.change_context(UserErrors::EmailParsingError)?;
|
||||
}
|
||||
let mut metadata = utils::insert_merchant_scoped_metadata_to_db(
|
||||
state,
|
||||
user.user_id.clone(),
|
||||
@ -523,19 +515,23 @@ async fn insert_metadata(
|
||||
let hubspot_body = state
|
||||
.crm_client
|
||||
.make_body(CrmPayload {
|
||||
legal_business_name: data.legal_business_name,
|
||||
business_label: data.business_label,
|
||||
legal_business_name: data.legal_business_name.map(|s| s.into_inner()),
|
||||
business_label: data.business_label.map(|s| s.into_inner()),
|
||||
business_location: data.business_location,
|
||||
display_name: data.display_name,
|
||||
poc_email: data.poc_email,
|
||||
business_type: data.business_type,
|
||||
business_identifier: data.business_identifier,
|
||||
business_website: data.business_website,
|
||||
poc_name: data.poc_name,
|
||||
poc_contact: data.poc_contact,
|
||||
comments: data.comments,
|
||||
display_name: data.display_name.map(|s| s.into_inner()),
|
||||
poc_email: data.poc_email.map(|s| Secret::new(s.peek().clone())),
|
||||
business_type: data.business_type.map(|s| s.into_inner()),
|
||||
business_identifier: data.business_identifier.map(|s| s.into_inner()),
|
||||
business_website: data.business_website.map(|s| s.into_inner()),
|
||||
poc_name: data
|
||||
.poc_name
|
||||
.map(|s| Secret::new(s.peek().clone().into_inner())),
|
||||
poc_contact: data
|
||||
.poc_contact
|
||||
.map(|s| Secret::new(s.peek().clone().into_inner())),
|
||||
comments: data.comments.map(|s| s.into_inner()),
|
||||
is_completed: data.is_completed,
|
||||
business_country_name: data.business_country_name,
|
||||
business_country_name: data.business_country_name.map(|s| s.into_inner()),
|
||||
})
|
||||
.await;
|
||||
let base_url = user_utils::get_base_url(state);
|
||||
|
||||
@ -3,7 +3,7 @@ use common_enums::{EntityType, MerchantProductType};
|
||||
use common_utils::{errors::CustomResult, pii, types::user::EmailThemeConfig};
|
||||
use error_stack::ResultExt;
|
||||
use external_services::email::{EmailContents, EmailData, EmailError};
|
||||
use masking::{ExposeInterface, Secret};
|
||||
use masking::{ExposeInterface, PeekInterface, Secret};
|
||||
|
||||
use crate::{configs, consts, routes::SessionState};
|
||||
#[cfg(feature = "olap")]
|
||||
@ -567,14 +567,26 @@ impl BizEmailProd {
|
||||
state.conf.email.prod_intent_recipient_email.clone(),
|
||||
)?,
|
||||
settings: state.conf.clone(),
|
||||
user_name: data.poc_name.unwrap_or_default(),
|
||||
poc_email: data.poc_email.unwrap_or_default(),
|
||||
legal_business_name: data.legal_business_name.unwrap_or_default(),
|
||||
user_name: data
|
||||
.poc_name
|
||||
.map(|s| Secret::new(s.peek().clone().into_inner()))
|
||||
.unwrap_or_default(),
|
||||
poc_email: data
|
||||
.poc_email
|
||||
.map(|s| Secret::new(s.peek().clone()))
|
||||
.unwrap_or_default(),
|
||||
legal_business_name: data
|
||||
.legal_business_name
|
||||
.map(|s| s.into_inner())
|
||||
.unwrap_or_default(),
|
||||
business_location: data
|
||||
.business_location
|
||||
.unwrap_or(common_enums::CountryAlpha2::AD)
|
||||
.to_string(),
|
||||
business_website: data.business_website.unwrap_or_default(),
|
||||
business_website: data
|
||||
.business_website
|
||||
.map(|s| s.into_inner())
|
||||
.unwrap_or_default(),
|
||||
theme_id,
|
||||
theme_config,
|
||||
product_type: data.product_type,
|
||||
|
||||
@ -287,8 +287,12 @@ pub fn is_prod_email_required(data: &ProdIntent, user_email: String) -> bool {
|
||||
data.poc_email.as_ref().map(|email| email.peek().as_str()),
|
||||
"juspay",
|
||||
);
|
||||
let business_website_check = not_contains_string(data.business_website.as_deref(), "juspay")
|
||||
&& not_contains_string(data.business_website.as_deref(), "hyperswitch");
|
||||
let business_website_check =
|
||||
not_contains_string(data.business_website.as_ref().map(|s| s.as_str()), "juspay")
|
||||
&& not_contains_string(
|
||||
data.business_website.as_ref().map(|s| s.as_str()),
|
||||
"hyperswitch",
|
||||
);
|
||||
let user_email_check = not_contains_string(Some(&user_email), "juspay");
|
||||
|
||||
if (poc_email_check && business_website_check && user_email_check).not() {
|
||||
|
||||
Reference in New Issue
Block a user