Skip to content

Commit 33c541f

Browse files
committed
Add e2e test
1 parent 80d5b98 commit 33c541f

File tree

7 files changed

+1003
-0
lines changed

7 files changed

+1003
-0
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,5 @@ jobs:
6666
- run: npm install
6767
- run: npm run build
6868
- run: npm run end2end
69+
env:
70+
GOOGLE_GENERATIVE_AI_API_KEY: ${{ secrets.GOOGLE_GENERATIVE_AI_API_KEY }}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
const t = require("tap");
2+
const { spawn } = require("child_process");
3+
const { resolve } = require("path");
4+
const timeout = require("../timeout");
5+
6+
const pathToApp = resolve(
7+
__dirname,
8+
"../../sample-apps/hono-sqlite-ai",
9+
"app.js"
10+
);
11+
12+
t.test("it blocks in blocking mode", (t) => {
13+
const server = spawn(`node`, [pathToApp, "4006"], {
14+
env: { ...process.env, AIKIDO_DEBUG: "true", AIKIDO_BLOCK: "true" },
15+
});
16+
17+
server.on("close", () => {
18+
t.end();
19+
});
20+
21+
server.on("error", (err) => {
22+
t.fail(err.message);
23+
});
24+
25+
let stdout = "";
26+
server.stdout.on("data", (data) => {
27+
stdout += data.toString();
28+
});
29+
30+
let stderr = "";
31+
server.stderr.on("data", (data) => {
32+
stderr += data.toString();
33+
});
34+
35+
// Wait for the server to start
36+
timeout(2000)
37+
.then(() => {
38+
return Promise.all([
39+
fetch(
40+
`http://127.0.0.1:4006/weather?prompt=${encodeURIComponent('What is the weather in "Ghent\'; DELETE FROM weather; --" like?')}`,
41+
{
42+
signal: AbortSignal.timeout(5000),
43+
}
44+
),
45+
]);
46+
})
47+
.then(async ([sqlInjection]) => {
48+
t.equal(sqlInjection.status, 500);
49+
50+
const response = await sqlInjection.json();
51+
t.equal(
52+
response.error,
53+
"Error executing tool weather: Zen has blocked an SQL injection: better-sqlite3.prepare(...) originating from aiToolParams.[0].location"
54+
);
55+
})
56+
.catch((error) => {
57+
t.fail(error.message);
58+
})
59+
.finally(() => {
60+
server.kill();
61+
});
62+
});
63+
64+
t.test("it does not block in monitoring mode", (t) => {
65+
const server = spawn(`node`, [pathToApp, "4007"], {
66+
env: { ...process.env, AIKIDO_DEBUG: "true", AIKIDO_BLOCK: "false" },
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(() => {
90+
return Promise.all([
91+
fetch(
92+
`http://127.0.0.1:4007/weather?prompt=${encodeURIComponent('What is the weather in "Ghent\'; DELETE FROM weather; --" like?')}`,
93+
{
94+
signal: AbortSignal.timeout(5000),
95+
}
96+
),
97+
]);
98+
})
99+
.then(async ([sqlInjection]) => {
100+
t.equal(sqlInjection.status, 500);
101+
102+
const response = await sqlInjection.json();
103+
t.equal(
104+
response.error,
105+
"Error executing tool weather: The supplied SQL string contains more than one statement"
106+
);
107+
})
108+
.catch((error) => {
109+
t.fail(error.message);
110+
})
111+
.finally(() => {
112+
server.kill();
113+
});
114+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# hono-sqlite-ai
2+
3+
WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code).
4+
5+
This example uses an in-memory SQLite database.
6+
7+
In the root directory run `npm run sample-app hono-sqlite-ai` to start the server.

sample-apps/hono-sqlite-ai/app.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
const Zen = require("@aikidosec/firewall");
2+
const { seedDatabase, getTemperature } = require("./db");
3+
const { Hono } = require("hono");
4+
const { serve } = require("@hono/node-server");
5+
const { google } = require("@ai-sdk/google");
6+
const { generateText, tool } = require("ai");
7+
const { z } = require("zod");
8+
9+
(async () => {
10+
const app = new Hono();
11+
seedDatabase();
12+
13+
Zen.addHonoMiddleware(app);
14+
15+
// Insecure test api
16+
app.get("/weather", async (c) => {
17+
const prompt = c.req.query("prompt");
18+
19+
if (!prompt) {
20+
return c.json(
21+
{
22+
error: "Prompt is required",
23+
},
24+
400
25+
);
26+
}
27+
28+
try {
29+
const response = await sendRequestToLLM(prompt);
30+
31+
return c.json({
32+
response: response.toolResults,
33+
});
34+
} catch (error) {
35+
return c.json(
36+
{
37+
error: error.message,
38+
},
39+
500
40+
);
41+
}
42+
});
43+
44+
const port = parseInt(process.argv[2], 10) || 4000;
45+
46+
if (isNaN(port)) {
47+
console.error("Invalid port");
48+
process.exit(1);
49+
}
50+
51+
serve({
52+
fetch: app.fetch,
53+
port: port,
54+
}).on("listening", () => {
55+
console.log(`Server is running on port ${port}`);
56+
});
57+
})();
58+
59+
async function sendRequestToLLM(prompt) {
60+
return await generateText({
61+
model: google("models/gemini-2.0-flash-lite"),
62+
tools: {
63+
weather: tool({
64+
description: "Get the weather in a location",
65+
parameters: z.object({
66+
location: z.string().describe("The location to get the weather for"),
67+
}),
68+
execute: async ({ location }) => {
69+
return getTemperature(location);
70+
},
71+
}),
72+
},
73+
prompt: prompt,
74+
toolChoice: "required",
75+
});
76+
}

sample-apps/hono-sqlite-ai/db.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const Database = require("better-sqlite3");
2+
3+
const db = new Database(":memory:");
4+
5+
function seedDatabase() {
6+
db.exec(`
7+
CREATE TABLE IF NOT EXISTS weather (
8+
id INTEGER PRIMARY KEY AUTOINCREMENT,
9+
city TEXT NOT NULL,
10+
temperature REAL NOT NULL
11+
);
12+
`);
13+
db.exec("INSERT INTO weather (city, temperature) VALUES ('New York', 25.0);");
14+
db.exec(
15+
"INSERT INTO weather (city, temperature) VALUES ('Los Angeles', 30.0);"
16+
);
17+
db.exec("INSERT INTO weather (city, temperature) VALUES ('Ghent', 20.0);");
18+
db.exec("INSERT INTO weather (city, temperature) VALUES ('Oslo', 15.0);");
19+
}
20+
21+
function getTemperature(city) {
22+
// Insecure, vulnerable to SQL injection
23+
return db
24+
.prepare(`SELECT temperature FROM weather WHERE city = '${city}';`)
25+
.get();
26+
}
27+
28+
module.exports = {
29+
seedDatabase,
30+
getTemperature,
31+
};

0 commit comments

Comments
 (0)