Skip to content

Commit fe750d1

Browse files
authored
Audit safe exec (#380)
1 parent 7b3fa29 commit fe750d1

File tree

9 files changed

+145
-70
lines changed

9 files changed

+145
-70
lines changed

web/apps/web/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@
9090
"uuid": "^10.0.0",
9191
"zod": "^3.23.8",
9292
"zustand": "^4.5.4",
93-
"zustand-computed-state": "^0.1.8"
93+
"zustand-computed-state": "^0.1.8",
94+
"isomorphic-dompurify": "^2.14.0"
9495
},
9596
"devDependencies": {
9697
"@shellagent/eslint-config": "workspace:*",

web/apps/web/src/components/app/node-form/widgets/highlight-input/variable-value-block/components/index.tsx

+13-10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
44
import { $getNodeByKey, COMMAND_PRIORITY_EDITOR } from 'lexical';
55
import { memo, useRef, useEffect } from 'react';
6+
import { sanitize } from 'isomorphic-dompurify';
67

78
import { cn } from '@/utils/cn';
89

@@ -125,21 +126,23 @@ const VariableValueBlockComponent = ({
125126
};
126127

127128
const renderParts = () => {
128-
return parts
129-
.map(part => {
130-
if (part.type === PartType.TEXT) {
131-
return part.content;
132-
} else if (part.type === PartType.VARIABLE) {
133-
return `<span
129+
return sanitize(
130+
parts
131+
.map(part => {
132+
if (part.type === PartType.TEXT) {
133+
return part.content;
134+
} else if (part.type === PartType.VARIABLE) {
135+
return `<span
134136
data-origin-value="${part.content}"
135137
data-display="${part.display}"
136138
contenteditable="false"
137139
class="inline-block text-blue-700 cursor-text px-0.5"
138140
>${part.display}</span>`;
139-
}
140-
return '';
141-
})
142-
.join('');
141+
}
142+
return '';
143+
})
144+
.join(''),
145+
);
143146
};
144147

145148
return (

web/apps/web/src/components/app/plugins/comfyui/widgets/comfyui-editor.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import { observer } from 'mobx-react-lite';
2222
import React, { useEffect, useRef } from 'react';
2323
import { Box, Flex } from 'react-system';
24+
import { sanitize } from 'isomorphic-dompurify';
2425

2526
import {
2627
DEFAULT_MODAL_STYLES,
@@ -226,7 +227,9 @@ export const ComfyUIEditorModal = observer(() => {
226227
]}>
227228
<div
228229
dangerouslySetInnerHTML={{
229-
__html: model.messageDetail?.replaceAll('\n', '<br />') || '',
230+
__html: sanitize(
231+
model.messageDetail?.replaceAll('\n', '<br />') || '',
232+
),
230233
}}
231234
/>
232235
</Modal>

web/apps/web/src/services/base.ts

+19-3
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,27 @@ export const customFetch = async <T>(
128128
{ toastId: 'login-error' },
129129
);
130130
setTimeout(() => {
131+
const currentUrl = window.location.href;
132+
const isValidRedirectUrl = (() => {
133+
try {
134+
const url = new URL(currentUrl);
135+
return (
136+
url.hostname.endsWith('myshell.fun') ||
137+
url.hostname.endsWith('myshell.ai') ||
138+
url.hostname.endsWith('myshell.life')
139+
);
140+
} catch (e) {
141+
return false;
142+
}
143+
})();
144+
145+
const redirectUrl = isValidRedirectUrl
146+
? currentUrl
147+
: 'https://myshell.ai';
148+
131149
window.location.href = `${
132150
process.env.NEXT_PUBLIC_LOGIN_URL
133-
}?login=true&redirect=${decodeURIComponent(
134-
window.location.href,
135-
)}`;
151+
}?login=true&redirect=${encodeURIComponent(redirectUrl)}`;
136152
}, 3000);
137153
}
138154

web/packages/form-engine/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"react-dnd": "^16.0.1",
3131
"react-dnd-html5-backend": "^16.0.1",
3232
"react-error-boundary": "^4.0.13",
33+
"sval": "^0.6.1",
3334
"tailwind-merge": "^2.5.2",
3435
"zod": "^3.23.8",
3536
"zustand": "^4.5.5"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { exec } from './exec';
2+
import Sval from 'sval';
3+
4+
describe('exec', () => {
5+
it('should return the result of the code', () => {
6+
const result = exec(`$this.value === "image"`, {
7+
$this: {
8+
value: 'text',
9+
},
10+
});
11+
expect(result).toBe(false);
12+
});
13+
14+
it('exec sval ver', () => {
15+
const result = exec(`$this.value === "image"`, {
16+
$this: {
17+
value: 'image',
18+
},
19+
});
20+
expect(result).toBe(true);
21+
22+
const result2 = exec(`$this.value === "image"`, {
23+
$this: {
24+
value: 'text',
25+
},
26+
});
27+
expect(result2).toBe(false);
28+
});
29+
30+
it('sval', () => {
31+
const interpreter = new Sval({
32+
ecmaVer: 'latest',
33+
sourceType: 'script',
34+
sandBox: true,
35+
});
36+
const code = `
37+
globalThis.$this = {
38+
value: "text"
39+
}
40+
exports.a = $this.value === "image"
41+
`;
42+
interpreter.run(code);
43+
expect(interpreter.exports.a).toBe(false);
44+
});
45+
});
+24-13
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,29 @@
11
import { TContext } from '../types';
2+
import Sval from 'sval';
23

3-
function exec(code: string, scope: TContext) {
4-
try {
5-
const str = `
6-
var ${'____data'} = arguments[0];
7-
with(${'____data'}) {
8-
return ${code}
9-
}
10-
`;
4+
function safeExec(expression: string, scope: TContext) {
5+
const interpreter = new Sval({
6+
ecmaVer: 'latest',
7+
sourceType: 'script',
8+
sandBox: true,
9+
});
1110

12-
return new Function(str)(scope);
13-
} catch (e) {
14-
console.log(e);
15-
}
11+
let globalThisAssignments = '';
12+
13+
Object.keys(scope).forEach(key => {
14+
const value = scope[key];
15+
globalThisAssignments += `globalThis[${JSON.stringify(
16+
key,
17+
)}] = ${JSON.stringify(value)};\n`;
18+
});
19+
20+
const code = `
21+
${globalThisAssignments}
22+
exports.a = ${expression};`;
23+
24+
interpreter.run(code);
25+
26+
return interpreter.exports.a;
1627
}
1728

18-
export { exec };
29+
export { safeExec as exec };

web/packages/form-engine/wallaby.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = function (wallaby) {
2+
return {
3+
autoDetect: ['jest'],
4+
};
5+
};

0 commit comments

Comments
 (0)