Security findings from April 30 review were claimed fixed but never committed. Applied all remediations: HIGH: - WebhookHandler: fail fast when DARKWATCH_WEBHOOK_SECRET missing instead of defaulting to hardcoded secret - field-encryption.service: require PII_ENCRYPTION_KEY at startup instead of defaulting MEDIUM: - WebhookHandler: make signature required (was optional, accepted unsigned events) - WebhookHandler: reject unknown event types instead of silently defaulting to SCAN_TRIGGER - scheduler.routes + webhook.routes: add ownership checks on /:userId endpoints (IDOR) LOW: - webhook.routes: generic error responses, full error logged server-side Co-Authored-By: Paperclip <noreply@paperclip.ing>
67 lines
1.9 KiB
TypeScript
67 lines
1.9 KiB
TypeScript
import { FastifyInstance } from "fastify";
|
|
import { WebhookHandler } from "@shieldai/darkwatch";
|
|
|
|
export function webhookRoutes(fastify: FastifyInstance) {
|
|
const handler = new WebhookHandler();
|
|
|
|
fastify.post(
|
|
"/",
|
|
async (request, reply) => {
|
|
const body = request.body as {
|
|
eventType: string;
|
|
payload: Record<string, unknown>;
|
|
source?: string;
|
|
};
|
|
|
|
const signature =
|
|
(request.headers["x-webhook-signature"] as string) ||
|
|
(request.headers["x-hub-signature-256"] as string) ||
|
|
undefined;
|
|
|
|
try {
|
|
const result = await handler.processEvent(
|
|
body.eventType,
|
|
body.payload,
|
|
body.source,
|
|
signature
|
|
);
|
|
|
|
return reply.code(200).send({
|
|
eventId: result.eventId,
|
|
scanTriggered: result.scanTriggered,
|
|
});
|
|
} catch (err) {
|
|
console.error("[Webhook] Event processing error:", err);
|
|
return reply.code(400).send({ error: "Webhook processing failed" });
|
|
}
|
|
}
|
|
);
|
|
|
|
fastify.get(
|
|
"/history",
|
|
async (request, reply) => {
|
|
const limit = parseInt((request.query as { limit?: string }).limit || "50");
|
|
const offset = parseInt((request.query as { offset?: string }).offset || "0");
|
|
|
|
const events = await handler.getEventHistory(limit, offset);
|
|
return reply.send(events);
|
|
}
|
|
);
|
|
|
|
fastify.get(
|
|
"/user/:userId",
|
|
async (request, reply) => {
|
|
const params = request.params as { userId: string };
|
|
const authedUser = (request.user as { id: string })?.id;
|
|
if (authedUser !== params.userId) {
|
|
return reply.code(403).send({ error: "Forbidden" });
|
|
}
|
|
const limit = parseInt((request.query as { limit?: string }).limit || "50");
|
|
const offset = parseInt((request.query as { offset?: string }).offset || "0");
|
|
|
|
const events = await handler.getUserEvents(params.userId, limit, offset);
|
|
return reply.send(events);
|
|
}
|
|
);
|
|
}
|