@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 = JSON.parse(decrypt(body, password.key, password.iv));
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
- /** @internal */
70
- const decrypt = (body, key, iv) => {
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,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;YACvE,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,gBAAgB;AAChB,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,GAAW,EAAE,EAAU,EAAU,EAAE;IAChE,IAAI,CAAC;QACH,OAAO,mBAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IACzC,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"}
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.1",
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.1"
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.1"
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 = JSON.parse(decrypt(body, password.key, password.iv));
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
- /** @internal */
80
- const decrypt = (body: string, key: string, iv: string): string => {
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(