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.cjs +120 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +120 -3
- package/dist/index.js.map +1 -1
- package/dist/pilot/index.cjs +120 -3
- package/dist/pilot/index.cjs.map +1 -1
- package/dist/pilot/index.js +120 -3
- package/dist/pilot/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1217,6 +1217,12 @@ async function handleSignCommand(command, originalClient, gateClient, options) {
|
|
|
1217
1217
|
if (options.mode === "dry-run") {
|
|
1218
1218
|
return await originalClient.send(new clientKms.SignCommand(command));
|
|
1219
1219
|
}
|
|
1220
|
+
const GATEWAY_STAGES = ["HARD_KMS_GATEWAY", "HARD_GCP_GATEWAY", "HARD_HSM_GATEWAY"];
|
|
1221
|
+
const currentStage = gateClient.heartbeatManager?.getAdoptionStage?.();
|
|
1222
|
+
if (currentStage && GATEWAY_STAGES.includes(currentStage)) {
|
|
1223
|
+
emitMetric(options.metricsSink, "sign_success_total", labels);
|
|
1224
|
+
return await signViaProxy(gateClient, decision, command, signerId);
|
|
1225
|
+
}
|
|
1220
1226
|
return await originalClient.send(new clientKms.SignCommand(command));
|
|
1221
1227
|
} catch (error) {
|
|
1222
1228
|
if (error instanceof BlockIntelBlockedError) {
|
|
@@ -1230,6 +1236,72 @@ async function handleSignCommand(command, originalClient, gateClient, options) {
|
|
|
1230
1236
|
throw error;
|
|
1231
1237
|
}
|
|
1232
1238
|
}
|
|
1239
|
+
async function signViaProxy(gateClient, decision, command, signerId) {
|
|
1240
|
+
const config = gateClient.config;
|
|
1241
|
+
const baseUrl = config?.baseUrl || config?.controlPlaneUrl;
|
|
1242
|
+
const tenantId = config?.tenantId;
|
|
1243
|
+
if (!baseUrl || !tenantId) {
|
|
1244
|
+
throw new Error("[Gate SDK] Cannot use signing proxy: baseUrl or tenantId not configured on GateClient");
|
|
1245
|
+
}
|
|
1246
|
+
const message = command.input?.Message ?? command.Message;
|
|
1247
|
+
if (!message) {
|
|
1248
|
+
throw new Error("[Gate SDK] SignCommand missing Message for proxy signing");
|
|
1249
|
+
}
|
|
1250
|
+
const messageBuffer = message instanceof Buffer ? message : Buffer.from(message);
|
|
1251
|
+
const messageBase64 = messageBuffer.toString("base64");
|
|
1252
|
+
const keyId = command.input?.KeyId ?? command.KeyId;
|
|
1253
|
+
if (!keyId) {
|
|
1254
|
+
throw new Error("[Gate SDK] SignCommand missing KeyId for proxy signing");
|
|
1255
|
+
}
|
|
1256
|
+
const signingAlgorithm = command.input?.SigningAlgorithm ?? command.SigningAlgorithm ?? "ECDSA_SHA_256";
|
|
1257
|
+
const messageType = command.input?.MessageType ?? command.MessageType ?? "RAW";
|
|
1258
|
+
const proxyUrl = `${baseUrl.replace("/defense", "")}/tenants/${tenantId}/defense/sign`;
|
|
1259
|
+
const headers = {
|
|
1260
|
+
"Content-Type": "application/json"
|
|
1261
|
+
};
|
|
1262
|
+
const authHeaders = gateClient.getAuthHeaders?.();
|
|
1263
|
+
if (authHeaders) {
|
|
1264
|
+
Object.assign(headers, authHeaders);
|
|
1265
|
+
} else {
|
|
1266
|
+
const auth = config?.auth;
|
|
1267
|
+
if (auth?.mode === "api_key" && auth?.apiKey) {
|
|
1268
|
+
headers["x-api-key"] = auth.apiKey;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
const jwt = gateClient.jwt || gateClient.config?.jwt;
|
|
1272
|
+
if (jwt) {
|
|
1273
|
+
headers["Authorization"] = `Bearer ${jwt}`;
|
|
1274
|
+
}
|
|
1275
|
+
const response = await fetch(proxyUrl, {
|
|
1276
|
+
method: "POST",
|
|
1277
|
+
headers,
|
|
1278
|
+
body: JSON.stringify({
|
|
1279
|
+
requestId: decision.decisionId || decision.requestId,
|
|
1280
|
+
decisionToken: decision.decisionToken,
|
|
1281
|
+
keyId,
|
|
1282
|
+
message: messageBase64,
|
|
1283
|
+
signingAlgorithm,
|
|
1284
|
+
messageType
|
|
1285
|
+
})
|
|
1286
|
+
});
|
|
1287
|
+
if (!response.ok) {
|
|
1288
|
+
const errorBody = await response.json().catch(() => ({}));
|
|
1289
|
+
const code = errorBody?.error?.code || "SIGN_PROXY_FAILED";
|
|
1290
|
+
const msg = errorBody?.error?.message || `Signing proxy returned ${response.status}`;
|
|
1291
|
+
throw new Error(`[Gate SDK] ${code}: ${msg}`);
|
|
1292
|
+
}
|
|
1293
|
+
const result = await response.json();
|
|
1294
|
+
const data = result?.data;
|
|
1295
|
+
if (!data?.signature) {
|
|
1296
|
+
throw new Error("[Gate SDK] Signing proxy returned no signature");
|
|
1297
|
+
}
|
|
1298
|
+
return {
|
|
1299
|
+
Signature: Buffer.from(data.signature, "base64"),
|
|
1300
|
+
KeyId: data.keyId || keyId,
|
|
1301
|
+
SigningAlgorithm: data.signingAlgorithm || signingAlgorithm,
|
|
1302
|
+
$metadata: { httpStatusCode: 200 }
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1233
1305
|
|
|
1234
1306
|
// src/provenance/ProvenanceProvider.ts
|
|
1235
1307
|
var ProvenanceProvider = class {
|
|
@@ -1287,6 +1359,8 @@ var HeartbeatManager = class {
|
|
|
1287
1359
|
started = false;
|
|
1288
1360
|
maxBackoffSeconds = 30;
|
|
1289
1361
|
// Maximum backoff interval
|
|
1362
|
+
/** Server's current adoption stage for this tenant (cached from heartbeat response) */
|
|
1363
|
+
adoptionStage = null;
|
|
1290
1364
|
maxSigners;
|
|
1291
1365
|
signerIdleTtlMs;
|
|
1292
1366
|
localRateLimitMs;
|
|
@@ -1560,6 +1634,9 @@ var HeartbeatManager = class {
|
|
|
1560
1634
|
policyHash: response.data.policyHash
|
|
1561
1635
|
};
|
|
1562
1636
|
entry.consecutiveFailures = 0;
|
|
1637
|
+
if (response.data.adoptionStage != null) {
|
|
1638
|
+
this.adoptionStage = response.data.adoptionStage;
|
|
1639
|
+
}
|
|
1563
1640
|
console.log("[HEARTBEAT] Acquired heartbeat token", {
|
|
1564
1641
|
expiresAt,
|
|
1565
1642
|
signerId,
|
|
@@ -1588,6 +1665,14 @@ var HeartbeatManager = class {
|
|
|
1588
1665
|
getClientInstanceId() {
|
|
1589
1666
|
return this.clientInstanceId;
|
|
1590
1667
|
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Get the server's current adoption stage for this tenant.
|
|
1670
|
+
* Populated after the first successful heartbeat response.
|
|
1671
|
+
* Returns null if not yet received.
|
|
1672
|
+
*/
|
|
1673
|
+
getAdoptionStage() {
|
|
1674
|
+
return this.adoptionStage;
|
|
1675
|
+
}
|
|
1591
1676
|
};
|
|
1592
1677
|
|
|
1593
1678
|
// src/security/IamPermissionRiskChecker.ts
|
|
@@ -1920,6 +2005,8 @@ var GateClient = class {
|
|
|
1920
2005
|
apiKey: heartbeatApiKey
|
|
1921
2006
|
});
|
|
1922
2007
|
this.heartbeatManager.start();
|
|
2008
|
+
this.checkAdoptionStageMismatch().catch(() => {
|
|
2009
|
+
});
|
|
1923
2010
|
}
|
|
1924
2011
|
if (!config.local) {
|
|
1925
2012
|
const enforcementMode = config.enforcementMode || "SOFT";
|
|
@@ -1965,9 +2052,38 @@ var GateClient = class {
|
|
|
1965
2052
|
console.warn("[GATE CLIENT] Async IAM risk check warning:", error instanceof Error ? error.message : String(error));
|
|
1966
2053
|
}
|
|
1967
2054
|
}
|
|
2055
|
+
/**
|
|
2056
|
+
* Warn if the local SDK mode is SHADOW but the server's adoption stage is enforcing.
|
|
2057
|
+
* Runs non-blocking after heartbeat startup; never throws.
|
|
2058
|
+
*/
|
|
2059
|
+
async checkAdoptionStageMismatch() {
|
|
2060
|
+
if (!this.heartbeatManager) return;
|
|
2061
|
+
const signerId = this.config.signerId ?? DEFAULT_SIGNER_ID;
|
|
2062
|
+
try {
|
|
2063
|
+
await this.heartbeatManager.getTokenForSigner(signerId, 5e3);
|
|
2064
|
+
} catch {
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
2067
|
+
const adoptionStage = this.heartbeatManager.getAdoptionStage();
|
|
2068
|
+
if (!adoptionStage) return;
|
|
2069
|
+
const ENFORCING_STAGES = [
|
|
2070
|
+
"SOFT_ENFORCE",
|
|
2071
|
+
"HARD_ENFORCE",
|
|
2072
|
+
"PROVENANCE",
|
|
2073
|
+
"HARD_KMS_GATEWAY",
|
|
2074
|
+
"HARD_KMS_ATTESTED",
|
|
2075
|
+
"HARD_KMS_ATTESTED_ENCLAVE",
|
|
2076
|
+
"HARD_GCP_CONFIDENTIAL_VM"
|
|
2077
|
+
];
|
|
2078
|
+
if (this.mode === "SHADOW" && ENFORCING_STAGES.includes(adoptionStage)) {
|
|
2079
|
+
console.warn(
|
|
2080
|
+
`[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.`
|
|
2081
|
+
);
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
1968
2084
|
/**
|
|
1969
2085
|
* Evaluate a transaction defense request
|
|
1970
|
-
*
|
|
2086
|
+
*
|
|
1971
2087
|
* Implements:
|
|
1972
2088
|
* - Shadow Mode (SHADOW: monitor-only, ENFORCE: enforce decisions)
|
|
1973
2089
|
* - Connection failure strategy (FAIL_OPEN vs FAIL_CLOSED)
|
|
@@ -2228,7 +2344,8 @@ var GateClient = class {
|
|
|
2228
2344
|
}
|
|
2229
2345
|
}
|
|
2230
2346
|
if (result.decision === "BLOCK") {
|
|
2231
|
-
|
|
2347
|
+
const effectiveMode = result.mode ?? requestMode;
|
|
2348
|
+
if (effectiveMode === "SOFT_ENFORCE") {
|
|
2232
2349
|
console.warn("[SOFT ENFORCE] Policy violation detected - app can override", {
|
|
2233
2350
|
requestId,
|
|
2234
2351
|
reasonCodes: result.reasonCodes
|
|
@@ -2242,7 +2359,7 @@ var GateClient = class {
|
|
|
2242
2359
|
warning: "Policy violation detected. Override at your own risk."
|
|
2243
2360
|
};
|
|
2244
2361
|
}
|
|
2245
|
-
if (
|
|
2362
|
+
if (effectiveMode === "SHADOW") {
|
|
2246
2363
|
console.warn("[GATE SHADOW MODE] Would have blocked transaction", {
|
|
2247
2364
|
requestId,
|
|
2248
2365
|
reasonCodes: result.reasonCodes,
|