blockintel-gate-sdk 0.4.4 → 0.4.6

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.
package/dist/index.d.cts CHANGED
@@ -273,6 +273,11 @@ declare class GateClient {
273
273
  * Logs warnings but doesn't block (initialization already completed).
274
274
  */
275
275
  private performIamRiskCheckAsync;
276
+ /**
277
+ * Warn if the local SDK mode is SHADOW but the server's adoption stage is enforcing.
278
+ * Runs non-blocking after heartbeat startup; never throws.
279
+ */
280
+ private checkAdoptionStageMismatch;
276
281
  /**
277
282
  * Evaluate a transaction defense request
278
283
  *
@@ -597,6 +602,8 @@ declare class HeartbeatManager {
597
602
  private evictionTimer;
598
603
  private started;
599
604
  private maxBackoffSeconds;
605
+ /** Server's current adoption stage for this tenant (cached from heartbeat response) */
606
+ private adoptionStage;
600
607
  private readonly maxSigners;
601
608
  private readonly signerIdleTtlMs;
602
609
  private readonly localRateLimitMs;
@@ -662,6 +669,12 @@ declare class HeartbeatManager {
662
669
  * Get client instance ID (for tracking)
663
670
  */
664
671
  getClientInstanceId(): string;
672
+ /**
673
+ * Get the server's current adoption stage for this tenant.
674
+ * Populated after the first successful heartbeat response.
675
+ * Returns null if not yet received.
676
+ */
677
+ getAdoptionStage(): string | null;
665
678
  }
666
679
 
667
680
  /**
package/dist/index.d.ts CHANGED
@@ -273,6 +273,11 @@ declare class GateClient {
273
273
  * Logs warnings but doesn't block (initialization already completed).
274
274
  */
275
275
  private performIamRiskCheckAsync;
276
+ /**
277
+ * Warn if the local SDK mode is SHADOW but the server's adoption stage is enforcing.
278
+ * Runs non-blocking after heartbeat startup; never throws.
279
+ */
280
+ private checkAdoptionStageMismatch;
276
281
  /**
277
282
  * Evaluate a transaction defense request
278
283
  *
@@ -597,6 +602,8 @@ declare class HeartbeatManager {
597
602
  private evictionTimer;
598
603
  private started;
599
604
  private maxBackoffSeconds;
605
+ /** Server's current adoption stage for this tenant (cached from heartbeat response) */
606
+ private adoptionStage;
600
607
  private readonly maxSigners;
601
608
  private readonly signerIdleTtlMs;
602
609
  private readonly localRateLimitMs;
@@ -662,6 +669,12 @@ declare class HeartbeatManager {
662
669
  * Get client instance ID (for tracking)
663
670
  */
664
671
  getClientInstanceId(): string;
672
+ /**
673
+ * Get the server's current adoption stage for this tenant.
674
+ * Populated after the first successful heartbeat response.
675
+ * Returns null if not yet received.
676
+ */
677
+ getAdoptionStage(): string | null;
665
678
  }
666
679
 
667
680
  /**
package/dist/index.js CHANGED
@@ -1212,6 +1212,12 @@ async function handleSignCommand(command, originalClient, gateClient, options) {
1212
1212
  if (options.mode === "dry-run") {
1213
1213
  return await originalClient.send(new SignCommand(command));
1214
1214
  }
1215
+ const GATEWAY_STAGES = ["HARD_KMS_GATEWAY", "HARD_GCP_GATEWAY", "HARD_HSM_GATEWAY"];
1216
+ const currentStage = gateClient.heartbeatManager?.getAdoptionStage?.();
1217
+ if (currentStage && GATEWAY_STAGES.includes(currentStage)) {
1218
+ emitMetric(options.metricsSink, "sign_success_total", labels);
1219
+ return await signViaProxy(gateClient, decision, command, signerId);
1220
+ }
1215
1221
  return await originalClient.send(new SignCommand(command));
1216
1222
  } catch (error) {
1217
1223
  if (error instanceof BlockIntelBlockedError) {
@@ -1225,6 +1231,72 @@ async function handleSignCommand(command, originalClient, gateClient, options) {
1225
1231
  throw error;
1226
1232
  }
1227
1233
  }
1234
+ async function signViaProxy(gateClient, decision, command, signerId) {
1235
+ const config = gateClient.config;
1236
+ const baseUrl = config?.baseUrl || config?.controlPlaneUrl;
1237
+ const tenantId = config?.tenantId;
1238
+ if (!baseUrl || !tenantId) {
1239
+ throw new Error("[Gate SDK] Cannot use signing proxy: baseUrl or tenantId not configured on GateClient");
1240
+ }
1241
+ const message = command.input?.Message ?? command.Message;
1242
+ if (!message) {
1243
+ throw new Error("[Gate SDK] SignCommand missing Message for proxy signing");
1244
+ }
1245
+ const messageBuffer = message instanceof Buffer ? message : Buffer.from(message);
1246
+ const messageBase64 = messageBuffer.toString("base64");
1247
+ const keyId = command.input?.KeyId ?? command.KeyId;
1248
+ if (!keyId) {
1249
+ throw new Error("[Gate SDK] SignCommand missing KeyId for proxy signing");
1250
+ }
1251
+ const signingAlgorithm = command.input?.SigningAlgorithm ?? command.SigningAlgorithm ?? "ECDSA_SHA_256";
1252
+ const messageType = command.input?.MessageType ?? command.MessageType ?? "RAW";
1253
+ const proxyUrl = `${baseUrl.replace("/defense", "")}/tenants/${tenantId}/defense/sign`;
1254
+ const headers = {
1255
+ "Content-Type": "application/json"
1256
+ };
1257
+ const authHeaders = gateClient.getAuthHeaders?.();
1258
+ if (authHeaders) {
1259
+ Object.assign(headers, authHeaders);
1260
+ } else {
1261
+ const auth = config?.auth;
1262
+ if (auth?.mode === "api_key" && auth?.apiKey) {
1263
+ headers["x-api-key"] = auth.apiKey;
1264
+ }
1265
+ }
1266
+ const jwt = gateClient.jwt || gateClient.config?.jwt;
1267
+ if (jwt) {
1268
+ headers["Authorization"] = `Bearer ${jwt}`;
1269
+ }
1270
+ const response = await fetch(proxyUrl, {
1271
+ method: "POST",
1272
+ headers,
1273
+ body: JSON.stringify({
1274
+ requestId: decision.decisionId || decision.requestId,
1275
+ decisionToken: decision.decisionToken,
1276
+ keyId,
1277
+ message: messageBase64,
1278
+ signingAlgorithm,
1279
+ messageType
1280
+ })
1281
+ });
1282
+ if (!response.ok) {
1283
+ const errorBody = await response.json().catch(() => ({}));
1284
+ const code = errorBody?.error?.code || "SIGN_PROXY_FAILED";
1285
+ const msg = errorBody?.error?.message || `Signing proxy returned ${response.status}`;
1286
+ throw new Error(`[Gate SDK] ${code}: ${msg}`);
1287
+ }
1288
+ const result = await response.json();
1289
+ const data = result?.data;
1290
+ if (!data?.signature) {
1291
+ throw new Error("[Gate SDK] Signing proxy returned no signature");
1292
+ }
1293
+ return {
1294
+ Signature: Buffer.from(data.signature, "base64"),
1295
+ KeyId: data.keyId || keyId,
1296
+ SigningAlgorithm: data.signingAlgorithm || signingAlgorithm,
1297
+ $metadata: { httpStatusCode: 200 }
1298
+ };
1299
+ }
1228
1300
 
1229
1301
  // src/provenance/ProvenanceProvider.ts
1230
1302
  var ProvenanceProvider = class {
@@ -1282,6 +1354,8 @@ var HeartbeatManager = class {
1282
1354
  started = false;
1283
1355
  maxBackoffSeconds = 30;
1284
1356
  // Maximum backoff interval
1357
+ /** Server's current adoption stage for this tenant (cached from heartbeat response) */
1358
+ adoptionStage = null;
1285
1359
  maxSigners;
1286
1360
  signerIdleTtlMs;
1287
1361
  localRateLimitMs;
@@ -1555,6 +1629,9 @@ var HeartbeatManager = class {
1555
1629
  policyHash: response.data.policyHash
1556
1630
  };
1557
1631
  entry.consecutiveFailures = 0;
1632
+ if (response.data.adoptionStage != null) {
1633
+ this.adoptionStage = response.data.adoptionStage;
1634
+ }
1558
1635
  console.log("[HEARTBEAT] Acquired heartbeat token", {
1559
1636
  expiresAt,
1560
1637
  signerId,
@@ -1583,6 +1660,14 @@ var HeartbeatManager = class {
1583
1660
  getClientInstanceId() {
1584
1661
  return this.clientInstanceId;
1585
1662
  }
1663
+ /**
1664
+ * Get the server's current adoption stage for this tenant.
1665
+ * Populated after the first successful heartbeat response.
1666
+ * Returns null if not yet received.
1667
+ */
1668
+ getAdoptionStage() {
1669
+ return this.adoptionStage;
1670
+ }
1586
1671
  };
1587
1672
 
1588
1673
  // src/security/IamPermissionRiskChecker.ts
@@ -1915,6 +2000,8 @@ var GateClient = class {
1915
2000
  apiKey: heartbeatApiKey
1916
2001
  });
1917
2002
  this.heartbeatManager.start();
2003
+ this.checkAdoptionStageMismatch().catch(() => {
2004
+ });
1918
2005
  }
1919
2006
  if (!config.local) {
1920
2007
  const enforcementMode = config.enforcementMode || "SOFT";
@@ -1960,9 +2047,38 @@ var GateClient = class {
1960
2047
  console.warn("[GATE CLIENT] Async IAM risk check warning:", error instanceof Error ? error.message : String(error));
1961
2048
  }
1962
2049
  }
2050
+ /**
2051
+ * Warn if the local SDK mode is SHADOW but the server's adoption stage is enforcing.
2052
+ * Runs non-blocking after heartbeat startup; never throws.
2053
+ */
2054
+ async checkAdoptionStageMismatch() {
2055
+ if (!this.heartbeatManager) return;
2056
+ const signerId = this.config.signerId ?? DEFAULT_SIGNER_ID;
2057
+ try {
2058
+ await this.heartbeatManager.getTokenForSigner(signerId, 5e3);
2059
+ } catch {
2060
+ return;
2061
+ }
2062
+ const adoptionStage = this.heartbeatManager.getAdoptionStage();
2063
+ if (!adoptionStage) return;
2064
+ const ENFORCING_STAGES = [
2065
+ "SOFT_ENFORCE",
2066
+ "HARD_ENFORCE",
2067
+ "PROVENANCE",
2068
+ "HARD_KMS_GATEWAY",
2069
+ "HARD_KMS_ATTESTED",
2070
+ "HARD_KMS_ATTESTED_ENCLAVE",
2071
+ "HARD_GCP_CONFIDENTIAL_VM"
2072
+ ];
2073
+ if (this.mode === "SHADOW" && ENFORCING_STAGES.includes(adoptionStage)) {
2074
+ console.warn(
2075
+ `[GATE SDK] Server adoption stage is ${adoptionStage} but SDK mode is SHADOW. Consider updating mode to 'ENFORCE' so your application handles blocks correctly. Until updated, the SDK will allow transactions the server would block.`
2076
+ );
2077
+ }
2078
+ }
1963
2079
  /**
1964
2080
  * Evaluate a transaction defense request
1965
- *
2081
+ *
1966
2082
  * Implements:
1967
2083
  * - Shadow Mode (SHADOW: monitor-only, ENFORCE: enforce decisions)
1968
2084
  * - Connection failure strategy (FAIL_OPEN vs FAIL_CLOSED)
@@ -2223,7 +2339,8 @@ var GateClient = class {
2223
2339
  }
2224
2340
  }
2225
2341
  if (result.decision === "BLOCK") {
2226
- if (requestMode === "SOFT_ENFORCE") {
2342
+ const effectiveMode = result.mode ?? requestMode;
2343
+ if (effectiveMode === "SOFT_ENFORCE") {
2227
2344
  console.warn("[SOFT ENFORCE] Policy violation detected - app can override", {
2228
2345
  requestId,
2229
2346
  reasonCodes: result.reasonCodes
@@ -2237,7 +2354,7 @@ var GateClient = class {
2237
2354
  warning: "Policy violation detected. Override at your own risk."
2238
2355
  };
2239
2356
  }
2240
- if (requestMode === "SHADOW") {
2357
+ if (effectiveMode === "SHADOW") {
2241
2358
  console.warn("[GATE SHADOW MODE] Would have blocked transaction", {
2242
2359
  requestId,
2243
2360
  reasonCodes: result.reasonCodes,