Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(framework): Add disableOutputSanitization flag for channel step definitions #6521

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 77 additions & 2 deletions packages/framework/src/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1244,7 +1244,7 @@ describe('Novu Client', () => {
await expect(client.executeWorkflow(event)).rejects.toThrow(Error);
});

it('should sanitize the step result of all delivery channel step types', async () => {
it('should sanitize the step output of all channel step types by default', async () => {
const script = `<script>alert('Hello there')</script>`;

client.addWorkflows([
Expand Down Expand Up @@ -1274,9 +1274,84 @@ describe('Novu Client', () => {
expect(executionResult.outputs.subject).toBe('Start of subject. ');
});

it('should not sanitize the step result of custom step type', async () => {
it('should sanitize the step output of channel step types when `disableOutputSanitization: false`', async () => {
const script = `<script>alert('Hello there')</script>`;

client.addWorkflows([
workflow('test-workflow', async ({ step }) => {
await step.email(
'send-email',
async () => ({
body: `Start of body. ${script}`,
subject: `Start of subject. ${script}`,
}),
{
disableOutputSanitization: false,
}
);
}),
]);

const event: Event = {
action: PostActionEnum.EXECUTE,
workflowId: 'test-workflow',
stepId: 'send-email',
subscriber: {},
state: [],
data: {},
payload: {},
inputs: {},
controls: {},
};

const executionResult = await client.executeWorkflow(event);
expect(executionResult.outputs).toBeDefined();
expect(executionResult.outputs.body).toBe('Start of body. ');
expect(executionResult.outputs.subject).toBe('Start of subject. ');
});

it('should NOT sanitize the step output of channel step type when `disableOutputSanitization: true`', async () => {
const link =
'/pipeline/Oee4d54-ca52-4d70-86b3-cd10a67b6810/requirements?requirementId=dc25a578-ecf1-4835-9310-2236f8244bd&commentId=e259b16b-68f9-43af-b252-fce68bc7cb2f';

client.addWorkflows([
workflow('test-workflow', async ({ step }) => {
await step.inApp(
'send-inapp',
async () => ({
body: `Start of body.`,
data: {
someVal: link,
},
}),
{
disableOutputSanitization: true,
}
);
}),
]);

const event: Event = {
action: PostActionEnum.EXECUTE,
workflowId: 'test-workflow',
stepId: 'send-inapp',
subscriber: {},
state: [],
data: {},
payload: {},
inputs: {},
controls: {},
};

const executionResult = await client.executeWorkflow(event);
expect(executionResult.outputs).toBeDefined();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
expect((executionResult.outputs.data as any).someVal).toBe(link);
});

it('should not sanitize the step result of custom step type', async () => {
const script = `<script>alert('Hello there')</a>`;

client.addWorkflows([
workflow('test-workflow', async ({ step }) => {
await step.custom(
Expand Down
8 changes: 6 additions & 2 deletions packages/framework/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { JSONSchemaFaker } from 'json-schema-faker';
import { Liquid } from 'liquidjs';
import ora from 'ora';
import {} from './types';

import { ChannelStepEnum, FRAMEWORK_VERSION, PostActionEnum, SDK_VERSION } from './constants';
import {
Expand Down Expand Up @@ -309,7 +308,12 @@ export class Client {
resolve: stepResolve as typeof step.resolve,
});

if (Object.values(ChannelStepEnum).includes(step.type as ChannelStepEnum)) {
if (
Object.values(ChannelStepEnum).includes(step.type as ChannelStepEnum) &&
// TODO: Update return type to include ChannelStep and fix typings
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(options as any)?.disableOutputSanitization !== true
) {
// Sanitize the outputs to avoid XSS attacks via Channel content.
stepResult = {
...stepResult,
Expand Down
6 changes: 6 additions & 0 deletions packages/framework/src/types/step.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,12 @@ export type ChannelStep<
* The providers for the step. Used to override the behaviour of the providers for the step.
*/
providers?: Prettify<Providers<T_StepType, T_Controls, T_Outputs>>;
/**
* A flag to disable output sanitization for the step.
*
* @default false
*/
disableOutputSanitization?: boolean;
}
) => StepOutput<T_Result>;

Expand Down
Loading