From b571c1a1a50fd932e8c434f8a0d0b778080a2483 Mon Sep 17 00:00:00 2001 From: Sanjai Kumar <161328623+sanjaikumar-bruno@users.noreply.github.com> Date: Wed, 30 Jul 2025 00:26:45 +0530 Subject: [PATCH] Feat/add warnings for sensitive fields other auths (#5100) --- .../EnvironmentVariables/constants.js | 11 +++ .../EnvironmentVariables/index.js | 45 ++++++++++++- .../RequestPane/Auth/AwsV4Auth/index.js | 9 ++- .../RequestPane/Auth/BasicAuth/index.js | 7 +- .../RequestPane/Auth/BearerAuth/index.js | 7 +- .../RequestPane/Auth/DigestAuth/index.js | 9 ++- .../RequestPane/Auth/NTLMAuth/index.js | 7 +- .../Auth/OAuth2/AuthorizationCode/index.js | 12 +++- .../Auth/OAuth2/ClientCredentials/index.js | 12 +++- .../Auth/OAuth2/PasswordCredentials/index.js | 12 +++- .../RequestPane/Auth/WsseAuth/index.js | 7 +- .../SensitiveFieldWarning/StyledWrapper.js | 10 +++ .../components/SensitiveFieldWarning/index.js | 29 ++++++++ .../hooks/useDetectSensitiveField/index.js | 67 +++++++++++++++++++ 14 files changed, 224 insertions(+), 20 deletions(-) create mode 100644 packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/constants.js create mode 100644 packages/bruno-app/src/components/SensitiveFieldWarning/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/SensitiveFieldWarning/index.js create mode 100644 packages/bruno-app/src/hooks/useDetectSensitiveField/index.js diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/constants.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/constants.js new file mode 100644 index 000000000..c7dbe0ef5 --- /dev/null +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/constants.js @@ -0,0 +1,11 @@ +const sensitiveFields = [ + 'request.auth.oauth2.clientSecret', + 'request.auth.basic.password', + 'request.auth.digest.password', + 'request.auth.wsse.password', + 'request.auth.ntlm.password', + 'request.auth.awsv4.secretAccessKey', + 'request.auth.bearer.token' +]; + +export { sensitiveFields }; diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js index c777aa85f..27cab21ce 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js @@ -1,4 +1,4 @@ -import React, { useRef, useEffect } from 'react'; +import React, { useRef, useEffect, useMemo } from 'react'; import cloneDeep from 'lodash/cloneDeep'; import { IconTrash, IconAlertCircle, IconDeviceFloppy, IconRefresh, IconCircleCheck } from '@tabler/icons'; import { useTheme } from 'providers/Theme'; @@ -13,7 +13,10 @@ import { variableNameRegex } from 'utils/common/regex'; import { saveEnvironment } from 'providers/ReduxStore/slices/collections/actions'; import toast from 'react-hot-toast'; import { Tooltip } from 'react-tooltip'; -import { getGlobalEnvironmentVariables } from 'utils/collections'; +import SensitiveFieldWarning from 'components/SensitiveFieldWarning'; +import { getGlobalEnvironmentVariables, flattenItems } from 'utils/collections'; +import { isItemARequest } from 'utils/collections'; +import { sensitiveFields } from './constants'; const EnvironmentVariables = ({ environment, collection, setIsModified, originalEnvironmentVariables, onClose }) => { const dispatch = useDispatch(); @@ -26,6 +29,34 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid }); _collection.globalEnvironmentVariables = globalEnvironmentVariables; + const nonSecretSensitiveVarUsageMap = useMemo(() => { + const result = {}; + if (!collection || !environment?.variables) { + return result; + } + const nonSecretVars = environment.variables.filter((v) => v.enabled && !v.secret && v.name); + if (!nonSecretVars.length) { + return result; + } + const varNames = new Set(nonSecretVars.map((v) => v.name)); + const items = flattenItems(collection.items || []); + items.forEach((item) => { + if (!isItemARequest(item)) return; + const requestObj = item.draft ? item.draft : item; + sensitiveFields.forEach((fieldPath) => { + const value = fieldPath.split('.').reduce((obj, key) => (obj ? obj[key] : undefined), requestObj); + if (typeof value === 'string') { + varNames.forEach((varName) => { + if (new RegExp(`\{\{\s*${varName}\s*\}\}`).test(value)) { + result[varName] = true; + } + }); + } + }); + }); + return result; + }, [collection, environment]); + const formik = useFormik({ enableReinitialize: true, initialValues: environment.variables || [], @@ -61,6 +92,8 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original } }); + const hasSensitiveUsage = (name) => !!nonSecretSensitiveVarUsageMap[name]; + // Effect to track modifications. React.useEffect(() => { setIsModified(formik.dirty); @@ -163,7 +196,7 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original - +
formik.setFieldValue(`${index}.value`, newValue, true)} />
+ {!variable.secret && hasSensitiveUsage(variable.name) && ( + + )} { const dispatch = useDispatch(); const { storedTheme } = useTheme(); const awsv4Auth = get(request, 'auth.awsv4', {}); + const { isSensitive } = useDetectSensitiveField(collection); + const { showWarning, warningMessage } = isSensitive(awsv4Auth?.secretAccessKey); const handleRun = () => dispatch(sendRequest(item, collection.uid)); @@ -144,7 +147,7 @@ const AwsV4Auth = ({ item, collection, updateAuth, request, save }) => { -
+
{ item={item} isSecret={true} /> + + {showWarning && }
diff --git a/packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/index.js index 752d7ce33..355b2bfd1 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/index.js @@ -1,4 +1,6 @@ import React from 'react'; +import SensitiveFieldWarning from 'components/SensitiveFieldWarning'; +import { useDetectSensitiveField } from 'hooks/useDetectSensitiveField'; import get from 'lodash/get'; import { useTheme } from 'providers/Theme'; import { useDispatch } from 'react-redux'; @@ -12,6 +14,8 @@ const BasicAuth = ({ item, collection, updateAuth, request, save }) => { const { storedTheme } = useTheme(); const basicAuth = get(request, 'auth.basic', {}); + const { isSensitive } = useDetectSensitiveField(collection); + const { showWarning, warningMessage } = isSensitive(basicAuth?.password); const handleRun = () => dispatch(sendRequest(item, collection.uid)); @@ -63,7 +67,7 @@ const BasicAuth = ({ item, collection, updateAuth, request, save }) => {
-
+
{ item={item} isSecret={true} /> + {showWarning && }
); diff --git a/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/index.js index c8ba9d1c6..12d65cdbc 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/index.js @@ -1,4 +1,6 @@ import React from 'react'; +import SensitiveFieldWarning from 'components/SensitiveFieldWarning'; +import { useDetectSensitiveField } from 'hooks/useDetectSensitiveField'; import get from 'lodash/get'; import { useTheme } from 'providers/Theme'; import { useDispatch } from 'react-redux'; @@ -13,6 +15,8 @@ const BearerAuth = ({ item, collection, updateAuth, request, save }) => { // Use the request prop directly like OAuth2ClientCredentials does const bearerToken = get(request, 'auth.bearer.token', ''); + const { isSensitive } = useDetectSensitiveField(collection); + const { showWarning, warningMessage } = isSensitive(bearerToken); const handleRun = () => dispatch(sendRequest(item, collection.uid)); @@ -36,7 +40,7 @@ const BearerAuth = ({ item, collection, updateAuth, request, save }) => { return ( -
+
{ item={item} isSecret={true} /> + {showWarning && }
); diff --git a/packages/bruno-app/src/components/RequestPane/Auth/DigestAuth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/DigestAuth/index.js index a4ff3012e..d17126c37 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/DigestAuth/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/DigestAuth/index.js @@ -1,4 +1,6 @@ import React from 'react'; +import SensitiveFieldWarning from 'components/SensitiveFieldWarning'; +import { useDetectSensitiveField } from 'hooks/useDetectSensitiveField'; import get from 'lodash/get'; import { useTheme } from 'providers/Theme'; import { useDispatch } from 'react-redux'; @@ -11,6 +13,8 @@ const DigestAuth = ({ item, collection, updateAuth, request, save }) => { const { storedTheme } = useTheme(); const digestAuth = get(request, 'auth.digest', {}); + const { isSensitive } = useDetectSensitiveField(collection); + const { showWarning, warningMessage } = isSensitive(digestAuth?.password); const handleRun = () => dispatch(sendRequest(item, collection.uid)); @@ -62,9 +66,9 @@ const DigestAuth = ({ item, collection, updateAuth, request, save }) => {
-
+
handlePasswordChange(val)} @@ -73,6 +77,7 @@ const DigestAuth = ({ item, collection, updateAuth, request, save }) => { item={item} isSecret={true} /> + {showWarning && }
); diff --git a/packages/bruno-app/src/components/RequestPane/Auth/NTLMAuth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/NTLMAuth/index.js index 44f87656e..b1cdf474c 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/NTLMAuth/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/NTLMAuth/index.js @@ -1,4 +1,6 @@ import React from 'react'; +import SensitiveFieldWarning from 'components/SensitiveFieldWarning'; +import { useDetectSensitiveField } from 'hooks/useDetectSensitiveField'; import get from 'lodash/get'; import { useTheme } from 'providers/Theme'; import { useDispatch } from 'react-redux'; @@ -12,6 +14,8 @@ const NTLMAuth = ({ item, collection, request, save, updateAuth }) => { const { storedTheme } = useTheme(); const ntlmAuth = get(request, 'auth.ntlm', {}); + const { isSensitive } = useDetectSensitiveField(collection); + const { showWarning, warningMessage } = isSensitive(ntlmAuth?.password); const handleRun = () => dispatch(sendRequest(item, collection.uid)); @@ -80,7 +84,7 @@ const NTLMAuth = ({ item, collection, request, save, updateAuth }) => {
-
+
{ item={item} isSecret={true} /> + {showWarning && }
diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AuthorizationCode/index.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AuthorizationCode/index.js index c00964d82..c46ce951d 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AuthorizationCode/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AuthorizationCode/index.js @@ -1,4 +1,5 @@ import React, { useRef, forwardRef } from 'react'; +import { useDetectSensitiveField } from 'hooks/useDetectSensitiveField'; import get from 'lodash/get'; import { useTheme } from 'providers/Theme'; import { useDispatch } from 'react-redux'; @@ -9,13 +10,14 @@ import StyledWrapper from './StyledWrapper'; import { inputsConfig } from './inputsConfig'; import Oauth2TokenViewer from '../Oauth2TokenViewer/index'; import Oauth2ActionButtons from '../Oauth2ActionButtons/index'; +import SensitiveFieldWarning from 'components/SensitiveFieldWarning'; const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAuth, collection, folder }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); const dropdownTippyRef = useRef(); const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); - + const { isSensitive } = useDetectSensitiveField(collection); const oAuth = get(request, 'auth.oauth2', {}); const { callbackUrl, @@ -129,12 +131,15 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
{inputsConfig.map((input) => { const { key, label, isSecret } = input; + const value = oAuth[key] || ''; + const { showWarning, warningMessage } = isSensitive(value); + return (
-
+
handleChange(key, val)} @@ -143,6 +148,7 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu item={item} isSecret={isSecret} /> + {isSecret && showWarning && }
); diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/ClientCredentials/index.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/ClientCredentials/index.js index 64ab7c408..667e965a6 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/ClientCredentials/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/ClientCredentials/index.js @@ -1,4 +1,5 @@ import React, { useRef, forwardRef } from 'react'; +import { useDetectSensitiveField } from 'hooks/useDetectSensitiveField'; import get from 'lodash/get'; import { useTheme } from 'providers/Theme'; import { useDispatch } from 'react-redux'; @@ -9,13 +10,14 @@ import { inputsConfig } from './inputsConfig'; import Dropdown from 'components/Dropdown'; import Oauth2TokenViewer from '../Oauth2TokenViewer/index'; import Oauth2ActionButtons from '../Oauth2ActionButtons/index'; +import SensitiveFieldWarning from 'components/SensitiveFieldWarning'; const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAuth, collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); const dropdownTippyRef = useRef(); const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); - + const { isSensitive } = useDetectSensitiveField(collection); const oAuth = get(request, 'auth.oauth2', {}); const { @@ -96,12 +98,15 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu
{inputsConfig.map((input) => { const { key, label, isSecret } = input; + const value = oAuth[key] || ''; + const { showWarning, warningMessage } = isSensitive(value); + return (
-
+
handleChange(key, val)} @@ -110,6 +115,7 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu item={item} isSecret={isSecret} /> + {isSecret && showWarning && }
); diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/PasswordCredentials/index.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/PasswordCredentials/index.js index 385607848..db969d8cc 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/PasswordCredentials/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/PasswordCredentials/index.js @@ -1,4 +1,5 @@ import React, { useRef, forwardRef } from 'react'; +import { useDetectSensitiveField } from 'hooks/useDetectSensitiveField'; import get from 'lodash/get'; import { useTheme } from 'providers/Theme'; import { useDispatch } from 'react-redux'; @@ -9,14 +10,15 @@ import { inputsConfig } from './inputsConfig'; import Dropdown from 'components/Dropdown'; import Oauth2TokenViewer from '../Oauth2TokenViewer/index'; import Oauth2ActionButtons from '../Oauth2ActionButtons/index'; +import SensitiveFieldWarning from 'components/SensitiveFieldWarning/index'; const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, updateAuth, collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); const dropdownTippyRef = useRef(); const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); - const oAuth = get(request, 'auth.oauth2', {}); + const { isSensitive } = useDetectSensitiveField(collection); const { accessTokenUrl, @@ -99,12 +101,15 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update
{inputsConfig.map((input) => { const { key, label, isSecret } = input; + const value = oAuth[key] || ''; + const { showWarning, warningMessage } = isSensitive(value); + return (
-
+
handleChange(key, val)} @@ -113,6 +118,7 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update item={item} isSecret={isSecret} /> + {isSecret && showWarning && }
); diff --git a/packages/bruno-app/src/components/RequestPane/Auth/WsseAuth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/WsseAuth/index.js index 05e9daaf1..fde2310c9 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/WsseAuth/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/WsseAuth/index.js @@ -1,4 +1,6 @@ import React from 'react'; +import SensitiveFieldWarning from 'components/SensitiveFieldWarning'; +import { useDetectSensitiveField } from 'hooks/useDetectSensitiveField'; import get from 'lodash/get'; import { useTheme } from 'providers/Theme'; import { useDispatch } from 'react-redux'; @@ -12,6 +14,8 @@ const WsseAuth = ({ item, collection, updateAuth, request, save }) => { const { storedTheme } = useTheme(); const wsseAuth = get(request, 'auth.wsse', {}); + const { isSensitive } = useDetectSensitiveField(collection); + const { showWarning, warningMessage } = isSensitive(wsseAuth?.password); const handleRun = () => dispatch(sendRequest(item, collection.uid)); @@ -63,7 +67,7 @@ const WsseAuth = ({ item, collection, updateAuth, request, save }) => {
-
+
{ item={item} isSecret={true} /> + {showWarning && }
); diff --git a/packages/bruno-app/src/components/SensitiveFieldWarning/StyledWrapper.js b/packages/bruno-app/src/components/SensitiveFieldWarning/StyledWrapper.js new file mode 100644 index 000000000..97a9389d8 --- /dev/null +++ b/packages/bruno-app/src/components/SensitiveFieldWarning/StyledWrapper.js @@ -0,0 +1,10 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + .tooltip-mod { + font-size: 11px !important; + width: 150px !important; + } +`; + +export default Wrapper; \ No newline at end of file diff --git a/packages/bruno-app/src/components/SensitiveFieldWarning/index.js b/packages/bruno-app/src/components/SensitiveFieldWarning/index.js new file mode 100644 index 000000000..2b2cce326 --- /dev/null +++ b/packages/bruno-app/src/components/SensitiveFieldWarning/index.js @@ -0,0 +1,29 @@ +import React from 'react'; +import { IconAlertTriangle } from '@tabler/icons'; +import { Tooltip } from 'react-tooltip'; +import StyledWrapper from './StyledWrapper'; + +const SensitiveFieldWarning = ({ fieldName, warningMessage }) => { + const tooltipId = `sensitive-field-warning-${fieldName}`; + + return ( + + + + +

+ {warningMessage} +

+
+ } + /> + +
+ ); +}; + +export default SensitiveFieldWarning; diff --git a/packages/bruno-app/src/hooks/useDetectSensitiveField/index.js b/packages/bruno-app/src/hooks/useDetectSensitiveField/index.js new file mode 100644 index 000000000..257b116e9 --- /dev/null +++ b/packages/bruno-app/src/hooks/useDetectSensitiveField/index.js @@ -0,0 +1,67 @@ +import { useMemo } from 'react'; + +const VARIABLE_NAME_REGEX = /\{\{([^}]+)\}\}/g; +const ENV_VAR_REFERENCE_REGEX = /^\s*\{\{.*\}\}\s*$/; + +export const useDetectSensitiveField = (collection) => { + const envVars = useMemo(() => { + if (!collection) { + return []; + } + const activeEnv = collection?.environments?.find((env) => env.uid === collection.activeEnvironmentUid); + if (!activeEnv || !Array.isArray(activeEnv.variables)) { + return []; + } + return activeEnv.variables; + }, [collection]); + + // Checks if the value is a single environment variable reference (e.g., {{API_KEY}}) + const isEnvVarReference = (value) => { + return typeof value === 'string' && ENV_VAR_REFERENCE_REGEX.test(value); + }; + + // Extracts all variable names from a string (e.g., "Bearer {{TOKEN}}-{{SUFFIX}}" → ["TOKEN", "SUFFIX"]) + const extractVarNames = (value) => { + if (!value || typeof value !== 'string') { + return []; + } + const matches = []; + let match; + while ((match = VARIABLE_NAME_REGEX.exec(value)) !== null) { + matches.push(match[1].trim()); + } + return matches; + }; + + // Checks if a variable is present and not marked as secret in the environment + const isVarNotSecret = (varName, envVars = []) => { + const found = envVars.find((v) => v.name === varName); + return found && !found.secret; + }; + + const isSensitive = (value) => { + if (value && !isEnvVarReference(value)) { + return { + showWarning: true, + warningMessage: 'Store sensitive info as a secret variable or in a .env file' + }; + } + + if (value && typeof value === 'string') { + const varNames = extractVarNames(value); + if (varNames.some((varName) => isVarNotSecret(varName, envVars))) { + return { + showWarning: true, + warningMessage: 'Mark the environment variable as secret for better security.' + }; + } + } + + // No warning needed + return { showWarning: false }; + }; + + return { + isSensitive + }; +};