skalpel 2.0.19 → 2.0.21

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.
@@ -40,7 +40,9 @@ var init_dispatcher = __esm({
40
40
  keepAliveTimeout: 1e4,
41
41
  keepAliveMaxTimeout: 6e4,
42
42
  connections: 100,
43
- pipelining: 1
43
+ pipelining: 1,
44
+ allowH2: false
45
+ // Force HTTP/1.1 to prevent GCP LB WebSocket downgrade
44
46
  });
45
47
  }
46
48
  });
@@ -660,6 +662,12 @@ __export(handler_exports, {
660
662
  isSkalpelBackendFailure: () => isSkalpelBackendFailure,
661
663
  shouldRouteToSkalpel: () => shouldRouteToSkalpel
662
664
  });
665
+ function validateCodexAuth(oauthToken, inboundAuth) {
666
+ if (!oauthToken && !inboundAuth) {
667
+ return { valid: false, error: "no_credentials" };
668
+ }
669
+ return { valid: true };
670
+ }
663
671
  function collectBody(req) {
664
672
  return new Promise((resolve, reject) => {
665
673
  const chunks = [];
@@ -924,6 +932,18 @@ async function handleWebSocketBridge(clientWs, req, config, source, logger) {
924
932
  const oauthHeader = req.headers["authorization"] ?? "";
925
933
  oauthToken = oauthHeader.toLowerCase().startsWith("bearer ") ? oauthHeader.slice(7).trim() : "";
926
934
  }
935
+ const authResult = validateCodexAuth(freshOauthToken, oauthToken);
936
+ if (!authResult.valid) {
937
+ clientWs.send(JSON.stringify({
938
+ type: "error",
939
+ error: {
940
+ code: "no_credentials",
941
+ message: "Codex requires OAuth login. Run: codex login"
942
+ }
943
+ }));
944
+ clientWs.close(4e3, "no credentials");
945
+ return;
946
+ }
927
947
  const backend = new BackendWsClient({
928
948
  url: backendUrl,
929
949
  apiKey: config.apiKey,
@@ -1031,6 +1051,18 @@ function parseSseEvents(buffer) {
1031
1051
  return { events, rest };
1032
1052
  }
1033
1053
  async function fallbackToHttp(clientWs, config, source, logger, requestBody, inboundAuth) {
1054
+ const preflightAuth = validateCodexAuth(getFreshAccessToken(), inboundAuth);
1055
+ if (!preflightAuth.valid) {
1056
+ clientWs.send(JSON.stringify({
1057
+ type: "error",
1058
+ error: {
1059
+ code: "no_credentials",
1060
+ message: "Codex requires OAuth login. Run: codex login"
1061
+ }
1062
+ }));
1063
+ clientWs.close(4e3, "no credentials");
1064
+ return;
1065
+ }
1034
1066
  try {
1035
1067
  const freshToken = getFreshAccessToken();
1036
1068
  const authHeader = freshToken !== null ? `Bearer ${freshToken}` : inboundAuth;
@@ -1060,17 +1092,37 @@ async function fallbackToHttp(clientWs, config, source, logger, requestBody, inb
1060
1092
  },
1061
1093
  body: requestBody
1062
1094
  });
1063
- if (!resp.ok || resp.body === null) {
1064
- clientWs.send(
1065
- JSON.stringify({
1066
- type: "error",
1067
- error: { code: resp.status, message: `http fallback status ${resp.status}` }
1068
- })
1069
- );
1095
+ if (!resp.ok) {
1096
+ let errorBody = "";
1070
1097
  try {
1071
- clientWs.close(1011, "http fallback failed");
1098
+ const bodyPromise = resp.text();
1099
+ const timeoutPromise = new Promise(
1100
+ (_, reject) => setTimeout(() => reject(new Error("body read timeout")), 5e3)
1101
+ );
1102
+ errorBody = await Promise.race([bodyPromise, timeoutPromise]);
1103
+ } catch (e) {
1104
+ errorBody = `status ${resp.status}`;
1105
+ }
1106
+ let errorMessage = `Backend error: ${resp.status}`;
1107
+ try {
1108
+ const parsed = JSON.parse(errorBody);
1109
+ errorMessage = parsed?.error?.message || parsed?.detail || errorMessage;
1072
1110
  } catch {
1111
+ if (errorBody.length < 200) errorMessage = errorBody;
1073
1112
  }
1113
+ clientWs.send(JSON.stringify({
1114
+ type: "error",
1115
+ error: { code: resp.status, message: errorMessage }
1116
+ }));
1117
+ clientWs.close(1011, "backend error");
1118
+ return;
1119
+ }
1120
+ if (resp.body === null) {
1121
+ clientWs.send(JSON.stringify({
1122
+ type: "error",
1123
+ error: { code: resp.status, message: "empty response body" }
1124
+ }));
1125
+ clientWs.close(1011, "empty body");
1074
1126
  return;
1075
1127
  }
1076
1128
  const reader = resp.body.getReader();