Skip to content

Commit 7999b60

Browse files
committed
Merge branch 'main' into update-firewall-tester-action
2 parents c4f9e74 + 7c6da5a commit 7999b60

49 files changed

Lines changed: 1960 additions & 454 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/alpine-test.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ jobs:
1616
node-version: [16, 18, 20, 22, 24, 25, 26]
1717
steps:
1818
- name: Checkout repository
19-
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
19+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
20+
with:
21+
persist-credentials: false
2022

2123
- name: Download build artifacts
2224
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1

.github/workflows/benchmark.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ jobs:
3333

3434
steps:
3535
- name: Checkout repository
36-
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
36+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
37+
with:
38+
persist-credentials: false
3739

3840
- name: Use Node.js ${{ matrix.node-version }}
3941
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0

.github/workflows/build-and-release.yml

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,18 @@ jobs:
3434
id-token: write
3535
timeout-minutes: 15
3636
steps:
37-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
38-
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
37+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
38+
with:
39+
persist-credentials: false
40+
41+
- name: Use Node.js 24.x
42+
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
3943
with:
4044
node-version: "24.x"
4145
registry-url: "https://registry.npmjs.org"
4246
scope: "@aikidosec"
47+
package-manager-cache: false
48+
4349
- name: Set up Rust
4450
run: |
4551
rustup toolchain install stable
@@ -56,17 +62,24 @@ jobs:
5662
id: get_version
5763
run: echo "tag=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
5864
- name: Set the version
59-
run: cd library && npm --no-git-tag-version version ${{ steps.get_version.outputs.tag }}
65+
run: cd library && npm --no-git-tag-version version ${GIT_TAG}
66+
env:
67+
GIT_TAG: ${{ steps.get_version.outputs.tag }}
6068
- name: Build the library
6169
run: npm run build
6270
- name: Linting
6371
run: npm run lint
72+
- name: Upgrade npm for staged publishing
73+
# npm stage publish requires npm 11.15.0+
74+
run: npm i -g npm@11.15.0
6475
- name: Publish to NPM
6576
run: |
6677
if [ "${{ github.event.release.prerelease }}" = "true" ]; then
67-
echo "Publishing beta version ${{ steps.get_version.outputs.tag }} to NPM"
68-
cd build && npm publish --provenance --access public --tag beta
78+
echo "Publishing beta version ${GIT_TAG} to NPM"
79+
cd build && npm stage publish --provenance --access public --tag beta
6980
else
70-
echo "Publishing version ${{ steps.get_version.outputs.tag }} to NPM"
71-
cd build && npm publish --provenance --access public
81+
echo "Publishing version ${GIT_TAG} to NPM"
82+
cd build && npm stage publish --provenance --access public
7283
fi
84+
env:
85+
GIT_TAG: ${{ steps.get_version.outputs.tag }}

.github/workflows/build.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ jobs:
1515
timeout-minutes: 15
1616
steps:
1717
- name: Checkout repository
18-
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
18+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
19+
with:
20+
persist-credentials: false
1921

2022
- name: Use Node.js ${{ env.node_version }}
2123

.github/workflows/end-to-end-tests.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ jobs:
2929

3030
steps:
3131
- name: Checkout repository
32-
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
32+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
33+
with:
34+
persist-credentials: false
3335

3436
- name: Use Node.js ${{ matrix.node-version }}
3537
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0

.github/workflows/lint-code.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ jobs:
1515
node-version: [24.x]
1616
steps:
1717
- name: Checkout repository
18-
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
18+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
19+
with:
20+
persist-credentials: false
1921

2022
- name: Use Node.js ${{ matrix.node-version }}
2123
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0

.github/workflows/qa-tests.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,18 @@ jobs:
1212
timeout-minutes: 30
1313
steps:
1414
- name: Checkout firewall-node
15-
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
15+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
1616
with:
1717
path: firewall-node
18+
persist-credentials: false
1819

1920
- name: Checkout zen-demo-nodejs
20-
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
21+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
2122
with:
2223
repository: Aikido-demo-apps/zen-demo-nodejs
2324
path: zen-demo-nodejs
2425
submodules: true
26+
persist-credentials: false
2527

2628
- name: Use Node.js 24.x
2729
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0

.github/workflows/unit-test.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ jobs:
5858

5959
steps:
6060
- name: Checkout repository
61-
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
61+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
62+
with:
63+
persist-credentials: false
6264

6365
- name: Use Node.js ${{ matrix.node-version }}
6466
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
@@ -115,7 +117,7 @@ jobs:
115117
command: npm run test:esm
116118

117119
- name: Upload coverage
118-
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
120+
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
119121
with:
120122
files: ./library/.tap/report/lcov.info,./.esm-tests/tests/lcov.info
121123
use_oidc: true

end2end/tests/micro.test.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
const t = require("tap");
2+
const { spawn } = require("child_process");
3+
const { resolve, join } = require("path");
4+
const timeout = require("../timeout");
5+
6+
const appDir = resolve(__dirname, "../../sample-apps/micro");
7+
const testServerUrl = "http://localhost:5874";
8+
9+
let token;
10+
t.beforeEach(async () => {
11+
const response = await fetch(`${testServerUrl}/api/runtime/apps`, {
12+
method: "POST",
13+
});
14+
const body = await response.json();
15+
token = body.token;
16+
17+
// Apply rate limiting
18+
const updateConfigResponse = await fetch(
19+
`${testServerUrl}/api/runtime/config`,
20+
{
21+
method: "POST",
22+
headers: {
23+
"Content-Type": "application/json",
24+
Authorization: token,
25+
},
26+
body: JSON.stringify({
27+
endpoints: [
28+
{
29+
route: "/",
30+
method: "GET",
31+
forceProtectionOff: false,
32+
rateLimiting: {
33+
enabled: true,
34+
maxRequests: 3,
35+
windowSizeInMS: 60 * 1000,
36+
},
37+
},
38+
],
39+
}),
40+
}
41+
);
42+
t.same(updateConfigResponse.status, 200);
43+
});
44+
45+
t.test("it rate limits requests", (t) => {
46+
const server = spawn(
47+
`node`,
48+
[
49+
"--preserve-symlinks", // isMicroInstalled will not work otherwise
50+
"-r",
51+
"@aikidosec/firewall",
52+
join(appDir, "node_modules/.bin/micro"),
53+
"-l",
54+
"tcp://127.0.0.1:4000",
55+
],
56+
{
57+
cwd: appDir,
58+
env: {
59+
...process.env,
60+
AIKIDO_DEBUG: "true",
61+
AIKIDO_BLOCK: "true",
62+
AIKIDO_TOKEN: token,
63+
AIKIDO_ENDPOINT: testServerUrl,
64+
AIKIDO_REALTIME_ENDPOINT: testServerUrl,
65+
},
66+
}
67+
);
68+
69+
server.on("close", () => {
70+
t.end();
71+
});
72+
73+
server.on("error", (err) => {
74+
t.fail(err.message);
75+
});
76+
77+
let stdout = "";
78+
server.stdout.on("data", (data) => {
79+
stdout += data.toString();
80+
});
81+
82+
let stderr = "";
83+
server.stderr.on("data", (data) => {
84+
stderr += data.toString();
85+
});
86+
87+
// Wait for the server to start
88+
timeout(2000)
89+
.then(async () => {
90+
for (let i = 0; i < 3; i++) {
91+
const resp = await fetch("http://127.0.0.1:4000/", {
92+
method: "GET",
93+
signal: AbortSignal.timeout(5000),
94+
});
95+
t.same(resp.status, 200);
96+
}
97+
98+
const respRateLimited = await fetch("http://127.0.0.1:4000/", {
99+
method: "GET",
100+
signal: AbortSignal.timeout(5000),
101+
});
102+
t.same(respRateLimited.status, 429);
103+
104+
const ssrfResponse = await fetch("http://127.0.0.1:4000/image", {
105+
method: "POST",
106+
headers: {
107+
"Content-Type": "application/json",
108+
},
109+
body: JSON.stringify({
110+
url: "http://172.17.0.1:2375/containers/json",
111+
}),
112+
signal: AbortSignal.timeout(5000),
113+
});
114+
t.same(ssrfResponse.status, 500);
115+
t.match(stdout, /Zen has blocked a server-side request forgery/);
116+
})
117+
.catch((error) => {
118+
t.fail(error.message);
119+
})
120+
.finally(() => {
121+
server.kill();
122+
});
123+
});

end2end/tests/nextjs-standalone.v14.test.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const timeout = require("../timeout");
55
const { cpSync, writeFileSync } = require("fs");
66

77
const pathToApp = resolve(__dirname, "../../sample-apps/nextjs-standalone");
8+
const testServerUrl = "http://localhost:5874";
89

910
t.setTimeout(2 * 60 * 1000);
1011

@@ -100,6 +101,7 @@ t.test("it blocks in blocking mode", (t) => {
100101
t.match(stdout, /Starting agent/);
101102
t.match(stderr, /Zen has blocked a shell injection/);
102103
t.match(stderr, /Zen has blocked an SQL injection/);
104+
t.notMatch(stderr, /Zen is NOT protecting your application/);
103105
}
104106
)
105107
.catch((error) => {
@@ -110,6 +112,85 @@ t.test("it blocks in blocking mode", (t) => {
110112
});
111113
});
112114

115+
t.test("it rate limits requests", async (t) => {
116+
const appResponse = await fetch(`${testServerUrl}/api/runtime/apps`, {
117+
method: "POST",
118+
signal: AbortSignal.timeout(5000),
119+
});
120+
const { token } = await appResponse.json();
121+
122+
const configResponse = await fetch(`${testServerUrl}/api/runtime/config`, {
123+
method: "POST",
124+
headers: {
125+
"Content-Type": "application/json",
126+
Authorization: token,
127+
},
128+
body: JSON.stringify({
129+
endpoints: [
130+
{
131+
route: "/cats",
132+
method: "GET",
133+
forceProtectionOff: false,
134+
rateLimiting: {
135+
enabled: true,
136+
maxRequests: 1,
137+
windowSizeInMS: 60 * 1000,
138+
},
139+
},
140+
],
141+
}),
142+
signal: AbortSignal.timeout(5000),
143+
});
144+
t.equal(configResponse.status, 200);
145+
146+
const server = spawn(`node`, ["-r", "@aikidosec/firewall", "server.js"], {
147+
env: {
148+
...process.env,
149+
AIKIDO_DEBUG: "true",
150+
AIKIDO_BLOCK: "true",
151+
AIKIDO_TOKEN: token,
152+
AIKIDO_ENDPOINT: testServerUrl,
153+
AIKIDO_REALTIME_ENDPOINT: testServerUrl,
154+
PORT: "4003",
155+
},
156+
cwd: join(pathToApp, ".next/standalone"),
157+
});
158+
159+
server.on("error", (err) => {
160+
t.fail(err.message);
161+
});
162+
163+
await timeout(5000);
164+
165+
try {
166+
const resp1 = await fetch("http://127.0.0.1:4003/cats", {
167+
method: "GET",
168+
signal: AbortSignal.timeout(5000),
169+
headers: {
170+
"X-Forwarded-For": "1.2.3.4",
171+
},
172+
});
173+
t.not(resp1.status, 429, "first request should not be rate limited");
174+
175+
const resp2 = await fetch("http://127.0.0.1:4003/cats", {
176+
method: "GET",
177+
signal: AbortSignal.timeout(5000),
178+
headers: {
179+
"X-Forwarded-For": "1.2.3.4",
180+
},
181+
});
182+
t.equal(resp2.status, 429, "second request should be rate limited");
183+
t.ok(
184+
resp2.headers.get("retry-after"),
185+
"Retry-After header should be present"
186+
);
187+
} catch (error) {
188+
t.fail(error.message);
189+
} finally {
190+
server.kill();
191+
}
192+
});
193+
113194
t.test("it does not block in dry mode", (t) => {
114195
const server = spawn(`node`, ["-r", "@aikidosec/firewall", "server.js"], {
115196
env: {
@@ -177,6 +258,7 @@ t.test("it does not block in dry mode", (t) => {
177258
t.match(stdout, /Starting agent/);
178259
t.notMatch(stderr, /Zen has blocked a shell injection/);
179260
t.notMatch(stderr, /Zen has blocked an SQL injection/);
261+
t.notMatch(stderr, /Zen is NOT protecting your application/);
180262
}
181263
)
182264
.catch((error) => {

0 commit comments

Comments
 (0)