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/pilot/index.js
CHANGED
|
@@ -1184,6 +1184,12 @@ async function handleSignCommand(command, originalClient, gateClient, options) {
|
|
|
1184
1184
|
if (options.mode === "dry-run") {
|
|
1185
1185
|
return await originalClient.send(new SignCommand(command));
|
|
1186
1186
|
}
|
|
1187
|
+
const GATEWAY_STAGES = ["HARD_KMS_GATEWAY", "HARD_GCP_GATEWAY", "HARD_HSM_GATEWAY"];
|
|
1188
|
+
const currentStage = gateClient.heartbeatManager?.getAdoptionStage?.();
|
|
1189
|
+
if (currentStage && GATEWAY_STAGES.includes(currentStage)) {
|
|
1190
|
+
emitMetric(options.metricsSink, "sign_success_total", labels);
|
|
1191
|
+
return await signViaProxy(gateClient, decision, command, signerId);
|
|
1192
|
+
}
|
|
1187
1193
|
return await originalClient.send(new SignCommand(command));
|
|
1188
1194
|
} catch (error) {
|
|
1189
1195
|
if (error instanceof BlockIntelBlockedError) {
|
|
@@ -1197,6 +1203,72 @@ async function handleSignCommand(command, originalClient, gateClient, options) {
|
|
|
1197
1203
|
throw error;
|
|
1198
1204
|
}
|
|
1199
1205
|
}
|
|
1206
|
+
async function signViaProxy(gateClient, decision, command, signerId) {
|
|
1207
|
+
const config = gateClient.config;
|
|
1208
|
+
const baseUrl = config?.baseUrl || config?.controlPlaneUrl;
|
|
1209
|
+
const tenantId = config?.tenantId;
|
|
1210
|
+
if (!baseUrl || !tenantId) {
|
|
1211
|
+
throw new Error("[Gate SDK] Cannot use signing proxy: baseUrl or tenantId not configured on GateClient");
|
|
1212
|
+
}
|
|
1213
|
+
const message = command.input?.Message ?? command.Message;
|
|
1214
|
+
if (!message) {
|
|
1215
|
+
throw new Error("[Gate SDK] SignCommand missing Message for proxy signing");
|
|
1216
|
+
}
|
|
1217
|
+
const messageBuffer = message instanceof Buffer ? message : Buffer.from(message);
|
|
1218
|
+
const messageBase64 = messageBuffer.toString("base64");
|
|
1219
|
+
const keyId = command.input?.KeyId ?? command.KeyId;
|
|
1220
|
+
if (!keyId) {
|
|
1221
|
+
throw new Error("[Gate SDK] SignCommand missing KeyId for proxy signing");
|
|
1222
|
+
}
|
|
1223
|
+
const signingAlgorithm = command.input?.SigningAlgorithm ?? command.SigningAlgorithm ?? "ECDSA_SHA_256";
|
|
1224
|
+
const messageType = command.input?.MessageType ?? command.MessageType ?? "RAW";
|
|
1225
|
+
const proxyUrl = `${baseUrl.replace("/defense", "")}/tenants/${tenantId}/defense/sign`;
|
|
1226
|
+
const headers = {
|
|
1227
|
+
"Content-Type": "application/json"
|
|
1228
|
+
};
|
|
1229
|
+
const authHeaders = gateClient.getAuthHeaders?.();
|
|
1230
|
+
if (authHeaders) {
|
|
1231
|
+
Object.assign(headers, authHeaders);
|
|
1232
|
+
} else {
|
|
1233
|
+
const auth = config?.auth;
|
|
1234
|
+
if (auth?.mode === "api_key" && auth?.apiKey) {
|
|
1235
|
+
headers["x-api-key"] = auth.apiKey;
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
const jwt = gateClient.jwt || gateClient.config?.jwt;
|
|
1239
|
+
if (jwt) {
|
|
1240
|
+
headers["Authorization"] = `Bearer ${jwt}`;
|
|
1241
|
+
}
|
|
1242
|
+
const response = await fetch(proxyUrl, {
|
|
1243
|
+
method: "POST",
|
|
1244
|
+
headers,
|
|
1245
|
+
body: JSON.stringify({
|
|
1246
|
+
requestId: decision.decisionId || decision.requestId,
|
|
1247
|
+
decisionToken: decision.decisionToken,
|
|
1248
|
+
keyId,
|
|
1249
|
+
message: messageBase64,
|
|
1250
|
+
signingAlgorithm,
|
|
1251
|
+
messageType
|
|
1252
|
+
})
|
|
1253
|
+
});
|
|
1254
|
+
if (!response.ok) {
|
|
1255
|
+
const errorBody = await response.json().catch(() => ({}));
|
|
1256
|
+
const code = errorBody?.error?.code || "SIGN_PROXY_FAILED";
|
|
1257
|
+
const msg = errorBody?.error?.message || `Signing proxy returned ${response.status}`;
|
|
1258
|
+
throw new Error(`[Gate SDK] ${code}: ${msg}`);
|
|
1259
|
+
}
|
|
1260
|
+
const result = await response.json();
|
|
1261
|
+
const data = result?.data;
|
|
1262
|
+
if (!data?.signature) {
|
|
1263
|
+
throw new Error("[Gate SDK] Signing proxy returned no signature");
|
|
1264
|
+
}
|
|
1265
|
+
return {
|
|
1266
|
+
Signature: Buffer.from(data.signature, "base64"),
|
|
1267
|
+
KeyId: data.keyId || keyId,
|
|
1268
|
+
SigningAlgorithm: data.signingAlgorithm || signingAlgorithm,
|
|
1269
|
+
$metadata: { httpStatusCode: 200 }
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1200
1272
|
|
|
1201
1273
|
// src/provenance/ProvenanceProvider.ts
|
|
1202
1274
|
var ProvenanceProvider = class {
|
|
@@ -1254,6 +1326,8 @@ var HeartbeatManager = class {
|
|
|
1254
1326
|
started = false;
|
|
1255
1327
|
maxBackoffSeconds = 30;
|
|
1256
1328
|
// Maximum backoff interval
|
|
1329
|
+
/** Server's current adoption stage for this tenant (cached from heartbeat response) */
|
|
1330
|
+
adoptionStage = null;
|
|
1257
1331
|
maxSigners;
|
|
1258
1332
|
signerIdleTtlMs;
|
|
1259
1333
|
localRateLimitMs;
|
|
@@ -1527,6 +1601,9 @@ var HeartbeatManager = class {
|
|
|
1527
1601
|
policyHash: response.data.policyHash
|
|
1528
1602
|
};
|
|
1529
1603
|
entry.consecutiveFailures = 0;
|
|
1604
|
+
if (response.data.adoptionStage != null) {
|
|
1605
|
+
this.adoptionStage = response.data.adoptionStage;
|
|
1606
|
+
}
|
|
1530
1607
|
console.log("[HEARTBEAT] Acquired heartbeat token", {
|
|
1531
1608
|
expiresAt,
|
|
1532
1609
|
signerId,
|
|
@@ -1555,6 +1632,14 @@ var HeartbeatManager = class {
|
|
|
1555
1632
|
getClientInstanceId() {
|
|
1556
1633
|
return this.clientInstanceId;
|
|
1557
1634
|
}
|
|
1635
|
+
/**
|
|
1636
|
+
* Get the server's current adoption stage for this tenant.
|
|
1637
|
+
* Populated after the first successful heartbeat response.
|
|
1638
|
+
* Returns null if not yet received.
|
|
1639
|
+
*/
|
|
1640
|
+
getAdoptionStage() {
|
|
1641
|
+
return this.adoptionStage;
|
|
1642
|
+
}
|
|
1558
1643
|
};
|
|
1559
1644
|
|
|
1560
1645
|
// src/security/IamPermissionRiskChecker.ts
|
|
@@ -1887,6 +1972,8 @@ var GateClient = class {
|
|
|
1887
1972
|
apiKey: heartbeatApiKey
|
|
1888
1973
|
});
|
|
1889
1974
|
this.heartbeatManager.start();
|
|
1975
|
+
this.checkAdoptionStageMismatch().catch(() => {
|
|
1976
|
+
});
|
|
1890
1977
|
}
|
|
1891
1978
|
if (!config.local) {
|
|
1892
1979
|
const enforcementMode = config.enforcementMode || "SOFT";
|
|
@@ -1932,9 +2019,38 @@ var GateClient = class {
|
|
|
1932
2019
|
console.warn("[GATE CLIENT] Async IAM risk check warning:", error instanceof Error ? error.message : String(error));
|
|
1933
2020
|
}
|
|
1934
2021
|
}
|
|
2022
|
+
/**
|
|
2023
|
+
* Warn if the local SDK mode is SHADOW but the server's adoption stage is enforcing.
|
|
2024
|
+
* Runs non-blocking after heartbeat startup; never throws.
|
|
2025
|
+
*/
|
|
2026
|
+
async checkAdoptionStageMismatch() {
|
|
2027
|
+
if (!this.heartbeatManager) return;
|
|
2028
|
+
const signerId = this.config.signerId ?? DEFAULT_SIGNER_ID;
|
|
2029
|
+
try {
|
|
2030
|
+
await this.heartbeatManager.getTokenForSigner(signerId, 5e3);
|
|
2031
|
+
} catch {
|
|
2032
|
+
return;
|
|
2033
|
+
}
|
|
2034
|
+
const adoptionStage = this.heartbeatManager.getAdoptionStage();
|
|
2035
|
+
if (!adoptionStage) return;
|
|
2036
|
+
const ENFORCING_STAGES = [
|
|
2037
|
+
"SOFT_ENFORCE",
|
|
2038
|
+
"HARD_ENFORCE",
|
|
2039
|
+
"PROVENANCE",
|
|
2040
|
+
"HARD_KMS_GATEWAY",
|
|
2041
|
+
"HARD_KMS_ATTESTED",
|
|
2042
|
+
"HARD_KMS_ATTESTED_ENCLAVE",
|
|
2043
|
+
"HARD_GCP_CONFIDENTIAL_VM"
|
|
2044
|
+
];
|
|
2045
|
+
if (this.mode === "SHADOW" && ENFORCING_STAGES.includes(adoptionStage)) {
|
|
2046
|
+
console.warn(
|
|
2047
|
+
`[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.`
|
|
2048
|
+
);
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
1935
2051
|
/**
|
|
1936
2052
|
* Evaluate a transaction defense request
|
|
1937
|
-
*
|
|
2053
|
+
*
|
|
1938
2054
|
* Implements:
|
|
1939
2055
|
* - Shadow Mode (SHADOW: monitor-only, ENFORCE: enforce decisions)
|
|
1940
2056
|
* - Connection failure strategy (FAIL_OPEN vs FAIL_CLOSED)
|
|
@@ -2195,7 +2311,8 @@ var GateClient = class {
|
|
|
2195
2311
|
}
|
|
2196
2312
|
}
|
|
2197
2313
|
if (result.decision === "BLOCK") {
|
|
2198
|
-
|
|
2314
|
+
const effectiveMode = result.mode ?? requestMode;
|
|
2315
|
+
if (effectiveMode === "SOFT_ENFORCE") {
|
|
2199
2316
|
console.warn("[SOFT ENFORCE] Policy violation detected - app can override", {
|
|
2200
2317
|
requestId,
|
|
2201
2318
|
reasonCodes: result.reasonCodes
|
|
@@ -2209,7 +2326,7 @@ var GateClient = class {
|
|
|
2209
2326
|
warning: "Policy violation detected. Override at your own risk."
|
|
2210
2327
|
};
|
|
2211
2328
|
}
|
|
2212
|
-
if (
|
|
2329
|
+
if (effectiveMode === "SHADOW") {
|
|
2213
2330
|
console.warn("[GATE SHADOW MODE] Would have blocked transaction", {
|
|
2214
2331
|
requestId,
|
|
2215
2332
|
reasonCodes: result.reasonCodes,
|