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

refactor: remove ASTDeps class and rename Anaysis to SourceFile #187

Merged
merged 1 commit into from
Dec 18, 2023
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
15 changes: 6 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,22 +68,19 @@ require(Buffer.from("6673", "hex").toString());
Then use `js-x-ray` to run an analysis of the JavaScript code:
```js
import { runASTAnalysis } from "@nodesecure/js-x-ray";
import { readFileSync } from "fs";
import { readFileSync } from "node:fs";

const str = readFileSync("./file.js", "utf-8");
const { warnings, dependencies } = runASTAnalysis(str);
const { warnings, dependencies } = runASTAnalysis(
readFileSync("./file.js", "utf-8")
);

const dependenciesName = [...dependencies];
const inTryDeps = [...dependencies.getDependenciesInTryStatement()];

console.log(dependenciesName);
console.log(inTryDeps);
console.log(dependencies);
console.dir(warnings, { depth: null });
```

The analysis will return: `http` (in try), `crypto`, `util` and `fs`.

> [!NOTE]
> [!TIP]
> There is also a lot of suspicious code example in the `./examples` cases directory. Feel free to try the tool on these files.

## Warnings
Expand Down
7 changes: 4 additions & 3 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import {
Report,
ReportOnFile,
RuntimeFileOptions,
RuntimeOptions
RuntimeOptions,
SourceLocation,
Dependency
} from "./types/api.js";
import {
Warning,
Expand All @@ -13,7 +15,6 @@ import {
WarningName,
WarningNameWithValue
} from "./types/warnings.js";
import { ASTDeps, Dependency } from "./types/astdeps.js";

declare const warnings: Record<WarningName, Pick<WarningDefault, "experimental" | "i18n" | "severity">>;

Expand All @@ -25,7 +26,7 @@ export {
ReportOnFile,
RuntimeFileOptions,
RuntimeOptions,
ASTDeps,
SourceLocation,
Dependency,
Warning,
WarningDefault,
Expand Down
27 changes: 15 additions & 12 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as meriyah from "meriyah";
import isMinified from "is-minified-code";

// Import Internal Dependencies
import Analysis from "./src/Analysis.js";
import { SourceFile } from "./src/SourceFile.js";
import { warnings } from "./src/warnings.js";
import * as utils from "./src/utils.js";

Expand All @@ -20,7 +20,10 @@ const kMeriyahDefaultOptions = {
jsx: true
};

export function runASTAnalysis(str, options = Object.create(null)) {
export function runASTAnalysis(
str,
options = Object.create(null)
) {
const {
module = true,
isMinified = false,
Expand All @@ -35,8 +38,7 @@ export function runASTAnalysis(str, options = Object.create(null)) {
removeHTMLComments
});

const sastAnalysis = new Analysis();
sastAnalysis.analyzeSourceString(str);
const source = new SourceFile(str);

// we walk each AST Nodes, this is a purely synchronous I/O
walk(body, {
Expand All @@ -46,23 +48,24 @@ export function runASTAnalysis(str, options = Object.create(null)) {
return;
}

const action = sastAnalysis.walk(node);
const action = source.walk(node);
if (action === "skip") {
this.skip();
}
}
});

const dependencies = sastAnalysis.dependencies;
const { idsLengthAvg, stringScore, warnings } = sastAnalysis.getResult(isMinified);
const isOneLineRequire = body.length <= 1 && dependencies.size <= 1;

return {
dependencies, warnings, idsLengthAvg, stringScore, isOneLineRequire
...source.getResult(isMinified),
dependencies: source.dependencies,
isOneLineRequire: body.length <= 1 && source.dependencies.size <= 1
};
}

export async function runASTAnalysisOnFile(pathToFile, options = {}) {
export async function runASTAnalysisOnFile(
pathToFile,
options = {}
) {
try {
const {
packageName = null,
Expand All @@ -80,7 +83,7 @@ export async function runASTAnalysisOnFile(pathToFile, options = {}) {
removeHTMLComments
});
if (packageName !== null) {
data.dependencies.removeByName(packageName);
data.dependencies.delete(packageName);
}

return {
Expand Down
63 changes: 0 additions & 63 deletions src/ASTDeps.js

This file was deleted.

36 changes: 24 additions & 12 deletions src/Analysis.js → src/SourceFile.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { VariableTracer } from "@nodesecure/estree-ast-utils";
// Import Internal Dependencies
import { rootLocation, toArrayLocation } from "./utils.js";
import { generateWarning } from "./warnings.js";
import ASTDeps from "./ASTDeps.js";
import { isObfuscatedCode, hasTrojanSource } from "./obfuscators/index.js";
import { runOnProbes } from "./probes/index.js";

Expand All @@ -18,7 +17,8 @@ const kDictionaryStrParts = [

const kMaximumEncodedLiterals = 10;

export default class Analysis {
export class SourceFile {
inTryStatement = false;
hasDictionaryString = false;
hasPrefixedIdentifiers = false;
varkinds = { var: 0, let: 0, const: 0 };
Expand All @@ -34,17 +34,35 @@ export default class Analysis {
};
identifiersName = [];

constructor() {
constructor(sourceCodeString) {
this.tracer = new VariableTracer()
.enableDefaultTracing()
.trace("crypto.createHash", {
followConsecutiveAssignment: true, moduleName: "crypto"
});

this.dependencies = new ASTDeps();
this.dependencies = new Map();
this.encodedLiterals = new Map();
this.warnings = [];
this.literalScores = [];

if (hasTrojanSource(sourceCodeString)) {
this.addWarning("obfuscated-code", "trojan-source");
}
}

addDependency(name, location = null, unsafe = false) {
if (typeof name !== "string" || name.trim() === "") {
return;
}

const dependencyName = name.charAt(name.length - 1) === "/" ?
name.slice(0, -1) : name;
this.dependencies.set(dependencyName, {
unsafe,
inTry: this.inTryStatement,
...(location === null ? {} : { location })
});
}

addWarning(name, value, location = rootLocation()) {
Expand All @@ -68,12 +86,6 @@ export default class Analysis {
}
}

analyzeSourceString(sourceString) {
if (hasTrojanSource(sourceString)) {
this.addWarning("obfuscated-code", "trojan-source");
}
}

analyzeString(str) {
const score = Utils.stringSuspicionScore(str);
if (score !== 0) {
Expand Down Expand Up @@ -143,10 +155,10 @@ export default class Analysis {

// Detect TryStatement and CatchClause to known which dependency is required in a Try {} clause
if (node.type === "TryStatement" && typeof node.handler !== "undefined") {
this.dependencies.isInTryStmt = true;
this.inTryStatement = true;
}
else if (node.type === "CatchClause") {
this.dependencies.isInTryStmt = false;
this.inTryStatement = false;
}

return runOnProbes(node, this);
Expand Down
2 changes: 1 addition & 1 deletion src/probes/isImportDeclaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function main(node, options) {
if (node.source.value.startsWith("data:text/javascript")) {
analysis.addWarning("unsafe-import", node.source.value, node.loc);
}
analysis.dependencies.add(node.source.value, node.loc);
analysis.addDependency(node.source.value, node.loc);
}

export default {
Expand Down
2 changes: 1 addition & 1 deletion src/probes/isLiteral.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function main(node, options) {
// If the value we are retrieving is the name of a Node.js dependency,
// then we add it to the dependencies list and we throw an unsafe-import at the current location.
if (kNodeDeps.has(value)) {
analysis.dependencies.add(value, node.loc);
analysis.addDependency(value, node.loc);
analysis.addWarning("unsafe-import", null, node.loc);
}
else if (value === "require" || !Hex.isSafe(node.value)) {
Expand Down
10 changes: 5 additions & 5 deletions src/probes/isRequire.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function main(node, options) {
// const foo = "http"; require(foo);
case "Identifier":
if (analysis.tracer.literalIdentifiers.has(arg.name)) {
analysis.dependencies.add(
analysis.addDependency(
analysis.tracer.literalIdentifiers.get(arg.name),
node.loc
);
Expand All @@ -50,7 +50,7 @@ function main(node, options) {

// require("http")
case "Literal":
analysis.dependencies.add(arg.value, node.loc);
analysis.addDependency(arg.value, node.loc);
break;

// require(["ht", "tp"])
Expand All @@ -63,7 +63,7 @@ function main(node, options) {
analysis.addWarning("unsafe-import", null, node.loc);
}
else {
analysis.dependencies.add(value, node.loc);
analysis.addDependency(value, node.loc);
}
break;
}
Expand All @@ -80,7 +80,7 @@ function main(node, options) {
tracer, stopOnUnsupportedNode: true
});

analysis.dependencies.add([...iter].join(""), node.loc);
analysis.addDependency([...iter].join(""), node.loc);
}
catch {
analysis.addWarning("unsafe-import", null, node.loc);
Expand All @@ -91,7 +91,7 @@ function main(node, options) {
// require(Buffer.from("...", "hex").toString());
case "CallExpression": {
walkRequireCallExpression(arg, tracer)
.forEach((depName) => analysis.dependencies.add(depName, node.loc, true));
.forEach((depName) => analysis.addDependency(depName, node.loc, true));

analysis.addWarning("unsafe-import", null, node.loc);

Expand Down
Loading
Loading