Compare commits

...
Author SHA1 Message Date
sonarly-bot 6ad5616c76 fix(workflow): guard iterator variable schema access
https://sonarly.com/issue/38763?type=bug

Opening a specific workflow record crashes the page with a frontend TypeError when rendering workflow variable labels. The failure is caused by unsafe access to iterator schema fields during variable-chip rendering.

Fix: Implemented a defensive fix in iterator variable search so malformed iterator schemas no longer crash rendering.

What changed:
- In `searchVariableThroughIteratorOutputSchema`, I now guard `iteratorOutputSchema.currentItem` before any property access.
- If `currentItem` is missing, the function safely returns `{ variableLabel: undefined, variablePathLabel: undefined }` (same non-crashing fallback used elsewhere).
- Subsequent label/type reads now use a local `currentItem` variable, avoiding repeated unsafe property access.

Why this fixes the incident:
- The production crash was caused by direct dereference of `currentItem.value` when `currentItem` was undefined for a specific workflow schema payload.
- With this guard, malformed/incomplete iterator output schema data degrades gracefully instead of throwing in render.

I also added a regression test that explicitly covers “currentItem missing from iterator output schema” and asserts the safe undefined-label fallback.

Authored by Sonarly by autonomous analysis (run 44106).
2026-05-19 19:19:08 +00:00
3 changed files with 45 additions and 12 deletions
@@ -44,20 +44,29 @@ export const PromiseRejectionEffect = () => {
error?.networkError?.name === 'AbortError' ||
error?.name === 'AbortError';
if (!isAbortError) {
enqueueErrorSnackBar(
error instanceof Error ? { message: error.message } : {},
);
if (isAbortError) {
return;
}
enqueueErrorSnackBar(
error instanceof Error ? { message: error.message } : {},
);
try {
const { captureException } = await import('@sentry/react');
captureException(error, (scope) => {
scope.setExtras({ mechanism: 'onUnhandle' });
const fingerprint = hasErrorCode(error) ? error.code : error.message;
scope.setFingerprint([fingerprint]);
error.name = error.message;
if (isDefined(fingerprint)) {
scope.setFingerprint([fingerprint]);
}
if (error instanceof Error) {
error.name = error.message;
}
return scope;
});
} catch (sentryError) {
@@ -129,6 +129,23 @@ describe('searchVariableThroughIteratorOutputSchema', () => {
});
});
it('should return undefined when currentItem is missing from iterator output schema', () => {
const result = searchVariableThroughIteratorOutputSchema({
stepName: 'Iterate Companies',
iteratorOutputSchema: {
currentItemIndex: 0,
hasProcessedAllItems: false,
} as any,
rawVariableName: '{{step1.currentItem.name}}',
isFullRecord: false,
});
expect(result).toEqual({
variableLabel: undefined,
variablePathLabel: undefined,
});
});
it('should return undefined when stepId or iteratorResultKey is undefined', () => {
const result = searchVariableThroughIteratorOutputSchema({
stepName: 'Iterate Companies',
@@ -84,7 +84,16 @@ export const searchVariableThroughIteratorOutputSchema = ({
}
if (iteratorResultKey === 'currentItem') {
const schema = iteratorOutputSchema.currentItem.value;
const currentItem = iteratorOutputSchema.currentItem;
if (!isDefined(currentItem)) {
return {
variableLabel: undefined,
variablePathLabel: undefined,
};
}
const schema = currentItem.value;
if (!isDefined(schema)) {
return {
@@ -113,11 +122,9 @@ export const searchVariableThroughIteratorOutputSchema = ({
}
return {
variableLabel: iteratorOutputSchema.currentItem.label,
variablePathLabel: `${stepName} > ${iteratorOutputSchema.currentItem.label}`,
variableType: iteratorOutputSchema.currentItem.isLeaf
? iteratorOutputSchema.currentItem.type
: 'unknown',
variableLabel: currentItem.label,
variablePathLabel: `${stepName} > ${currentItem.label}`,
variableType: currentItem.isLeaf ? currentItem.type : 'unknown',
};
}