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
+ };
+};
|