Skip to content

Commit

Permalink
Merge branch 'main' into log-levels
Browse files Browse the repository at this point in the history
  • Loading branch information
timokoessler committed Dec 27, 2024
2 parents d5fe9a7 + eea81cb commit adff07b
Show file tree
Hide file tree
Showing 26 changed files with 675 additions and 113 deletions.
111 changes: 110 additions & 1 deletion library/agent/Agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ wrap(fetch, "fetch", function mock() {
};
});

let logs: string[] = [];
wrap(console, "log", function log() {
return function log(...args: string[]) {
logs.push(...args);
};
});

t.test("it throws error if serverless is empty string", async () => {
t.throws(
() =>
Expand All @@ -58,6 +65,7 @@ t.test("it sends started event", async (t) => {
api,
logger,
token: new Token("123"),
suppressConsoleLog: false,
});
agent.start([new MongoDB()]);

Expand Down Expand Up @@ -103,6 +111,7 @@ t.test("it throws error if already started", async () => {
api,
logger,
token: new Token("123"),
suppressConsoleLog: false,
});
agent.start([new MongoDB()]);
t.throws(() => agent.start([new MongoDB()]), "Agent already started!");
Expand All @@ -121,6 +130,7 @@ t.test("it logs if package is supported or not", async () => {
api,
logger,
token: new Token("123"),
suppressConsoleLog: false,
});
agent.start([new WrapperForTesting()]);

Expand All @@ -141,6 +151,7 @@ t.test("it starts in non-blocking mode", async () => {
api,
logger,
token: new Token("123"),
suppressConsoleLog: false,
});
agent.start([]);

Expand All @@ -158,6 +169,7 @@ t.test("when prevent prototype pollution is enabled", async (t) => {
api,
logger,
token: new Token("123"),
suppressConsoleLog: false,
serverless: "lambda",
});
agent.onPrototypePollutionPrevented();
Expand All @@ -179,20 +191,26 @@ t.test("it does not start interval in serverless mode", async () => {
api,
logger,
token: new Token("123"),
suppressConsoleLog: false,
serverless: "lambda",
});
// This would otherwise keep the process running
agent.start([]);
});

t.test("when attack detected", async () => {
t.test("when attack detected in blocking mode", async () => {
logs = []; // Clear console logs

const logger = new LoggerNoop();
const api = new ReportingAPIForTesting();
const agent = createTestAgent({
api,
logger,
token: new Token("123"),
suppressConsoleLog: false,
block: true,
});

agent.onDetectedAttack({
module: "mongodb",
kind: "nosql_injection",
Expand Down Expand Up @@ -221,6 +239,82 @@ t.test("when attack detected", async () => {
},
});

t.same(logs, [
'Zen has blocked a NoSQL injection: kind="nosql_injection" operation="operation(...)" source="body.nested" ip="::1"',
]);

t.match(api.getEvents(), [
{
type: "detected_attack",
attack: {
module: "mongodb",
kind: "nosql_injection",
blocked: true,
source: "body",
path: ".nested",
stack: "stack",
metadata: {
db: "app",
},
},
request: {
method: "POST",
ipAddress: "::1",
userAgent: "agent",
url: "http://localhost:4000",
headers: {},
body: "{}",
route: "/posts/:id",
},
},
]);
});

t.test("when attack detected in detection only mode", async () => {
logs = []; // Clear console logs

const logger = new LoggerNoop();
const api = new ReportingAPIForTesting();
const agent = createTestAgent({
api,
logger,
token: new Token("123"),
suppressConsoleLog: false,
block: false,
});

agent.onDetectedAttack({
module: "mongodb",
kind: "nosql_injection",
blocked: true,
source: "body",
request: {
method: "POST",
cookies: {},
query: {},
headers: {
"user-agent": "agent",
},
body: {},
url: "http://localhost:4000",
remoteAddress: "::1",
source: "express",
route: "/posts/:id",
routeParams: {},
},
operation: "operation",
payload: "payload",
stack: "stack",
paths: [".nested"],
metadata: {
db: "app",
},
});

t.same(logs, [
'Zen has blocked a NoSQL injection: kind="nosql_injection" operation="operation(...)" source="body.nested" ip="::1"',
]);

t.match(api.getEvents(), [
{
type: "detected_attack",
Expand Down Expand Up @@ -255,6 +349,7 @@ t.test("it checks if user agent is a string", async () => {
api,
logger,
token: new Token("123"),
suppressConsoleLog: false,
});
agent.onDetectedAttack({
module: "mongodb",
Expand Down Expand Up @@ -329,6 +424,7 @@ t.test(
api,
logger,
token: new Token("123"),
suppressConsoleLog: false,
});
agent.start([]);
t.match(api.getEvents(), [
Expand Down Expand Up @@ -397,6 +493,7 @@ t.test(
api,
logger,
token: new Token("123"),
suppressConsoleLog: false,
});
agent.start([]);
t.match(api.getEvents(), [
Expand Down Expand Up @@ -436,6 +533,7 @@ t.test("it sends heartbeat when reached max timings", async () => {
api,
logger,
token: new Token("123"),
suppressConsoleLog: false,
});
agent.start([]);
for (let i = 0; i < 1000; i++) {
Expand Down Expand Up @@ -524,6 +622,7 @@ t.test("it logs when failed to report event", async () => {
api,
logger,
token: new Token("123"),
suppressConsoleLog: false,
});
agent.start([]);

Expand Down Expand Up @@ -584,6 +683,7 @@ t.test("unable to prevent prototype pollution", async () => {
api,
logger,
token: new Token("123"),
suppressConsoleLog: false,
});
agent.start([]);
agent.unableToPreventPrototypePollution({ mongoose: "1.0.0" });
Expand Down Expand Up @@ -615,6 +715,7 @@ t.test("when payload is object", async () => {
api,
logger,
token: new Token("123"),
suppressConsoleLog: false,
});
agent.onDetectedAttack({
module: "mongodb",
Expand Down Expand Up @@ -717,6 +818,7 @@ t.test("it sends hostnames and routes along with heartbeat", async () => {
api,
logger,
token: new Token("123"),
suppressConsoleLog: false,
});
agent.start([]);

Expand Down Expand Up @@ -824,6 +926,7 @@ t.test(
api,
logger,
token: new Token("123"),
suppressConsoleLog: false,
});
t.same(agent.shouldBlock(), true);
agent.start([]);
Expand All @@ -845,6 +948,7 @@ t.test(
api,
logger,
token: new Token("123"),
suppressConsoleLog: false,
});
t.same(agent.shouldBlock(), false);
agent.start([]);
Expand All @@ -869,6 +973,7 @@ t.test("it enables blocking mode after sending startup event", async () => {
});
const agent = createTestAgent({
token: new Token("123"),
suppressConsoleLog: false,
block: false,
api,
logger,
Expand Down Expand Up @@ -897,6 +1002,7 @@ t.test("it goes into monitoring mode after sending startup event", async () => {
api,
logger,
token: new Token("123"),
suppressConsoleLog: false,
});
t.same(agent.shouldBlock(), true);
agent.start([]);
Expand All @@ -916,6 +1022,7 @@ t.test("it sends middleware installed with heartbeat", async () => {
api,
logger,
token: new Token("123"),
suppressConsoleLog: false,
});
agent.start([]);

Expand All @@ -940,6 +1047,7 @@ t.test("it sends middleware installed with heartbeat", async () => {
t.test("it fetches blocked lists", async () => {
const agent = createTestAgent({
token: new Token("123"),
suppressConsoleLog: false,
});

agent.start([]);
Expand Down Expand Up @@ -981,6 +1089,7 @@ t.test("it fetches blocked lists", async () => {
t.test("it does not fetch blocked IPs if serverless", async () => {
const agent = createTestAgent({
token: new Token("123"),
suppressConsoleLog: false,
serverless: "gcp",
});

Expand Down
82 changes: 39 additions & 43 deletions library/agent/Agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { limitLengthMetadata } from "../helpers/limitLengthMetadata";
import { RateLimiter } from "../ratelimiting/RateLimiter";
import { fetchBlockedLists } from "./api/fetchBlockedLists";
import { ReportingAPI, ReportingAPIResponse } from "./api/ReportingAPI";
import { AgentInfo } from "./api/Event";
import { AgentInfo, DetectedAttack } from "./api/Event";
import { Token } from "./api/Token";
import { attackKindHumanName, Kind } from "./Attack";
import { pollForChanges } from "./realtime/pollForChanges";
Expand All @@ -24,7 +24,7 @@ import { Users } from "./Users";
import { wrapInstalledPackages } from "./wrapInstalledPackages";
import { Wrapper } from "./Wrapper";
import { isAikidoCI } from "../helpers/isAikidoCI";
import { escapeLog } from "../helpers/escapeLog";
import { AttackLogger } from "./AttackLogger";

type WrappedPackage = { version: string | null; supported: boolean };

Expand All @@ -49,6 +49,7 @@ export class Agent {
maxCompressedStatsInMemory: 100,
});
private middlewareInstalled = false;
private attackLogger = new AttackLogger(1000);

constructor(
private block: boolean,
Expand Down Expand Up @@ -158,48 +159,43 @@ export class Agent {
metadata: Record<string, string>;
payload: unknown;
}) {
this.logger.info(
`Zen has ${blocked ? "blocked" : "detected"} ${attackKindHumanName(kind)}: kind="${kind}" operation="${operation}(...)" source="${source}${escapeLog((paths || []).join())}" ip="${escapeLog(request.remoteAddress)}"`
);
const attack: DetectedAttack = {
type: "detected_attack",
time: Date.now(),
attack: {
module: module,
operation: operation,
blocked: blocked,
path: paths.length > 0 ? paths[0] : "",
stack: stack,
source: source,
metadata: limitLengthMetadata(metadata, 4096),
kind: kind,
payload: JSON.stringify(payload).substring(0, 4096),
user: request.user,
},
request: {
method: request.method,
url: request.url,
ipAddress: request.remoteAddress,
userAgent:
typeof request.headers["user-agent"] === "string"
? request.headers["user-agent"]
: undefined,
body: convertRequestBodyToString(request.body),
headers: filterEmptyRequestHeaders(request.headers),
source: request.source,
route: request.route,
},
agent: this.getAgentInfo(),
};

this.attackLogger.log(attack);

if (this.token) {
this.api
.report(
this.token,
{
type: "detected_attack",
time: Date.now(),
attack: {
module: module,
operation: operation,
blocked: blocked,
path: paths.length > 0 ? paths[0] : "",
stack: stack,
source: source,
metadata: limitLengthMetadata(metadata, 4096),
kind: kind,
payload: JSON.stringify(payload).substring(0, 4096),
user: request.user,
},
request: {
method: request.method,
url: request.url,
ipAddress: request.remoteAddress,
userAgent:
typeof request.headers["user-agent"] === "string"
? request.headers["user-agent"]
: undefined,
body: convertRequestBodyToString(request.body),
headers: filterEmptyRequestHeaders(request.headers),
source: request.source,
route: request.route,
},
agent: this.getAgentInfo(),
},
this.timeoutInMS
)
.catch(() => {
this.logger.error("Failed to report attack");
});
this.api.report(this.token, attack, this.timeoutInMS).catch(() => {
this.logger.log("Failed to report attack");
});
}
}

Expand Down
Loading

0 comments on commit adff07b

Please sign in to comment.