mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge remote-tracking branch 'origin/main' into ENG-3639
This commit is contained in:
@@ -630,11 +630,16 @@ export const expandSecretReferencesFactory = ({
|
||||
const stackTrace = { ...dto, key: "root", children: [] } as TSecretReferenceTraceNode;
|
||||
|
||||
if (!dto.value) return { expandedValue: "", stackTrace };
|
||||
const stack = [{ ...dto, depth: 0, trace: stackTrace }];
|
||||
|
||||
// Track visited secrets to prevent circular references
|
||||
const createSecretId = (env: string, secretPath: string, key: string) => `${env}:${secretPath}:${key}`;
|
||||
|
||||
const currentSecretId = createSecretId(dto.environment, dto.secretPath, dto.secretKey);
|
||||
const stack = [{ ...dto, depth: 0, trace: stackTrace, visitedSecrets: new Set<string>([currentSecretId]) }];
|
||||
let expandedValue = dto.value;
|
||||
|
||||
while (stack.length) {
|
||||
const { value, secretPath, environment, depth, trace } = stack.pop()!;
|
||||
const { value, secretPath, environment, depth, trace, visitedSecrets } = stack.pop()!;
|
||||
|
||||
// eslint-disable-next-line no-continue
|
||||
if (depth > MAX_SECRET_REFERENCE_DEPTH) continue;
|
||||
@@ -710,17 +715,27 @@ export const expandSecretReferencesFactory = ({
|
||||
trace
|
||||
};
|
||||
|
||||
const shouldExpandMore = INTERPOLATION_TEST_REGEX.test(referencedSecretValue);
|
||||
// Check for circular reference
|
||||
const referencedSecretId = createSecretId(
|
||||
referencedSecretEnvironmentSlug,
|
||||
referencedSecretPath,
|
||||
referencedSecretKey
|
||||
);
|
||||
const isCircular = visitedSecrets.has(referencedSecretId);
|
||||
|
||||
const newVisitedSecrets = new Set([...visitedSecrets, referencedSecretId]);
|
||||
|
||||
const shouldExpandMore = INTERPOLATION_TEST_REGEX.test(referencedSecretValue) && !isCircular;
|
||||
if (dto.shouldStackTrace) {
|
||||
const stackTraceNode = { ...node, children: [], key: referencedSecretKey, trace: null };
|
||||
trace?.children.push(stackTraceNode);
|
||||
// if stack trace this would be child node
|
||||
if (shouldExpandMore) {
|
||||
stack.push({ ...node, trace: stackTraceNode });
|
||||
stack.push({ ...node, trace: stackTraceNode, visitedSecrets: newVisitedSecrets });
|
||||
}
|
||||
} else if (shouldExpandMore) {
|
||||
// if no stack trace is needed we just keep going with root node
|
||||
stack.push(node);
|
||||
stack.push({ ...node, visitedSecrets: newVisitedSecrets });
|
||||
}
|
||||
|
||||
if (referencedSecretValue) {
|
||||
|
||||
@@ -29,17 +29,54 @@ const INTERPOLATION_SYNTAX_REG = /\${([^}]+)}/;
|
||||
export const hasSecretReference = (value: string | undefined) =>
|
||||
value ? INTERPOLATION_SYNTAX_REG.test(value) : false;
|
||||
|
||||
const createNodeId = (node: TSecretReferenceTraceNode): string =>
|
||||
`${node.environment}:${node.secretPath}:${node.key}`;
|
||||
|
||||
const isCircularReference = (
|
||||
node: TSecretReferenceTraceNode,
|
||||
visitedPath: Set<string>
|
||||
): boolean => {
|
||||
const nodeId = createNodeId(node);
|
||||
return visitedPath.has(nodeId);
|
||||
};
|
||||
|
||||
const hasCircularReferences = (
|
||||
node: TSecretReferenceTraceNode,
|
||||
visitedPath: Set<string> = new Set()
|
||||
): boolean => {
|
||||
const nodeId = createNodeId(node);
|
||||
|
||||
if (visitedPath.has(nodeId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const newVisitedPath = new Set([...visitedPath, nodeId]);
|
||||
|
||||
return node.children.some((child) => hasCircularReferences(child, newVisitedPath));
|
||||
};
|
||||
|
||||
export const SecretReferenceNode = ({
|
||||
node,
|
||||
isRoot,
|
||||
secretKey
|
||||
secretKey,
|
||||
visitedPath = new Set()
|
||||
}: {
|
||||
node: TSecretReferenceTraceNode;
|
||||
isRoot?: boolean;
|
||||
secretKey?: string;
|
||||
visitedPath?: Set<string>;
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const hasChildren = node.children.length > 0;
|
||||
|
||||
const nodeId = createNodeId(node);
|
||||
const isCircular = !isRoot && isCircularReference(node, visitedPath);
|
||||
|
||||
const newVisitedPath = isCircular ? visitedPath : new Set([...visitedPath, nodeId]);
|
||||
|
||||
const safeChildren = isCircular
|
||||
? []
|
||||
: node.children.filter((child) => !isCircularReference(child, newVisitedPath));
|
||||
const hasChildren = safeChildren.length > 0;
|
||||
|
||||
return (
|
||||
<li>
|
||||
@@ -77,8 +114,12 @@ export const SecretReferenceNode = ({
|
||||
<Collapsible.Content className={twMerge("mt-4", style.collapsibleContent)}>
|
||||
{hasChildren && (
|
||||
<ul>
|
||||
{node.children.map((el, index) => (
|
||||
<SecretReferenceNode node={el} key={`${el.key}-${index + 1}`} />
|
||||
{safeChildren.map((el, index) => (
|
||||
<SecretReferenceNode
|
||||
node={el}
|
||||
key={`${el.key}-${index + 1}`}
|
||||
visitedPath={newVisitedPath}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
@@ -102,6 +143,9 @@ export const SecretReferenceTree = ({ secretPath, environment, secretKey }: Prop
|
||||
const tree = data?.tree;
|
||||
const secretValue = data?.value;
|
||||
|
||||
// Check if the tree contains circular references
|
||||
const hasCirculars = tree ? hasCircularReferences(tree) : false;
|
||||
|
||||
useEffect(() => {
|
||||
if (error instanceof AxiosError) {
|
||||
const err = error?.response?.data as TApiErrors;
|
||||
@@ -140,7 +184,15 @@ export const SecretReferenceTree = ({ secretPath, environment, secretKey }: Prop
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormControl label="Expanded value">
|
||||
<FormControl
|
||||
label="Expanded value"
|
||||
tooltipText={
|
||||
hasCirculars
|
||||
? "This secret contains circular references. Value shown is resolved once, with circular paths truncated in the reference tree below."
|
||||
: undefined
|
||||
}
|
||||
tooltipClassName="max-w-md break-words"
|
||||
>
|
||||
<SecretInput
|
||||
key="value-overriden"
|
||||
isReadOnly
|
||||
|
||||
Reference in New Issue
Block a user