@context-engine-bridge/context-engine-mcp-bridge 0.0.86 → 0.0.88

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.
Files changed (2) hide show
  1. package/package.json +2 -1
  2. package/src/oauthHandler.js +41 -21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@context-engine-bridge/context-engine-mcp-bridge",
3
- "version": "0.0.86",
3
+ "version": "0.0.88",
4
4
  "description": "Context Engine MCP bridge (http/stdio proxy combining indexer + memory servers)",
5
5
  "bin": {
6
6
  "ctxce": "bin/ctxce.js",
@@ -17,6 +17,7 @@
17
17
  "test:e2e:ui": "playwright test --ui"
18
18
  },
19
19
  "dependencies": {
20
+ "@context-engine-bridge/context-engine-mcp-bridge": "^0.0.87",
20
21
  "@modelcontextprotocol/sdk": "^1.24.3",
21
22
  "ignore": "^7.0.5",
22
23
  "tar": "^7.5.9",
@@ -458,6 +458,9 @@ export function handleOAuthStoreSession(req, res) {
458
458
  code_challenge_method,
459
459
  client_id,
460
460
  } = data;
461
+ const normalizedCodeChallengeMethod = code_challenge
462
+ ? (code_challenge_method || "S256")
463
+ : (code_challenge_method || null);
461
464
 
462
465
  if (!session_id || !backend_url) {
463
466
  res.statusCode = 400;
@@ -488,6 +491,15 @@ export function handleOAuthStoreSession(req, res) {
488
491
  return;
489
492
  }
490
493
 
494
+ if (code_challenge && normalizedCodeChallengeMethod !== "S256") {
495
+ res.statusCode = 400;
496
+ res.end(JSON.stringify({
497
+ error: "invalid_request",
498
+ error_description: "Unsupported code_challenge_method",
499
+ }));
500
+ return;
501
+ }
502
+
491
503
  // Additional CSRF protection: verify request came from a local browser origin
492
504
  // Require Origin or Referer header to be present and from localhost
493
505
  const origin = req.headers["origin"] || req.headers["referer"];
@@ -528,7 +540,7 @@ export function handleOAuthStoreSession(req, res) {
528
540
  sessionId: session_id,
529
541
  backendUrl: backend_url,
530
542
  codeChallenge: code_challenge,
531
- codeChallengeMethod: code_challenge_method,
543
+ codeChallengeMethod: normalizedCodeChallengeMethod,
532
544
  redirectUri: redirect_uri,
533
545
  createdAt: Date.now(),
534
546
  });
@@ -560,8 +572,7 @@ export function handleOAuthToken(req, res) {
560
572
  const code = data.get("code");
561
573
  const redirectUri = data.get("redirect_uri");
562
574
  const clientId = data.get("client_id");
563
- // PKCE code_verifier - extracted but not validated yet (local bridge, trusted)
564
- data.get("code_verifier");
575
+ const codeVerifier = data.get("code_verifier");
565
576
  const grantType = data.get("grant_type");
566
577
 
567
578
  res.setHeader("Content-Type", "application/json");
@@ -605,24 +616,33 @@ export function handleOAuthToken(req, res) {
605
616
  return;
606
617
  }
607
618
 
608
- // TODO: PKCE validation - disabled for now, no clients implement it yet
609
- // if (pendingData.codeChallenge && pendingData.codeChallengeMethod === "S256") {
610
- // const codeVerifier = data.get("code_verifier");
611
- // if (!codeVerifier) {
612
- // pendingCodes.delete(code);
613
- // res.statusCode = 400;
614
- // res.end(JSON.stringify({ error: "invalid_grant", error_description: "code_verifier required for PKCE" }));
615
- // return;
616
- // }
617
- // const crypto = await import("node:crypto");
618
- // const expectedChallenge = crypto.createHash("sha256").update(codeVerifier).digest("base64url");
619
- // if (expectedChallenge !== pendingData.codeChallenge) {
620
- // pendingCodes.delete(code);
621
- // res.statusCode = 400;
622
- // res.end(JSON.stringify({ error: "invalid_grant", error_description: "code_verifier validation failed" }));
623
- // return;
624
- // }
625
- // }
619
+ // PKCE validation (RFC 7636)
620
+ if (pendingData.codeChallenge) {
621
+ const codeChallengeMethod = pendingData.codeChallengeMethod || "S256";
622
+ if (codeChallengeMethod !== "S256") {
623
+ pendingCodes.delete(code);
624
+ res.statusCode = 400;
625
+ res.end(JSON.stringify({
626
+ error: "invalid_grant",
627
+ error_description: "Unsupported code_challenge_method",
628
+ }));
629
+ return;
630
+ }
631
+ if (!codeVerifier) {
632
+ pendingCodes.delete(code);
633
+ res.statusCode = 400;
634
+ res.end(JSON.stringify({ error: "invalid_grant", error_description: "code_verifier required for PKCE" }));
635
+ return;
636
+ }
637
+ const crypto = await import("node:crypto");
638
+ const expectedChallenge = crypto.createHash("sha256").update(codeVerifier).digest("base64url");
639
+ if (expectedChallenge !== pendingData.codeChallenge) {
640
+ pendingCodes.delete(code);
641
+ res.statusCode = 400;
642
+ res.end(JSON.stringify({ error: "invalid_grant", error_description: "code_verifier validation failed" }));
643
+ return;
644
+ }
645
+ }
626
646
 
627
647
  // Clean up expired tokens periodically to prevent unbounded growth
628
648
  cleanupExpiredTokens();