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

Add ESM support (WIP) #402

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Integrate esm logic into agent
  • Loading branch information
timokoessler committed Sep 27, 2024
commit 20444db736b8b0955a1a69c504009f0a8b08de21
38 changes: 0 additions & 38 deletions .github/workflows/build-and-release.yml

This file was deleted.

1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ install:

.PHONY: build
build:
rm -r library/build
cd library && npm run build

.PHONY: watch
Expand Down
6 changes: 4 additions & 2 deletions benchmarks/api-discovery/benchmark.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/**
* Runs benchmarks for the api discovery (api schema collection)
*/
const { Routes } = require("../../build/agent/Routes");
const { isFeatureEnabled } = require("../../build/helpers/featureFlags");
const { Routes } = require("../../library/build/agent/Routes");
const {
isFeatureEnabled,
} = require("../../library/build/helpers/featureFlags");
const reqBodies = require("./reqBodies");

const MAX_TIME_LIMIT = 0.05; // milliseconds / statement
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/api-discovery/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion benchmarks/hono-pg/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion benchmarks/nosql-injection/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions benchmarks/nosql-injection/withGuard.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
require("../../build");
require("../../library/build");

const measure = require("./measure");
const getUser = require("./getUser");
const getClient = require("./getClient");
const { runWithContext } = require("../../build/agent/Context");
const { runWithContext } = require("../../library/build/agent/Context");

async function main() {
const client = await getClient();
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/shell-injection/benchmark.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
const {
detectShellInjection,
} = require("../../build/vulnerabilities/shell-injection/detectShellInjection");
} = require("../../library/build/vulnerabilities/shell-injection/detectShellInjection");

const MAX_TIME_LIMIT = 0.05; // milliseconds / statement

Expand Down
2 changes: 1 addition & 1 deletion benchmarks/shell-injection/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions benchmarks/sql-injection/benchmark.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ const fs = require("fs");
const { join } = require("path");
const {
detectSQLInjection,
} = require("../../build/vulnerabilities/sql-injection/detectSQLInjection");
} = require("../../library/build/vulnerabilities/sql-injection/detectSQLInjection");
const {
SQLDialectMySQL,
} = require("../../build/vulnerabilities/sql-injection/dialects/SQLDialectMySQL");
} = require("../../library/build/vulnerabilities/sql-injection/dialects/SQLDialectMySQL");

const MAX_TIME_LIMIT = 0.05; // milliseconds / statement

Expand Down
2 changes: 1 addition & 1 deletion benchmarks/sql-injection/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions library/agent/Agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ export class Agent {
private readonly logger: Logger,
private readonly api: ReportingAPI,
private readonly token: Token | undefined,
private readonly serverless: string | undefined
private readonly serverless: string | undefined,
private readonly isESM: boolean | undefined = false
) {
if (typeof this.serverless === "string" && this.serverless.length === 0) {
throw new Error("Serverless cannot be an empty string");
Expand Down Expand Up @@ -415,7 +416,7 @@ export class Agent {
setInstance(this);
}

wrapInstalledPackages(wrappers);
wrapInstalledPackages(wrappers, this.isESM || false);

// Send startup event and wait for config
// Then start heartbeats and polling for config changes
Expand Down
20 changes: 16 additions & 4 deletions library/agent/applyHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
wrapRequire,
} from "./hooks/wrapRequire";
import { wrapExport } from "./hooks/wrapExport";
import {
setImportBuiltinModulesToPatch,
setImportPackagesToPatch,
wrapImport,
} from "./hooks/wrapImport";

/**
* Hooks allows you to register packages and then wrap specific methods on
Expand All @@ -13,10 +18,17 @@
* This method wraps the require function and sets up the hooks.
* Globals are wrapped directly.
*/
export function applyHooks(hooks: Hooks) {
setPackagesToPatch(hooks.getPackages());
setBuiltinModulesToPatch(hooks.getBuiltInModules());
wrapRequire();
export function applyHooks(hooks: Hooks, isESM: boolean) {
if (!isESM) {
// Todo check if we need to wrap require too in ESM mode under certain conditions

Check failure on line 23 in library/agent/applyHooks.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Unexpected 'todo' comment: 'Todo check if we need to wrap require...'
setPackagesToPatch(hooks.getPackages());
setBuiltinModulesToPatch(hooks.getBuiltInModules());
wrapRequire();
} else {
setImportPackagesToPatch(hooks.getPackages());
setImportBuiltinModulesToPatch(hooks.getBuiltInModules());
wrapImport();
}

hooks.getGlobals().forEach((g) => {
const name = g.getName();
Expand Down
2 changes: 1 addition & 1 deletion library/agent/hooks/isBuiltinModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const moduleList = mod.builtinModules;
/**
* Returns true if the module is a builtin module, otherwise false.
*/
export function isBuiltinModule(moduleName: string) {
export function isBuiltinModule(moduleName: string): boolean {
// Added in Node.js v18.6.0, v16.17.0
if (typeof mod.isBuiltin === "function") {
return mod.isBuiltin(moduleName);
Expand Down
66 changes: 66 additions & 0 deletions library/agent/hooks/wrapImport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* eslint-disable max-lines-per-function */
import { register } from "module";
import { pathToFileURL } from "url";
import { Hook } from "import-in-the-middle";
import { BuiltinModule } from "./BuiltinModule";
import { Package } from "./Package";

let isImportHookRegistered = false;

let packages: Package[] = [];
let builtinModules: BuiltinModule[] = [];
let pkgCache = new Map<string, unknown>();
let builtinCache = new Map<string, unknown>();

/**
* Intercept esm package imports.
* This function makes sure that the import function is only wrapped once.
*/
export function wrapImport() {
if (isImportHookRegistered) {
return;
}

// Prevent registering the import hook multiple times
isImportHookRegistered = true;

// Register import-in-the-middle hook
register("import-in-the-middle/hook.mjs", pathToFileURL(__filename));

const allPackageNames = [packages, builtinModules]
.flat()
.map((p) => p.getName());

new Hook(
allPackageNames,
{
internals: true,
},
(exports, name, baseDir) => {
// Todo ...

Check failure on line 40 in library/agent/hooks/wrapImport.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Unexpected 'todo' comment: 'Todo ...'
//console.log(name);
//console.log(baseDir);
//console.log(process.pid);
}
);
}

/**
* Update the list of external packages that should be patched.
*/
export function setImportPackagesToPatch(packagesToPatch: Package[]) {
packages = packagesToPatch;
// Reset cache
pkgCache = new Map();
}

/**
* Update the list of builtin modules that should be patched.
*/
export function setImportBuiltinModulesToPatch(
builtinModulesToPatch: BuiltinModule[]
) {
builtinModules = builtinModulesToPatch;
// Reset cache
builtinCache = new Map();
}
14 changes: 11 additions & 3 deletions library/agent/protect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,13 @@
: undefined;
}

function getAgent({ serverless }: { serverless: string | undefined }) {
function getAgent({
serverless,
isESM,
}: {
serverless: string | undefined;
isESM?: boolean;
}) {
const current = getInstance();

if (current) {
Expand All @@ -105,7 +111,8 @@
getLogger(),
getAPI(),
getTokenFromEnv(),
serverless
serverless,
isESM
);

setInstance(agent);
Expand Down Expand Up @@ -142,9 +149,10 @@
];
}

export function protect() {
export function protect(isESM = false) {

Check warning on line 152 in library/agent/protect.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

exported declaration 'protect' not used within other modules
const agent = getAgent({
serverless: undefined,
isESM,
});

agent.start(getWrappers());
Expand Down
4 changes: 2 additions & 2 deletions library/agent/wrapInstalledPackages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { applyHooks } from "./applyHooks";
import { Hooks } from "./hooks/Hooks";
import { Wrapper } from "./Wrapper";

export function wrapInstalledPackages(wrappers: Wrapper[]) {
export function wrapInstalledPackages(wrappers: Wrapper[], isESM: boolean) {
const hooks = new Hooks();
wrappers.forEach((wrapper) => {
wrapper.wrap(hooks);
});

return applyHooks(hooks);
return applyHooks(hooks, isESM);
}
25 changes: 0 additions & 25 deletions library/esm/hook.ts

This file was deleted.

39 changes: 24 additions & 15 deletions library/esm/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
import { Hook, createAddHookMessageChannel } from "import-in-the-middle";
import { register } from "module";
import { pathToFileURL } from "url";
import isFirewallSupported from "../helpers/isFirewallSupported";
import {
getMajorNodeVersion,
getMinorNodeVersion,
} from "../helpers/getNodeVersion";

// Todo init agent?
// Was added in v20.6.0 and v18.19.0
function isESMSupported() {
const nodeMajor = getMajorNodeVersion();
const nodeMinor = getMinorNodeVersion();
return (
nodeMajor >= 22 ||
(nodeMajor === 20 && nodeMinor >= 6) ||
(nodeMajor === 18 && nodeMinor >= 19)
);
}

const { registerOptions, waitForAllMessagesAcknowledged } =
createAddHookMessageChannel();

register(
"import-in-the-middle/hook.mjs",
pathToFileURL(__filename),
// @ts-ignore test
registerOptions
);

// Todo
if (isFirewallSupported()) {
if (isESMSupported()) {
require("../agent/protect").protect(true);
} else {
console.error(

Check failure on line 22 in library/esm/index.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Unexpected console statement
"Error: Aikido Firewall requires Node.js v20.6.0 / v18.19.0 or higher to support ESM."
);
}
}
2 changes: 1 addition & 1 deletion library/helpers/getAgentVersion.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { resolve } from "path";

export function getAgentVersion(): string {
const json = require(resolve(__dirname, "../package.json"));
const json = require(resolve(__dirname, "../../package.json"));

/* c8 ignore start */
if (!json.version) {
Expand Down
Loading
Loading