Skip to content

Commit e1fcd3e

Browse files
authored
Merge pull request #240 from OlaGreat/fix/evidence-integrity-async-leak
Fix/evidence integrity async leak
2 parents f1d72a9 + ff31060 commit e1fcd3e

5 files changed

Lines changed: 141 additions & 11 deletions

File tree

package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"ethers": "^6.16.0",
4949
"ioredis": "^5.9.3",
5050
"jsonwebtoken": "^9.0.3",
51+
"multiformats": "^14.0.0",
5152
"nestjs-pino": "^4.1.0",
5253
"passport": "^0.7.0",
5354
"passport-jwt": "^4.0.1",
@@ -103,6 +104,9 @@
103104
"transform": {
104105
"^.+\\.(t|j)s$": "ts-jest"
105106
},
107+
"moduleNameMapper": {
108+
"^multiformats/(.*)$": "<rootDir>/__mocks__/multiformats.js"
109+
},
106110
"collectCoverageFrom": [
107111
"**/*.(t|j)s"
108112
],

src/__mocks__/multiformats.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Stub for ESM-only multiformats package used in Jest tests.
2+
// The real implementation is mocked at the module level in tests that use cid-verifier.
3+
module.exports = {
4+
CID: {
5+
parse: jest.fn(),
6+
},
7+
sha256: {
8+
digest: jest.fn(),
9+
},
10+
};
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { EvidenceIntegrityMiddleware } from "./evidence-integrity.middleware";
2+
import * as cidVerifier from "../../storage/cid-verifier";
3+
4+
describe("EvidenceIntegrityMiddleware", () => {
5+
let middleware: EvidenceIntegrityMiddleware;
6+
let mockReq: any;
7+
let mockRes: any;
8+
let mockNext: jest.Mock;
9+
10+
beforeEach(() => {
11+
middleware = new EvidenceIntegrityMiddleware();
12+
mockNext = jest.fn();
13+
mockRes = {
14+
status: jest.fn().mockReturnThis(),
15+
json: jest.fn().mockReturnThis(),
16+
};
17+
});
18+
19+
it("should call next() when no file is present", async () => {
20+
mockReq = { body: { cid: "bafybeiabc123" } };
21+
await middleware.use(mockReq, mockRes, mockNext);
22+
expect(mockNext).toHaveBeenCalledTimes(1);
23+
expect(mockRes.status).not.toHaveBeenCalled();
24+
});
25+
26+
it("should call next() when no cid is present", async () => {
27+
mockReq = { file: { buffer: Buffer.from("data") }, body: {} };
28+
await middleware.use(mockReq, mockRes, mockNext);
29+
expect(mockNext).toHaveBeenCalledTimes(1);
30+
expect(mockRes.status).not.toHaveBeenCalled();
31+
});
32+
33+
it("should call next() when both file and cid are absent", async () => {
34+
mockReq = { body: {} };
35+
await middleware.use(mockReq, mockRes, mockNext);
36+
expect(mockNext).toHaveBeenCalledTimes(1);
37+
});
38+
39+
it("should call next() when CID integrity check passes", async () => {
40+
mockReq = {
41+
file: { buffer: Buffer.from("valid content") },
42+
body: { cid: "bafybeiabc123" },
43+
};
44+
jest.spyOn(cidVerifier, "verifyCIDIntegrity").mockResolvedValue(true);
45+
46+
await middleware.use(mockReq, mockRes, mockNext);
47+
48+
expect(cidVerifier.verifyCIDIntegrity).toHaveBeenCalledWith(
49+
mockReq.file.buffer,
50+
mockReq.body.cid,
51+
);
52+
expect(mockNext).toHaveBeenCalledTimes(1);
53+
expect(mockRes.status).not.toHaveBeenCalled();
54+
});
55+
56+
it("should return 400 when CID integrity check fails", async () => {
57+
mockReq = {
58+
file: { buffer: Buffer.from("tampered content") },
59+
body: { cid: "bafybeiabc123" },
60+
};
61+
jest.spyOn(cidVerifier, "verifyCIDIntegrity").mockResolvedValue(false);
62+
63+
await middleware.use(mockReq, mockRes, mockNext);
64+
65+
expect(mockRes.status).toHaveBeenCalledWith(400);
66+
expect(mockRes.json).toHaveBeenCalledWith({
67+
message: "Evidence integrity verification failed",
68+
});
69+
expect(mockNext).not.toHaveBeenCalled();
70+
});
71+
72+
it("should return 500 when verifyCIDIntegrity throws an error", async () => {
73+
mockReq = {
74+
file: { buffer: Buffer.from("content") },
75+
body: { cid: "bafybeiabc123" },
76+
};
77+
jest
78+
.spyOn(cidVerifier, "verifyCIDIntegrity")
79+
.mockRejectedValue(new Error("Unexpected failure"));
80+
81+
await middleware.use(mockReq, mockRes, mockNext);
82+
83+
expect(mockRes.status).toHaveBeenCalledWith(500);
84+
expect(mockRes.json).toHaveBeenCalledWith({
85+
message: "Evidence integrity verification encountered an error",
86+
});
87+
expect(mockNext).not.toHaveBeenCalled();
88+
});
89+
90+
it("should not call next() after sending a 400 response", async () => {
91+
mockReq = {
92+
file: { buffer: Buffer.from("bad") },
93+
body: { cid: "bafybeiabc123" },
94+
};
95+
jest.spyOn(cidVerifier, "verifyCIDIntegrity").mockResolvedValue(false);
96+
97+
await middleware.use(mockReq, mockRes, mockNext);
98+
99+
expect(mockNext).not.toHaveBeenCalled();
100+
});
101+
});

src/common/middleware/evidence-integrity.middleware.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,34 @@ import { verifyCIDIntegrity } from "../../storage/cid-verifier";
55
export class EvidenceIntegrityMiddleware implements NestMiddleware {
66
private readonly logger = new Logger(EvidenceIntegrityMiddleware.name);
77

8-
use(req: any, res: any, next: () => void) {
8+
async use(req: any, res: any, next: () => void): Promise<void> {
99
const file = req.file;
10-
const cid = req.body.cid;
10+
const cid = req.body?.cid;
1111

1212
if (!file || !cid) {
1313
return next();
1414
}
1515

16-
const isValid = verifyCIDIntegrity(file.buffer, cid);
16+
try {
17+
const isValid = await verifyCIDIntegrity(file.buffer, cid);
1718

18-
if (!isValid) {
19-
this.logger.warn(
20-
`Evidence hash mismatch detected. CID: ${cid}`
21-
);
19+
if (!isValid) {
20+
this.logger.warn(`Evidence hash mismatch detected. CID: ${cid}`);
21+
res.status(400).json({
22+
message: "Evidence integrity verification failed",
23+
});
24+
return;
25+
}
2226

23-
return res.status(400).json({
24-
message: "Evidence integrity verification failed",
27+
next();
28+
} catch (error) {
29+
this.logger.error(
30+
`Evidence integrity check threw an error. CID: ${cid}`,
31+
error instanceof Error ? error.stack : String(error),
32+
);
33+
res.status(500).json({
34+
message: "Evidence integrity verification encountered an error",
2535
});
2636
}
27-
28-
next();
2937
}
3038
}

0 commit comments

Comments
 (0)