Skip to content

Commit cdc5aff

Browse files
committed
Merge branch 'main' into bot-spoofing-protection
2 parents fe1cbfb + ee4617b commit cdc5aff

40 files changed

+696
-179
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ jobs:
4545
"CLICKHOUSE_DEFAULT_ACCESS": "MANAGEMENT=1"
4646
ports:
4747
- "27019:8123"
48-
timeout-minutes: 10
48+
timeout-minutes: 15
4949
strategy:
5050
matrix:
5151
node-version: [18.x]

.github/workflows/unit-test.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ jobs:
5252
ports:
5353
- "27019:8123"
5454
mongodb-replica:
55-
image: bitnami/mongodb:8.0
55+
image: bitnami/mongodb:4.4
5656
env:
5757
MONGODB_ADVERTISED_HOSTNAME: 127.0.0.1
5858
MONGODB_REPLICA_SET_MODE: primary
@@ -77,6 +77,14 @@ jobs:
7777
- name: Add local.aikido.io to /etc/hosts
7878
run: |
7979
sudo echo "127.0.0.1 local.aikido.io" | sudo tee -a /etc/hosts
80+
- name: "Install Google Cloud SDK"
81+
uses: google-github-actions/setup-gcloud@6189d56e4096ee891640bb02ac264be376592d6a # v2
82+
with:
83+
install_components: "beta,pubsub-emulator"
84+
version: "524.0.0"
85+
- name: "Start Pub/Sub emulator"
86+
run: |
87+
gcloud beta emulators pubsub start --project=sample-project --host-port='0.0.0.0:8085' &
8088
- run: npm run install-lib-only
8189
- run: npm run build
8290
- run: npm run test:ci

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ Zen for Node.js 16+ is compatible with:
6666
### Cloud providers
6767

6868
*[`@google-cloud/functions-framework`](https://www.npmjs.com/package/@google-cloud/functions-framework) 4.x, 3.x
69-
*[`@google-cloud/pubsub`](https://www.npmjs.com/package/@google-cloud/pubsub) 4.x
69+
*[`@google-cloud/pubsub`](https://www.npmjs.com/package/@google-cloud/pubsub) 5.x, 4.x
7070
* ✅ Google Cloud Functions
7171
* ✅ AWS Lambda
7272

end2end/server/app.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const checkToken = require("./src/middleware/checkToken");
88
const updateConfig = require("./src/handlers/updateConfig");
99
const lists = require("./src/handlers/lists");
1010
const updateLists = require("./src/handlers/updateLists");
11+
const realtimeConfig = require("./src/handlers/realtimeConfig");
1112

1213
const app = express();
1314

@@ -18,6 +19,9 @@ app.use(express.json());
1819
app.get("/api/runtime/config", checkToken, config);
1920
app.post("/api/runtime/config", checkToken, updateConfig);
2021

22+
// Realtime polling endpoint
23+
app.get("/config", checkToken, realtimeConfig);
24+
2125
app.get("/api/runtime/events", checkToken, listEvents);
2226
app.post("/api/runtime/events", checkToken, captureEvent);
2327

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const { getAppConfig } = require("../zen/config");
2+
3+
module.exports = function realtimeConfig(req, res) {
4+
if (!req.app) {
5+
throw new Error("App is missing");
6+
}
7+
8+
const config = getAppConfig(req.app);
9+
10+
res.json({
11+
serviceId: req.app.serviceId,
12+
configUpdatedAt: config.configUpdatedAt,
13+
});
14+
};

end2end/server/src/zen/config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ function generateConfig(app) {
99
endpoints: [],
1010
blockedUserIds: [],
1111
allowedIPAddresses: [],
12-
receivedAnyStats: true,
12+
receivedAnyStats: false,
1313
};
1414
}
1515

end2end/server/src/zen/events.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
const events = new Map();
22

33
function captureEvent(event, app) {
4-
if (event.type === "heartbeat") {
5-
// Ignore heartbeats
6-
return;
7-
}
8-
94
if (!events.has(app.id)) {
105
events.set(app.id, []);
116
}

end2end/tests/express-mongodb.ssrf.test.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ t.test("it blocks in blocking mode", (t) => {
5858
AIKIDO_BLOCKING: "true",
5959
AIKIDO_TOKEN: token,
6060
AIKIDO_ENDPOINT: testServerUrl,
61+
AIKIDO_REALTIME_ENDPOINT: testServerUrl,
6162
},
6263
});
6364

@@ -156,6 +157,7 @@ t.test("it does not block in dry mode", (t) => {
156157
AIKIDO_DEBUG: "true",
157158
AIKIDO_TOKEN: token,
158159
AIKIDO_ENDPOINT: testServerUrl,
160+
AIKIDO_REALTIME_ENDPOINT: testServerUrl,
159161
},
160162
});
161163

@@ -214,6 +216,7 @@ t.test("it blocks request to base URL if proxy is not trusted", (t) => {
214216
AIKIDO_BLOCKING: "true",
215217
AIKIDO_TOKEN: token,
216218
AIKIDO_ENDPOINT: testServerUrl,
219+
AIKIDO_REALTIME_ENDPOINT: testServerUrl,
217220
AIKIDO_TRUST_PROXY: "false",
218221
},
219222
});

end2end/tests/express-mysql.test.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@ const pathToApp = resolve(
99
"app.js"
1010
);
1111

12+
const testServerUrl = "http://localhost:5874";
13+
14+
let token;
15+
t.beforeEach(async () => {
16+
const response = await fetch(`${testServerUrl}/api/runtime/apps`, {
17+
method: "POST",
18+
});
19+
const body = await response.json();
20+
token = body.token;
21+
});
22+
1223
t.test("it blocks in blocking mode", (t) => {
1324
const server = spawn(`node`, [pathToApp, "4000"], {
1425
env: { ...process.env, AIKIDO_DEBUG: "true", AIKIDO_BLOCKING: "true" },
@@ -144,3 +155,86 @@ t.test("it does not block in dry mode", (t) => {
144155
server.kill();
145156
});
146157
});
158+
159+
t.setTimeout(80000);
160+
161+
t.test(
162+
"it increments sqlTokenizationFailures counter for invalid SQL queries",
163+
(t) => {
164+
const server = spawn(`node`, [pathToApp, "4003"], {
165+
env: {
166+
...process.env,
167+
AIKIDO_DEBUG: "true",
168+
AIKIDO_BLOCKING: "true",
169+
AIKIDO_TOKEN: token,
170+
AIKIDO_ENDPOINT: testServerUrl,
171+
AIKIDO_REALTIME_ENDPOINT: testServerUrl,
172+
},
173+
});
174+
175+
server.on("close", () => {
176+
t.end();
177+
});
178+
179+
server.on("error", (err) => {
180+
t.fail(err);
181+
});
182+
183+
let stdout = "";
184+
server.stdout.on("data", (data) => {
185+
stdout += data.toString();
186+
});
187+
188+
let stderr = "";
189+
server.stderr.on("data", (data) => {
190+
stderr += data.toString();
191+
});
192+
193+
// Wait for the server to start
194+
timeout(2000)
195+
.then(() => {
196+
return fetch(
197+
`http://localhost:4003/invalid-query?sql=${encodeURIComponent("SELECT * FROM test")}`,
198+
{
199+
signal: AbortSignal.timeout(5000),
200+
method: "POST",
201+
}
202+
);
203+
})
204+
.then((response) => {
205+
return response.text();
206+
})
207+
.then((responseText) => {
208+
t.match(responseText, /You have an error in your SQL syntax/);
209+
210+
// Wait for the heartbeat event to be sent
211+
return timeout(60000);
212+
})
213+
.then(() => {
214+
return fetch(`${testServerUrl}/api/runtime/events`, {
215+
method: "GET",
216+
headers: {
217+
Authorization: token,
218+
},
219+
signal: AbortSignal.timeout(5000),
220+
});
221+
})
222+
.then((response) => {
223+
return response.json();
224+
})
225+
.then((events) => {
226+
const heartbeatEvents = events.filter(
227+
(event) => event.type === "heartbeat"
228+
);
229+
t.same(heartbeatEvents.length, 1);
230+
const [heartbeat] = heartbeatEvents;
231+
t.equal(heartbeat.stats.sqlTokenizationFailures, 1);
232+
})
233+
.catch((error) => {
234+
t.error(error);
235+
})
236+
.finally(() => {
237+
server.kill();
238+
});
239+
}
240+
);

end2end/tests/hono-xml-allowlists.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ t.test("it blocks non-allowed IP addresses", (t) => {
7878
AIKIDO_BLOCK: "true",
7979
AIKIDO_TOKEN: token,
8080
AIKIDO_ENDPOINT: testServerUrl,
81+
AIKIDO_REALTIME_ENDPOINT: testServerUrl,
8182
},
8283
});
8384

0 commit comments

Comments
 (0)