Skip to content

Commit 4d097cc

Browse files
authored
refactor: remove trojan-source detection from SourceFile constructor (#387)
Update workspaces/js-x-ray/test/probes/isSerializeEnv.spec.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> chore: add changeset
1 parent 7c5af59 commit 4d097cc

15 files changed

+97
-98
lines changed

.changeset/poor-jars-smile.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@nodesecure/js-x-ray": minor
3+
---
4+
5+
Move trojan-source detection from SourceFile to AstAnalyser

workspaces/js-x-ray/src/AstAnalyser.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import { isOneLineExpressionExport } from "./utils/index.js";
2222
import { JsSourceParser, type SourceParser } from "./JsSourceParser.js";
2323
import { ProbeRunner, type Probe } from "./ProbeRunner.js";
24+
import * as trojan from "./obfuscators/trojan-source.js";
2425

2526
export interface Dependency {
2627
unsafe: boolean;
@@ -143,7 +144,13 @@ export class AstAnalyser {
143144
const body = this.parser.parse(this.prepareSource(str, { removeHTMLComments }), {
144145
isEcmaScriptModule: Boolean(module)
145146
});
146-
const source = new SourceFile(str);
147+
const source = new SourceFile();
148+
if (trojan.verify(str)) {
149+
source.warnings.push(
150+
generateWarning("obfuscated-code", { value: "trojan-source" })
151+
);
152+
}
153+
147154
const runner = new ProbeRunner(source, this.probes);
148155

149156
if (initialize) {

workspaces/js-x-ray/src/SourceFile.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
} from "./warnings.js";
1212
import type { Dependency } from "./AstAnalyser.js";
1313
import { Deobfuscator } from "./Deobfuscator.js";
14-
import * as trojan from "./obfuscators/trojan-source.js";
1514

1615
// CONSTANTS
1716
const kMaximumEncodedLiterals = 10;
@@ -30,17 +29,6 @@ export class SourceFile {
3029
encodedLiterals = new Map<string, string>();
3130
warnings: Warning[] = [];
3231
flags = new Set<SourceFlags>();
33-
34-
constructor(
35-
sourceCodeString: string
36-
) {
37-
if (trojan.verify(sourceCodeString)) {
38-
this.warnings.push(
39-
generateWarning("obfuscated-code", { value: "trojan-source" })
40-
);
41-
}
42-
}
43-
4432
addDependency(
4533
name: string,
4634
location?: ESTree.SourceLocation | null,

workspaces/js-x-ray/test/ProbeRunner.spec.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { SourceFile } from "../src/SourceFile.js";
1515
describe("ProbeRunner", () => {
1616
describe("constructor", () => {
1717
it("should instanciate class with Defaults probes when none are provide", () => {
18-
const pr = new ProbeRunner(new SourceFile(""));
18+
const pr = new ProbeRunner(new SourceFile());
1919

2020
assert.strictEqual(pr.probes, ProbeRunner.Defaults);
2121
});
@@ -30,7 +30,7 @@ describe("ProbeRunner", () => {
3030
];
3131

3232
// @ts-expect-error
33-
const pr = new ProbeRunner(new SourceFile(""), fakeProbe);
33+
const pr = new ProbeRunner(new SourceFile(), fakeProbe);
3434
assert.strictEqual(pr.probes, fakeProbe);
3535
});
3636

@@ -44,7 +44,7 @@ describe("ProbeRunner", () => {
4444
];
4545

4646
// @ts-expect-error
47-
const pr = new ProbeRunner(new SourceFile(""), fakeProbe);
47+
const pr = new ProbeRunner(new SourceFile(), fakeProbe);
4848
assert.strictEqual(pr.probes, fakeProbe);
4949
});
5050

@@ -56,7 +56,7 @@ describe("ProbeRunner", () => {
5656

5757
function instantiateProbeRunner() {
5858
// @ts-expect-error
59-
return new ProbeRunner(new SourceFile(""), [fakeProbe]);
59+
return new ProbeRunner(new SourceFile(), [fakeProbe]);
6060
}
6161

6262
assert.throws(instantiateProbeRunner, Error, "Invalid probe");
@@ -70,7 +70,7 @@ describe("ProbeRunner", () => {
7070

7171
function instantiateProbeRunner() {
7272
// @ts-expect-error
73-
return new ProbeRunner(new SourceFile(""), [fakeProbe]);
73+
return new ProbeRunner(new SourceFile(), [fakeProbe]);
7474
}
7575

7676
assert.throws(instantiateProbeRunner, Error, "Invalid probe");
@@ -85,7 +85,7 @@ describe("ProbeRunner", () => {
8585

8686
function instantiateProbeRunner() {
8787
// @ts-expect-error
88-
return new ProbeRunner(new SourceFile(""), [fakeProbe]);
88+
return new ProbeRunner(new SourceFile(), [fakeProbe]);
8989
}
9090

9191
assert.throws(instantiateProbeRunner, Error, "Invalid probe");
@@ -94,7 +94,7 @@ describe("ProbeRunner", () => {
9494

9595
describe("walk", () => {
9696
it("should pass validateNode, enter main and then teardown", () => {
97-
const sourceFile = new SourceFile("");
97+
const sourceFile = new SourceFile();
9898
const fakeProbe = {
9999
validateNode: (node: ESTree.Node) => [node.type === "Literal"],
100100
main: mock.fn(),
@@ -130,7 +130,7 @@ describe("ProbeRunner", () => {
130130
};
131131

132132
const pr = new ProbeRunner(
133-
new SourceFile(""),
133+
new SourceFile(),
134134
// @ts-expect-error
135135
[fakeProbe]
136136
);
@@ -170,7 +170,7 @@ describe("ProbeRunner", () => {
170170

171171
const probes = [fakeProbe, fakeProbeBreak, fakeProbeSkip];
172172

173-
const sourceFile = new SourceFile("");
173+
const sourceFile = new SourceFile();
174174

175175
const pr = new ProbeRunner(
176176
sourceFile,

workspaces/js-x-ray/test/probes/isArrayExpression.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ test("it should trigger analyzeLiteral method one time", (t) => {
1010
const str = "['foo']";
1111

1212
const ast = parseScript(str);
13-
const sastAnalysis = getSastAnalysis(str, isArrayExpression);
13+
const sastAnalysis = getSastAnalysis(isArrayExpression);
1414

1515
const analyzeLiteralMock = t.mock.method(sastAnalysis.sourceFile, "analyzeLiteral");
1616
sastAnalysis.execute(ast.body);
@@ -28,7 +28,7 @@ test("it should trigger analyzeLiteral method two times (ignoring the holey betw
2828
const str = "[5, ,10]";
2929

3030
const ast = parseScript(str);
31-
const sastAnalysis = getSastAnalysis(str, isArrayExpression);
31+
const sastAnalysis = getSastAnalysis(isArrayExpression);
3232

3333
const analyzeLiteralMock = t.mock.method(sastAnalysis.sourceFile, "analyzeLiteral");
3434
sastAnalysis.execute(ast.body);
@@ -43,7 +43,7 @@ test("it should trigger analyzeLiteral one time (ignoring non-literal Node)", (t
4343
const str = "[5, () => void 0]";
4444

4545
const ast = parseScript(str);
46-
const sastAnalysis = getSastAnalysis(str, isArrayExpression);
46+
const sastAnalysis = getSastAnalysis(isArrayExpression);
4747

4848
const analyzeLiteralMock = t.mock.method(sastAnalysis.sourceFile, "analyzeLiteral");
4949
sastAnalysis.execute(ast.body);

workspaces/js-x-ray/test/probes/isBinaryExpression.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import isBinaryExpression from "../../src/probes/isBinaryExpression.js";
99
test("should detect 1 deep binary expression", () => {
1010
const str = "0x1*-0x12df+-0x1fb9*-0x1+0x2*-0x66d";
1111
const ast = parseScript(str);
12-
const { sourceFile } = getSastAnalysis(str, isBinaryExpression)
12+
const { sourceFile } = getSastAnalysis(isBinaryExpression)
1313
.execute(ast.body);
1414

1515
assert.equal(sourceFile.deobfuscator.deepBinaryExpression, 1);
@@ -18,7 +18,7 @@ test("should detect 1 deep binary expression", () => {
1818
test("should not detect deep binary expression", () => {
1919
const str = "10 + 5 - (10)";
2020
const ast = parseScript(str);
21-
const { sourceFile } = getSastAnalysis(str, isBinaryExpression)
21+
const { sourceFile } = getSastAnalysis(isBinaryExpression)
2222
.execute(ast.body);
2323

2424
assert.equal(sourceFile.deobfuscator.deepBinaryExpression, 0);

workspaces/js-x-ray/test/probes/isImportDeclaration.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import isImportDeclaration from "../../src/probes/isImportDeclaration.js";
99
test("should detect 1 dependency for an ImportNamespaceSpecifier", () => {
1010
const str = "import * as foo from \"bar\"";
1111
const ast = parseScript(str);
12-
const { sourceFile } = getSastAnalysis(str, isImportDeclaration)
12+
const { sourceFile } = getSastAnalysis(isImportDeclaration)
1313
.execute(ast.body);
1414

1515
const { dependencies } = sourceFile;
@@ -19,7 +19,7 @@ test("should detect 1 dependency for an ImportNamespaceSpecifier", () => {
1919
test("should detect 1 dependency for an ImportDefaultSpecifier", () => {
2020
const str = "import foo from \"bar\"";
2121
const ast = parseScript(str);
22-
const { sourceFile } = getSastAnalysis(str, isImportDeclaration)
22+
const { sourceFile } = getSastAnalysis(isImportDeclaration)
2323
.execute(ast.body);
2424

2525
const { dependencies } = sourceFile;
@@ -29,7 +29,7 @@ test("should detect 1 dependency for an ImportDefaultSpecifier", () => {
2929
test("should detect 1 dependency for an ImportSpecifier", () => {
3030
const str = "import { xd } from \"bar\"";
3131
const ast = parseScript(str);
32-
const { sourceFile } = getSastAnalysis(str, isImportDeclaration)
32+
const { sourceFile } = getSastAnalysis(isImportDeclaration)
3333
.execute(ast.body);
3434

3535
const { dependencies } = sourceFile;
@@ -39,7 +39,7 @@ test("should detect 1 dependency for an ImportSpecifier", () => {
3939
test("should detect 1 dependency with no specificiers", () => {
4040
const str = "import \"bar\"";
4141
const ast = parseScript(str);
42-
const { sourceFile } = getSastAnalysis(str, isImportDeclaration)
42+
const { sourceFile } = getSastAnalysis(isImportDeclaration)
4343
.execute(ast.body);
4444

4545
const { dependencies } = sourceFile;
@@ -49,7 +49,7 @@ test("should detect 1 dependency with no specificiers", () => {
4949
test("should detect 1 dependency for an ImportExpression", () => {
5050
const str = "import(\"bar\")";
5151
const ast = parseScript(str);
52-
const { sourceFile } = getSastAnalysis(str, isImportDeclaration)
52+
const { sourceFile } = getSastAnalysis(isImportDeclaration)
5353
.execute(ast.body);
5454

5555
const { dependencies } = sourceFile;
@@ -66,7 +66,7 @@ test("should detect an unsafe import using data:text/javascript and throw a unsa
6666

6767
importNodes.forEach((str) => {
6868
const ast = parseScript(str);
69-
const sastAnalysis = getSastAnalysis(str, isImportDeclaration)
69+
const sastAnalysis = getSastAnalysis(isImportDeclaration)
7070
.execute(ast.body);
7171

7272
assert.strictEqual(sastAnalysis.warnings().length, 1);

workspaces/js-x-ray/test/probes/isLiteral.spec.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ test("should throw an unsafe-import because the hexadecimal string is equal to t
1010
const str = "const foo = '68747470'";
1111
const ast = parseScript(str);
1212

13-
const sastAnalysis = getSastAnalysis(str, isLiteral);
13+
const sastAnalysis = getSastAnalysis(isLiteral);
1414
const analyzeStringMock = t.mock.method(sastAnalysis.sourceFile.deobfuscator, "analyzeString");
1515
sastAnalysis.execute(ast.body);
1616

@@ -28,7 +28,7 @@ test("should throw an encoded-literal warning because the hexadecimal value is e
2828
const str = "const _t = globalThis['72657175697265']";
2929
const ast = parseScript(str);
3030

31-
const sastAnalysis = getSastAnalysis(str, isLiteral);
31+
const sastAnalysis = getSastAnalysis(isLiteral);
3232
const analyzeStringMock = t.mock.method(sastAnalysis.sourceFile.deobfuscator, "analyzeString");
3333
sastAnalysis.execute(ast.body);
3434

@@ -44,7 +44,7 @@ test("should throw an encoded-literal warning because the hexadecimal value is e
4444
test("should not throw an encoded-literal warning because hexadecimal value is safe", () => {
4545
const str = "const foo = '123456789'";
4646
const ast = parseScript(str);
47-
const sastAnalysis = getSastAnalysis(str, isLiteral)
47+
const sastAnalysis = getSastAnalysis(isLiteral)
4848
.execute(ast.body);
4949

5050
assert.strictEqual(sastAnalysis.warnings().length, 0);
@@ -54,7 +54,7 @@ test("should throw an encoded-literal warning because hexadecimal value is not s
5454
// Note: hexadecimal equal 'hello world'
5555
const str = "const foo = '68656c6c6f20776f726c64'";
5656
const ast = parseScript(str);
57-
const sastAnalysis = getSastAnalysis(str, isLiteral)
57+
const sastAnalysis = getSastAnalysis(isLiteral)
5858
.execute(ast.body);
5959

6060
assert.strictEqual(sastAnalysis.warnings().length, 1);
@@ -66,7 +66,7 @@ test("should not throw any warnings without hexadecimal value (and should call a
6666
const str = "const foo = 'hello world!'";
6767
const ast = parseScript(str);
6868

69-
const sastAnalysis = getSastAnalysis(str, isLiteral);
69+
const sastAnalysis = getSastAnalysis(isLiteral);
7070
const analyzeLiteralMock = t.mock.method(sastAnalysis.sourceFile, "analyzeLiteral");
7171
sastAnalysis.execute(ast.body);
7272

@@ -81,7 +81,7 @@ test("should not throw any warnings without hexadecimal value (and should call a
8181
test("should detect shady link when an URL is bit.ly", () => {
8282
const str = "const foo = 'http://bit.ly/foo'";
8383
const ast = parseScript(str);
84-
const sastAnalysis = getSastAnalysis(str, isLiteral).execute(ast.body);
84+
const sastAnalysis = getSastAnalysis(isLiteral).execute(ast.body);
8585

8686
assert.strictEqual(sastAnalysis.warnings().length, 1);
8787
const warning = sastAnalysis.getWarning("shady-link");
@@ -91,7 +91,7 @@ test("should detect shady link when an URL is bit.ly", () => {
9191
test("should detect shady link when an URL is ipinfo.io when protocol is http", () => {
9292
const str = "const foo = 'http://ipinfo.io/json'";
9393
const ast = parseScript(str);
94-
const sastAnalysis = getSastAnalysis(str, isLiteral).execute(ast.body);
94+
const sastAnalysis = getSastAnalysis(isLiteral).execute(ast.body);
9595
assert.strictEqual(sastAnalysis.warnings().length, 1);
9696
const warning = sastAnalysis.getWarning("shady-link");
9797
assert.strictEqual(warning.value, "http://ipinfo.io/json");
@@ -100,7 +100,7 @@ test("should detect shady link when an URL is ipinfo.io when protocol is http",
100100
test("should detect shady link when an URL is ipinfo.io when protocol is https", () => {
101101
const str = "const foo = 'https://ipinfo.io/json'";
102102
const ast = parseScript(str);
103-
const sastAnalysis = getSastAnalysis(str, isLiteral).execute(ast.body);
103+
const sastAnalysis = getSastAnalysis(isLiteral).execute(ast.body);
104104
assert.strictEqual(sastAnalysis.warnings().length, 1);
105105
const warning = sastAnalysis.getWarning("shady-link");
106106
assert.strictEqual(warning.value, "https://ipinfo.io/json");
@@ -109,7 +109,7 @@ test("should detect shady link when an URL is ipinfo.io when protocol is https",
109109
test("should detect shady link when an URL is httpbin.org when protocol is http", () => {
110110
const str = "const foo = 'http://httpbin.org/ip'";
111111
const ast = parseScript(str);
112-
const sastAnalysis = getSastAnalysis(str, isLiteral).execute(ast.body);
112+
const sastAnalysis = getSastAnalysis(isLiteral).execute(ast.body);
113113
assert.strictEqual(sastAnalysis.warnings().length, 1);
114114
const warning = sastAnalysis.getWarning("shady-link");
115115
assert.strictEqual(warning.value, "http://httpbin.org/ip");
@@ -118,7 +118,7 @@ test("should detect shady link when an URL is httpbin.org when protocol is http"
118118
test("should detect shady link when an URL is httpbin.org when protocol is https", () => {
119119
const str = "const foo = 'https://httpbin.org/ip'";
120120
const ast = parseScript(str);
121-
const sastAnalysis = getSastAnalysis(str, isLiteral).execute(ast.body);
121+
const sastAnalysis = getSastAnalysis(isLiteral).execute(ast.body);
122122
assert.strictEqual(sastAnalysis.warnings().length, 1);
123123
const warning = sastAnalysis.getWarning("shady-link");
124124
assert.strictEqual(warning.value, "https://httpbin.org/ip");
@@ -127,7 +127,7 @@ test("should detect shady link when an URL is httpbin.org when protocol is https
127127
test("should detect shady link when an URL has a suspicious domain", () => {
128128
const str = "const foo = 'http://foobar.link'";
129129
const ast = parseScript(str);
130-
const sastAnalysis = getSastAnalysis(str, isLiteral).execute(ast.body);
130+
const sastAnalysis = getSastAnalysis(isLiteral).execute(ast.body);
131131

132132
assert.strictEqual(sastAnalysis.warnings().length, 1);
133133
const warning = sastAnalysis.getWarning("shady-link");
@@ -137,23 +137,23 @@ test("should detect shady link when an URL has a suspicious domain", () => {
137137
test("should not mark suspicious links the IPv4 address range 127.0.0.0/8 (localhost 127.0.0.1)", () => {
138138
const str = "const IPv4URL = ['http://127.0.0.1/script', 'http://127.7.7.7/script']";
139139
const ast = parseScript(str);
140-
const sastAnalysis = getSastAnalysis(str, isLiteral).execute(ast.body);
140+
const sastAnalysis = getSastAnalysis(isLiteral).execute(ast.body);
141141

142142
assert.ok(!sastAnalysis.warnings().length);
143143
});
144144

145145
test("should not be considered suspicious a link with a raw IPv4 address 127.0.0.1 and a port", () => {
146146
const str = "const IPv4URL = 'http://127.0.0.1:80/script'";
147147
const ast = parseScript(str);
148-
const sastAnalysis = getSastAnalysis(str, isLiteral).execute(ast.body);
148+
const sastAnalysis = getSastAnalysis(isLiteral).execute(ast.body);
149149

150150
assert.ok(!sastAnalysis.warnings().length);
151151
});
152152

153153
test("should detect the link as suspicious when a URL contains a raw IPv4 address", () => {
154154
const str = "const IPv4URL = 'http://77.244.210.247/burpcollaborator.txt'";
155155
const ast = parseScript(str);
156-
const sastAnalysis = getSastAnalysis(str, isLiteral).execute(ast.body);
156+
const sastAnalysis = getSastAnalysis(isLiteral).execute(ast.body);
157157

158158
assert.strictEqual(sastAnalysis.warnings().length, 1);
159159
const warning = sastAnalysis.getWarning("shady-link");
@@ -163,7 +163,7 @@ test("should detect the link as suspicious when a URL contains a raw IPv4 addres
163163
test("should detect suspicious links when a URL contains a raw IPv4 address with port", () => {
164164
const str = "const IPv4URL = 'http://77.244.210.247:8080/script'";
165165
const ast = parseScript(str);
166-
const sastAnalysis = getSastAnalysis(str, isLiteral).execute(ast.body);
166+
const sastAnalysis = getSastAnalysis(isLiteral).execute(ast.body);
167167

168168
assert.strictEqual(sastAnalysis.warnings().length, 1);
169169
const warning = sastAnalysis.getWarning("shady-link");
@@ -173,7 +173,7 @@ test("should detect suspicious links when a URL contains a raw IPv4 address with
173173
test("should detect suspicious links when a URL contains a raw IPv6 address", () => {
174174
const str = "const IPv6URL = 'http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]/index.html'";
175175
const ast = parseScript(str);
176-
const sastAnalysis = getSastAnalysis(str, isLiteral).execute(ast.body);
176+
const sastAnalysis = getSastAnalysis(isLiteral).execute(ast.body);
177177

178178
assert.strictEqual(sastAnalysis.warnings().length, 1);
179179
const warning = sastAnalysis.getWarning("shady-link");
@@ -183,7 +183,7 @@ test("should detect suspicious links when a URL contains a raw IPv6 address", ()
183183
test("should detect suspicious links when a URL contains a raw IPv6 address with port", () => {
184184
const str = "const IPv6URL = 'http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:100/script'";
185185
const ast = parseScript(str);
186-
const sastAnalysis = getSastAnalysis(str, isLiteral).execute(ast.body);
186+
const sastAnalysis = getSastAnalysis(isLiteral).execute(ast.body);
187187

188188
assert.strictEqual(sastAnalysis.warnings().length, 1);
189189
const warning = sastAnalysis.getWarning("shady-link");

workspaces/js-x-ray/test/probes/isLiteralRegex.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import isLiteralRegex from "../../src/probes/isLiteralRegex.js";
99
test("should throw a 'unsafe-regex' warning because the given RegExp Literal is unsafe", () => {
1010
const str = "const foo = /(a+){10}/g;";
1111
const ast = parseScript(str);
12-
const sastAnalysis = getSastAnalysis(str, isLiteralRegex)
12+
const sastAnalysis = getSastAnalysis(isLiteralRegex)
1313
.execute(ast.body);
1414

1515
assert.strictEqual(sastAnalysis.warnings().length, 1);

0 commit comments

Comments
 (0)