Skip to content

Commit 29ec19e

Browse files
authored
Merge pull request #713 from AikidoSec/warn-if-bundled
Print warning if lib is bundled
2 parents 70743fa + 40cb6d7 commit 29ec19e

File tree

6 files changed

+90
-0
lines changed

6 files changed

+90
-0
lines changed

docs/bundler.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Installing Zen in a Node.js Application that uses a bundler
2+
3+
⚠️ Note: Zen runs only on the server side, it does not run in the browser.
4+
5+
Zen might not work out of the box with bundlers, depending on how they are configured.
6+
To ensure that Zen can properly instrument your code and protect your application, you may need to adjust your bundler settings.
7+
8+
In order to be compatible with Zen, your bundler needs to be configured to exclude all external packages from the bundle.
9+
You can also choose to only exclude Zen and all the packages that should be protected.
10+
In this case, your production environment still needs the `node_modules` folder.
11+
12+
If you are using esbuild, you can find [more information here](./esbuild.md).
13+
14+
If it is not possible to exclude all packages from bundling, Zen provides the following helper function to get a list of all the packages that need to be excluded:
15+
16+
```javascript
17+
const { externals } = require("@aikidosec/firewall/bundler");
18+
19+
externals(); // Returns an array of package names
20+
```

end2end/tests/express-postgres.test.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ t.before(() => {
1515
if (stderr && stderr.toString().length > 0) {
1616
throw new Error(`Failed to build: ${stderr.toString()}`);
1717
}
18+
19+
const { stderr2 } = spawnSync("node", ["esbuild-wrong.js"], {
20+
cwd: directory,
21+
});
22+
23+
if (stderr2 && stderr2.toString().length > 0) {
24+
throw new Error(`Failed to build: ${stderr2.toString()}`);
25+
}
1826
});
1927

2028
entrypoints.forEach((entrypoint) => {
@@ -77,6 +85,10 @@ entrypoints.forEach((entrypoint) => {
7785
t.equal(normalSearch.status, 200);
7886
t.match(stdout, /Starting agent/);
7987
t.match(stderr, /Zen has blocked an SQL injection/);
88+
t.notMatch(
89+
stderr,
90+
/Your application seems to be using a bundler without externalizing Zen/
91+
);
8092
}
8193
)
8294
.catch((error) => {
@@ -229,3 +241,34 @@ t.test("it blocks in blocking mode (with dd-trace)", (t) => {
229241
server.kill();
230242
});
231243
});
244+
245+
t.test("it prints warning before crashing if bundled", (t) => {
246+
const server = spawn(`node`, ["compiled-bundled.js", "4003"], {
247+
env: { ...process.env, AIKIDO_DEBUG: "true", AIKIDO_BLOCKING: "true" },
248+
cwd: directory,
249+
});
250+
251+
server.on("close", () => {
252+
t.match(
253+
stderr,
254+
/Your application seems to be using a bundler without externalizing Zen/
255+
);
256+
t.match(stderr, /ENOENT: no such file or directory/); // Can't load wasm
257+
258+
t.end();
259+
});
260+
261+
server.on("error", (err) => {
262+
t.fail(err.message);
263+
});
264+
265+
let stdout = "";
266+
server.stdout.on("data", (data) => {
267+
stdout += data.toString();
268+
});
269+
270+
let stderr = "";
271+
server.stderr.on("data", (data) => {
272+
stderr += data.toString();
273+
});
274+
});

library/helpers/isLibBundled.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Detect at runtime if the library is bundled inside an application
2+
export function isLibBundled(): boolean {
3+
// Replace Windows backslashes with forward slashes
4+
const normalizedDirName = __dirname.replace(/\\/g, "/");
5+
6+
return (
7+
!normalizedDirName.includes("node_modules/@aikidosec/firewall/helpers") &&
8+
!normalizedDirName.includes("firewall-node/build/helpers") // In case of e2e tests
9+
);
10+
}

library/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { addRestifyMiddleware } from "./middleware/restify";
1313
import { isESM } from "./helpers/isESM";
1414
import { checkIndexImportGuard } from "./helpers/indexImportGuard";
1515
import { setRateLimitGroup } from "./ratelimiting/group";
16+
import { isLibBundled } from "./helpers/isLibBundled";
1617

1718
const supported = isFirewallSupported();
1819
const shouldEnable = shouldEnableFirewall();
@@ -25,6 +26,12 @@ if (supported && shouldEnable && notAlreadyImported) {
2526
);
2627
}
2728

29+
if (isLibBundled()) {
30+
console.warn(
31+
"AIKIDO: Your application seems to be using a bundler without externalizing Zen and the packages that should be protected. Zen will not function as intended. See https://github.com/AikidoSec/firewall-node/blob/main/docs/bundler.md for more information."
32+
);
33+
}
34+
2835
require("./agent/protect").protect();
2936
}
3037

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
/compiled.js
2+
/compiled-bundled.js
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const { build } = require("esbuild");
2+
3+
build({
4+
entryPoints: ["./app.js"],
5+
bundle: true,
6+
platform: "node",
7+
target: "node18",
8+
outfile: "./compiled-bundled.js",
9+
});

0 commit comments

Comments
 (0)