@nestia/core 13.0.1 → 13.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -58,7 +58,7 @@ function EncryptedBody(validator) {
|
|
|
58
58
|
? param({ headers: headers.get(), body, direction: "decode" })
|
|
59
59
|
: param;
|
|
60
60
|
// PARSE AND VALIDATE DATA
|
|
61
|
-
const data =
|
|
61
|
+
const data = decode(body, password.key, password.iv);
|
|
62
62
|
const error = checker(data);
|
|
63
63
|
if (error !== null)
|
|
64
64
|
throw error;
|
|
@@ -66,10 +66,28 @@ function EncryptedBody(validator) {
|
|
|
66
66
|
});
|
|
67
67
|
})();
|
|
68
68
|
}
|
|
69
|
-
/**
|
|
70
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Decrypt and JSON-parse the request body behind a single failure response.
|
|
71
|
+
*
|
|
72
|
+
* `AesPkcs5` is AES-CBC without an authentication tag, so a MITM attacker can
|
|
73
|
+
* forge ciphertexts. Splitting the two failure modes of a forged body — a bad
|
|
74
|
+
* PKCS#5 padding (decryption throws) versus valid padding whose plaintext is
|
|
75
|
+
* not JSON (`JSON.parse` throws) — into two different HTTP status codes turns
|
|
76
|
+
* this decorator into a padding oracle: one observable bit per request lets the
|
|
77
|
+
* attacker recover the plaintext byte by byte without the key
|
|
78
|
+
* (GHSA-pqj4-gvf7-6fq5).
|
|
79
|
+
*
|
|
80
|
+
* Both failure modes must therefore collapse into one indistinguishable
|
|
81
|
+
* `BadRequestException` (identical status, message, and body). Only the
|
|
82
|
+
* downstream type validation of an already-parsed body may report its own
|
|
83
|
+
* distinct error, because reaching it requires plaintext the attacker cannot
|
|
84
|
+
* forge without already knowing it.
|
|
85
|
+
*
|
|
86
|
+
* @internal
|
|
87
|
+
*/
|
|
88
|
+
const decode = (body, key, iv) => {
|
|
71
89
|
try {
|
|
72
|
-
return AesPkcs5_1.AesPkcs5.decrypt(body, key, iv);
|
|
90
|
+
return JSON.parse(AesPkcs5_1.AesPkcs5.decrypt(body, key, iv));
|
|
73
91
|
}
|
|
74
92
|
catch (exp) {
|
|
75
93
|
if (exp instanceof Error)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EncryptedBody.js","sourceRoot":"","sources":["../../src/decorators/EncryptedBody.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,2DAAwD;AACxD,2CAIwB;AAKxB,kDAA+C;AAC/C,oEAAuE;AACvE,4DAAyD;AACzD,oEAAiE;AACjE,4DAAyD;AACzD,4EAAyE;AAEzE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,uBACE,SAAoC;IAEpC,MAAM,OAAO,GAAG,IAAA,6CAAqB,EAAC,eAAe,CAAC,CAAC,SAAS,CAAC,CAAC;IAClE,OAAO,IAAA,6BAAoB,EAAC,SAAe,aAAa,CACtD,QAAa,EACb,OAAyB;;YAEzB,MAAM,OAAO,GAAqC,OAAO;iBACtD,YAAY,EAAE;iBACd,UAAU,EAAE,CAAC;YAChB,IAAI,IAAA,6BAAa,EAAC,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,YAAY,CAAC,KAAK,KAAK;gBACxE,MAAM,IAAI,4BAAmB,CAAC,wCAAwC,CAAC,CAAC;YAE1E,MAAM,KAAK,GACT,OAAO,CAAC,WAAW,CAAC,2CAAuB,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;YACnE,IAAI,CAAC,KAAK;gBACR,MAAM,IAAI,KAAK,CACb,wEAAwE,CACzE,CAAC;YAEJ,gBAAgB;YAChB,MAAM,OAAO,GAAsC,IAAI,qBAAS,CAAC,GAAG,EAAE,CACpE,IAAA,qCAAiB,EAAC,OAAO,CAAC,OAAO,CAAC,CACnC,CAAC;YACF,MAAM,IAAI,GAAW,MAAM,IAAA,6BAAa,EAAC,OAAO,CAAC,CAAC;YAClD,MAAM,QAAQ,GACZ,OAAO,KAAK,KAAK,UAAU;gBACzB,CAAC,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;gBAC9D,CAAC,CAAC,KAAK,CAAC;YAEZ,0BAA0B;YAC1B,MAAM,IAAI,GAAQ,
|
|
1
|
+
{"version":3,"file":"EncryptedBody.js","sourceRoot":"","sources":["../../src/decorators/EncryptedBody.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,2DAAwD;AACxD,2CAIwB;AAKxB,kDAA+C;AAC/C,oEAAuE;AACvE,4DAAyD;AACzD,oEAAiE;AACjE,4DAAyD;AACzD,4EAAyE;AAEzE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,uBACE,SAAoC;IAEpC,MAAM,OAAO,GAAG,IAAA,6CAAqB,EAAC,eAAe,CAAC,CAAC,SAAS,CAAC,CAAC;IAClE,OAAO,IAAA,6BAAoB,EAAC,SAAe,aAAa,CACtD,QAAa,EACb,OAAyB;;YAEzB,MAAM,OAAO,GAAqC,OAAO;iBACtD,YAAY,EAAE;iBACd,UAAU,EAAE,CAAC;YAChB,IAAI,IAAA,6BAAa,EAAC,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,YAAY,CAAC,KAAK,KAAK;gBACxE,MAAM,IAAI,4BAAmB,CAAC,wCAAwC,CAAC,CAAC;YAE1E,MAAM,KAAK,GACT,OAAO,CAAC,WAAW,CAAC,2CAAuB,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;YACnE,IAAI,CAAC,KAAK;gBACR,MAAM,IAAI,KAAK,CACb,wEAAwE,CACzE,CAAC;YAEJ,gBAAgB;YAChB,MAAM,OAAO,GAAsC,IAAI,qBAAS,CAAC,GAAG,EAAE,CACpE,IAAA,qCAAiB,EAAC,OAAO,CAAC,OAAO,CAAC,CACnC,CAAC;YACF,MAAM,IAAI,GAAW,MAAM,IAAA,6BAAa,EAAC,OAAO,CAAC,CAAC;YAClD,MAAM,QAAQ,GACZ,OAAO,KAAK,KAAK,UAAU;gBACzB,CAAC,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;gBAC9D,CAAC,CAAC,KAAK,CAAC;YAEZ,0BAA0B;YAC1B,MAAM,IAAI,GAAQ,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;YAC1D,MAAM,KAAK,GAAiB,OAAO,CAAC,IAAI,CAAC,CAAC;YAC1C,IAAI,KAAK,KAAK,IAAI;gBAAE,MAAM,KAAK,CAAC;YAChC,OAAO,IAAI,CAAC;QACd,CAAC;KAAA,CAAC,EAAE,CAAC;AACP,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,GAAW,EAAE,EAAU,EAAW,EAAE;IAChE,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,mBAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;IACrD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,KAAK;YACtB,MAAM,IAAI,4BAAmB,CAC3B,qFAAqF,CACtF,CAAC;;YACC,MAAM,GAAG,CAAC;IACjB,CAAC;AACH,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nestia/core",
|
|
3
|
-
"version": "13.0.
|
|
3
|
+
"version": "13.0.2",
|
|
4
4
|
"description": "Super-fast validation decorators of NestJS",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"tgrid": "^1.1.0",
|
|
57
57
|
"typia": "^14.0.2",
|
|
58
58
|
"ws": "^7.5.3",
|
|
59
|
-
"@nestia/fetcher": "^13.0.
|
|
59
|
+
"@nestia/fetcher": "^13.0.2"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"@modelcontextprotocol/sdk": "^1.18.0",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"rxjs": ">=6.0.3",
|
|
67
67
|
"ttsc": ">=0.19.2",
|
|
68
68
|
"typia": "^14.0.2",
|
|
69
|
-
"@nestia/fetcher": "^13.0.
|
|
69
|
+
"@nestia/fetcher": "^13.0.2"
|
|
70
70
|
},
|
|
71
71
|
"peerDependenciesMeta": {
|
|
72
72
|
"@modelcontextprotocol/sdk": {
|
|
@@ -69,17 +69,35 @@ export function EncryptedBody<T>(
|
|
|
69
69
|
: param;
|
|
70
70
|
|
|
71
71
|
// PARSE AND VALIDATE DATA
|
|
72
|
-
const data: any =
|
|
72
|
+
const data: any = decode(body, password.key, password.iv);
|
|
73
73
|
const error: Error | null = checker(data);
|
|
74
74
|
if (error !== null) throw error;
|
|
75
75
|
return data;
|
|
76
76
|
})();
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
/**
|
|
80
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Decrypt and JSON-parse the request body behind a single failure response.
|
|
81
|
+
*
|
|
82
|
+
* `AesPkcs5` is AES-CBC without an authentication tag, so a MITM attacker can
|
|
83
|
+
* forge ciphertexts. Splitting the two failure modes of a forged body — a bad
|
|
84
|
+
* PKCS#5 padding (decryption throws) versus valid padding whose plaintext is
|
|
85
|
+
* not JSON (`JSON.parse` throws) — into two different HTTP status codes turns
|
|
86
|
+
* this decorator into a padding oracle: one observable bit per request lets the
|
|
87
|
+
* attacker recover the plaintext byte by byte without the key
|
|
88
|
+
* (GHSA-pqj4-gvf7-6fq5).
|
|
89
|
+
*
|
|
90
|
+
* Both failure modes must therefore collapse into one indistinguishable
|
|
91
|
+
* `BadRequestException` (identical status, message, and body). Only the
|
|
92
|
+
* downstream type validation of an already-parsed body may report its own
|
|
93
|
+
* distinct error, because reaching it requires plaintext the attacker cannot
|
|
94
|
+
* forge without already knowing it.
|
|
95
|
+
*
|
|
96
|
+
* @internal
|
|
97
|
+
*/
|
|
98
|
+
const decode = (body: string, key: string, iv: string): unknown => {
|
|
81
99
|
try {
|
|
82
|
-
return AesPkcs5.decrypt(body, key, iv);
|
|
100
|
+
return JSON.parse(AesPkcs5.decrypt(body, key, iv));
|
|
83
101
|
} catch (exp) {
|
|
84
102
|
if (exp instanceof Error)
|
|
85
103
|
throw new BadRequestException(
|