fix: hidden secret value in secret approvals

This commit is contained in:
Daniel Hougaard
2025-06-13 18:45:48 +04:00
parent b43ecef112
commit d5e9ac82d0
5 changed files with 78 additions and 26 deletions
@@ -285,6 +285,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
commits: secretRawSchema commits: secretRawSchema
.omit({ _id: true, environment: true, workspace: true, type: true, version: true, secretValue: true }) .omit({ _id: true, environment: true, workspace: true, type: true, version: true, secretValue: true })
.extend({ .extend({
secretValueHidden: z.boolean(),
secretValue: z.string().optional(), secretValue: z.string().optional(),
isRotatedSecret: z.boolean().optional(), isRotatedSecret: z.boolean().optional(),
op: z.string(), op: z.string(),
@@ -296,6 +297,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
version: z.number(), version: z.number(),
secretKey: z.string(), secretKey: z.string(),
secretValue: z.string().optional(), secretValue: z.string().optional(),
secretValueHidden: z.boolean(),
secretComment: z.string().optional() secretComment: z.string().optional()
}) })
.optional() .optional()
@@ -306,6 +308,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
version: z.number(), version: z.number(),
secretKey: z.string(), secretKey: z.string(),
secretValue: z.string().optional(), secretValue: z.string().optional(),
secretValueHidden: z.boolean(),
secretComment: z.string().optional(), secretComment: z.string().optional(),
tags: SanitizedTagSchema.array().optional(), tags: SanitizedTagSchema.array().optional(),
secretMetadata: ResourceMetadataSchema.nullish() secretMetadata: ResourceMetadataSchema.nullish()
@@ -39,7 +39,8 @@ import {
fnSecretBulkDelete, fnSecretBulkDelete,
fnSecretBulkInsert, fnSecretBulkInsert,
fnSecretBulkUpdate, fnSecretBulkUpdate,
getAllNestedSecretReferences getAllNestedSecretReferences,
INFISICAL_SECRET_VALUE_HIDDEN_MASK
} from "@app/services/secret/secret-fns"; } from "@app/services/secret/secret-fns";
import { TSecretQueueFactory } from "@app/services/secret/secret-queue"; import { TSecretQueueFactory } from "@app/services/secret/secret-queue";
import { SecretOperations } from "@app/services/secret/secret-types"; import { SecretOperations } from "@app/services/secret/secret-types";
@@ -267,7 +268,6 @@ export const secretApprovalRequestServiceFactory = ({
ProjectPermissionSecretActions.DescribeAndReadValue, ProjectPermissionSecretActions.DescribeAndReadValue,
ProjectPermissionSub.Secrets ProjectPermissionSub.Secrets
); );
const hiddenSecretValue = "******";
let secrets; let secrets;
if (shouldUseSecretV2Bridge) { if (shouldUseSecretV2Bridge) {
@@ -285,8 +285,9 @@ export const secretApprovalRequestServiceFactory = ({
version: el.version, version: el.version,
secretMetadata: el.secretMetadata as ResourceMetadataDTO, secretMetadata: el.secretMetadata as ResourceMetadataDTO,
isRotatedSecret: el.secret?.isRotatedSecret ?? false, isRotatedSecret: el.secret?.isRotatedSecret ?? false,
secretValueHidden: !hasSecretReadAccess,
secretValue: !hasSecretReadAccess secretValue: !hasSecretReadAccess
? hiddenSecretValue ? INFISICAL_SECRET_VALUE_HIDDEN_MASK
: el.secret && el.secret.isRotatedSecret : el.secret && el.secret.isRotatedSecret
? undefined ? undefined
: el.encryptedValue : el.encryptedValue
@@ -300,8 +301,9 @@ export const secretApprovalRequestServiceFactory = ({
secretKey: el.secret.key, secretKey: el.secret.key,
id: el.secret.id, id: el.secret.id,
version: el.secret.version, version: el.secret.version,
secretValueHidden: !hasSecretReadAccess,
secretValue: !hasSecretReadAccess secretValue: !hasSecretReadAccess
? hiddenSecretValue ? INFISICAL_SECRET_VALUE_HIDDEN_MASK
: el.secret.encryptedValue : el.secret.encryptedValue
? secretManagerDecryptor({ cipherTextBlob: el.secret.encryptedValue }).toString() ? secretManagerDecryptor({ cipherTextBlob: el.secret.encryptedValue }).toString()
: "", : "",
@@ -315,8 +317,9 @@ export const secretApprovalRequestServiceFactory = ({
secretKey: el.secretVersion.key, secretKey: el.secretVersion.key,
id: el.secretVersion.id, id: el.secretVersion.id,
version: el.secretVersion.version, version: el.secretVersion.version,
secretValueHidden: !hasSecretReadAccess,
secretValue: !hasSecretReadAccess secretValue: !hasSecretReadAccess
? hiddenSecretValue ? INFISICAL_SECRET_VALUE_HIDDEN_MASK
: el.secretVersion.encryptedValue : el.secretVersion.encryptedValue
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedValue }).toString() ? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedValue }).toString()
: "", : "",
@@ -333,11 +336,13 @@ export const secretApprovalRequestServiceFactory = ({
const encryptedSecrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id); const encryptedSecrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id);
secrets = encryptedSecrets.map((el) => ({ secrets = encryptedSecrets.map((el) => ({
...el, ...el,
secretValueHidden: !hasSecretReadAccess,
...decryptSecretWithBot(el, botKey), ...decryptSecretWithBot(el, botKey),
secret: el.secret secret: el.secret
? { ? {
id: el.secret.id, id: el.secret.id,
version: el.secret.version, version: el.secret.version,
secretValueHidden: false,
...decryptSecretWithBot(el.secret, botKey) ...decryptSecretWithBot(el.secret, botKey)
} }
: undefined, : undefined,
@@ -345,6 +350,7 @@ export const secretApprovalRequestServiceFactory = ({
? { ? {
id: el.secretVersion.id, id: el.secretVersion.id,
version: el.secretVersion.version, version: el.secretVersion.version,
secretValueHidden: false,
...decryptSecretWithBot(el.secretVersion, botKey) ...decryptSecretWithBot(el.secretVersion, botKey)
} }
: undefined : undefined
@@ -353,6 +359,7 @@ export const secretApprovalRequestServiceFactory = ({
const secretPath = await folderDAL.findSecretPathByFolderIds(secretApprovalRequest.projectId, [ const secretPath = await folderDAL.findSecretPathByFolderIds(secretApprovalRequest.projectId, [
secretApprovalRequest.folderId secretApprovalRequest.folderId
]); ]);
return { ...secretApprovalRequest, secretPath: secretPath?.[0]?.path || "/", commits: secrets }; return { ...secretApprovalRequest, secretPath: secretPath?.[0]?.path || "/", commits: secrets };
}; };
@@ -48,6 +48,7 @@ const syntaxHighlight = (content?: string | null, isVisible?: boolean, isImport?
type Props = TextareaHTMLAttributes<HTMLTextAreaElement> & { type Props = TextareaHTMLAttributes<HTMLTextAreaElement> & {
value?: string | null; value?: string | null;
isVisible?: boolean; isVisible?: boolean;
valueAlwaysHidden?: boolean;
isImport?: boolean; isImport?: boolean;
isReadOnly?: boolean; isReadOnly?: boolean;
isDisabled?: boolean; isDisabled?: boolean;
@@ -63,6 +64,7 @@ export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
value, value,
isVisible, isVisible,
isImport, isImport,
valueAlwaysHidden,
containerClassName, containerClassName,
onBlur, onBlur,
isDisabled, isDisabled,
@@ -84,7 +86,11 @@ export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
<pre aria-hidden className="m-0"> <pre aria-hidden className="m-0">
<code className={`inline-block w-full ${commonClassName}`}> <code className={`inline-block w-full ${commonClassName}`}>
<span style={{ whiteSpace: "break-spaces" }}> <span style={{ whiteSpace: "break-spaces" }}>
{syntaxHighlight(value, isVisible || isSecretFocused, isImport)} {syntaxHighlight(
value,
isVisible || (isSecretFocused && !valueAlwaysHidden),
isImport
)}
</span> </span>
</code> </code>
</pre> </pre>
@@ -32,6 +32,7 @@ export type TSecretApprovalSecChange = {
version: number; version: number;
secretKey: string; secretKey: string;
secretValue?: string; secretValue?: string;
secretValueHidden?: boolean;
secretComment?: string; secretComment?: string;
isRotatedSecret?: boolean; isRotatedSecret?: boolean;
tags?: string[]; tags?: string[];
@@ -14,6 +14,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { SecretInput, Tag, Tooltip } from "@app/components/v2"; import { SecretInput, Tag, Tooltip } from "@app/components/v2";
import { CommitType, SecretV3Raw, TSecretApprovalSecChange, WsTag } from "@app/hooks/api/types"; import { CommitType, SecretV3Raw, TSecretApprovalSecChange, WsTag } from "@app/hooks/api/types";
import { twMerge } from "tailwind-merge";
export type Props = { export type Props = {
op: CommitType; op: CommitType;
@@ -105,21 +106,38 @@ export const SecretApprovalRequestChangeItem = ({
</span> </span>
) : ( ) : (
<div className="relative"> <div className="relative">
{secretVersion?.secretValueHidden && (
<div className="absolute left-1 top-1/2 -translate-y-1/2">
<Tooltip content="You do not have access to view the old secret value.">
<FontAwesomeIcon
className="pl-2 text-mineshaft-300"
size="sm"
icon={faEyeSlash}
/>
</Tooltip>
</div>
)}
<SecretInput <SecretInput
isReadOnly isReadOnly
isVisible={isOldSecretValueVisible} isVisible={isOldSecretValueVisible}
valueAlwaysHidden={secretVersion?.secretValueHidden}
value={secretVersion?.secretValue} value={secretVersion?.secretValue}
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-bunker-700 px-2 py-1.5" containerClassName={twMerge(
"border border-mineshaft-600 bg-bunker-700 py-1.5 text-bunker-300 hover:border-primary-400/50",
secretVersion?.secretValueHidden ? "pl-8 pr-2" : "px-2"
)}
/> />
<div {!secretVersion?.secretValueHidden && (
className="absolute right-1 top-1" <div
onClick={() => setIsOldSecretValueVisible(!isOldSecretValueVisible)} className="absolute right-1 top-1"
> onClick={() => setIsOldSecretValueVisible(!isOldSecretValueVisible)}
<FontAwesomeIcon >
icon={isOldSecretValueVisible ? faEyeSlash : faEye} <FontAwesomeIcon
className="cursor-pointer rounded-md border border-mineshaft-500 bg-mineshaft-800 p-1.5 text-mineshaft-300 hover:bg-mineshaft-700" icon={isOldSecretValueVisible ? faEyeSlash : faEye}
/> className="cursor-pointer rounded-md border border-mineshaft-500 bg-mineshaft-800 p-1.5 text-mineshaft-300 hover:bg-mineshaft-700"
</div> />
</div>
)}
</div> </div>
)} )}
</div> </div>
@@ -211,21 +229,38 @@ export const SecretApprovalRequestChangeItem = ({
</span> </span>
) : ( ) : (
<div className="relative"> <div className="relative">
{newVersion?.secretValueHidden && (
<div className="absolute left-1 top-1/2 -translate-y-1/2">
<Tooltip content="You do not have access to view the new secret value.">
<FontAwesomeIcon
className="pl-2 text-mineshaft-300"
size="sm"
icon={faEyeSlash}
/>
</Tooltip>
</div>
)}
<SecretInput <SecretInput
isReadOnly isReadOnly
valueAlwaysHidden={newVersion?.secretValueHidden}
isVisible={isNewSecretValueVisible} isVisible={isNewSecretValueVisible}
value={newVersion?.secretValue} value={newVersion?.secretValue}
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-bunker-700 px-2 py-1.5" containerClassName={twMerge(
"border border-mineshaft-600 bg-bunker-700 py-1.5 text-bunker-300 hover:border-primary-400/50",
newVersion?.secretValueHidden ? "pl-8 pr-2" : "px-2"
)}
/> />
<div {!newVersion?.secretValueHidden && (
className="absolute right-1 top-1" <div
onClick={() => setIsNewSecretValueVisible(!isNewSecretValueVisible)} className="absolute right-1 top-1"
> onClick={() => setIsNewSecretValueVisible(!isNewSecretValueVisible)}
<FontAwesomeIcon >
icon={isNewSecretValueVisible ? faEyeSlash : faEye} <FontAwesomeIcon
className="cursor-pointer rounded-md border border-mineshaft-500 bg-mineshaft-800 p-1.5 text-mineshaft-300 hover:bg-mineshaft-700" icon={isNewSecretValueVisible ? faEyeSlash : faEye}
/> className="cursor-pointer rounded-md border border-mineshaft-500 bg-mineshaft-800 p-1.5 text-mineshaft-300 hover:bg-mineshaft-700"
</div> />
</div>
)}
</div> </div>
)} )}
</div> </div>