@nextera.one/axis-server-sdk 1.2.1 → 1.4.0
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/README.md +25 -0
- package/dist/bin/generate-keys.d.mts +2 -0
- package/dist/bin/generate-keys.d.ts +2 -0
- package/dist/bin/generate-keys.js +159 -0
- package/dist/bin/generate-keys.js.map +1 -0
- package/dist/bin/generate-keys.mjs +169 -0
- package/dist/bin/generate-keys.mjs.map +1 -0
- package/dist/core/index.d.mts +2 -25
- package/dist/core/index.d.ts +2 -25
- package/dist/core/index.js +13 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +12 -0
- package/dist/core/index.mjs.map +1 -1
- package/dist/index-B5xzROld.d.mts +122 -0
- package/dist/index-B5xzROld.d.ts +122 -0
- package/dist/index.d.mts +1476 -10
- package/dist/index.d.ts +1476 -10
- package/dist/index.js +3086 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3064 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +14 -2
package/dist/index.js
CHANGED
|
@@ -166,34 +166,45 @@ __export(index_exports, {
|
|
|
166
166
|
buildAts1Hdr: () => buildAts1Hdr,
|
|
167
167
|
buildDtoDecoder: () => buildDtoDecoder,
|
|
168
168
|
buildPacket: () => buildPacket,
|
|
169
|
+
buildQueueMessage: () => buildQueueMessage,
|
|
169
170
|
buildReceiptHash: () => buildReceiptHash,
|
|
170
171
|
buildTLVs: () => buildTLVs,
|
|
172
|
+
buildUnsignedWitness: () => buildUnsignedWitness,
|
|
171
173
|
bytes: () => bytes,
|
|
172
174
|
canAccessResource: () => canAccessResource,
|
|
173
175
|
canonicalJson: () => canonicalJson,
|
|
174
176
|
canonicalJsonExcluding: () => canonicalJsonExcluding,
|
|
177
|
+
canonicalizeObservation: () => canonicalizeObservation,
|
|
175
178
|
classifyIntent: () => classifyIntent,
|
|
176
179
|
computeReceiptHash: () => computeReceiptHash,
|
|
177
180
|
computeSignaturePayload: () => computeSignaturePayload,
|
|
181
|
+
core: () => core_exports,
|
|
182
|
+
crypto: () => crypto_exports,
|
|
178
183
|
decodeArray: () => import_axis_protocol.decodeArray,
|
|
179
184
|
decodeAxis1Frame: () => decodeAxis1Frame,
|
|
180
185
|
decodeFrame: () => decodeFrame,
|
|
181
186
|
decodeObject: () => import_axis_protocol.decodeObject,
|
|
187
|
+
decodeQueueMessage: () => decodeQueueMessage,
|
|
182
188
|
decodeTLVs: () => import_axis_protocol.decodeTLVs,
|
|
183
189
|
decodeTLVsList: () => import_axis_protocol.decodeTLVsList,
|
|
184
190
|
decodeVarint: () => import_axis_protocol3.decodeVarint,
|
|
191
|
+
decorators: () => decorators_exports,
|
|
185
192
|
encVarint: () => encVarint,
|
|
186
193
|
encodeAxis1Frame: () => encodeAxis1Frame,
|
|
187
194
|
encodeFrame: () => encodeFrame,
|
|
195
|
+
encodeQueueMessage: () => encodeQueueMessage,
|
|
188
196
|
encodeTLVs: () => import_axis_protocol.encodeTLVs,
|
|
189
197
|
encodeVarint: () => import_axis_protocol3.encodeVarint,
|
|
198
|
+
engine: () => engine_exports,
|
|
190
199
|
extractDtoSchema: () => extractDtoSchema,
|
|
191
200
|
generateEd25519KeyPair: () => generateEd25519KeyPair,
|
|
192
201
|
getSignTarget: () => getSignTarget,
|
|
193
202
|
hasScope: () => hasScope,
|
|
203
|
+
hashObservation: () => hashObservation,
|
|
194
204
|
isAdminOpcode: () => isAdminOpcode,
|
|
195
205
|
isKnownOpcode: () => isKnownOpcode,
|
|
196
206
|
isTimestampValid: () => isTimestampValid,
|
|
207
|
+
loom: () => loom_exports,
|
|
197
208
|
nonce16: () => nonce16,
|
|
198
209
|
normalizeSensorDecision: () => normalizeSensorDecision,
|
|
199
210
|
packPasskeyLoginOptionsReq: () => packPasskeyLoginOptionsReq,
|
|
@@ -201,21 +212,29 @@ __export(index_exports, {
|
|
|
201
212
|
packPasskeyLoginVerifyReq: () => packPasskeyLoginVerifyReq,
|
|
202
213
|
packPasskeyLoginVerifyRes: () => packPasskeyLoginVerifyRes,
|
|
203
214
|
packPasskeyRegisterOptionsReq: () => packPasskeyRegisterOptionsReq,
|
|
215
|
+
parseAutoClaimEntries: () => parseAutoClaimEntries,
|
|
204
216
|
parseScope: () => parseScope,
|
|
217
|
+
parseStreamEntries: () => parseStreamEntries,
|
|
205
218
|
resolveTimeout: () => resolveTimeout,
|
|
219
|
+
schemas: () => schemas_exports,
|
|
220
|
+
security: () => security_exports,
|
|
206
221
|
sensitivityName: () => sensitivityName,
|
|
222
|
+
sensors: () => sensors_exports,
|
|
207
223
|
sha256: () => sha256,
|
|
208
224
|
signFrame: () => signFrame,
|
|
225
|
+
stableJsonStringify: () => stableJsonStringify,
|
|
209
226
|
tlv: () => tlv,
|
|
210
227
|
u64be: () => u64be,
|
|
211
228
|
unpackPasskeyLoginOptionsReq: () => unpackPasskeyLoginOptionsReq,
|
|
212
229
|
unpackPasskeyLoginVerifyReq: () => unpackPasskeyLoginVerifyReq,
|
|
213
230
|
unpackPasskeyRegisterOptionsReq: () => unpackPasskeyRegisterOptionsReq,
|
|
214
231
|
utf8: () => utf8,
|
|
232
|
+
utils: () => utils_exports,
|
|
215
233
|
validateFrameShape: () => validateFrameShape,
|
|
216
234
|
varintLength: () => import_axis_protocol3.varintLength,
|
|
217
235
|
varintU: () => varintU,
|
|
218
|
-
verifyFrameSignature: () => verifyFrameSignature
|
|
236
|
+
verifyFrameSignature: () => verifyFrameSignature,
|
|
237
|
+
verifyResponse: () => verifyResponse
|
|
219
238
|
});
|
|
220
239
|
module.exports = __toCommonJS(index_exports);
|
|
221
240
|
|
|
@@ -910,9 +929,213 @@ IntentRouter = __decorateClass([
|
|
|
910
929
|
__decorateParam(0, (0, import_common2.Optional)())
|
|
911
930
|
], IntentRouter);
|
|
912
931
|
|
|
932
|
+
// src/engine/observation/stable-json.ts
|
|
933
|
+
function normalize(value) {
|
|
934
|
+
if (Array.isArray(value)) {
|
|
935
|
+
return value.map((item) => normalize(item));
|
|
936
|
+
}
|
|
937
|
+
if (value && typeof value === "object") {
|
|
938
|
+
const entries = Object.entries(value).filter(([, nested]) => nested !== void 0).sort(([left], [right]) => left.localeCompare(right));
|
|
939
|
+
const normalized = {};
|
|
940
|
+
for (const [key, nested] of entries) {
|
|
941
|
+
normalized[key] = normalize(nested);
|
|
942
|
+
}
|
|
943
|
+
return normalized;
|
|
944
|
+
}
|
|
945
|
+
return value;
|
|
946
|
+
}
|
|
947
|
+
function stableJsonStringify(value) {
|
|
948
|
+
return JSON.stringify(normalize(value));
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// src/engine/observation/observation-queue.codec.ts
|
|
952
|
+
function buildQueueMessage(observation, sourceNodeId, previous, lastError) {
|
|
953
|
+
const now = Date.now();
|
|
954
|
+
return {
|
|
955
|
+
v: 1,
|
|
956
|
+
observation,
|
|
957
|
+
attempts: previous ? previous.attempts + 1 : 0,
|
|
958
|
+
firstEnqueuedAt: previous?.firstEnqueuedAt ?? now,
|
|
959
|
+
lastEnqueuedAt: now,
|
|
960
|
+
sourceNodeId,
|
|
961
|
+
lastError
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
function encodeQueueMessage(message) {
|
|
965
|
+
return JSON.stringify(message);
|
|
966
|
+
}
|
|
967
|
+
function decodeQueueMessage(raw) {
|
|
968
|
+
try {
|
|
969
|
+
const parsed = JSON.parse(raw);
|
|
970
|
+
if (!parsed || parsed.v !== 1 || !parsed.observation?.id) {
|
|
971
|
+
return null;
|
|
972
|
+
}
|
|
973
|
+
return parsed;
|
|
974
|
+
} catch {
|
|
975
|
+
return null;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
function parseStreamEntries(raw) {
|
|
979
|
+
if (!Array.isArray(raw)) {
|
|
980
|
+
return [];
|
|
981
|
+
}
|
|
982
|
+
const entries = [];
|
|
983
|
+
for (const streamRow of raw) {
|
|
984
|
+
if (!Array.isArray(streamRow) || streamRow.length < 2) {
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
const messageRows = streamRow[1];
|
|
988
|
+
if (!Array.isArray(messageRows)) {
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
for (const row of messageRows) {
|
|
992
|
+
if (!Array.isArray(row) || row.length < 2) {
|
|
993
|
+
continue;
|
|
994
|
+
}
|
|
995
|
+
const id = String(row[0]);
|
|
996
|
+
const fields = Array.isArray(row[1]) ? row[1] : [];
|
|
997
|
+
const fieldMap = fieldsToMap(fields);
|
|
998
|
+
const payload = fieldMap.get("payload");
|
|
999
|
+
if (!payload) {
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
const message = decodeQueueMessage(payload);
|
|
1003
|
+
if (!message) {
|
|
1004
|
+
continue;
|
|
1005
|
+
}
|
|
1006
|
+
entries.push({ id, message });
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
return entries;
|
|
1010
|
+
}
|
|
1011
|
+
function parseAutoClaimEntries(raw) {
|
|
1012
|
+
if (!Array.isArray(raw) || raw.length < 2) {
|
|
1013
|
+
return [];
|
|
1014
|
+
}
|
|
1015
|
+
const rows = Array.isArray(raw[1]) ? raw[1] : [];
|
|
1016
|
+
return parseStreamEntries([["stream", rows]]);
|
|
1017
|
+
}
|
|
1018
|
+
function fieldsToMap(fields) {
|
|
1019
|
+
const map3 = /* @__PURE__ */ new Map();
|
|
1020
|
+
for (let i = 0; i < fields.length; i += 2) {
|
|
1021
|
+
const key = fields[i];
|
|
1022
|
+
const value = fields[i + 1];
|
|
1023
|
+
if (key !== void 0 && value !== void 0) {
|
|
1024
|
+
map3.set(String(key), String(value));
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
return map3;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// src/engine/observation/observation-hash.ts
|
|
1031
|
+
var import_crypto = require("crypto");
|
|
1032
|
+
function canonicalizeObservation(obs) {
|
|
1033
|
+
const obj = {
|
|
1034
|
+
id: obs.id,
|
|
1035
|
+
startMs: obs.startMs,
|
|
1036
|
+
endMs: obs.endMs,
|
|
1037
|
+
transport: obs.transport,
|
|
1038
|
+
ip: obs.ip,
|
|
1039
|
+
intent: obs.intent,
|
|
1040
|
+
actorId: obs.actorId,
|
|
1041
|
+
capsuleId: obs.capsuleId,
|
|
1042
|
+
decision: obs.decision,
|
|
1043
|
+
resultCode: obs.resultCode,
|
|
1044
|
+
statusCode: obs.statusCode,
|
|
1045
|
+
durationMs: obs.durationMs,
|
|
1046
|
+
stages: obs.stages.map((s) => ({
|
|
1047
|
+
name: s.name,
|
|
1048
|
+
status: s.status,
|
|
1049
|
+
startMs: s.startMs,
|
|
1050
|
+
endMs: s.endMs,
|
|
1051
|
+
durationMs: s.durationMs,
|
|
1052
|
+
reason: s.reason,
|
|
1053
|
+
code: s.code
|
|
1054
|
+
})),
|
|
1055
|
+
sensors: obs.sensors.map((s) => ({
|
|
1056
|
+
name: s.name,
|
|
1057
|
+
allowed: s.allowed,
|
|
1058
|
+
riskScore: s.riskScore,
|
|
1059
|
+
durationMs: s.durationMs,
|
|
1060
|
+
reasons: s.reasons,
|
|
1061
|
+
code: s.code
|
|
1062
|
+
}))
|
|
1063
|
+
};
|
|
1064
|
+
return stableJsonStringify(obj);
|
|
1065
|
+
}
|
|
1066
|
+
function hashObservation(obs) {
|
|
1067
|
+
const canonical = canonicalizeObservation(obs);
|
|
1068
|
+
return (0, import_crypto.createHash)("sha256").update(canonical).digest("hex");
|
|
1069
|
+
}
|
|
1070
|
+
function buildUnsignedWitness(obs) {
|
|
1071
|
+
if (!obs.decision || !obs.endMs) {
|
|
1072
|
+
return null;
|
|
1073
|
+
}
|
|
1074
|
+
return {
|
|
1075
|
+
v: 1,
|
|
1076
|
+
observationId: obs.id,
|
|
1077
|
+
payloadHash: hashObservation(obs),
|
|
1078
|
+
sealedAt: Date.now(),
|
|
1079
|
+
summary: {
|
|
1080
|
+
intent: obs.intent,
|
|
1081
|
+
actorId: obs.actorId,
|
|
1082
|
+
decision: obs.decision,
|
|
1083
|
+
statusCode: obs.statusCode,
|
|
1084
|
+
durationMs: obs.durationMs,
|
|
1085
|
+
sensorCount: obs.sensors.length,
|
|
1086
|
+
stageCount: obs.stages.length
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
|
|
913
1091
|
// src/core/constants.ts
|
|
914
1092
|
var import_axis_protocol2 = require("@nextera.one/axis-protocol");
|
|
915
1093
|
|
|
1094
|
+
// src/engine/observation/response-observer.ts
|
|
1095
|
+
var SENSITIVE_RESPONSE_TAGS = [4, 5, 6];
|
|
1096
|
+
function verifyResponse(ctx, response) {
|
|
1097
|
+
if (!response.effect || typeof response.effect !== "string") {
|
|
1098
|
+
return {
|
|
1099
|
+
passed: false,
|
|
1100
|
+
code: "OBSERVER_INVALID_EFFECT",
|
|
1101
|
+
reason: "Response effect is missing or invalid"
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
if (response.ok && (!response.body || response.body.length === 0)) {
|
|
1105
|
+
return {
|
|
1106
|
+
passed: false,
|
|
1107
|
+
code: "OBSERVER_EMPTY_BODY",
|
|
1108
|
+
reason: "Successful response must contain a body"
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
if (response.body && response.body.length > import_axis_protocol2.MAX_BODY_LEN) {
|
|
1112
|
+
return {
|
|
1113
|
+
passed: false,
|
|
1114
|
+
code: "OBSERVER_BODY_OVERFLOW",
|
|
1115
|
+
reason: `Response body exceeds ${import_axis_protocol2.MAX_BODY_LEN} bytes`
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
if (response.headers) {
|
|
1119
|
+
for (const tag of SENSITIVE_RESPONSE_TAGS) {
|
|
1120
|
+
if (response.headers.has(tag)) {
|
|
1121
|
+
return {
|
|
1122
|
+
passed: false,
|
|
1123
|
+
code: "OBSERVER_DATA_LEAK",
|
|
1124
|
+
reason: `Response must not contain sensitive TLV tag ${tag}`
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
if (response.effect.includes("Error:") || response.effect.includes("stack") || response.effect.includes("at /")) {
|
|
1130
|
+
return {
|
|
1131
|
+
passed: false,
|
|
1132
|
+
code: "OBSERVER_INFO_LEAK",
|
|
1133
|
+
reason: "Response effect may contain internal error details"
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
return { passed: true };
|
|
1137
|
+
}
|
|
1138
|
+
|
|
916
1139
|
// src/core/varint.ts
|
|
917
1140
|
var import_axis_protocol3 = require("@nextera.one/axis-protocol");
|
|
918
1141
|
|
|
@@ -1188,7 +1411,7 @@ __export(ats1_exports, {
|
|
|
1188
1411
|
tlvsToMap: () => tlvsToMap,
|
|
1189
1412
|
validateTLVsAgainstSchema: () => validateTLVsAgainstSchema
|
|
1190
1413
|
});
|
|
1191
|
-
var
|
|
1414
|
+
var import_crypto2 = require("crypto");
|
|
1192
1415
|
var DEFAULT_LIMITS = {
|
|
1193
1416
|
maxVarintBytes: 10,
|
|
1194
1417
|
maxTlvCount: 512,
|
|
@@ -1238,7 +1461,7 @@ function decodeU64BE(buf) {
|
|
|
1238
1461
|
return buf.readBigUInt64BE(0);
|
|
1239
1462
|
}
|
|
1240
1463
|
function sha2562(data) {
|
|
1241
|
-
return (0,
|
|
1464
|
+
return (0, import_crypto2.createHash)("sha256").update(data).digest();
|
|
1242
1465
|
}
|
|
1243
1466
|
function encodeTLV(tag, value) {
|
|
1244
1467
|
if (!Number.isInteger(tag) || tag <= 0)
|
|
@@ -1827,7 +2050,7 @@ function packPasskeyLoginVerifyRes(params) {
|
|
|
1827
2050
|
}
|
|
1828
2051
|
|
|
1829
2052
|
// src/codec/tlv.encode.ts
|
|
1830
|
-
var
|
|
2053
|
+
var import_crypto3 = require("crypto");
|
|
1831
2054
|
function encVarint(x) {
|
|
1832
2055
|
if (x < 0n) throw new Error("VARINT_NEG");
|
|
1833
2056
|
const out = [];
|
|
@@ -1855,7 +2078,7 @@ function bytes(b) {
|
|
|
1855
2078
|
return Buffer.isBuffer(b) ? b : Buffer.from(b);
|
|
1856
2079
|
}
|
|
1857
2080
|
function nonce16() {
|
|
1858
|
-
return (0,
|
|
2081
|
+
return (0, import_crypto3.randomBytes)(16);
|
|
1859
2082
|
}
|
|
1860
2083
|
function tlv(type, value) {
|
|
1861
2084
|
if (!Number.isSafeInteger(type) || type < 0) throw new Error("TLV_BAD_TYPE");
|
|
@@ -2401,9 +2624,9 @@ function isAdminOpcode(op) {
|
|
|
2401
2624
|
}
|
|
2402
2625
|
|
|
2403
2626
|
// src/core/receipt.ts
|
|
2404
|
-
var
|
|
2627
|
+
var import_crypto4 = require("crypto");
|
|
2405
2628
|
function buildReceiptHash(prevHash, pid, actorId, intent, effect, ts) {
|
|
2406
|
-
const h = (0,
|
|
2629
|
+
const h = (0, import_crypto4.createHash)("sha256");
|
|
2407
2630
|
if (prevHash) h.update(prevHash);
|
|
2408
2631
|
h.update(pid);
|
|
2409
2632
|
h.update(Buffer.from(actorId, "utf8"));
|
|
@@ -2807,6 +3030,2842 @@ var DiskUploadFileStore = class {
|
|
|
2807
3030
|
return fs.createReadStream(tempPath);
|
|
2808
3031
|
}
|
|
2809
3032
|
};
|
|
3033
|
+
|
|
3034
|
+
// src/core/index.ts
|
|
3035
|
+
var core_exports = {};
|
|
3036
|
+
__export(core_exports, {
|
|
3037
|
+
AXIS_MAGIC: () => import_axis_protocol2.AXIS_MAGIC,
|
|
3038
|
+
AXIS_VERSION: () => import_axis_protocol2.AXIS_VERSION,
|
|
3039
|
+
AxisError: () => AxisError,
|
|
3040
|
+
AxisFrameZ: () => AxisFrameZ,
|
|
3041
|
+
BodyProfile: () => import_axis_protocol2.BodyProfile,
|
|
3042
|
+
ERR_BAD_SIGNATURE: () => import_axis_protocol2.ERR_BAD_SIGNATURE,
|
|
3043
|
+
ERR_CONTRACT_VIOLATION: () => import_axis_protocol2.ERR_CONTRACT_VIOLATION,
|
|
3044
|
+
ERR_INVALID_PACKET: () => import_axis_protocol2.ERR_INVALID_PACKET,
|
|
3045
|
+
ERR_REPLAY_DETECTED: () => import_axis_protocol2.ERR_REPLAY_DETECTED,
|
|
3046
|
+
FLAG_BODY_TLV: () => import_axis_protocol2.FLAG_BODY_TLV,
|
|
3047
|
+
FLAG_CHAIN_REQ: () => import_axis_protocol2.FLAG_CHAIN_REQ,
|
|
3048
|
+
FLAG_HAS_WITNESS: () => import_axis_protocol2.FLAG_HAS_WITNESS,
|
|
3049
|
+
MAX_BODY_LEN: () => import_axis_protocol2.MAX_BODY_LEN,
|
|
3050
|
+
MAX_FRAME_LEN: () => import_axis_protocol2.MAX_FRAME_LEN,
|
|
3051
|
+
MAX_HDR_LEN: () => import_axis_protocol2.MAX_HDR_LEN,
|
|
3052
|
+
MAX_SIG_LEN: () => import_axis_protocol2.MAX_SIG_LEN,
|
|
3053
|
+
NCERT_ALG: () => import_axis_protocol2.NCERT_ALG,
|
|
3054
|
+
NCERT_EXP: () => import_axis_protocol2.NCERT_EXP,
|
|
3055
|
+
NCERT_ISSUER_KID: () => import_axis_protocol2.NCERT_ISSUER_KID,
|
|
3056
|
+
NCERT_KID: () => import_axis_protocol2.NCERT_KID,
|
|
3057
|
+
NCERT_NBF: () => import_axis_protocol2.NCERT_NBF,
|
|
3058
|
+
NCERT_NODE_ID: () => import_axis_protocol2.NCERT_NODE_ID,
|
|
3059
|
+
NCERT_PAYLOAD: () => import_axis_protocol2.NCERT_PAYLOAD,
|
|
3060
|
+
NCERT_PUB: () => import_axis_protocol2.NCERT_PUB,
|
|
3061
|
+
NCERT_SCOPE: () => import_axis_protocol2.NCERT_SCOPE,
|
|
3062
|
+
NCERT_SIG: () => import_axis_protocol2.NCERT_SIG,
|
|
3063
|
+
PROOF_CAPSULE: () => import_axis_protocol2.PROOF_CAPSULE,
|
|
3064
|
+
PROOF_JWT: () => import_axis_protocol2.PROOF_JWT,
|
|
3065
|
+
PROOF_LOOM: () => import_axis_protocol2.PROOF_LOOM,
|
|
3066
|
+
PROOF_MTLS: () => import_axis_protocol2.PROOF_MTLS,
|
|
3067
|
+
PROOF_NONE: () => import_axis_protocol2.PROOF_NONE,
|
|
3068
|
+
PROOF_WITNESS: () => import_axis_protocol2.PROOF_WITNESS,
|
|
3069
|
+
ProofType: () => import_axis_protocol2.ProofType,
|
|
3070
|
+
TLV: () => import_axis_protocol.TLV,
|
|
3071
|
+
TLV_ACTOR_ID: () => import_axis_protocol2.TLV_ACTOR_ID,
|
|
3072
|
+
TLV_AUD: () => import_axis_protocol2.TLV_AUD,
|
|
3073
|
+
TLV_BODY_ARR: () => import_axis_protocol2.TLV_BODY_ARR,
|
|
3074
|
+
TLV_BODY_OBJ: () => import_axis_protocol2.TLV_BODY_OBJ,
|
|
3075
|
+
TLV_CAPSULE: () => import_axis_protocol2.TLV_CAPSULE,
|
|
3076
|
+
TLV_EFFECT: () => import_axis_protocol2.TLV_EFFECT,
|
|
3077
|
+
TLV_ERROR_CODE: () => import_axis_protocol2.TLV_ERROR_CODE,
|
|
3078
|
+
TLV_ERROR_MSG: () => import_axis_protocol2.TLV_ERROR_MSG,
|
|
3079
|
+
TLV_INDEX: () => import_axis_protocol2.TLV_INDEX,
|
|
3080
|
+
TLV_INTENT: () => import_axis_protocol2.TLV_INTENT,
|
|
3081
|
+
TLV_KID: () => import_axis_protocol2.TLV_KID,
|
|
3082
|
+
TLV_LOOM_PRESENCE_ID: () => import_axis_protocol2.TLV_LOOM_PRESENCE_ID,
|
|
3083
|
+
TLV_LOOM_THREAD_HASH: () => import_axis_protocol2.TLV_LOOM_THREAD_HASH,
|
|
3084
|
+
TLV_LOOM_WRIT: () => import_axis_protocol2.TLV_LOOM_WRIT,
|
|
3085
|
+
TLV_NODE: () => import_axis_protocol2.TLV_NODE,
|
|
3086
|
+
TLV_NODE_CERT_HASH: () => import_axis_protocol2.TLV_NODE_CERT_HASH,
|
|
3087
|
+
TLV_NODE_KID: () => import_axis_protocol2.TLV_NODE_KID,
|
|
3088
|
+
TLV_NONCE: () => import_axis_protocol2.TLV_NONCE,
|
|
3089
|
+
TLV_OFFSET: () => import_axis_protocol2.TLV_OFFSET,
|
|
3090
|
+
TLV_OK: () => import_axis_protocol2.TLV_OK,
|
|
3091
|
+
TLV_PID: () => import_axis_protocol2.TLV_PID,
|
|
3092
|
+
TLV_PREV_HASH: () => import_axis_protocol2.TLV_PREV_HASH,
|
|
3093
|
+
TLV_PROOF_REF: () => import_axis_protocol2.TLV_PROOF_REF,
|
|
3094
|
+
TLV_PROOF_TYPE: () => import_axis_protocol2.TLV_PROOF_TYPE,
|
|
3095
|
+
TLV_REALM: () => import_axis_protocol2.TLV_REALM,
|
|
3096
|
+
TLV_RECEIPT_HASH: () => import_axis_protocol2.TLV_RECEIPT_HASH,
|
|
3097
|
+
TLV_RID: () => import_axis_protocol2.TLV_RID,
|
|
3098
|
+
TLV_SHA256_CHUNK: () => import_axis_protocol2.TLV_SHA256_CHUNK,
|
|
3099
|
+
TLV_TRACE_ID: () => import_axis_protocol2.TLV_TRACE_ID,
|
|
3100
|
+
TLV_TS: () => import_axis_protocol2.TLV_TS,
|
|
3101
|
+
TLV_UPLOAD_ID: () => import_axis_protocol2.TLV_UPLOAD_ID,
|
|
3102
|
+
computeReceiptHash: () => computeReceiptHash,
|
|
3103
|
+
computeSignaturePayload: () => computeSignaturePayload,
|
|
3104
|
+
decodeArray: () => import_axis_protocol.decodeArray,
|
|
3105
|
+
decodeFrame: () => decodeFrame,
|
|
3106
|
+
decodeObject: () => import_axis_protocol.decodeObject,
|
|
3107
|
+
decodeTLVs: () => import_axis_protocol.decodeTLVs,
|
|
3108
|
+
decodeTLVsList: () => import_axis_protocol.decodeTLVsList,
|
|
3109
|
+
decodeVarint: () => import_axis_protocol3.decodeVarint,
|
|
3110
|
+
encodeFrame: () => encodeFrame,
|
|
3111
|
+
encodeTLVs: () => import_axis_protocol.encodeTLVs,
|
|
3112
|
+
encodeVarint: () => import_axis_protocol3.encodeVarint,
|
|
3113
|
+
generateEd25519KeyPair: () => generateEd25519KeyPair,
|
|
3114
|
+
getSignTarget: () => getSignTarget,
|
|
3115
|
+
sha256: () => sha256,
|
|
3116
|
+
signFrame: () => signFrame,
|
|
3117
|
+
varintLength: () => import_axis_protocol3.varintLength,
|
|
3118
|
+
verifyFrameSignature: () => verifyFrameSignature
|
|
3119
|
+
});
|
|
3120
|
+
|
|
3121
|
+
// src/core/axis-error.ts
|
|
3122
|
+
var AxisError = class extends Error {
|
|
3123
|
+
constructor(code, message, httpStatus = 400, details) {
|
|
3124
|
+
super(message);
|
|
3125
|
+
this.code = code;
|
|
3126
|
+
this.httpStatus = httpStatus;
|
|
3127
|
+
this.details = details;
|
|
3128
|
+
this.name = "AxisError";
|
|
3129
|
+
}
|
|
3130
|
+
};
|
|
3131
|
+
|
|
3132
|
+
// src/crypto/index.ts
|
|
3133
|
+
var crypto_exports = {};
|
|
3134
|
+
__export(crypto_exports, {
|
|
3135
|
+
ProofVerificationService: () => ProofVerificationService,
|
|
3136
|
+
b64urlDecode: () => b64urlDecode,
|
|
3137
|
+
b64urlDecodeString: () => b64urlDecodeString,
|
|
3138
|
+
b64urlEncode: () => b64urlEncode,
|
|
3139
|
+
b64urlEncodeString: () => b64urlEncodeString,
|
|
3140
|
+
canonicalJson: () => canonicalJson,
|
|
3141
|
+
canonicalJsonExcluding: () => canonicalJsonExcluding
|
|
3142
|
+
});
|
|
3143
|
+
|
|
3144
|
+
// src/crypto/proof-verification.service.ts
|
|
3145
|
+
var import_common4 = require("@nestjs/common");
|
|
3146
|
+
var crypto3 = __toESM(require("crypto"));
|
|
3147
|
+
var nacl = __toESM(require("tweetnacl"));
|
|
3148
|
+
var ProofVerificationService = class {
|
|
3149
|
+
constructor() {
|
|
3150
|
+
this.logger = new import_common4.Logger(ProofVerificationService.name);
|
|
3151
|
+
// Cache of registered device public keys (deviceId -> pubKey)
|
|
3152
|
+
this.deviceKeys = /* @__PURE__ */ new Map();
|
|
3153
|
+
// Cache of trusted mTLS certificate fingerprints
|
|
3154
|
+
this.trustedCerts = /* @__PURE__ */ new Map();
|
|
3155
|
+
}
|
|
3156
|
+
/**
|
|
3157
|
+
* Verifies an authentication proof based on its type.
|
|
3158
|
+
*
|
|
3159
|
+
* **Supported Types:**
|
|
3160
|
+
* - 1 (CAPSULE): Delegated to `verifyCapsuleProof`
|
|
3161
|
+
* - 2 (JWT): Verified by `verifyJWTProof`
|
|
3162
|
+
* - 3 (MTLS_ID): Verified by `verifyMTLSProof`
|
|
3163
|
+
* - 4 (DEVICE_SE): Verified by `verifyDeviceSEProof`
|
|
3164
|
+
*
|
|
3165
|
+
* @param {ProofType} proofType - The numeric AXIS proof type
|
|
3166
|
+
* @param {Uint8Array} proofRef - The binary reference or token for the proof
|
|
3167
|
+
* @param {Object} context - Additional metadata required for specific proof types
|
|
3168
|
+
* @param {Uint8Array} [context.signTarget] - The canonical bytes that were signed (for Ed25519)
|
|
3169
|
+
* @param {Uint8Array} [context.signature] - The signature to verify (for Ed25519)
|
|
3170
|
+
* @param {MTLSContext} [context.mtls] - mTLS certificate data
|
|
3171
|
+
* @param {DeviceSEContext} [context.deviceSE] - Device Secure Element information
|
|
3172
|
+
* @returns {Promise<ProofVerificationResult>} The outcome of the verification
|
|
3173
|
+
*/
|
|
3174
|
+
async verifyProof(proofType, proofRef, context) {
|
|
3175
|
+
switch (proofType) {
|
|
3176
|
+
case 1:
|
|
3177
|
+
return this.verifyCapsuleProof(proofRef);
|
|
3178
|
+
case 2:
|
|
3179
|
+
return this.verifyJWTProof(proofRef);
|
|
3180
|
+
case 3:
|
|
3181
|
+
return this.verifyMTLSProof(context.mtls);
|
|
3182
|
+
case 4:
|
|
3183
|
+
return this.verifyDeviceSEProof(
|
|
3184
|
+
context.signTarget,
|
|
3185
|
+
context.signature,
|
|
3186
|
+
context.deviceSE
|
|
3187
|
+
);
|
|
3188
|
+
default:
|
|
3189
|
+
return { valid: false, error: `Unknown proof type: ${proofType}` };
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
/**
|
|
3193
|
+
* Verify CAPSULE proof (delegated to CapsuleService)
|
|
3194
|
+
*/
|
|
3195
|
+
async verifyCapsuleProof(proofRef) {
|
|
3196
|
+
const capsuleId = new TextDecoder().decode(proofRef);
|
|
3197
|
+
return {
|
|
3198
|
+
valid: true,
|
|
3199
|
+
metadata: { capsuleId, requiresCapsuleValidation: true }
|
|
3200
|
+
};
|
|
3201
|
+
}
|
|
3202
|
+
/**
|
|
3203
|
+
* Verifies a JSON Web Token (JWT) proof.
|
|
3204
|
+
*
|
|
3205
|
+
* **Validation Logic:**
|
|
3206
|
+
* 1. Decodes the token string.
|
|
3207
|
+
* 2. Checks for valid 3-part JWT structure.
|
|
3208
|
+
* 3. Validates `exp` (expiration) and `nbf` (not before) claims.
|
|
3209
|
+
* 4. Extracts `actor_id` or `sub` as the identity.
|
|
3210
|
+
*
|
|
3211
|
+
* @param {Uint8Array} proofRef - Binary representation of the JWT string
|
|
3212
|
+
* @returns {Promise<ProofVerificationResult>} Result including the actor identifier
|
|
3213
|
+
*/
|
|
3214
|
+
async verifyJWTProof(proofRef) {
|
|
3215
|
+
try {
|
|
3216
|
+
const token = new TextDecoder().decode(proofRef);
|
|
3217
|
+
const parts = token.split(".");
|
|
3218
|
+
if (parts.length !== 3) {
|
|
3219
|
+
return { valid: false, error: "Invalid JWT format" };
|
|
3220
|
+
}
|
|
3221
|
+
const header = JSON.parse(Buffer.from(parts[0], "base64url").toString());
|
|
3222
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
3223
|
+
if (payload.exp && Date.now() / 1e3 > payload.exp) {
|
|
3224
|
+
return { valid: false, error: "JWT expired" };
|
|
3225
|
+
}
|
|
3226
|
+
if (payload.nbf && Date.now() / 1e3 < payload.nbf) {
|
|
3227
|
+
return { valid: false, error: "JWT not yet valid" };
|
|
3228
|
+
}
|
|
3229
|
+
return {
|
|
3230
|
+
valid: true,
|
|
3231
|
+
actorId: payload.sub || payload.actor_id,
|
|
3232
|
+
metadata: { iss: payload.iss, scope: payload.scope }
|
|
3233
|
+
};
|
|
3234
|
+
} catch (e) {
|
|
3235
|
+
const message = e instanceof Error ? e.message : "Unknown error";
|
|
3236
|
+
return { valid: false, error: `JWT parse error: ${message}` };
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
/**
|
|
3240
|
+
* Verify mTLS client certificate proof
|
|
3241
|
+
*/
|
|
3242
|
+
async verifyMTLSProof(mtls) {
|
|
3243
|
+
if (!mtls) {
|
|
3244
|
+
return { valid: false, error: "No mTLS context provided" };
|
|
3245
|
+
}
|
|
3246
|
+
if (!mtls.verified) {
|
|
3247
|
+
return { valid: false, error: "mTLS not verified by TLS terminator" };
|
|
3248
|
+
}
|
|
3249
|
+
if (mtls.clientCertFingerprint) {
|
|
3250
|
+
const trusted = this.trustedCerts.get(mtls.clientCertFingerprint);
|
|
3251
|
+
if (trusted) {
|
|
3252
|
+
return {
|
|
3253
|
+
valid: true,
|
|
3254
|
+
actorId: trusted.actorId,
|
|
3255
|
+
metadata: {
|
|
3256
|
+
fingerprint: mtls.clientCertFingerprint,
|
|
3257
|
+
subject: mtls.clientCertSubject
|
|
3258
|
+
}
|
|
3259
|
+
};
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
if (mtls.clientCertSubject) {
|
|
3263
|
+
const cnMatch = mtls.clientCertSubject.match(/CN=([^,]+)/);
|
|
3264
|
+
if (cnMatch) {
|
|
3265
|
+
return {
|
|
3266
|
+
valid: true,
|
|
3267
|
+
actorId: cnMatch[1],
|
|
3268
|
+
metadata: {
|
|
3269
|
+
subject: mtls.clientCertSubject,
|
|
3270
|
+
issuer: mtls.clientCertIssuer
|
|
3271
|
+
}
|
|
3272
|
+
};
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
return { valid: false, error: "Could not extract actor from certificate" };
|
|
3276
|
+
}
|
|
3277
|
+
/**
|
|
3278
|
+
* Verify Device Secure Element signature
|
|
3279
|
+
*/
|
|
3280
|
+
async verifyDeviceSEProof(signTarget, signature, deviceSE) {
|
|
3281
|
+
if (!deviceSE || !signTarget || !signature) {
|
|
3282
|
+
return { valid: false, error: "Missing Device SE context" };
|
|
3283
|
+
}
|
|
3284
|
+
let publicKey = deviceSE.publicKey;
|
|
3285
|
+
const registeredKey = this.deviceKeys.get(deviceSE.deviceId);
|
|
3286
|
+
if (registeredKey) {
|
|
3287
|
+
publicKey = registeredKey;
|
|
3288
|
+
}
|
|
3289
|
+
if (!publicKey || publicKey.length !== 32) {
|
|
3290
|
+
return {
|
|
3291
|
+
valid: false,
|
|
3292
|
+
error: "Invalid or unregistered device public key"
|
|
3293
|
+
};
|
|
3294
|
+
}
|
|
3295
|
+
try {
|
|
3296
|
+
const valid = nacl.sign.detached.verify(signTarget, signature, publicKey);
|
|
3297
|
+
if (!valid) {
|
|
3298
|
+
return { valid: false, error: "Device signature verification failed" };
|
|
3299
|
+
}
|
|
3300
|
+
return {
|
|
3301
|
+
valid: true,
|
|
3302
|
+
actorId: deviceSE.deviceId,
|
|
3303
|
+
metadata: { deviceId: deviceSE.deviceId, proofType: "DEVICE_SE" }
|
|
3304
|
+
};
|
|
3305
|
+
} catch (e) {
|
|
3306
|
+
const message = e instanceof Error ? e.message : "Unknown error";
|
|
3307
|
+
return {
|
|
3308
|
+
valid: false,
|
|
3309
|
+
error: `Signature verification error: ${message}`
|
|
3310
|
+
};
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
/**
|
|
3314
|
+
* Registers a public key for a trusted device.
|
|
3315
|
+
* This key will be used for future `DEVICE_SE` proof verifications.
|
|
3316
|
+
*
|
|
3317
|
+
* @param {string} deviceId - Unique identifier for the device
|
|
3318
|
+
* @param {Uint8Array} publicKey - 32-byte Ed25519 public key
|
|
3319
|
+
* @throws {Error} If the public key is not 32 bytes
|
|
3320
|
+
*/
|
|
3321
|
+
registerDeviceKey(deviceId, publicKey) {
|
|
3322
|
+
if (publicKey.length !== 32) {
|
|
3323
|
+
throw new Error("Device public key must be 32 bytes (Ed25519)");
|
|
3324
|
+
}
|
|
3325
|
+
this.deviceKeys.set(deviceId, publicKey);
|
|
3326
|
+
this.logger.log(`Registered device key for ${deviceId}`);
|
|
3327
|
+
}
|
|
3328
|
+
/**
|
|
3329
|
+
* Unregister a device
|
|
3330
|
+
*/
|
|
3331
|
+
unregisterDevice(deviceId) {
|
|
3332
|
+
return this.deviceKeys.delete(deviceId);
|
|
3333
|
+
}
|
|
3334
|
+
/**
|
|
3335
|
+
* Registers a trusted mTLS certificate fingerprint and associates it with an actor.
|
|
3336
|
+
*
|
|
3337
|
+
* @param {string} fingerprint - SHA-256 fingerprint of the client certificate
|
|
3338
|
+
* @param {string} actorId - The actor to associate with this certificate
|
|
3339
|
+
*/
|
|
3340
|
+
registerMTLSCert(fingerprint, actorId) {
|
|
3341
|
+
this.trustedCerts.set(fingerprint, { actorId, issuedAt: Date.now() });
|
|
3342
|
+
this.logger.log(`Registered mTLS cert ${fingerprint} for actor ${actorId}`);
|
|
3343
|
+
}
|
|
3344
|
+
/**
|
|
3345
|
+
* Revoke an mTLS certificate
|
|
3346
|
+
*/
|
|
3347
|
+
revokeMTLSCert(fingerprint) {
|
|
3348
|
+
return this.trustedCerts.delete(fingerprint);
|
|
3349
|
+
}
|
|
3350
|
+
/**
|
|
3351
|
+
* Calculate certificate fingerprint (SHA-256)
|
|
3352
|
+
*/
|
|
3353
|
+
static calculateFingerprint(certPem) {
|
|
3354
|
+
const der = Buffer.from(
|
|
3355
|
+
certPem.replace(/-----BEGIN CERTIFICATE-----/, "").replace(/-----END CERTIFICATE-----/, "").replace(/\s/g, ""),
|
|
3356
|
+
"base64"
|
|
3357
|
+
);
|
|
3358
|
+
return crypto3.createHash("sha256").update(der).digest("hex");
|
|
3359
|
+
}
|
|
3360
|
+
};
|
|
3361
|
+
ProofVerificationService = __decorateClass([
|
|
3362
|
+
(0, import_common4.Injectable)()
|
|
3363
|
+
], ProofVerificationService);
|
|
3364
|
+
|
|
3365
|
+
// src/decorators/index.ts
|
|
3366
|
+
var decorators_exports = {};
|
|
3367
|
+
__export(decorators_exports, {
|
|
3368
|
+
AxisContext: () => AxisContext,
|
|
3369
|
+
AxisDemoPubkey: () => AxisDemoPubkey,
|
|
3370
|
+
AxisFrame: () => AxisFrame3,
|
|
3371
|
+
AxisIp: () => AxisIp,
|
|
3372
|
+
AxisRaw: () => AxisRaw,
|
|
3373
|
+
HANDLER_METADATA_KEY: () => HANDLER_METADATA_KEY,
|
|
3374
|
+
Handler: () => Handler,
|
|
3375
|
+
INTENT_BODY_KEY: () => INTENT_BODY_KEY,
|
|
3376
|
+
INTENT_METADATA_KEY: () => INTENT_METADATA_KEY,
|
|
3377
|
+
INTENT_ROUTES_KEY: () => INTENT_ROUTES_KEY,
|
|
3378
|
+
INTENT_SENSORS_KEY: () => INTENT_SENSORS_KEY,
|
|
3379
|
+
Intent: () => Intent,
|
|
3380
|
+
IntentBody: () => IntentBody,
|
|
3381
|
+
IntentSensors: () => IntentSensors,
|
|
3382
|
+
SENSOR_METADATA_KEY: () => SENSOR_METADATA_KEY,
|
|
3383
|
+
Sensor: () => Sensor,
|
|
3384
|
+
TLV_FIELDS_KEY: () => TLV_FIELDS_KEY,
|
|
3385
|
+
TLV_VALIDATORS_KEY: () => TLV_VALIDATORS_KEY,
|
|
3386
|
+
TlvEnum: () => TlvEnum,
|
|
3387
|
+
TlvField: () => TlvField,
|
|
3388
|
+
TlvMinLen: () => TlvMinLen,
|
|
3389
|
+
TlvRange: () => TlvRange,
|
|
3390
|
+
TlvUtf8Pattern: () => TlvUtf8Pattern,
|
|
3391
|
+
TlvValidate: () => TlvValidate,
|
|
3392
|
+
buildDtoDecoder: () => buildDtoDecoder,
|
|
3393
|
+
extractDtoSchema: () => extractDtoSchema
|
|
3394
|
+
});
|
|
3395
|
+
|
|
3396
|
+
// src/decorators/axis-request.decorator.ts
|
|
3397
|
+
var import_common5 = require("@nestjs/common");
|
|
3398
|
+
function resolveIp(req) {
|
|
3399
|
+
return req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.headers["x-real-ip"] || req.socket.remoteAddress || void 0;
|
|
3400
|
+
}
|
|
3401
|
+
var AxisRaw = (0, import_common5.createParamDecorator)(
|
|
3402
|
+
(_data, ctx) => {
|
|
3403
|
+
const req = ctx.switchToHttp().getRequest();
|
|
3404
|
+
return req.body;
|
|
3405
|
+
}
|
|
3406
|
+
);
|
|
3407
|
+
var AxisIp = (0, import_common5.createParamDecorator)(
|
|
3408
|
+
(_data, ctx) => {
|
|
3409
|
+
const req = ctx.switchToHttp().getRequest();
|
|
3410
|
+
return resolveIp(req);
|
|
3411
|
+
}
|
|
3412
|
+
);
|
|
3413
|
+
var AxisContext = (0, import_common5.createParamDecorator)(
|
|
3414
|
+
(_data, ctx) => {
|
|
3415
|
+
const req = ctx.switchToHttp().getRequest();
|
|
3416
|
+
const axisData = req.axis || {};
|
|
3417
|
+
return {
|
|
3418
|
+
raw: req.body,
|
|
3419
|
+
ip: resolveIp(req),
|
|
3420
|
+
preDecodeInput: axisData.preDecodeInput,
|
|
3421
|
+
frameBytesCount: axisData.frameBytesCount || 0
|
|
3422
|
+
};
|
|
3423
|
+
}
|
|
3424
|
+
);
|
|
3425
|
+
var AxisDemoPubkey = (0, import_common5.createParamDecorator)(
|
|
3426
|
+
(_data, ctx) => {
|
|
3427
|
+
if (process.env.NODE_ENV !== "development") return void 0;
|
|
3428
|
+
const req = ctx.switchToHttp().getRequest();
|
|
3429
|
+
return req.headers["x-demo-pubkey"];
|
|
3430
|
+
}
|
|
3431
|
+
);
|
|
3432
|
+
var AxisFrame3 = (0, import_common5.createParamDecorator)(
|
|
3433
|
+
(_data, ctx) => {
|
|
3434
|
+
const req = ctx.switchToHttp().getRequest();
|
|
3435
|
+
const decoded = req.axisDecoded;
|
|
3436
|
+
if (!decoded) {
|
|
3437
|
+
throw new Error(
|
|
3438
|
+
"@AxisFrame() requires AxisDecodeInterceptor on the route. Add @UseInterceptors(AxisDecodeInterceptor) to use this decorator."
|
|
3439
|
+
);
|
|
3440
|
+
}
|
|
3441
|
+
return decoded;
|
|
3442
|
+
}
|
|
3443
|
+
);
|
|
3444
|
+
|
|
3445
|
+
// src/decorators/sensor.decorator.ts
|
|
3446
|
+
var import_common6 = require("@nestjs/common");
|
|
3447
|
+
var SENSOR_METADATA_KEY = "axis:sensor";
|
|
3448
|
+
function Sensor(options) {
|
|
3449
|
+
return (0, import_common6.SetMetadata)(SENSOR_METADATA_KEY, options ?? true);
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3452
|
+
// src/engine/index.ts
|
|
3453
|
+
var engine_exports = {};
|
|
3454
|
+
__export(engine_exports, {
|
|
3455
|
+
BAND: () => BAND,
|
|
3456
|
+
HandlerDiscoveryService: () => HandlerDiscoveryService,
|
|
3457
|
+
IntentRouter: () => IntentRouter,
|
|
3458
|
+
PRE_DECODE_BOUNDARY: () => PRE_DECODE_BOUNDARY,
|
|
3459
|
+
SensorDiscoveryService: () => SensorDiscoveryService,
|
|
3460
|
+
SensorRegistry: () => SensorRegistry,
|
|
3461
|
+
createObservation: () => createObservation,
|
|
3462
|
+
endStage: () => endStage,
|
|
3463
|
+
finalizeObservation: () => finalizeObservation,
|
|
3464
|
+
observation: () => observation_exports,
|
|
3465
|
+
recordSensor: () => recordSensor,
|
|
3466
|
+
startStage: () => startStage
|
|
3467
|
+
});
|
|
3468
|
+
|
|
3469
|
+
// src/engine/axis-observation.ts
|
|
3470
|
+
var import_crypto5 = require("crypto");
|
|
3471
|
+
function createObservation(transport, ip) {
|
|
3472
|
+
return {
|
|
3473
|
+
id: (0, import_crypto5.randomBytes)(16).toString("hex"),
|
|
3474
|
+
startMs: Date.now(),
|
|
3475
|
+
transport,
|
|
3476
|
+
ip,
|
|
3477
|
+
stages: [],
|
|
3478
|
+
sensors: [],
|
|
3479
|
+
facts: {}
|
|
3480
|
+
};
|
|
3481
|
+
}
|
|
3482
|
+
function startStage(obs, name) {
|
|
3483
|
+
const stage = { name, status: "ok", startMs: Date.now() };
|
|
3484
|
+
obs.stages.push(stage);
|
|
3485
|
+
return stage;
|
|
3486
|
+
}
|
|
3487
|
+
function endStage(stage, status = "ok", reason, code) {
|
|
3488
|
+
stage.endMs = Date.now();
|
|
3489
|
+
stage.durationMs = stage.endMs - stage.startMs;
|
|
3490
|
+
stage.status = status;
|
|
3491
|
+
if (reason) stage.reason = reason;
|
|
3492
|
+
if (code) stage.code = code;
|
|
3493
|
+
}
|
|
3494
|
+
function recordSensor(obs, name, allowed, riskScore, durationMs, reasons, code) {
|
|
3495
|
+
obs.sensors.push({ name, allowed, riskScore, durationMs, reasons, code });
|
|
3496
|
+
}
|
|
3497
|
+
function finalizeObservation(obs, decision, statusCode, resultCode) {
|
|
3498
|
+
obs.endMs = Date.now();
|
|
3499
|
+
obs.durationMs = obs.endMs - obs.startMs;
|
|
3500
|
+
obs.decision = decision;
|
|
3501
|
+
obs.statusCode = statusCode;
|
|
3502
|
+
if (resultCode) obs.resultCode = resultCode;
|
|
3503
|
+
}
|
|
3504
|
+
|
|
3505
|
+
// src/engine/handler-discovery.service.ts
|
|
3506
|
+
var import_common7 = require("@nestjs/common");
|
|
3507
|
+
var HandlerDiscoveryService = class {
|
|
3508
|
+
constructor(discovery, scanner, router) {
|
|
3509
|
+
this.discovery = discovery;
|
|
3510
|
+
this.scanner = scanner;
|
|
3511
|
+
this.router = router;
|
|
3512
|
+
this.logger = new import_common7.Logger(HandlerDiscoveryService.name);
|
|
3513
|
+
}
|
|
3514
|
+
onModuleInit() {
|
|
3515
|
+
const providers = this.discovery.getProviders();
|
|
3516
|
+
let totalIntents = 0;
|
|
3517
|
+
for (const wrapper of providers) {
|
|
3518
|
+
const { instance, metatype } = wrapper;
|
|
3519
|
+
if (!instance || !metatype) continue;
|
|
3520
|
+
const handlerMeta = Reflect.getMetadata(HANDLER_METADATA_KEY, metatype);
|
|
3521
|
+
if (!handlerMeta) continue;
|
|
3522
|
+
const handlerName = handlerMeta.intent || metatype.name;
|
|
3523
|
+
const proto = Object.getPrototypeOf(instance);
|
|
3524
|
+
const methods = this.scanner.getAllMethodNames(proto);
|
|
3525
|
+
let registered = 0;
|
|
3526
|
+
for (const methodName of methods) {
|
|
3527
|
+
const meta = Reflect.getMetadata(
|
|
3528
|
+
INTENT_METADATA_KEY,
|
|
3529
|
+
proto,
|
|
3530
|
+
methodName
|
|
3531
|
+
);
|
|
3532
|
+
if (!meta?.intent) continue;
|
|
3533
|
+
if (!this.router.has(meta.intent)) {
|
|
3534
|
+
this.router.register(
|
|
3535
|
+
meta.intent,
|
|
3536
|
+
instance[methodName].bind(instance)
|
|
3537
|
+
);
|
|
3538
|
+
registered++;
|
|
3539
|
+
totalIntents++;
|
|
3540
|
+
}
|
|
3541
|
+
this.router.registerIntentMeta(meta.intent, proto, methodName);
|
|
3542
|
+
}
|
|
3543
|
+
if (registered > 0) {
|
|
3544
|
+
this.logger.log(
|
|
3545
|
+
`Auto-registered ${registered} intents from ${handlerName}`
|
|
3546
|
+
);
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
this.logger.log(
|
|
3550
|
+
`Handler discovery complete: ${totalIntents} intents auto-registered`
|
|
3551
|
+
);
|
|
3552
|
+
}
|
|
3553
|
+
};
|
|
3554
|
+
HandlerDiscoveryService = __decorateClass([
|
|
3555
|
+
(0, import_common7.Injectable)()
|
|
3556
|
+
], HandlerDiscoveryService);
|
|
3557
|
+
|
|
3558
|
+
// src/engine/sensor-bands.ts
|
|
3559
|
+
var BAND = {
|
|
3560
|
+
/** Pre-decode: raw byte validation, geo, budget, magic */
|
|
3561
|
+
WIRE: 0,
|
|
3562
|
+
/** Post-decode: identity resolution, capsule, proof */
|
|
3563
|
+
IDENTITY: 40,
|
|
3564
|
+
/** Post-decode: authorization, signature, rate limiting */
|
|
3565
|
+
POLICY: 90,
|
|
3566
|
+
/** Post-decode: content validation, TLV, schema, files */
|
|
3567
|
+
CONTENT: 140,
|
|
3568
|
+
/** Post-decode: business logic sensors, streams, WS */
|
|
3569
|
+
BUSINESS: 200,
|
|
3570
|
+
/** Post-decode: audit, logging (always last) */
|
|
3571
|
+
AUDIT: 900
|
|
3572
|
+
};
|
|
3573
|
+
var PRE_DECODE_BOUNDARY = 40;
|
|
3574
|
+
|
|
3575
|
+
// src/engine/sensor-discovery.service.ts
|
|
3576
|
+
var import_common8 = require("@nestjs/common");
|
|
3577
|
+
var SensorDiscoveryService = class {
|
|
3578
|
+
constructor(discovery, reflector, registry) {
|
|
3579
|
+
this.discovery = discovery;
|
|
3580
|
+
this.reflector = reflector;
|
|
3581
|
+
this.registry = registry;
|
|
3582
|
+
this.logger = new import_common8.Logger(SensorDiscoveryService.name);
|
|
3583
|
+
}
|
|
3584
|
+
onApplicationBootstrap() {
|
|
3585
|
+
const providers = this.discovery.getProviders();
|
|
3586
|
+
let count = 0;
|
|
3587
|
+
for (const wrapper of providers) {
|
|
3588
|
+
const { instance } = wrapper;
|
|
3589
|
+
if (!instance || !instance.constructor) continue;
|
|
3590
|
+
const meta = this.reflector.get(
|
|
3591
|
+
SENSOR_METADATA_KEY,
|
|
3592
|
+
instance.constructor
|
|
3593
|
+
);
|
|
3594
|
+
if (!meta) continue;
|
|
3595
|
+
const sensor = instance;
|
|
3596
|
+
if (!sensor.name || sensor.order === void 0) {
|
|
3597
|
+
this.logger.warn(
|
|
3598
|
+
`@Sensor() on ${instance.constructor.name} missing name or order \u2014 skipped`
|
|
3599
|
+
);
|
|
3600
|
+
continue;
|
|
3601
|
+
}
|
|
3602
|
+
if (!sensor.phase) {
|
|
3603
|
+
const decoratorPhase = meta !== true ? meta.phase : void 0;
|
|
3604
|
+
sensor.phase = decoratorPhase ?? (sensor.order < PRE_DECODE_BOUNDARY ? "PRE_DECODE" : "POST_DECODE");
|
|
3605
|
+
}
|
|
3606
|
+
this.registry.register(sensor);
|
|
3607
|
+
count++;
|
|
3608
|
+
}
|
|
3609
|
+
this.logger.log(`Auto-registered ${count} sensors via @Sensor()`);
|
|
3610
|
+
}
|
|
3611
|
+
};
|
|
3612
|
+
SensorDiscoveryService = __decorateClass([
|
|
3613
|
+
(0, import_common8.Injectable)()
|
|
3614
|
+
], SensorDiscoveryService);
|
|
3615
|
+
|
|
3616
|
+
// src/engine/registry/sensor.registry.ts
|
|
3617
|
+
var import_common9 = require("@nestjs/common");
|
|
3618
|
+
var SensorRegistry = class {
|
|
3619
|
+
constructor(configService) {
|
|
3620
|
+
this.configService = configService;
|
|
3621
|
+
this.sensors = [];
|
|
3622
|
+
this.logger = new import_common9.Logger(SensorRegistry.name);
|
|
3623
|
+
}
|
|
3624
|
+
/**
|
|
3625
|
+
* Registers a new sensor in the registry.
|
|
3626
|
+
*
|
|
3627
|
+
* Validates that:
|
|
3628
|
+
* - AxisSensor has a unique name
|
|
3629
|
+
* - AxisSensor has an order field
|
|
3630
|
+
* - Pre-decode sensors have order < 40
|
|
3631
|
+
* - Post-decode sensors have order >= 40
|
|
3632
|
+
*
|
|
3633
|
+
* @param {AxisSensor} sensor - The sensor instance to register
|
|
3634
|
+
* @throws Error if validation fails
|
|
3635
|
+
*/
|
|
3636
|
+
register(sensor) {
|
|
3637
|
+
if (!sensor.name) {
|
|
3638
|
+
throw new Error("AxisSensor must have a name");
|
|
3639
|
+
}
|
|
3640
|
+
const enabledSensorsStr = this.configService.get("ENABLED_SENSORS");
|
|
3641
|
+
const disabledSensorsStr = this.configService.get("DISABLED_SENSORS");
|
|
3642
|
+
const enabledSensors = enabledSensorsStr ? enabledSensorsStr.split(",").map((s) => s.trim()) : null;
|
|
3643
|
+
const disabledSensors = disabledSensorsStr ? disabledSensorsStr.split(",").map((s) => s.trim()) : [];
|
|
3644
|
+
if (enabledSensors && !enabledSensors.includes(sensor.name)) {
|
|
3645
|
+
this.logger.log(`Skipping disabled sensor (not in ENABLED_SENSORS): ${sensor.name}`);
|
|
3646
|
+
return;
|
|
3647
|
+
}
|
|
3648
|
+
if (disabledSensors.includes(sensor.name)) {
|
|
3649
|
+
this.logger.log(`Skipping disabled sensor (in DISABLED_SENSORS): ${sensor.name}`);
|
|
3650
|
+
return;
|
|
3651
|
+
}
|
|
3652
|
+
if (sensor.order === void 0) {
|
|
3653
|
+
throw new Error(`AxisSensor "${sensor.name}" must have an order field`);
|
|
3654
|
+
}
|
|
3655
|
+
const isPreDecodeSensor = this.isPreDecodeSensor(sensor);
|
|
3656
|
+
const isPostDecodeSensor = this.isPostDecodeSensor(sensor);
|
|
3657
|
+
if (isPreDecodeSensor && sensor.order >= 40) {
|
|
3658
|
+
this.logger.warn(
|
|
3659
|
+
`AxisSensor "${sensor.name}" is marked as PRE_DECODE but has order ${sensor.order} (should be < 40)`
|
|
3660
|
+
);
|
|
3661
|
+
}
|
|
3662
|
+
if (isPostDecodeSensor && sensor.order < 40) {
|
|
3663
|
+
this.logger.warn(
|
|
3664
|
+
`AxisSensor "${sensor.name}" is marked as POST_DECODE but has order ${sensor.order} (should be >= 40)`
|
|
3665
|
+
);
|
|
3666
|
+
}
|
|
3667
|
+
this.sensors.push(sensor);
|
|
3668
|
+
const phaseLabel = typeof sensor.phase === "string" ? sensor.phase : sensor.phase?.phase || "UNKNOWN";
|
|
3669
|
+
this.logger.debug(
|
|
3670
|
+
`Registered sensor: ${sensor.name} (order: ${sensor.order}, phase: ${phaseLabel})`
|
|
3671
|
+
);
|
|
3672
|
+
}
|
|
3673
|
+
/**
|
|
3674
|
+
* Returns all registered sensors, sorted by their execution order.
|
|
3675
|
+
*
|
|
3676
|
+
* @returns {AxisSensor[]} A sorted array of sensors
|
|
3677
|
+
*/
|
|
3678
|
+
list() {
|
|
3679
|
+
return [...this.sensors].sort(
|
|
3680
|
+
(a, b) => (a.order ?? 999) - (b.order ?? 999)
|
|
3681
|
+
);
|
|
3682
|
+
}
|
|
3683
|
+
/**
|
|
3684
|
+
* Returns only pre-decode sensors (order < 40).
|
|
3685
|
+
* These sensors run in middleware on raw bytes before frame decoding.
|
|
3686
|
+
*
|
|
3687
|
+
* @returns {AxisPreSensor[]} Pre-decode sensors sorted by order
|
|
3688
|
+
*/
|
|
3689
|
+
getPreDecodeSensors() {
|
|
3690
|
+
return this.list().filter((s) => (s.order ?? 999) < 40);
|
|
3691
|
+
}
|
|
3692
|
+
/**
|
|
3693
|
+
* Returns only post-decode sensors (order >= 40).
|
|
3694
|
+
* These sensors run in the controller on fully decoded frames.
|
|
3695
|
+
*
|
|
3696
|
+
* @returns {AxisPostSensor[]} Post-decode sensors sorted by order
|
|
3697
|
+
*/
|
|
3698
|
+
getPostDecodeSensors() {
|
|
3699
|
+
return this.list().filter(
|
|
3700
|
+
(s) => (s.order ?? 999) >= 40
|
|
3701
|
+
);
|
|
3702
|
+
}
|
|
3703
|
+
/**
|
|
3704
|
+
* Helper: Check if a sensor is a pre-decode sensor.
|
|
3705
|
+
*
|
|
3706
|
+
* @private
|
|
3707
|
+
* @param {AxisSensor} sensor - The sensor to check
|
|
3708
|
+
* @returns {boolean} True if sensor is pre-decode
|
|
3709
|
+
*/
|
|
3710
|
+
isPreDecodeSensor(sensor) {
|
|
3711
|
+
const phase = typeof sensor.phase === "string" ? sensor.phase : sensor.phase?.phase;
|
|
3712
|
+
return phase === "PRE_DECODE" || (sensor.order ?? 999) < 40;
|
|
3713
|
+
}
|
|
3714
|
+
/**
|
|
3715
|
+
* Helper: Check if a sensor is a post-decode sensor.
|
|
3716
|
+
*
|
|
3717
|
+
* @private
|
|
3718
|
+
* @param {AxisSensor} sensor - The sensor to check
|
|
3719
|
+
* @returns {boolean} True if sensor is post-decode
|
|
3720
|
+
*/
|
|
3721
|
+
isPostDecodeSensor(sensor) {
|
|
3722
|
+
const phase = typeof sensor.phase === "string" ? sensor.phase : sensor.phase?.phase;
|
|
3723
|
+
return phase === "POST_DECODE" || (sensor.order ?? 999) >= 40;
|
|
3724
|
+
}
|
|
3725
|
+
/**
|
|
3726
|
+
* Returns sensor count by phase.
|
|
3727
|
+
* Useful for diagnostics and monitoring.
|
|
3728
|
+
*
|
|
3729
|
+
* @returns {{preDecodeCount: number, postDecodeCount: number}}
|
|
3730
|
+
*/
|
|
3731
|
+
getSensorCountByPhase() {
|
|
3732
|
+
return {
|
|
3733
|
+
preDecodeCount: this.getPreDecodeSensors().length,
|
|
3734
|
+
postDecodeCount: this.getPostDecodeSensors().length
|
|
3735
|
+
};
|
|
3736
|
+
}
|
|
3737
|
+
/**
|
|
3738
|
+
* Clears all registered sensors.
|
|
3739
|
+
* Useful for testing.
|
|
3740
|
+
*
|
|
3741
|
+
* @internal
|
|
3742
|
+
*/
|
|
3743
|
+
clear() {
|
|
3744
|
+
this.sensors = [];
|
|
3745
|
+
}
|
|
3746
|
+
};
|
|
3747
|
+
SensorRegistry = __decorateClass([
|
|
3748
|
+
(0, import_common9.Injectable)()
|
|
3749
|
+
], SensorRegistry);
|
|
3750
|
+
|
|
3751
|
+
// src/engine/observation/index.ts
|
|
3752
|
+
var observation_exports = {};
|
|
3753
|
+
__export(observation_exports, {
|
|
3754
|
+
buildQueueMessage: () => buildQueueMessage,
|
|
3755
|
+
buildUnsignedWitness: () => buildUnsignedWitness,
|
|
3756
|
+
canonicalizeObservation: () => canonicalizeObservation,
|
|
3757
|
+
decodeQueueMessage: () => decodeQueueMessage,
|
|
3758
|
+
encodeQueueMessage: () => encodeQueueMessage,
|
|
3759
|
+
hashObservation: () => hashObservation,
|
|
3760
|
+
parseAutoClaimEntries: () => parseAutoClaimEntries,
|
|
3761
|
+
parseStreamEntries: () => parseStreamEntries,
|
|
3762
|
+
stableJsonStringify: () => stableJsonStringify,
|
|
3763
|
+
verifyResponse: () => verifyResponse
|
|
3764
|
+
});
|
|
3765
|
+
|
|
3766
|
+
// src/loom/index.ts
|
|
3767
|
+
var loom_exports = {};
|
|
3768
|
+
__export(loom_exports, {
|
|
3769
|
+
PROOF_LOOM: () => import_axis_protocol2.PROOF_LOOM,
|
|
3770
|
+
TLV_PRESENCE_ID: () => import_axis_protocol2.TLV_LOOM_PRESENCE_ID,
|
|
3771
|
+
TLV_THREAD_HASH: () => import_axis_protocol2.TLV_LOOM_THREAD_HASH,
|
|
3772
|
+
TLV_WRIT: () => import_axis_protocol2.TLV_LOOM_WRIT,
|
|
3773
|
+
canonicalizeGrant: () => canonicalizeGrant,
|
|
3774
|
+
canonicalizeWrit: () => canonicalizeWrit,
|
|
3775
|
+
deriveAnchorReflection: () => deriveAnchorReflection
|
|
3776
|
+
});
|
|
3777
|
+
|
|
3778
|
+
// src/loom/loom.types.ts
|
|
3779
|
+
function deriveAnchorReflection(softid, context = "openlogs", scope = "loom") {
|
|
3780
|
+
return `ar:${context}:${scope}:${softid}`;
|
|
3781
|
+
}
|
|
3782
|
+
function canonicalizeWrit(writ) {
|
|
3783
|
+
const ordered = {
|
|
3784
|
+
head: { tid: writ.head.tid, seq: writ.head.seq },
|
|
3785
|
+
body: {
|
|
3786
|
+
who: writ.body.who,
|
|
3787
|
+
act: writ.body.act,
|
|
3788
|
+
res: writ.body.res,
|
|
3789
|
+
law: writ.body.law
|
|
3790
|
+
},
|
|
3791
|
+
meta: { iat: writ.meta.iat, exp: writ.meta.exp, prev: writ.meta.prev }
|
|
3792
|
+
};
|
|
3793
|
+
return JSON.stringify(ordered);
|
|
3794
|
+
}
|
|
3795
|
+
function canonicalizeGrant(grant) {
|
|
3796
|
+
const ordered = {
|
|
3797
|
+
grant_id: grant.grant_id,
|
|
3798
|
+
issuer: grant.issuer,
|
|
3799
|
+
subject: grant.subject,
|
|
3800
|
+
grant_type: grant.grant_type,
|
|
3801
|
+
caps: grant.caps,
|
|
3802
|
+
meta: grant.meta
|
|
3803
|
+
};
|
|
3804
|
+
return JSON.stringify(ordered);
|
|
3805
|
+
}
|
|
3806
|
+
|
|
3807
|
+
// src/schemas/index.ts
|
|
3808
|
+
var schemas_exports = {};
|
|
3809
|
+
__export(schemas_exports, {
|
|
3810
|
+
AccessProfileZ: () => AccessProfileZ,
|
|
3811
|
+
AxisContextZ: () => AxisContextZ,
|
|
3812
|
+
AxisErrorZ: () => AxisErrorZ,
|
|
3813
|
+
BodyBudgetInputZ: () => BodyBudgetInputZ,
|
|
3814
|
+
BodyBudgetPolicyZ: () => BodyBudgetPolicyZ,
|
|
3815
|
+
BodyProfile: () => BodyProfile2,
|
|
3816
|
+
BodyProfileValidator: () => BodyProfileValidator,
|
|
3817
|
+
BodyProfileZ: () => BodyProfileZ,
|
|
3818
|
+
CapsuleClaimsZ: () => CapsuleClaimsZ,
|
|
3819
|
+
CapsuleValidationResultZ: () => CapsuleValidationResultZ,
|
|
3820
|
+
CapsuleVerifyResultZ: () => CapsuleVerifyResultZ,
|
|
3821
|
+
CapsuleVerifySensorInputZ: () => CapsuleVerifySensorInputZ,
|
|
3822
|
+
CapsuleZ: () => CapsuleZ,
|
|
3823
|
+
ChunkHashInputZ: () => ChunkHashInputZ,
|
|
3824
|
+
CountryBlockDecisionZ: () => CountryBlockDecisionZ,
|
|
3825
|
+
CountryBlockSensorInputZ: () => CountryBlockSensorInputZ,
|
|
3826
|
+
EntropySensorInputZ: () => EntropySensorInputZ,
|
|
3827
|
+
ExecutionMetricsZ: () => ExecutionMetricsZ,
|
|
3828
|
+
IPReputationInputZ: () => IPReputationInputZ,
|
|
3829
|
+
IPReputationZ: () => IPReputationZ,
|
|
3830
|
+
IntentPolicyDecisionZ: () => IntentPolicyDecisionZ,
|
|
3831
|
+
IntentPolicySensorInputZ: () => IntentPolicySensorInputZ,
|
|
3832
|
+
IntentPolicyZ: () => IntentPolicyZ,
|
|
3833
|
+
IntentSchemaZ: () => IntentSchemaZ,
|
|
3834
|
+
PassportZ: () => PassportZ,
|
|
3835
|
+
ProofKindZ: () => ProofKindZ,
|
|
3836
|
+
ProofPresenceInputZ: () => ProofPresenceInputZ,
|
|
3837
|
+
ProofType: () => ProofType2,
|
|
3838
|
+
ProtocolStrictInputZ: () => ProtocolStrictInputZ,
|
|
3839
|
+
RateLimitConfigZ: () => RateLimitConfigZ,
|
|
3840
|
+
RateLimitInputZ: () => RateLimitInputZ,
|
|
3841
|
+
RateLimitProfileZ: () => RateLimitProfileZ,
|
|
3842
|
+
ScanBurstDecisionZ: () => ScanBurstDecisionZ,
|
|
3843
|
+
ScanBurstSensorInputZ: () => ScanBurstSensorInputZ,
|
|
3844
|
+
SchemaFieldKindZ: () => SchemaFieldKindZ,
|
|
3845
|
+
SchemaFieldZ: () => SchemaFieldZ,
|
|
3846
|
+
ScopeZ: () => ScopeZ,
|
|
3847
|
+
SensitivityLevelZ: () => SensitivityLevelZ,
|
|
3848
|
+
SensorChainInputZ: () => SensorChainInputZ,
|
|
3849
|
+
SensorDecisionWithMetadataZ: () => SensorDecisionWithMetadataZ,
|
|
3850
|
+
SensorDecisionZ: () => SensorDecisionZ,
|
|
3851
|
+
SensorResultZ: () => SensorResultZ,
|
|
3852
|
+
UploadSessionZ: () => UploadSessionZ,
|
|
3853
|
+
UploadStatusZ: () => UploadStatusZ,
|
|
3854
|
+
WsHandshakeDecisionZ: () => WsHandshakeDecisionZ,
|
|
3855
|
+
WsHandshakeInputZ: () => WsHandshakeInputZ
|
|
3856
|
+
});
|
|
3857
|
+
|
|
3858
|
+
// src/schemas/axis-schemas.ts
|
|
3859
|
+
var z2 = __toESM(require("zod"));
|
|
3860
|
+
var SensorDecisionZ = z2.union([
|
|
3861
|
+
z2.object({ action: z2.literal("ALLOW"), meta: z2.any().optional() }),
|
|
3862
|
+
z2.object({
|
|
3863
|
+
action: z2.literal("DENY"),
|
|
3864
|
+
code: z2.string(),
|
|
3865
|
+
reason: z2.string().optional(),
|
|
3866
|
+
meta: z2.any().optional()
|
|
3867
|
+
})
|
|
3868
|
+
]);
|
|
3869
|
+
var SensorDecisionWithMetadataZ = z2.union([
|
|
3870
|
+
z2.object({ action: z2.literal("ALLOW"), meta: z2.any().optional() }),
|
|
3871
|
+
z2.object({
|
|
3872
|
+
action: z2.literal("DENY"),
|
|
3873
|
+
code: z2.string(),
|
|
3874
|
+
reason: z2.string().optional(),
|
|
3875
|
+
retryAfterMs: z2.number().int().positive().optional(),
|
|
3876
|
+
meta: z2.any().optional()
|
|
3877
|
+
})
|
|
3878
|
+
]);
|
|
3879
|
+
var CountryBlockSensorInputZ = z2.object({
|
|
3880
|
+
ip: z2.string().min(1),
|
|
3881
|
+
country: z2.string().length(2).toUpperCase().optional()
|
|
3882
|
+
});
|
|
3883
|
+
var CountryBlockDecisionZ = SensorDecisionZ;
|
|
3884
|
+
var ScanBurstSensorInputZ = z2.object({
|
|
3885
|
+
ip: z2.string().min(1),
|
|
3886
|
+
isFailure: z2.boolean().optional()
|
|
3887
|
+
});
|
|
3888
|
+
var ScanBurstDecisionZ = SensorDecisionWithMetadataZ;
|
|
3889
|
+
var ProofKindZ = z2.enum([
|
|
3890
|
+
"NONE",
|
|
3891
|
+
"CAPSULE",
|
|
3892
|
+
"PASSPORT",
|
|
3893
|
+
"MTLS",
|
|
3894
|
+
"JWT"
|
|
3895
|
+
]);
|
|
3896
|
+
var AccessProfileZ = z2.enum(["PUBLIC", "PARTNER", "INTERNAL", "NODE"]);
|
|
3897
|
+
var ProofPresenceInputZ = z2.object({
|
|
3898
|
+
profile: AccessProfileZ,
|
|
3899
|
+
visibility: z2.enum(["PUBLIC", "GUARDED"]),
|
|
3900
|
+
requiredProof: z2.array(ProofKindZ).min(1),
|
|
3901
|
+
hasCapsule: z2.boolean(),
|
|
3902
|
+
hasPassportSignature: z2.boolean(),
|
|
3903
|
+
intent: z2.string().min(1)
|
|
3904
|
+
});
|
|
3905
|
+
var SensitivityLevelZ = z2.enum(["LOW", "MEDIUM", "HIGH", "CRITICAL"]);
|
|
3906
|
+
var IntentPolicyZ = z2.object({
|
|
3907
|
+
intent: z2.string().min(1),
|
|
3908
|
+
sensitivity: SensitivityLevelZ,
|
|
3909
|
+
maxFrameBytes: z2.number().int().positive(),
|
|
3910
|
+
maxHeaderBytes: z2.number().int().positive(),
|
|
3911
|
+
maxBodyBytes: z2.number().int().positive(),
|
|
3912
|
+
maxSigBytes: z2.number().int().positive().optional(),
|
|
3913
|
+
rateLimitPerMinute: z2.number().int().positive().optional(),
|
|
3914
|
+
rateLimitPerHour: z2.number().int().positive().optional(),
|
|
3915
|
+
requiresSignature: z2.boolean(),
|
|
3916
|
+
requiresCapsule: z2.boolean(),
|
|
3917
|
+
timeoutMs: z2.number().int().positive()
|
|
3918
|
+
});
|
|
3919
|
+
var IntentPolicySensorInputZ = z2.object({
|
|
3920
|
+
frame: AxisFrameZ,
|
|
3921
|
+
intent: z2.string().min(1),
|
|
3922
|
+
rawFrameSize: z2.number().int().positive()
|
|
3923
|
+
});
|
|
3924
|
+
var IntentPolicyDecisionZ = z2.union([
|
|
3925
|
+
z2.object({
|
|
3926
|
+
action: z2.literal("ALLOW"),
|
|
3927
|
+
policy: IntentPolicyZ
|
|
3928
|
+
}),
|
|
3929
|
+
z2.object({
|
|
3930
|
+
action: z2.literal("DENY"),
|
|
3931
|
+
reason: z2.string()
|
|
3932
|
+
})
|
|
3933
|
+
]);
|
|
3934
|
+
var CapsuleClaimsZ = z2.object({
|
|
3935
|
+
capsuleId: z2.string().min(8),
|
|
3936
|
+
allowIntents: z2.array(z2.string()).min(1),
|
|
3937
|
+
limits: z2.object({
|
|
3938
|
+
maxBodyBytes: z2.number().int().positive().optional()
|
|
3939
|
+
}).optional(),
|
|
3940
|
+
scopes: z2.record(z2.string(), z2.any()).optional()
|
|
3941
|
+
});
|
|
3942
|
+
var CapsuleZ = z2.object({
|
|
3943
|
+
id: z2.string(),
|
|
3944
|
+
claims: CapsuleClaimsZ,
|
|
3945
|
+
issuedAt: z2.number().int(),
|
|
3946
|
+
expiresAt: z2.number().int(),
|
|
3947
|
+
tier: z2.enum(["FREE", "STANDARD", "PREMIUM"])
|
|
3948
|
+
});
|
|
3949
|
+
var CapsuleValidationResultZ = z2.object({
|
|
3950
|
+
valid: z2.boolean(),
|
|
3951
|
+
capsule: CapsuleZ.optional(),
|
|
3952
|
+
reason: z2.string().optional(),
|
|
3953
|
+
requiresStepUp: z2.boolean().optional()
|
|
3954
|
+
});
|
|
3955
|
+
var CapsuleVerifySensorInputZ = z2.object({
|
|
3956
|
+
headers: z2.map(
|
|
3957
|
+
z2.number(),
|
|
3958
|
+
z2.custom((v) => v instanceof Uint8Array)
|
|
3959
|
+
),
|
|
3960
|
+
intent: z2.string().min(1),
|
|
3961
|
+
ctx: z2.any()
|
|
3962
|
+
// AxisContext - avoid circular dependency
|
|
3963
|
+
});
|
|
3964
|
+
var CapsuleVerifyResultZ = z2.object({
|
|
3965
|
+
ok: z2.literal(true),
|
|
3966
|
+
capsule: CapsuleZ
|
|
3967
|
+
});
|
|
3968
|
+
var RateLimitProfileZ = z2.enum([
|
|
3969
|
+
"PUBLIC",
|
|
3970
|
+
"PARTNER",
|
|
3971
|
+
"INTERNAL",
|
|
3972
|
+
"NODE"
|
|
3973
|
+
]);
|
|
3974
|
+
var RateLimitInputZ = z2.object({
|
|
3975
|
+
ip: z2.string().min(1),
|
|
3976
|
+
userAgent: z2.string().optional(),
|
|
3977
|
+
actorId: z2.string().optional(),
|
|
3978
|
+
capsuleId: z2.string().optional(),
|
|
3979
|
+
intent: z2.string().min(1),
|
|
3980
|
+
profile: RateLimitProfileZ
|
|
3981
|
+
});
|
|
3982
|
+
var RateLimitConfigZ = z2.object({
|
|
3983
|
+
windowSec: z2.number().int().positive(),
|
|
3984
|
+
max: z2.number().int().positive(),
|
|
3985
|
+
key: z2.enum(["ip_fingerprint", "actor_capsule"])
|
|
3986
|
+
});
|
|
3987
|
+
var SensorResultZ = z2.object({
|
|
3988
|
+
ok: z2.literal(true)
|
|
3989
|
+
});
|
|
3990
|
+
var PassportZ = z2.object({
|
|
3991
|
+
id: z2.string(),
|
|
3992
|
+
public_key: z2.custom((v) => Buffer.isBuffer(v)),
|
|
3993
|
+
status: z2.enum(["ACTIVE", "REVOKED", "EXPIRED", "PENDING"]),
|
|
3994
|
+
issuedAt: z2.number().int(),
|
|
3995
|
+
expiresAt: z2.number().int().optional()
|
|
3996
|
+
});
|
|
3997
|
+
var ExecutionMetricsZ = z2.object({
|
|
3998
|
+
dbWrites: z2.number().int(),
|
|
3999
|
+
dbReads: z2.number().int(),
|
|
4000
|
+
externalCalls: z2.number().int(),
|
|
4001
|
+
elapsedMs: z2.number().int().optional()
|
|
4002
|
+
});
|
|
4003
|
+
var SensorChainInputZ = z2.object({
|
|
4004
|
+
ip: z2.string().min(1),
|
|
4005
|
+
path: z2.string().min(1),
|
|
4006
|
+
contentLength: z2.number().int().nonnegative(),
|
|
4007
|
+
peek: z2.instanceof(Uint8Array),
|
|
4008
|
+
country: z2.string().optional()
|
|
4009
|
+
});
|
|
4010
|
+
var EntropySensorInputZ = z2.object({
|
|
4011
|
+
pid: z2.custom((v) => Buffer.isBuffer(v)).optional(),
|
|
4012
|
+
nonce: z2.custom((v) => Buffer.isBuffer(v)).optional(),
|
|
4013
|
+
ip: z2.string().min(1)
|
|
4014
|
+
});
|
|
4015
|
+
var ProtocolStrictInputZ = z2.object({
|
|
4016
|
+
rawBytes: z2.union([z2.custom((v) => Buffer.isBuffer(v)), z2.instanceof(Uint8Array)]).optional(),
|
|
4017
|
+
ip: z2.string().min(1),
|
|
4018
|
+
path: z2.string().min(1),
|
|
4019
|
+
contentLength: z2.number().int().nonnegative(),
|
|
4020
|
+
peek: z2.instanceof(Uint8Array),
|
|
4021
|
+
country: z2.string().optional(),
|
|
4022
|
+
contentType: z2.string().optional()
|
|
4023
|
+
});
|
|
4024
|
+
var SchemaFieldKindZ = z2.enum([
|
|
4025
|
+
"utf8",
|
|
4026
|
+
"u64",
|
|
4027
|
+
"bytes",
|
|
4028
|
+
"bytes16",
|
|
4029
|
+
"bool",
|
|
4030
|
+
"obj",
|
|
4031
|
+
"arr"
|
|
4032
|
+
]);
|
|
4033
|
+
var ScopeZ = z2.enum(["header", "body"]);
|
|
4034
|
+
var SchemaFieldZ = z2.object({
|
|
4035
|
+
name: z2.string().min(1),
|
|
4036
|
+
tlv: z2.number().int().positive(),
|
|
4037
|
+
kind: SchemaFieldKindZ,
|
|
4038
|
+
required: z2.boolean().optional(),
|
|
4039
|
+
maxLen: z2.number().int().positive().optional(),
|
|
4040
|
+
max: z2.string().optional(),
|
|
4041
|
+
scope: ScopeZ.optional()
|
|
4042
|
+
});
|
|
4043
|
+
var BodyProfileZ = z2.enum(["TLV_MAP", "RAW", "TLV_OBJ", "TLV_ARR"]);
|
|
4044
|
+
var IntentSchemaZ = z2.object({
|
|
4045
|
+
intent: z2.string().min(1),
|
|
4046
|
+
version: z2.number().int().positive(),
|
|
4047
|
+
bodyProfile: BodyProfileZ,
|
|
4048
|
+
fields: z2.array(SchemaFieldZ).min(1)
|
|
4049
|
+
});
|
|
4050
|
+
var WsHandshakeInputZ = z2.object({
|
|
4051
|
+
clientId: z2.string().min(1),
|
|
4052
|
+
isWs: z2.boolean(),
|
|
4053
|
+
ip: z2.string().min(1)
|
|
4054
|
+
});
|
|
4055
|
+
var WsHandshakeDecisionZ = z2.union([
|
|
4056
|
+
z2.object({ action: z2.literal("ALLOW") }),
|
|
4057
|
+
z2.object({ action: z2.literal("DENY"), code: z2.string() })
|
|
4058
|
+
]);
|
|
4059
|
+
var IPReputationInputZ = z2.object({
|
|
4060
|
+
ip: z2.string().min(1)
|
|
4061
|
+
});
|
|
4062
|
+
var IPReputationZ = z2.object({
|
|
4063
|
+
score: z2.number().min(-100).max(100),
|
|
4064
|
+
lastUpdated: z2.number().int(),
|
|
4065
|
+
totalRequests: z2.number().int().nonnegative(),
|
|
4066
|
+
failedRequests: z2.number().int().nonnegative(),
|
|
4067
|
+
blockedRequests: z2.number().int().nonnegative(),
|
|
4068
|
+
tags: z2.array(z2.string())
|
|
4069
|
+
});
|
|
4070
|
+
var UploadStatusZ = z2.enum([
|
|
4071
|
+
"INIT",
|
|
4072
|
+
"UPLOADING",
|
|
4073
|
+
"FINALIZING",
|
|
4074
|
+
"DONE",
|
|
4075
|
+
"ABORTED"
|
|
4076
|
+
]);
|
|
4077
|
+
var UploadSessionZ = z2.object({
|
|
4078
|
+
uploadIdHex: z2.string().min(1),
|
|
4079
|
+
fileName: z2.string().min(1),
|
|
4080
|
+
totalSize: z2.number().int().positive(),
|
|
4081
|
+
chunkSize: z2.number().int().positive(),
|
|
4082
|
+
totalChunks: z2.number().int().positive(),
|
|
4083
|
+
receivedCount: z2.number().int().nonnegative(),
|
|
4084
|
+
status: UploadStatusZ
|
|
4085
|
+
});
|
|
4086
|
+
var BodyBudgetInputZ = z2.object({
|
|
4087
|
+
intent: z2.string().min(1),
|
|
4088
|
+
headerLen: z2.number().int().nonnegative(),
|
|
4089
|
+
bodyLen: z2.number().int().nonnegative()
|
|
4090
|
+
});
|
|
4091
|
+
var BodyBudgetPolicyZ = z2.object({
|
|
4092
|
+
maxHeaderBytes: z2.number().int().positive(),
|
|
4093
|
+
maxBodyBytes: z2.number().int().positive()
|
|
4094
|
+
});
|
|
4095
|
+
var ChunkHashInputZ = z2.object({
|
|
4096
|
+
headerTLVs: z2.any(),
|
|
4097
|
+
// Map<number, Uint8Array> - flexible validation for compatibility
|
|
4098
|
+
bodyBytes: z2.any(),
|
|
4099
|
+
// Uint8Array - flexible validation for compatibility
|
|
4100
|
+
intent: z2.string().min(1)
|
|
4101
|
+
});
|
|
4102
|
+
var ProofType2 = /* @__PURE__ */ ((ProofType3) => {
|
|
4103
|
+
ProofType3[ProofType3["CAPSULE"] = 1] = "CAPSULE";
|
|
4104
|
+
ProofType3[ProofType3["JWT"] = 2] = "JWT";
|
|
4105
|
+
ProofType3[ProofType3["MTLS_ID"] = 3] = "MTLS_ID";
|
|
4106
|
+
ProofType3[ProofType3["DEVICE_SE"] = 4] = "DEVICE_SE";
|
|
4107
|
+
ProofType3[ProofType3["WITNESS_SIG"] = 5] = "WITNESS_SIG";
|
|
4108
|
+
return ProofType3;
|
|
4109
|
+
})(ProofType2 || {});
|
|
4110
|
+
var AxisContextZ = z2.object({
|
|
4111
|
+
pid: z2.custom((v) => Buffer.isBuffer(v)),
|
|
4112
|
+
// Process ID
|
|
4113
|
+
ts: z2.bigint(),
|
|
4114
|
+
// Timestamp
|
|
4115
|
+
intent: z2.string().min(1),
|
|
4116
|
+
actorId: z2.custom((v) => Buffer.isBuffer(v)),
|
|
4117
|
+
proofType: z2.enum(ProofType2),
|
|
4118
|
+
proofRef: z2.custom((v) => Buffer.isBuffer(v)),
|
|
4119
|
+
nonce: z2.custom((v) => Buffer.isBuffer(v)),
|
|
4120
|
+
ip: z2.string().min(1),
|
|
4121
|
+
nodeCertHash: z2.string().optional(),
|
|
4122
|
+
capsule: CapsuleZ.optional(),
|
|
4123
|
+
passport: PassportZ.optional(),
|
|
4124
|
+
meter: z2.any().optional()
|
|
4125
|
+
// ExecutionMeter instance - any to avoid circular dependency and allow class instance
|
|
4126
|
+
});
|
|
4127
|
+
var AxisErrorZ = z2.object({
|
|
4128
|
+
code: z2.string(),
|
|
4129
|
+
message: z2.string(),
|
|
4130
|
+
httpStatus: z2.number().int()
|
|
4131
|
+
});
|
|
4132
|
+
|
|
4133
|
+
// src/schemas/body-profile.validator.ts
|
|
4134
|
+
var import_common10 = require("@nestjs/common");
|
|
4135
|
+
var BodyProfile2 = /* @__PURE__ */ ((BodyProfile3) => {
|
|
4136
|
+
BodyProfile3[BodyProfile3["RAW"] = 0] = "RAW";
|
|
4137
|
+
BodyProfile3[BodyProfile3["TLV_MAP"] = 1] = "TLV_MAP";
|
|
4138
|
+
BodyProfile3[BodyProfile3["OBJ"] = 2] = "OBJ";
|
|
4139
|
+
BodyProfile3[BodyProfile3["ARR"] = 3] = "ARR";
|
|
4140
|
+
return BodyProfile3;
|
|
4141
|
+
})(BodyProfile2 || {});
|
|
4142
|
+
var BodyProfileValidator = class {
|
|
4143
|
+
constructor() {
|
|
4144
|
+
this.logger = new import_common10.Logger(BodyProfileValidator.name);
|
|
4145
|
+
}
|
|
4146
|
+
/**
|
|
4147
|
+
* Validate body matches declared profile
|
|
4148
|
+
*/
|
|
4149
|
+
validate(body, profile) {
|
|
4150
|
+
switch (profile) {
|
|
4151
|
+
case 0 /* RAW */:
|
|
4152
|
+
return this.validateRaw(body);
|
|
4153
|
+
case 1 /* TLV_MAP */:
|
|
4154
|
+
return this.validateTlvMap(body);
|
|
4155
|
+
case 2 /* OBJ */:
|
|
4156
|
+
return this.validateObj(body);
|
|
4157
|
+
case 3 /* ARR */:
|
|
4158
|
+
return this.validateArr(body);
|
|
4159
|
+
default:
|
|
4160
|
+
return {
|
|
4161
|
+
valid: false,
|
|
4162
|
+
error: `Unknown body profile: ${profile}`,
|
|
4163
|
+
profile
|
|
4164
|
+
};
|
|
4165
|
+
}
|
|
4166
|
+
}
|
|
4167
|
+
/**
|
|
4168
|
+
* RAW profile - no validation, any bytes accepted
|
|
4169
|
+
*/
|
|
4170
|
+
validateRaw(body) {
|
|
4171
|
+
return {
|
|
4172
|
+
valid: true,
|
|
4173
|
+
profile: 0 /* RAW */
|
|
4174
|
+
};
|
|
4175
|
+
}
|
|
4176
|
+
/**
|
|
4177
|
+
* TLV_MAP profile - flat TLV list (no nested structures)
|
|
4178
|
+
*/
|
|
4179
|
+
validateTlvMap(body) {
|
|
4180
|
+
try {
|
|
4181
|
+
const tlvs = (0, import_axis_protocol.decodeTLVsList)(body);
|
|
4182
|
+
for (const tlv2 of tlvs) {
|
|
4183
|
+
if (tlv2.type === 254 || tlv2.type === 255) {
|
|
4184
|
+
return {
|
|
4185
|
+
valid: false,
|
|
4186
|
+
error: "TLV_MAP profile cannot contain nested OBJ/ARR types",
|
|
4187
|
+
profile: 1 /* TLV_MAP */
|
|
4188
|
+
};
|
|
4189
|
+
}
|
|
4190
|
+
}
|
|
4191
|
+
return {
|
|
4192
|
+
valid: true,
|
|
4193
|
+
profile: 1 /* TLV_MAP */
|
|
4194
|
+
};
|
|
4195
|
+
} catch (error) {
|
|
4196
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4197
|
+
return {
|
|
4198
|
+
valid: false,
|
|
4199
|
+
error: `TLV_MAP decode failed: ${message}`,
|
|
4200
|
+
profile: 1 /* TLV_MAP */
|
|
4201
|
+
};
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
/**
|
|
4205
|
+
* OBJ profile - must be valid nested object
|
|
4206
|
+
*/
|
|
4207
|
+
validateObj(body) {
|
|
4208
|
+
try {
|
|
4209
|
+
const tlvs = (0, import_axis_protocol.decodeTLVsList)(body);
|
|
4210
|
+
const hasObj = tlvs.some((t) => t.type === 254);
|
|
4211
|
+
if (!hasObj && tlvs.length > 0) {
|
|
4212
|
+
return {
|
|
4213
|
+
valid: false,
|
|
4214
|
+
error: "OBJ profile must contain OBJ type (254)",
|
|
4215
|
+
profile: 2 /* OBJ */
|
|
4216
|
+
};
|
|
4217
|
+
}
|
|
4218
|
+
return {
|
|
4219
|
+
valid: true,
|
|
4220
|
+
profile: 2 /* OBJ */
|
|
4221
|
+
};
|
|
4222
|
+
} catch (error) {
|
|
4223
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4224
|
+
return {
|
|
4225
|
+
valid: false,
|
|
4226
|
+
error: `OBJ decode failed: ${message}`,
|
|
4227
|
+
profile: 2 /* OBJ */
|
|
4228
|
+
};
|
|
4229
|
+
}
|
|
4230
|
+
}
|
|
4231
|
+
/**
|
|
4232
|
+
* ARR profile - must be valid array
|
|
4233
|
+
*/
|
|
4234
|
+
validateArr(body) {
|
|
4235
|
+
try {
|
|
4236
|
+
const tlvs = (0, import_axis_protocol.decodeTLVsList)(body);
|
|
4237
|
+
const hasArr = tlvs.some((t) => t.type === 255);
|
|
4238
|
+
if (!hasArr && tlvs.length > 0) {
|
|
4239
|
+
return {
|
|
4240
|
+
valid: false,
|
|
4241
|
+
error: "ARR profile must contain ARR type (255)",
|
|
4242
|
+
profile: 3 /* ARR */
|
|
4243
|
+
};
|
|
4244
|
+
}
|
|
4245
|
+
return {
|
|
4246
|
+
valid: true,
|
|
4247
|
+
profile: 3 /* ARR */
|
|
4248
|
+
};
|
|
4249
|
+
} catch (error) {
|
|
4250
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4251
|
+
return {
|
|
4252
|
+
valid: false,
|
|
4253
|
+
error: `ARR decode failed: ${message}`,
|
|
4254
|
+
profile: 3 /* ARR */
|
|
4255
|
+
};
|
|
4256
|
+
}
|
|
4257
|
+
}
|
|
4258
|
+
};
|
|
4259
|
+
BodyProfileValidator = __decorateClass([
|
|
4260
|
+
(0, import_common10.Injectable)()
|
|
4261
|
+
], BodyProfileValidator);
|
|
4262
|
+
|
|
4263
|
+
// src/security/index.ts
|
|
4264
|
+
var security_exports = {};
|
|
4265
|
+
__export(security_exports, {
|
|
4266
|
+
CAPABILITIES: () => CAPABILITIES,
|
|
4267
|
+
INTENT_REQUIREMENTS: () => INTENT_REQUIREMENTS,
|
|
4268
|
+
PROOF_CAPABILITIES: () => PROOF_CAPABILITIES,
|
|
4269
|
+
canAccessResource: () => canAccessResource,
|
|
4270
|
+
hasScope: () => hasScope,
|
|
4271
|
+
parseScope: () => parseScope
|
|
4272
|
+
});
|
|
4273
|
+
|
|
4274
|
+
// src/sensors/index.ts
|
|
4275
|
+
var sensors_exports = {};
|
|
4276
|
+
__export(sensors_exports, {
|
|
4277
|
+
AccessProfileResolverSensor: () => AccessProfileResolverSensor,
|
|
4278
|
+
BodyBudgetSensor: () => BodyBudgetSensor,
|
|
4279
|
+
CapabilityEnforcementSensor: () => CapabilityEnforcementSensor,
|
|
4280
|
+
ChunkHashSensor: () => ChunkHashSensor,
|
|
4281
|
+
EntropySensor: () => EntropySensor,
|
|
4282
|
+
ExecutionTimeoutSensor: () => ExecutionTimeoutSensor,
|
|
4283
|
+
FrameBudgetSensor: () => FrameBudgetSensor,
|
|
4284
|
+
FrameHeaderSanitySensor: () => FrameHeaderSanitySensor,
|
|
4285
|
+
HeaderTLVLimitSensor: () => HeaderTLVLimitSensor,
|
|
4286
|
+
IntentAllowlistSensor: () => IntentAllowlistSensor,
|
|
4287
|
+
IntentRegistrySensor: () => IntentRegistrySensor,
|
|
4288
|
+
ProofPresenceSensor: () => ProofPresenceSensor,
|
|
4289
|
+
ProtocolStrictSensor: () => ProtocolStrictSensor,
|
|
4290
|
+
ReceiptPolicySensor: () => ReceiptPolicySensor,
|
|
4291
|
+
SchemaValidationSensor: () => SchemaValidationSensor,
|
|
4292
|
+
StreamScopeSensor: () => StreamScopeSensor,
|
|
4293
|
+
TLVParseSensor: () => TLVParseSensor,
|
|
4294
|
+
VarintHardeningSensor: () => VarintHardeningSensor
|
|
4295
|
+
});
|
|
4296
|
+
|
|
4297
|
+
// src/sensors/access-profile-resolver.sensor.ts
|
|
4298
|
+
var import_common11 = require("@nestjs/common");
|
|
4299
|
+
var AccessProfileResolverSensor = class {
|
|
4300
|
+
constructor() {
|
|
4301
|
+
/** AxisSensor identifier */
|
|
4302
|
+
this.name = "AccessProfileResolverSensor";
|
|
4303
|
+
/**
|
|
4304
|
+
* Execution order - runs early to establish the access profile
|
|
4305
|
+
* for downstream sensors.
|
|
4306
|
+
*/
|
|
4307
|
+
this.order = BAND.IDENTITY + 10;
|
|
4308
|
+
}
|
|
4309
|
+
supports() {
|
|
4310
|
+
return true;
|
|
4311
|
+
}
|
|
4312
|
+
async run(input) {
|
|
4313
|
+
const hasCapsule = !!input.metadata?.capsuleId;
|
|
4314
|
+
const hasPassport = !!input.metadata?.passportSig;
|
|
4315
|
+
const hasMTLS = !!input.metadata?.mtlsId;
|
|
4316
|
+
const profile = hasCapsule || hasPassport || hasMTLS ? "GUARDED" : "PUBLIC";
|
|
4317
|
+
if (!input.metadata) input.metadata = {};
|
|
4318
|
+
input.metadata.profile = profile;
|
|
4319
|
+
return { action: "ALLOW" };
|
|
4320
|
+
}
|
|
4321
|
+
};
|
|
4322
|
+
AccessProfileResolverSensor = __decorateClass([
|
|
4323
|
+
Sensor(),
|
|
4324
|
+
(0, import_common11.Injectable)()
|
|
4325
|
+
], AccessProfileResolverSensor);
|
|
4326
|
+
|
|
4327
|
+
// src/sensors/body-budget.sensor.ts
|
|
4328
|
+
var import_common12 = require("@nestjs/common");
|
|
4329
|
+
var BodyBudgetSensor = class {
|
|
4330
|
+
constructor() {
|
|
4331
|
+
/** AxisSensor identifier */
|
|
4332
|
+
this.name = "BodyBudgetSensor";
|
|
4333
|
+
/**
|
|
4334
|
+
* Execution order - after authentication
|
|
4335
|
+
*
|
|
4336
|
+
* Order 150 ensures:
|
|
4337
|
+
* - Authentication complete
|
|
4338
|
+
* - Runs before full body read
|
|
4339
|
+
* - Before schema validation (170)
|
|
4340
|
+
*/
|
|
4341
|
+
this.order = BAND.CONTENT + 10;
|
|
4342
|
+
}
|
|
4343
|
+
/**
|
|
4344
|
+
* Determines if this sensor should process the given input.
|
|
4345
|
+
*
|
|
4346
|
+
* Requires at least 8 bytes of peeked data to read headers.
|
|
4347
|
+
*
|
|
4348
|
+
* @param {SensorInput} input - Incoming request
|
|
4349
|
+
* @returns {boolean} True if sufficient peek data available
|
|
4350
|
+
*/
|
|
4351
|
+
supports(input) {
|
|
4352
|
+
return !!input.peek && input.peek.length >= 8;
|
|
4353
|
+
}
|
|
4354
|
+
/**
|
|
4355
|
+
* Validates header and body lengths against configured limits.
|
|
4356
|
+
*
|
|
4357
|
+
* **Frame Parsing:**
|
|
4358
|
+
* - Skip magic (5 bytes)
|
|
4359
|
+
* - Skip version (1 byte)
|
|
4360
|
+
* - Skip flags (1 byte)
|
|
4361
|
+
* - Read HDR_LEN varint
|
|
4362
|
+
* - Read BODY_LEN varint
|
|
4363
|
+
* - Compare against MAX_HDR_LEN and MAX_BODY_LEN
|
|
4364
|
+
*
|
|
4365
|
+
* @param {SensorInput} input - Request with peek data
|
|
4366
|
+
* @returns {Promise<SensorDecision>} ALLOW or DENY based on size limits
|
|
4367
|
+
*/
|
|
4368
|
+
async run(input) {
|
|
4369
|
+
const { peek } = input;
|
|
4370
|
+
if (!peek || peek.length < 8) {
|
|
4371
|
+
return { action: "ALLOW" };
|
|
4372
|
+
}
|
|
4373
|
+
try {
|
|
4374
|
+
let offset = 5;
|
|
4375
|
+
offset += 1;
|
|
4376
|
+
offset += 1;
|
|
4377
|
+
const { value: hdrLen, length: hdrBytes } = (0, import_axis_protocol3.decodeVarint)(peek, offset);
|
|
4378
|
+
offset += hdrBytes;
|
|
4379
|
+
const { value: bodyLen } = (0, import_axis_protocol3.decodeVarint)(peek, offset);
|
|
4380
|
+
if (hdrLen > import_axis_protocol2.MAX_HDR_LEN) {
|
|
4381
|
+
return {
|
|
4382
|
+
action: "DENY",
|
|
4383
|
+
code: "HEADER_TOO_LARGE",
|
|
4384
|
+
reason: `Header size ${hdrLen} exceeds limit ${import_axis_protocol2.MAX_HDR_LEN}`
|
|
4385
|
+
};
|
|
4386
|
+
}
|
|
4387
|
+
if (bodyLen > import_axis_protocol2.MAX_BODY_LEN) {
|
|
4388
|
+
return {
|
|
4389
|
+
action: "DENY",
|
|
4390
|
+
code: "BODY_TOO_LARGE",
|
|
4391
|
+
reason: `Body size ${bodyLen} exceeds limit ${import_axis_protocol2.MAX_BODY_LEN}`
|
|
4392
|
+
};
|
|
4393
|
+
}
|
|
4394
|
+
return { action: "ALLOW" };
|
|
4395
|
+
} catch (e) {
|
|
4396
|
+
return { action: "ALLOW" };
|
|
4397
|
+
}
|
|
4398
|
+
}
|
|
4399
|
+
};
|
|
4400
|
+
BodyBudgetSensor = __decorateClass([
|
|
4401
|
+
Sensor(),
|
|
4402
|
+
(0, import_common12.Injectable)()
|
|
4403
|
+
], BodyBudgetSensor);
|
|
4404
|
+
|
|
4405
|
+
// src/sensors/capability-enforcement.sensor.ts
|
|
4406
|
+
var import_common13 = require("@nestjs/common");
|
|
4407
|
+
var CapabilityEnforcementSensor = class {
|
|
4408
|
+
constructor() {
|
|
4409
|
+
this.logger = new import_common13.Logger(CapabilityEnforcementSensor.name);
|
|
4410
|
+
/** AxisSensor identifier for logging and registry */
|
|
4411
|
+
this.name = "CapabilityEnforcementSensor";
|
|
4412
|
+
/**
|
|
4413
|
+
* Execution order - runs after authentication
|
|
4414
|
+
*
|
|
4415
|
+
* Order 100 ensures:
|
|
4416
|
+
* - Capsule is verified (CapsuleVerifySensor @ 80)
|
|
4417
|
+
* - Signature is verified (SigVerifySensor @ 90)
|
|
4418
|
+
* - We know the proof type for capability lookup
|
|
4419
|
+
*/
|
|
4420
|
+
this.order = BAND.POLICY + 10;
|
|
4421
|
+
}
|
|
4422
|
+
/**
|
|
4423
|
+
* Determines if this sensor should process the given input.
|
|
4424
|
+
*
|
|
4425
|
+
* Only activates when an intent is present.
|
|
4426
|
+
*
|
|
4427
|
+
* @param {SensorInput} input - Incoming AXIS request
|
|
4428
|
+
* @returns {boolean} True if intent is present
|
|
4429
|
+
*/
|
|
4430
|
+
supports(input) {
|
|
4431
|
+
return !!input.intent;
|
|
4432
|
+
}
|
|
4433
|
+
/**
|
|
4434
|
+
* Enforces capability requirements for the requested intent.
|
|
4435
|
+
*
|
|
4436
|
+
* **Processing Flow:**
|
|
4437
|
+
* 1. Extract proof type from packet (default: 0/NONE)
|
|
4438
|
+
* 2. Look up capabilities granted by this proof type
|
|
4439
|
+
* 3. Look up capabilities required by the intent
|
|
4440
|
+
* 4. If no requirements, ALLOW
|
|
4441
|
+
* 5. Check if all required capabilities are granted
|
|
4442
|
+
* 6. If missing capabilities, DENY with details
|
|
4443
|
+
* 7. Otherwise, ALLOW
|
|
4444
|
+
*
|
|
4445
|
+
* @param {SensorInput} input - Request with intent and packet
|
|
4446
|
+
* @returns {Promise<SensorDecision>} ALLOW or DENY based on capabilities
|
|
4447
|
+
*/
|
|
4448
|
+
async run(input) {
|
|
4449
|
+
const { intent, packet } = input;
|
|
4450
|
+
if (!intent) {
|
|
4451
|
+
return { action: "ALLOW" };
|
|
4452
|
+
}
|
|
4453
|
+
const proofType = packet?.proofType ?? 0;
|
|
4454
|
+
const grantedCapabilities = PROOF_CAPABILITIES[proofType] || [];
|
|
4455
|
+
const requiredCapabilities = this.getRequiredCapabilities(intent);
|
|
4456
|
+
if (requiredCapabilities.length === 0) {
|
|
4457
|
+
return { action: "ALLOW" };
|
|
4458
|
+
}
|
|
4459
|
+
const missingCapabilities = requiredCapabilities.filter(
|
|
4460
|
+
(cap) => !grantedCapabilities.includes(cap)
|
|
4461
|
+
);
|
|
4462
|
+
if (missingCapabilities.length > 0) {
|
|
4463
|
+
this.logger.warn(
|
|
4464
|
+
`Capability denied for ${intent}: missing ${missingCapabilities.join(", ")} (has: ${grantedCapabilities.join(", ")})`
|
|
4465
|
+
);
|
|
4466
|
+
return {
|
|
4467
|
+
action: "DENY",
|
|
4468
|
+
code: "CAPABILITY_DENIED",
|
|
4469
|
+
reason: `Missing capabilities: ${missingCapabilities.join(", ")}`
|
|
4470
|
+
};
|
|
4471
|
+
}
|
|
4472
|
+
return { action: "ALLOW" };
|
|
4473
|
+
}
|
|
4474
|
+
/**
|
|
4475
|
+
* Gets required capabilities for an intent.
|
|
4476
|
+
*
|
|
4477
|
+
* **Lookup Strategy:**
|
|
4478
|
+
* 1. Check for exact intent match
|
|
4479
|
+
* 2. Check for prefix pattern match (*.suffix)
|
|
4480
|
+
* 3. Default to 'execute' for unknown intents
|
|
4481
|
+
*
|
|
4482
|
+
* @private
|
|
4483
|
+
* @param {string} intent - Intent name to look up
|
|
4484
|
+
* @returns {Capability[]} Array of required capabilities
|
|
4485
|
+
*/
|
|
4486
|
+
getRequiredCapabilities(intent) {
|
|
4487
|
+
if (INTENT_REQUIREMENTS[intent]) {
|
|
4488
|
+
return INTENT_REQUIREMENTS[intent];
|
|
4489
|
+
}
|
|
4490
|
+
for (const [pattern, caps] of Object.entries(INTENT_REQUIREMENTS)) {
|
|
4491
|
+
if (pattern.endsWith(".*")) {
|
|
4492
|
+
const prefix = pattern.slice(0, -1);
|
|
4493
|
+
if (intent.startsWith(prefix)) {
|
|
4494
|
+
return caps;
|
|
4495
|
+
}
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
return ["execute"];
|
|
4499
|
+
}
|
|
4500
|
+
};
|
|
4501
|
+
CapabilityEnforcementSensor = __decorateClass([
|
|
4502
|
+
Sensor(),
|
|
4503
|
+
(0, import_common13.Injectable)()
|
|
4504
|
+
], CapabilityEnforcementSensor);
|
|
4505
|
+
|
|
4506
|
+
// src/sensors/chunk-hash.sensor.ts
|
|
4507
|
+
var import_common14 = require("@nestjs/common");
|
|
4508
|
+
var import_crypto6 = require("crypto");
|
|
4509
|
+
var ChunkHashSensor = class {
|
|
4510
|
+
constructor() {
|
|
4511
|
+
/** Sensor identifier */
|
|
4512
|
+
this.name = "ChunkHashSensor";
|
|
4513
|
+
/**
|
|
4514
|
+
* Execution order - after session validation
|
|
4515
|
+
*
|
|
4516
|
+
* Order 190 ensures:
|
|
4517
|
+
* - Session validated (180)
|
|
4518
|
+
* - Chunk parameters verified
|
|
4519
|
+
* - Hash check before storage
|
|
4520
|
+
*/
|
|
4521
|
+
this.order = BAND.CONTENT + 50;
|
|
4522
|
+
}
|
|
4523
|
+
/**
|
|
4524
|
+
* Determines if this sensor should process the given input.
|
|
4525
|
+
*
|
|
4526
|
+
* Only processes file.chunk intents.
|
|
4527
|
+
*
|
|
4528
|
+
* @param {SensorInput} input - Incoming request
|
|
4529
|
+
* @returns {boolean} True if intent is 'file.chunk'
|
|
4530
|
+
*/
|
|
4531
|
+
supports(input) {
|
|
4532
|
+
return input.intent === "file.chunk";
|
|
4533
|
+
}
|
|
4534
|
+
/**
|
|
4535
|
+
* Validates chunk data against declared SHA-256 hash.
|
|
4536
|
+
*
|
|
4537
|
+
* **Processing Flow:**
|
|
4538
|
+
* 1. Check for required headerTLVs and body
|
|
4539
|
+
* 2. Extract expected hash from TLV 73
|
|
4540
|
+
* 3. Verify hash is exactly 32 bytes
|
|
4541
|
+
* 4. Compute SHA-256 of body
|
|
4542
|
+
* 5. Compare bytes (timing-safe)
|
|
4543
|
+
* 6. DENY on mismatch
|
|
4544
|
+
*
|
|
4545
|
+
* @param {SensorInput} input - Request with chunk body
|
|
4546
|
+
* @returns {Promise<SensorDecision>} ALLOW if hash matches, DENY otherwise
|
|
4547
|
+
*/
|
|
4548
|
+
async run(input) {
|
|
4549
|
+
const headerTLVs = input.headerTLVs;
|
|
4550
|
+
const bodyBytes = input.body;
|
|
4551
|
+
if (!headerTLVs || !bodyBytes) {
|
|
4552
|
+
return {
|
|
4553
|
+
action: "DENY",
|
|
4554
|
+
code: "SENSOR_INVALID_INPUT",
|
|
4555
|
+
reason: "Missing headerTLVs or body"
|
|
4556
|
+
};
|
|
4557
|
+
}
|
|
4558
|
+
const TLV_SHA256_CHUNK2 = 73;
|
|
4559
|
+
const expected = headerTLVs.get(TLV_SHA256_CHUNK2);
|
|
4560
|
+
if (!expected || expected.length !== 32) {
|
|
4561
|
+
return {
|
|
4562
|
+
action: "DENY",
|
|
4563
|
+
code: "FILE_CHUNK_HASH_MISSING",
|
|
4564
|
+
reason: "Missing sha256Chunk TLV in header"
|
|
4565
|
+
};
|
|
4566
|
+
}
|
|
4567
|
+
const actual = (0, import_crypto6.createHash)("sha256").update(bodyBytes).digest();
|
|
4568
|
+
if (!Buffer.from(actual).equals(Buffer.from(expected))) {
|
|
4569
|
+
return {
|
|
4570
|
+
action: "DENY",
|
|
4571
|
+
code: "FILE_CHUNK_HASH_MISMATCH",
|
|
4572
|
+
reason: "Chunk hash mismatch - data corrupted"
|
|
4573
|
+
};
|
|
4574
|
+
}
|
|
4575
|
+
return { action: "ALLOW" };
|
|
4576
|
+
}
|
|
4577
|
+
};
|
|
4578
|
+
ChunkHashSensor = __decorateClass([
|
|
4579
|
+
Sensor(),
|
|
4580
|
+
(0, import_common14.Injectable)()
|
|
4581
|
+
], ChunkHashSensor);
|
|
4582
|
+
|
|
4583
|
+
// src/sensors/entropy.sensor.ts
|
|
4584
|
+
var import_common15 = require("@nestjs/common");
|
|
4585
|
+
var crypto4 = __toESM(require("crypto"));
|
|
4586
|
+
var EntropySensor = class {
|
|
4587
|
+
constructor() {
|
|
4588
|
+
this.logger = new import_common15.Logger(EntropySensor.name);
|
|
4589
|
+
/**
|
|
4590
|
+
* Minimum acceptable entropy in bits per byte.
|
|
4591
|
+
*
|
|
4592
|
+
* 3.0 bits/byte is a conservative threshold:
|
|
4593
|
+
* - Random data: ~7.9 bits/byte
|
|
4594
|
+
* - English text: ~4.5 bits/byte
|
|
4595
|
+
* - Sequential data: ~0-2 bits/byte
|
|
4596
|
+
*/
|
|
4597
|
+
this.MIN_ENTROPY_THRESHOLD = 3;
|
|
4598
|
+
/** AxisSensor identifier */
|
|
4599
|
+
this.name = "EntropySensor";
|
|
4600
|
+
/**
|
|
4601
|
+
* Execution order - anomaly detection phase
|
|
4602
|
+
*
|
|
4603
|
+
* Order 130 ensures:
|
|
4604
|
+
* - Replay protection done (120)
|
|
4605
|
+
* - Runs before expensive policy lookups
|
|
4606
|
+
*/
|
|
4607
|
+
this.order = BAND.POLICY + 35;
|
|
4608
|
+
}
|
|
4609
|
+
/**
|
|
4610
|
+
* Calculates Shannon entropy of a byte array.
|
|
4611
|
+
*
|
|
4612
|
+
* **Algorithm:**
|
|
4613
|
+
* 1. Count frequency of each byte value (0-255)
|
|
4614
|
+
* 2. Calculate probability p = count / total
|
|
4615
|
+
* 3. Sum: -Σ(p * log2(p))
|
|
4616
|
+
*
|
|
4617
|
+
* @private
|
|
4618
|
+
* @param {Uint8Array} data - Bytes to analyze
|
|
4619
|
+
* @returns {number} Entropy in bits per byte (0-8 scale)
|
|
4620
|
+
*/
|
|
4621
|
+
calculateEntropy(data) {
|
|
4622
|
+
if (data.length === 0) return 0;
|
|
4623
|
+
const freq = /* @__PURE__ */ new Map();
|
|
4624
|
+
for (const byte of data) {
|
|
4625
|
+
freq.set(byte, (freq.get(byte) || 0) + 1);
|
|
4626
|
+
}
|
|
4627
|
+
let entropy = 0;
|
|
4628
|
+
const len = data.length;
|
|
4629
|
+
for (const count of freq.values()) {
|
|
4630
|
+
const p = count / len;
|
|
4631
|
+
entropy -= p * Math.log2(p);
|
|
4632
|
+
}
|
|
4633
|
+
return entropy;
|
|
4634
|
+
}
|
|
4635
|
+
/**
|
|
4636
|
+
* Checks for sequential patterns in data.
|
|
4637
|
+
*
|
|
4638
|
+
* Detects sequences like [1,2,3,4...] or [10,9,8,7...].
|
|
4639
|
+
* More than 50% sequential is considered suspicious.
|
|
4640
|
+
*
|
|
4641
|
+
* @private
|
|
4642
|
+
* @param {Uint8Array} data - Bytes to analyze
|
|
4643
|
+
* @returns {boolean} True if sequential pattern detected
|
|
4644
|
+
*/
|
|
4645
|
+
hasSequentialPattern(data) {
|
|
4646
|
+
if (data.length < 4) return false;
|
|
4647
|
+
let ascending = 0;
|
|
4648
|
+
let descending = 0;
|
|
4649
|
+
for (let i = 1; i < data.length; i++) {
|
|
4650
|
+
if (data[i] === data[i - 1] + 1) ascending++;
|
|
4651
|
+
if (data[i] === data[i - 1] - 1) descending++;
|
|
4652
|
+
}
|
|
4653
|
+
return ascending > data.length / 2 || descending > data.length / 2;
|
|
4654
|
+
}
|
|
4655
|
+
/**
|
|
4656
|
+
* Checks for repeated patterns in data.
|
|
4657
|
+
*
|
|
4658
|
+
* Detects patterns like [0xAB, 0xCD, 0xAB, 0xCD...].
|
|
4659
|
+
* Checks for 2, 4, and 8 byte repeating patterns.
|
|
4660
|
+
*
|
|
4661
|
+
* @private
|
|
4662
|
+
* @param {Uint8Array} data - Bytes to analyze
|
|
4663
|
+
* @returns {boolean} True if repeated pattern detected
|
|
4664
|
+
*/
|
|
4665
|
+
hasRepeatedPattern(data) {
|
|
4666
|
+
if (data.length < 8) return false;
|
|
4667
|
+
for (const patternLen of [2, 4, 8]) {
|
|
4668
|
+
if (data.length % patternLen !== 0) continue;
|
|
4669
|
+
let matches = 0;
|
|
4670
|
+
for (let i = patternLen; i < data.length; i++) {
|
|
4671
|
+
if (data[i] === data[i % patternLen]) matches++;
|
|
4672
|
+
}
|
|
4673
|
+
if (matches > (data.length - patternLen) * 0.9) {
|
|
4674
|
+
return true;
|
|
4675
|
+
}
|
|
4676
|
+
}
|
|
4677
|
+
return false;
|
|
4678
|
+
}
|
|
4679
|
+
/**
|
|
4680
|
+
* Analyzes entropy of PID and nonce in request headers.
|
|
4681
|
+
*
|
|
4682
|
+
* **Processing Flow:**
|
|
4683
|
+
* 1. Extract PID and nonce from header TLVs
|
|
4684
|
+
* 2. Calculate entropy for each
|
|
4685
|
+
* 3. Check for sequential patterns
|
|
4686
|
+
* 4. Check for repeated patterns
|
|
4687
|
+
* 5. Accumulate issues and score delta
|
|
4688
|
+
* 6. Return FLAG if issues found, ALLOW otherwise
|
|
4689
|
+
*
|
|
4690
|
+
* @param {SensorInput} input - Request with header TLVs
|
|
4691
|
+
* @returns {Promise<SensorDecision>} ALLOW or FLAG based on entropy analysis
|
|
4692
|
+
*/
|
|
4693
|
+
async run(input) {
|
|
4694
|
+
const headers = input.headerTLVs;
|
|
4695
|
+
if (!headers) {
|
|
4696
|
+
return { action: "ALLOW" };
|
|
4697
|
+
}
|
|
4698
|
+
const pid = headers.get(import_axis_protocol2.TLV_PID);
|
|
4699
|
+
const nonce = headers.get(import_axis_protocol2.TLV_NONCE);
|
|
4700
|
+
const issues = [];
|
|
4701
|
+
let totalDelta = 0;
|
|
4702
|
+
if (pid && pid.length > 0) {
|
|
4703
|
+
const pidEntropy = this.calculateEntropy(pid);
|
|
4704
|
+
if (pidEntropy < this.MIN_ENTROPY_THRESHOLD) {
|
|
4705
|
+
issues.push(`pid_low_entropy:${pidEntropy.toFixed(2)}`);
|
|
4706
|
+
totalDelta -= 3;
|
|
4707
|
+
}
|
|
4708
|
+
if (this.hasSequentialPattern(pid)) {
|
|
4709
|
+
issues.push("pid_sequential");
|
|
4710
|
+
totalDelta -= 5;
|
|
4711
|
+
}
|
|
4712
|
+
if (this.hasRepeatedPattern(pid)) {
|
|
4713
|
+
issues.push("pid_repeated");
|
|
4714
|
+
totalDelta -= 5;
|
|
4715
|
+
}
|
|
4716
|
+
}
|
|
4717
|
+
if (nonce && nonce.length > 0) {
|
|
4718
|
+
const nonceEntropy = this.calculateEntropy(nonce);
|
|
4719
|
+
if (nonceEntropy < this.MIN_ENTROPY_THRESHOLD) {
|
|
4720
|
+
issues.push(`nonce_low_entropy:${nonceEntropy.toFixed(2)}`);
|
|
4721
|
+
totalDelta -= 3;
|
|
4722
|
+
}
|
|
4723
|
+
if (this.hasSequentialPattern(nonce)) {
|
|
4724
|
+
issues.push("nonce_sequential");
|
|
4725
|
+
totalDelta -= 5;
|
|
4726
|
+
}
|
|
4727
|
+
if (this.hasRepeatedPattern(nonce)) {
|
|
4728
|
+
issues.push("nonce_repeated");
|
|
4729
|
+
totalDelta -= 5;
|
|
4730
|
+
}
|
|
4731
|
+
}
|
|
4732
|
+
if (issues.length > 0) {
|
|
4733
|
+
this.logger.warn(`Entropy issues from ${input.ip}: ${issues.join(", ")}`);
|
|
4734
|
+
return {
|
|
4735
|
+
action: "FLAG",
|
|
4736
|
+
scoreDelta: totalDelta,
|
|
4737
|
+
reasons: issues
|
|
4738
|
+
};
|
|
4739
|
+
}
|
|
4740
|
+
return { action: "ALLOW" };
|
|
4741
|
+
}
|
|
4742
|
+
/**
|
|
4743
|
+
* Generates cryptographically secure random bytes.
|
|
4744
|
+
*
|
|
4745
|
+
* Utility method for SDK/client code to ensure proper entropy.
|
|
4746
|
+
* Uses Node.js crypto.randomBytes for secure PRNG.
|
|
4747
|
+
*
|
|
4748
|
+
* @static
|
|
4749
|
+
* @param {number} length - Number of random bytes
|
|
4750
|
+
* @returns {Uint8Array} Cryptographically secure random bytes
|
|
4751
|
+
*/
|
|
4752
|
+
static generateSecureRandom(length) {
|
|
4753
|
+
return new Uint8Array(crypto4.randomBytes(length));
|
|
4754
|
+
}
|
|
4755
|
+
};
|
|
4756
|
+
EntropySensor = __decorateClass([
|
|
4757
|
+
Sensor(),
|
|
4758
|
+
(0, import_common15.Injectable)()
|
|
4759
|
+
], EntropySensor);
|
|
4760
|
+
|
|
4761
|
+
// src/sensors/execution-timeout.sensor.ts
|
|
4762
|
+
var import_common16 = require("@nestjs/common");
|
|
4763
|
+
var ExecutionTimeoutSensor = class {
|
|
4764
|
+
constructor() {
|
|
4765
|
+
this.logger = new import_common16.Logger(ExecutionTimeoutSensor.name);
|
|
4766
|
+
/** AxisSensor identifier */
|
|
4767
|
+
this.name = "ExecutionTimeoutSensor";
|
|
4768
|
+
/**
|
|
4769
|
+
* Execution order - late, near handler execution
|
|
4770
|
+
*
|
|
4771
|
+
* Order 210 ensures:
|
|
4772
|
+
* - All validation complete
|
|
4773
|
+
* - Deadline set just before handler
|
|
4774
|
+
*/
|
|
4775
|
+
this.order = BAND.BUSINESS + 10;
|
|
4776
|
+
}
|
|
4777
|
+
/**
|
|
4778
|
+
* Determines if this sensor should process the given input.
|
|
4779
|
+
*
|
|
4780
|
+
* @param {SensorInput} input - Incoming request
|
|
4781
|
+
* @returns {boolean} True if intent is present
|
|
4782
|
+
*/
|
|
4783
|
+
supports(input) {
|
|
4784
|
+
return !!input.intent;
|
|
4785
|
+
}
|
|
4786
|
+
/**
|
|
4787
|
+
* Sets execution deadline in the request context.
|
|
4788
|
+
*
|
|
4789
|
+
* **Processing Flow:**
|
|
4790
|
+
* 1. Look up timeout for intent
|
|
4791
|
+
* 2. Calculate absolute deadline
|
|
4792
|
+
* 3. Store in context for handler use
|
|
4793
|
+
* 4. Return ALLOW
|
|
4794
|
+
*
|
|
4795
|
+
* @param {SensorInput} input - Request with intent
|
|
4796
|
+
* @returns {Promise<SensorDecision>} Always ALLOW
|
|
4797
|
+
*/
|
|
4798
|
+
async run(input) {
|
|
4799
|
+
const { intent, context } = input;
|
|
4800
|
+
if (!intent) {
|
|
4801
|
+
return { action: "ALLOW" };
|
|
4802
|
+
}
|
|
4803
|
+
const timeout = resolveTimeout(intent);
|
|
4804
|
+
const deadline = Date.now() + timeout;
|
|
4805
|
+
if (context) {
|
|
4806
|
+
context.deadline = deadline;
|
|
4807
|
+
context.timeoutMs = timeout;
|
|
4808
|
+
}
|
|
4809
|
+
this.logger.debug(
|
|
4810
|
+
`Set ${timeout}ms timeout for ${intent} (deadline: ${new Date(deadline).toISOString()})`
|
|
4811
|
+
);
|
|
4812
|
+
return { action: "ALLOW" };
|
|
4813
|
+
}
|
|
4814
|
+
/**
|
|
4815
|
+
* Checks if a deadline has been exceeded.
|
|
4816
|
+
*
|
|
4817
|
+
* Utility method for handler code.
|
|
4818
|
+
*
|
|
4819
|
+
* @static
|
|
4820
|
+
* @param {object} ctx - Context with deadline
|
|
4821
|
+
* @returns {boolean} True if deadline passed
|
|
4822
|
+
*/
|
|
4823
|
+
static isExpired(ctx) {
|
|
4824
|
+
if (!ctx.deadline) return false;
|
|
4825
|
+
return Date.now() > ctx.deadline;
|
|
4826
|
+
}
|
|
4827
|
+
/**
|
|
4828
|
+
* Gets remaining time until deadline.
|
|
4829
|
+
*
|
|
4830
|
+
* Utility method for handler code.
|
|
4831
|
+
*
|
|
4832
|
+
* @static
|
|
4833
|
+
* @param {object} ctx - Context with deadline
|
|
4834
|
+
* @returns {number} Remaining milliseconds (0 if expired, Infinity if no deadline)
|
|
4835
|
+
*/
|
|
4836
|
+
static getRemainingMs(ctx) {
|
|
4837
|
+
if (!ctx.deadline) return Infinity;
|
|
4838
|
+
return Math.max(0, ctx.deadline - Date.now());
|
|
4839
|
+
}
|
|
4840
|
+
};
|
|
4841
|
+
ExecutionTimeoutSensor = __decorateClass([
|
|
4842
|
+
Sensor(),
|
|
4843
|
+
(0, import_common16.Injectable)()
|
|
4844
|
+
], ExecutionTimeoutSensor);
|
|
4845
|
+
|
|
4846
|
+
// src/sensors/frame-budget.sensor.ts
|
|
4847
|
+
var import_common17 = require("@nestjs/common");
|
|
4848
|
+
var FrameBudgetSensor = class {
|
|
4849
|
+
constructor(config) {
|
|
4850
|
+
this.config = config;
|
|
4851
|
+
/** AxisSensor identifier for logging and registry */
|
|
4852
|
+
this.name = "FrameBudgetSensor";
|
|
4853
|
+
/**
|
|
4854
|
+
* Execution order - runs after protocol validation
|
|
4855
|
+
*
|
|
4856
|
+
* Order 20 ensures:
|
|
4857
|
+
* - Protocol is valid (ProtocolStrictSensor @ 10)
|
|
4858
|
+
* - Size checked before expensive processing
|
|
4859
|
+
*/
|
|
4860
|
+
this.order = BAND.WIRE + 20;
|
|
4861
|
+
}
|
|
4862
|
+
/**
|
|
4863
|
+
* Determines if this sensor should process the given input.
|
|
4864
|
+
*
|
|
4865
|
+
* Only activates when Content-Length header is available.
|
|
4866
|
+
* WebSocket frames may not have Content-Length; they use different size tracking.
|
|
4867
|
+
*
|
|
4868
|
+
* @param {SensorInput} input - Incoming AXIS request
|
|
4869
|
+
* @returns {boolean} True if Content-Length is present
|
|
4870
|
+
*/
|
|
4871
|
+
supports(input) {
|
|
4872
|
+
return typeof input.contentLength === "number";
|
|
4873
|
+
}
|
|
4874
|
+
/**
|
|
4875
|
+
* Validates frame size against configured limits.
|
|
4876
|
+
*
|
|
4877
|
+
* **Current Implementation:** Stub that always allows.
|
|
4878
|
+
*
|
|
4879
|
+
* **TODO:** Full implementation should:
|
|
4880
|
+
* 1. Load intent policy for the request
|
|
4881
|
+
* 2. Get maxFrameBytes from policy
|
|
4882
|
+
* 3. Compare against contentLength
|
|
4883
|
+
* 4. DENY if exceeded
|
|
4884
|
+
*
|
|
4885
|
+
* @param {SensorInput} input - Request with contentLength
|
|
4886
|
+
* @returns {Promise<SensorDecision>} ALLOW or DENY based on size
|
|
4887
|
+
*/
|
|
4888
|
+
async run(input) {
|
|
4889
|
+
const maxBytes = this.config.get("AXIS_MAX_FRAME_SIZE") || 50 * 1024 * 1024;
|
|
4890
|
+
const contentLength = input.contentLength;
|
|
4891
|
+
if (typeof contentLength !== "number") {
|
|
4892
|
+
return { action: "ALLOW" };
|
|
4893
|
+
}
|
|
4894
|
+
if (contentLength > maxBytes) {
|
|
4895
|
+
return {
|
|
4896
|
+
action: "DENY",
|
|
4897
|
+
code: "FRAME_TOO_LARGE",
|
|
4898
|
+
reason: `Frame size ${contentLength} exceeds limit ${maxBytes}`
|
|
4899
|
+
};
|
|
4900
|
+
}
|
|
4901
|
+
return { action: "ALLOW" };
|
|
4902
|
+
}
|
|
4903
|
+
};
|
|
4904
|
+
FrameBudgetSensor = __decorateClass([
|
|
4905
|
+
Sensor({ phase: "PRE_DECODE" }),
|
|
4906
|
+
(0, import_common17.Injectable)()
|
|
4907
|
+
], FrameBudgetSensor);
|
|
4908
|
+
|
|
4909
|
+
// src/sensors/frame-header-sanity.sensor.ts
|
|
4910
|
+
var import_common18 = require("@nestjs/common");
|
|
4911
|
+
var FrameHeaderSanitySensor = class {
|
|
4912
|
+
constructor() {
|
|
4913
|
+
this.name = "FrameHeaderSanitySensor";
|
|
4914
|
+
this.order = BAND.WIRE + 30;
|
|
4915
|
+
}
|
|
4916
|
+
supports(input) {
|
|
4917
|
+
return !!input.peek && input.peek.length >= 7;
|
|
4918
|
+
}
|
|
4919
|
+
async run(input) {
|
|
4920
|
+
const peek = input.peek;
|
|
4921
|
+
const contentLen = input.contentLength || 0;
|
|
4922
|
+
if (peek.length < 5 || !this.bufferEqual(peek.slice(0, 5), import_axis_protocol2.AXIS_MAGIC)) {
|
|
4923
|
+
return {
|
|
4924
|
+
action: "DENY",
|
|
4925
|
+
code: "INVALID_MAGIC",
|
|
4926
|
+
reason: "Frame magic is not AXIS1"
|
|
4927
|
+
};
|
|
4928
|
+
}
|
|
4929
|
+
if (peek[5] !== import_axis_protocol2.AXIS_VERSION) {
|
|
4930
|
+
return {
|
|
4931
|
+
action: "DENY",
|
|
4932
|
+
code: "UNSUPPORTED_VERSION",
|
|
4933
|
+
reason: `Unsupported version: ${peek[5]}`
|
|
4934
|
+
};
|
|
4935
|
+
}
|
|
4936
|
+
if (contentLen > import_axis_protocol2.MAX_FRAME_LEN) {
|
|
4937
|
+
return {
|
|
4938
|
+
action: "DENY",
|
|
4939
|
+
code: "FRAME_TOO_LARGE",
|
|
4940
|
+
reason: `Frame size ${contentLen} exceeds max ${import_axis_protocol2.MAX_FRAME_LEN}`
|
|
4941
|
+
};
|
|
4942
|
+
}
|
|
4943
|
+
return { action: "ALLOW" };
|
|
4944
|
+
}
|
|
4945
|
+
bufferEqual(a, b) {
|
|
4946
|
+
if (a.length !== b.length) return false;
|
|
4947
|
+
for (let i = 0; i < a.length; i++) {
|
|
4948
|
+
if (a[i] !== b[i]) return false;
|
|
4949
|
+
}
|
|
4950
|
+
return true;
|
|
4951
|
+
}
|
|
4952
|
+
};
|
|
4953
|
+
FrameHeaderSanitySensor = __decorateClass([
|
|
4954
|
+
(0, import_common18.Injectable)(),
|
|
4955
|
+
Sensor({ phase: "PRE_DECODE" })
|
|
4956
|
+
], FrameHeaderSanitySensor);
|
|
4957
|
+
|
|
4958
|
+
// src/sensors/header-tlv-limit.sensor.ts
|
|
4959
|
+
var import_common19 = require("@nestjs/common");
|
|
4960
|
+
var HeaderTLVLimitSensor = class {
|
|
4961
|
+
constructor() {
|
|
4962
|
+
this.name = "HeaderTLVLimitSensor";
|
|
4963
|
+
this.order = BAND.CONTENT + 0;
|
|
4964
|
+
this.MAX_TLVS = 64;
|
|
4965
|
+
}
|
|
4966
|
+
supports(input) {
|
|
4967
|
+
return !!input.headerTLVs || !!input.packet;
|
|
4968
|
+
}
|
|
4969
|
+
async run(input) {
|
|
4970
|
+
if (input.headerTLVs && input.headerTLVs.size > this.MAX_TLVS) {
|
|
4971
|
+
return {
|
|
4972
|
+
action: "DENY",
|
|
4973
|
+
code: "TOO_MANY_TLVS",
|
|
4974
|
+
reason: `Header TLVs (${input.headerTLVs.size}) exceed max (${this.MAX_TLVS})`
|
|
4975
|
+
};
|
|
4976
|
+
}
|
|
4977
|
+
if (input.packet && input.packet.headerBytes) {
|
|
4978
|
+
const hdrLen = input.packet.headerBytes.length;
|
|
4979
|
+
if (hdrLen > import_axis_protocol2.MAX_HDR_LEN) {
|
|
4980
|
+
return {
|
|
4981
|
+
action: "DENY",
|
|
4982
|
+
code: "HEADER_TOO_LARGE",
|
|
4983
|
+
reason: `Header size ${hdrLen} exceeds max ${import_axis_protocol2.MAX_HDR_LEN}`
|
|
4984
|
+
};
|
|
4985
|
+
}
|
|
4986
|
+
}
|
|
4987
|
+
return { action: "ALLOW" };
|
|
4988
|
+
}
|
|
4989
|
+
};
|
|
4990
|
+
HeaderTLVLimitSensor = __decorateClass([
|
|
4991
|
+
(0, import_common19.Injectable)(),
|
|
4992
|
+
Sensor()
|
|
4993
|
+
], HeaderTLVLimitSensor);
|
|
4994
|
+
|
|
4995
|
+
// src/sensors/intent-allowlist.sensor.ts
|
|
4996
|
+
var import_common20 = require("@nestjs/common");
|
|
4997
|
+
var PUBLIC_INTENT_ALLOWLIST = [
|
|
4998
|
+
"public.",
|
|
4999
|
+
"schema.",
|
|
5000
|
+
"catalog.",
|
|
5001
|
+
"health.",
|
|
5002
|
+
"system."
|
|
5003
|
+
];
|
|
5004
|
+
var IntentAllowlistSensor = class {
|
|
5005
|
+
constructor() {
|
|
5006
|
+
this.name = "IntentAllowlistSensor";
|
|
5007
|
+
this.order = BAND.IDENTITY + 20;
|
|
5008
|
+
}
|
|
5009
|
+
supports(input) {
|
|
5010
|
+
return !!input.intent;
|
|
5011
|
+
}
|
|
5012
|
+
async run(input) {
|
|
5013
|
+
const profile = input.metadata?.profile || "PUBLIC";
|
|
5014
|
+
const intent = input.intent || "";
|
|
5015
|
+
if (profile === "PUBLIC") {
|
|
5016
|
+
const isAllowed = PUBLIC_INTENT_ALLOWLIST.some(
|
|
5017
|
+
(prefix) => intent.startsWith(prefix)
|
|
5018
|
+
);
|
|
5019
|
+
if (!isAllowed) {
|
|
5020
|
+
return {
|
|
5021
|
+
action: "DENY",
|
|
5022
|
+
code: "INTENT_NOT_ALLOWED",
|
|
5023
|
+
reason: `Intent '${intent}' not in public allowlist`
|
|
5024
|
+
};
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
5027
|
+
return { action: "ALLOW" };
|
|
5028
|
+
}
|
|
5029
|
+
};
|
|
5030
|
+
IntentAllowlistSensor = __decorateClass([
|
|
5031
|
+
(0, import_common20.Injectable)(),
|
|
5032
|
+
Sensor()
|
|
5033
|
+
], IntentAllowlistSensor);
|
|
5034
|
+
|
|
5035
|
+
// src/sensors/intent-registry.sensor.ts
|
|
5036
|
+
var import_common21 = require("@nestjs/common");
|
|
5037
|
+
var IntentRegistrySensor = class {
|
|
5038
|
+
constructor(router) {
|
|
5039
|
+
this.router = router;
|
|
5040
|
+
this.name = "IntentRegistrySensor";
|
|
5041
|
+
this.order = BAND.IDENTITY + 25;
|
|
5042
|
+
}
|
|
5043
|
+
supports(input) {
|
|
5044
|
+
return !!input.intent;
|
|
5045
|
+
}
|
|
5046
|
+
async run(input) {
|
|
5047
|
+
const intent = input.intent;
|
|
5048
|
+
if (this.router.has(intent)) {
|
|
5049
|
+
return { action: "ALLOW" };
|
|
5050
|
+
}
|
|
5051
|
+
return {
|
|
5052
|
+
action: "DENY",
|
|
5053
|
+
code: "INTENT_NOT_REGISTERED",
|
|
5054
|
+
reason: `Intent '${intent}' is not registered`
|
|
5055
|
+
};
|
|
5056
|
+
}
|
|
5057
|
+
};
|
|
5058
|
+
IntentRegistrySensor = __decorateClass([
|
|
5059
|
+
(0, import_common21.Injectable)(),
|
|
5060
|
+
Sensor({ phase: "POST_DECODE" })
|
|
5061
|
+
], IntentRegistrySensor);
|
|
5062
|
+
|
|
5063
|
+
// src/sensors/proof-presence.sensor.ts
|
|
5064
|
+
var import_common22 = require("@nestjs/common");
|
|
5065
|
+
var ProofPresenceSensor = class {
|
|
5066
|
+
constructor() {
|
|
5067
|
+
this.name = "ProofPresenceSensor";
|
|
5068
|
+
this.order = BAND.IDENTITY + 30;
|
|
5069
|
+
}
|
|
5070
|
+
supports(input) {
|
|
5071
|
+
return !!input.profile && !!input.visibility;
|
|
5072
|
+
}
|
|
5073
|
+
async run(input) {
|
|
5074
|
+
const validatedInput = ProofPresenceInputZ.safeParse(input);
|
|
5075
|
+
if (!validatedInput.success) {
|
|
5076
|
+
throw new AxisError(
|
|
5077
|
+
"SENSOR_INVALID_INPUT",
|
|
5078
|
+
`Input validation failed: ${validatedInput.error.message}`,
|
|
5079
|
+
400
|
|
5080
|
+
);
|
|
5081
|
+
}
|
|
5082
|
+
const {
|
|
5083
|
+
visibility,
|
|
5084
|
+
requiredProof,
|
|
5085
|
+
hasCapsule,
|
|
5086
|
+
hasPassportSignature,
|
|
5087
|
+
profile,
|
|
5088
|
+
intent
|
|
5089
|
+
} = validatedInput.data;
|
|
5090
|
+
if (visibility === "PUBLIC") {
|
|
5091
|
+
return { action: "ALLOW" };
|
|
5092
|
+
}
|
|
5093
|
+
if (requiredProof.includes("NONE")) {
|
|
5094
|
+
return { action: "ALLOW" };
|
|
5095
|
+
}
|
|
5096
|
+
const hasCapsuleProof = requiredProof.includes("CAPSULE") && hasCapsule;
|
|
5097
|
+
const hasPassportProof = requiredProof.includes("PASSPORT") && hasPassportSignature;
|
|
5098
|
+
const hasNodeProof = requiredProof.includes("MTLS") && profile === "NODE";
|
|
5099
|
+
const satisfied = hasCapsuleProof || hasPassportProof || hasNodeProof;
|
|
5100
|
+
if (!satisfied) {
|
|
5101
|
+
throw new AxisError(
|
|
5102
|
+
"SENSOR_PROOF_REQUIRED",
|
|
5103
|
+
`Proof required for guarded intent: ${intent}`,
|
|
5104
|
+
403
|
|
5105
|
+
);
|
|
5106
|
+
}
|
|
5107
|
+
return { action: "ALLOW" };
|
|
5108
|
+
}
|
|
5109
|
+
};
|
|
5110
|
+
ProofPresenceSensor = __decorateClass([
|
|
5111
|
+
Sensor(),
|
|
5112
|
+
(0, import_common22.Injectable)()
|
|
5113
|
+
], ProofPresenceSensor);
|
|
5114
|
+
|
|
5115
|
+
// src/sensors/protocol-strict.sensor.ts
|
|
5116
|
+
var import_common23 = require("@nestjs/common");
|
|
5117
|
+
var VALID_FLAGS = [
|
|
5118
|
+
0,
|
|
5119
|
+
// No flags
|
|
5120
|
+
import_axis_protocol2.FLAG_BODY_TLV,
|
|
5121
|
+
// Body contains TLVs
|
|
5122
|
+
import_axis_protocol2.FLAG_CHAIN_REQ,
|
|
5123
|
+
// Requires receipt chaining
|
|
5124
|
+
import_axis_protocol2.FLAG_HAS_WITNESS,
|
|
5125
|
+
// Has witness signatures
|
|
5126
|
+
import_axis_protocol2.FLAG_BODY_TLV | import_axis_protocol2.FLAG_CHAIN_REQ,
|
|
5127
|
+
import_axis_protocol2.FLAG_BODY_TLV | import_axis_protocol2.FLAG_HAS_WITNESS,
|
|
5128
|
+
import_axis_protocol2.FLAG_CHAIN_REQ | import_axis_protocol2.FLAG_HAS_WITNESS,
|
|
5129
|
+
import_axis_protocol2.FLAG_BODY_TLV | import_axis_protocol2.FLAG_CHAIN_REQ | import_axis_protocol2.FLAG_HAS_WITNESS
|
|
5130
|
+
];
|
|
5131
|
+
var ProtocolStrictSensor = class {
|
|
5132
|
+
constructor(config) {
|
|
5133
|
+
this.config = config;
|
|
5134
|
+
this.logger = new import_common23.Logger(ProtocolStrictSensor.name);
|
|
5135
|
+
/** Sensor identifier for logging and registry */
|
|
5136
|
+
this.name = "ProtocolStrictSensor";
|
|
5137
|
+
/**
|
|
5138
|
+
* Execution order - FIRST sensor in the chain
|
|
5139
|
+
*
|
|
5140
|
+
* Order 10 ensures:
|
|
5141
|
+
* - Runs before any other processing
|
|
5142
|
+
* - Invalid frames rejected immediately
|
|
5143
|
+
* - Protects all downstream sensors from malformed input
|
|
5144
|
+
*/
|
|
5145
|
+
this.order = BAND.WIRE + 10;
|
|
5146
|
+
this.protocolMagic = import_axis_protocol2.AXIS_MAGIC;
|
|
5147
|
+
this.protocolVersion = import_axis_protocol2.AXIS_VERSION;
|
|
5148
|
+
}
|
|
5149
|
+
/**
|
|
5150
|
+
* Static validation for streaming middleware (Fast Check)
|
|
5151
|
+
*/
|
|
5152
|
+
static validateMagic(chunk, expected) {
|
|
5153
|
+
if (chunk.length < expected.length) return { valid: true };
|
|
5154
|
+
const actual = chunk.subarray(0, expected.length);
|
|
5155
|
+
const valid = Buffer.from(actual).equals(Buffer.from(expected));
|
|
5156
|
+
return {
|
|
5157
|
+
valid,
|
|
5158
|
+
actual: valid ? void 0 : new TextDecoder().decode(actual)
|
|
5159
|
+
};
|
|
5160
|
+
}
|
|
5161
|
+
static validateVersion(version, expected) {
|
|
5162
|
+
return version === expected;
|
|
5163
|
+
}
|
|
5164
|
+
/**
|
|
5165
|
+
* Lifecycle hook: Registers this sensor in the chain on module initialization.
|
|
5166
|
+
*/
|
|
5167
|
+
onModuleInit() {
|
|
5168
|
+
const magicStr = this.config.get("AXIS_PROTOCOL_MAGIC");
|
|
5169
|
+
this.protocolMagic = magicStr ? Buffer.from(magicStr, "ascii") : import_axis_protocol2.AXIS_MAGIC;
|
|
5170
|
+
this.protocolVersion = this.config.get("AXIS_PROTOCOL_VERSION") || import_axis_protocol2.AXIS_VERSION;
|
|
5171
|
+
}
|
|
5172
|
+
/**
|
|
5173
|
+
* Evaluate protocol strictness
|
|
5174
|
+
*/
|
|
5175
|
+
async run(input) {
|
|
5176
|
+
const validatedInput = ProtocolStrictInputZ.safeParse(input);
|
|
5177
|
+
if (!validatedInput.success) {
|
|
5178
|
+
this.logger.error(
|
|
5179
|
+
`Invalid input: ${validatedInput.error.message}`,
|
|
5180
|
+
validatedInput.error.issues
|
|
5181
|
+
);
|
|
5182
|
+
return {
|
|
5183
|
+
action: "DENY",
|
|
5184
|
+
code: "INVALID_INPUT",
|
|
5185
|
+
reason: "Protocol validation input failed"
|
|
5186
|
+
};
|
|
5187
|
+
}
|
|
5188
|
+
const { contentType, peek } = validatedInput.data;
|
|
5189
|
+
const issues = [];
|
|
5190
|
+
if (peek.length >= 8) {
|
|
5191
|
+
const hex = Buffer.from(peek.subarray(0, 10)).toString("hex");
|
|
5192
|
+
this.logger.debug(`Raw Frame Header (Hex): ${hex} (IP: ${input.ip})`);
|
|
5193
|
+
}
|
|
5194
|
+
if (contentType !== void 0) {
|
|
5195
|
+
if (!this.isValidContentType(contentType)) {
|
|
5196
|
+
issues.push(`invalid_content_type:${contentType}`);
|
|
5197
|
+
}
|
|
5198
|
+
}
|
|
5199
|
+
if (peek.length < 9) {
|
|
5200
|
+
return {
|
|
5201
|
+
action: "DENY",
|
|
5202
|
+
code: "FRAME_TOO_SHORT",
|
|
5203
|
+
reason: "Frame too short for protocol header"
|
|
5204
|
+
};
|
|
5205
|
+
}
|
|
5206
|
+
const magicCheck = ProtocolStrictSensor.validateMagic(
|
|
5207
|
+
peek,
|
|
5208
|
+
this.protocolMagic
|
|
5209
|
+
);
|
|
5210
|
+
if (!magicCheck.valid) {
|
|
5211
|
+
return {
|
|
5212
|
+
action: "DENY",
|
|
5213
|
+
code: "INVALID_MAGIC",
|
|
5214
|
+
reason: `Expected ${new TextDecoder().decode(this.protocolMagic)} magic, got ${magicCheck.actual}`
|
|
5215
|
+
};
|
|
5216
|
+
}
|
|
5217
|
+
const version = peek[5];
|
|
5218
|
+
if (!ProtocolStrictSensor.validateVersion(version, this.protocolVersion)) {
|
|
5219
|
+
issues.push(`unsupported_version:${version}`);
|
|
5220
|
+
}
|
|
5221
|
+
const flags = peek[6];
|
|
5222
|
+
if (!this.isValidFlags(flags)) {
|
|
5223
|
+
issues.push(`invalid_flags:0x${flags.toString(16)}`);
|
|
5224
|
+
}
|
|
5225
|
+
if (peek.length >= 10) {
|
|
5226
|
+
const lengthCheck = this.checkVarintEncoding(peek.subarray(7));
|
|
5227
|
+
if (!lengthCheck.valid) {
|
|
5228
|
+
issues.push(`non_minimal_varint:${lengthCheck.reason}`);
|
|
5229
|
+
}
|
|
5230
|
+
}
|
|
5231
|
+
if (peek.length >= 20) {
|
|
5232
|
+
const tlvCheck = this.checkTLVOrdering(peek);
|
|
5233
|
+
if (!tlvCheck.valid) {
|
|
5234
|
+
issues.push(`tlv_not_canonical:${tlvCheck.reason}`);
|
|
5235
|
+
}
|
|
5236
|
+
const hasClientVersion = await this.checkForClientVersion(peek);
|
|
5237
|
+
if (!hasClientVersion) {
|
|
5238
|
+
issues.push("missing_client_version");
|
|
5239
|
+
}
|
|
5240
|
+
}
|
|
5241
|
+
if (issues.length > 0) {
|
|
5242
|
+
const critical = issues.some(
|
|
5243
|
+
(i) => i.startsWith("invalid_magic") || i.startsWith("unsupported_version")
|
|
5244
|
+
);
|
|
5245
|
+
if (critical) {
|
|
5246
|
+
return {
|
|
5247
|
+
action: "DENY",
|
|
5248
|
+
code: "PROTOCOL_VIOLATION",
|
|
5249
|
+
reason: issues.join(", ")
|
|
5250
|
+
};
|
|
5251
|
+
}
|
|
5252
|
+
this.logger.warn(
|
|
5253
|
+
`Protocol issues from ${input.ip}: ${issues.join(", ")}`
|
|
5254
|
+
);
|
|
5255
|
+
return {
|
|
5256
|
+
action: "FLAG",
|
|
5257
|
+
scoreDelta: -issues.length * 2,
|
|
5258
|
+
reasons: issues
|
|
5259
|
+
};
|
|
5260
|
+
}
|
|
5261
|
+
return { action: "ALLOW" };
|
|
5262
|
+
}
|
|
5263
|
+
/**
|
|
5264
|
+
* Compare two buffers for equality
|
|
5265
|
+
*/
|
|
5266
|
+
buffersEqual(a, b) {
|
|
5267
|
+
if (a.length !== b.length) return false;
|
|
5268
|
+
for (let i = 0; i < a.length; i++) {
|
|
5269
|
+
if (a[i] !== b[i]) return false;
|
|
5270
|
+
}
|
|
5271
|
+
return true;
|
|
5272
|
+
}
|
|
5273
|
+
/**
|
|
5274
|
+
* Check if Content-Type is valid for AXIS
|
|
5275
|
+
*/
|
|
5276
|
+
isValidContentType(contentType) {
|
|
5277
|
+
const valid = [
|
|
5278
|
+
"application/axis-bin",
|
|
5279
|
+
"application/octet-stream",
|
|
5280
|
+
"application/x-axis"
|
|
5281
|
+
];
|
|
5282
|
+
return valid.some((v) => contentType.toLowerCase().includes(v));
|
|
5283
|
+
}
|
|
5284
|
+
/**
|
|
5285
|
+
* Check if flags are a valid combination
|
|
5286
|
+
*/
|
|
5287
|
+
isValidFlags(flags) {
|
|
5288
|
+
return VALID_FLAGS.includes(flags);
|
|
5289
|
+
}
|
|
5290
|
+
/**
|
|
5291
|
+
* Check varint encoding is minimal (no leading zeros)
|
|
5292
|
+
*/
|
|
5293
|
+
checkVarintEncoding(data) {
|
|
5294
|
+
try {
|
|
5295
|
+
const { value, length: bytesRead } = (0, import_axis_protocol3.decodeVarint)(data, 0);
|
|
5296
|
+
if (value < 128 && bytesRead > 1) {
|
|
5297
|
+
return { valid: false, reason: "non-minimal-small-value" };
|
|
5298
|
+
}
|
|
5299
|
+
if (value < 16384 && bytesRead > 2) {
|
|
5300
|
+
return { valid: false, reason: "non-minimal-medium-value" };
|
|
5301
|
+
}
|
|
5302
|
+
return { valid: true };
|
|
5303
|
+
} catch {
|
|
5304
|
+
return { valid: false, reason: "varint-decode-error" };
|
|
5305
|
+
}
|
|
5306
|
+
}
|
|
5307
|
+
/**
|
|
5308
|
+
* Check TLV ordering is canonical (sorted by type, no duplicates)
|
|
5309
|
+
*/
|
|
5310
|
+
checkTLVOrdering(data) {
|
|
5311
|
+
try {
|
|
5312
|
+
let offset = 7;
|
|
5313
|
+
const { value: hdrLen, length: hdrBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
|
|
5314
|
+
offset += hdrBytes;
|
|
5315
|
+
const { length: bodyBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
|
|
5316
|
+
offset += bodyBytes;
|
|
5317
|
+
const { length: sigBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
|
|
5318
|
+
offset += sigBytes;
|
|
5319
|
+
const hdrStart = offset;
|
|
5320
|
+
const hdrEnd = hdrStart + Number(hdrLen);
|
|
5321
|
+
if (hdrEnd > data.length) {
|
|
5322
|
+
return { valid: true };
|
|
5323
|
+
}
|
|
5324
|
+
let lastType = -1;
|
|
5325
|
+
let pos = hdrStart;
|
|
5326
|
+
while (pos < hdrEnd && pos < data.length - 2) {
|
|
5327
|
+
const { value: type, length: typeBytes } = (0, import_axis_protocol3.decodeVarint)(data, pos);
|
|
5328
|
+
pos += typeBytes;
|
|
5329
|
+
if (pos >= hdrEnd) break;
|
|
5330
|
+
const { value: len, length: lenBytes } = (0, import_axis_protocol3.decodeVarint)(data, pos);
|
|
5331
|
+
pos += lenBytes;
|
|
5332
|
+
if (Number(type) <= lastType) {
|
|
5333
|
+
return {
|
|
5334
|
+
valid: false,
|
|
5335
|
+
reason: `type-${type}-after-${lastType}`
|
|
5336
|
+
};
|
|
5337
|
+
}
|
|
5338
|
+
lastType = Number(type);
|
|
5339
|
+
pos += Number(len);
|
|
5340
|
+
}
|
|
5341
|
+
return { valid: true };
|
|
5342
|
+
} catch {
|
|
5343
|
+
return { valid: true };
|
|
5344
|
+
}
|
|
5345
|
+
}
|
|
5346
|
+
/**
|
|
5347
|
+
* Check if TLV 100 (Client Version) exists in the headers
|
|
5348
|
+
*/
|
|
5349
|
+
async checkForClientVersion(data) {
|
|
5350
|
+
try {
|
|
5351
|
+
let offset = 7;
|
|
5352
|
+
const { value: hdrLen, length: hdrBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
|
|
5353
|
+
offset += hdrBytes;
|
|
5354
|
+
const { length: bodyBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
|
|
5355
|
+
offset += bodyBytes;
|
|
5356
|
+
const { length: sigBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
|
|
5357
|
+
offset += sigBytes;
|
|
5358
|
+
const hdrEnd = offset + Number(hdrLen);
|
|
5359
|
+
let pos = offset;
|
|
5360
|
+
while (pos < hdrEnd && pos < data.length) {
|
|
5361
|
+
const { value: type, length: typeBytes } = (0, import_axis_protocol3.decodeVarint)(data, pos);
|
|
5362
|
+
pos += typeBytes;
|
|
5363
|
+
const { length: lenBytes } = (0, import_axis_protocol3.decodeVarint)(data, pos);
|
|
5364
|
+
pos += lenBytes;
|
|
5365
|
+
const { value: valLen, length: valLenBytes } = (0, import_axis_protocol3.decodeVarint)(
|
|
5366
|
+
data,
|
|
5367
|
+
pos - lenBytes
|
|
5368
|
+
);
|
|
5369
|
+
}
|
|
5370
|
+
pos = offset;
|
|
5371
|
+
while (pos < hdrEnd && pos < data.length) {
|
|
5372
|
+
const t = (0, import_axis_protocol3.decodeVarint)(data, pos);
|
|
5373
|
+
pos += t.length;
|
|
5374
|
+
const l = (0, import_axis_protocol3.decodeVarint)(data, pos);
|
|
5375
|
+
pos += l.length;
|
|
5376
|
+
if (t.value === 100) return true;
|
|
5377
|
+
pos += Number(l.value);
|
|
5378
|
+
}
|
|
5379
|
+
return false;
|
|
5380
|
+
} catch {
|
|
5381
|
+
return false;
|
|
5382
|
+
}
|
|
5383
|
+
}
|
|
5384
|
+
};
|
|
5385
|
+
ProtocolStrictSensor = __decorateClass([
|
|
5386
|
+
Sensor({ phase: "PRE_DECODE" }),
|
|
5387
|
+
(0, import_common23.Injectable)()
|
|
5388
|
+
], ProtocolStrictSensor);
|
|
5389
|
+
|
|
5390
|
+
// src/sensors/receipt-policy.sensor.ts
|
|
5391
|
+
var import_common24 = require("@nestjs/common");
|
|
5392
|
+
var ReceiptPolicySensor = class {
|
|
5393
|
+
constructor() {
|
|
5394
|
+
this.name = "ReceiptPolicySensor";
|
|
5395
|
+
this.order = BAND.BUSINESS + 20;
|
|
5396
|
+
}
|
|
5397
|
+
supports() {
|
|
5398
|
+
return true;
|
|
5399
|
+
}
|
|
5400
|
+
async run() {
|
|
5401
|
+
return { action: "ALLOW" };
|
|
5402
|
+
}
|
|
5403
|
+
};
|
|
5404
|
+
ReceiptPolicySensor = __decorateClass([
|
|
5405
|
+
(0, import_common24.Injectable)(),
|
|
5406
|
+
Sensor()
|
|
5407
|
+
], ReceiptPolicySensor);
|
|
5408
|
+
|
|
5409
|
+
// src/sensors/schema-validation.sensor.ts
|
|
5410
|
+
var import_common25 = require("@nestjs/common");
|
|
5411
|
+
function readU64be(b) {
|
|
5412
|
+
if (b.length !== 8)
|
|
5413
|
+
throw new AxisError("SCHEMA_TYPE_MISMATCH", "u64 must be 8 bytes", 400);
|
|
5414
|
+
let x = 0n;
|
|
5415
|
+
for (const by of b) x = x << 8n | BigInt(by);
|
|
5416
|
+
return x;
|
|
5417
|
+
}
|
|
5418
|
+
var SchemaValidationSensor = class {
|
|
5419
|
+
constructor() {
|
|
5420
|
+
/** Sensor identifier for logging and registry */
|
|
5421
|
+
this.name = "SchemaValidationSensor";
|
|
5422
|
+
/**
|
|
5423
|
+
* Execution order - runs late in the pipeline
|
|
5424
|
+
*
|
|
5425
|
+
* Order 170 ensures:
|
|
5426
|
+
* - All authentication complete
|
|
5427
|
+
* - All policy checks complete
|
|
5428
|
+
* - Data validated before handler execution
|
|
5429
|
+
*/
|
|
5430
|
+
this.order = BAND.CONTENT + 35;
|
|
5431
|
+
}
|
|
5432
|
+
/**
|
|
5433
|
+
* Determines if this sensor should process the given input.
|
|
5434
|
+
*
|
|
5435
|
+
* Only activates when a schema is provided for the intent (post-decode phase).
|
|
5436
|
+
*
|
|
5437
|
+
* @param {any} input - Sensor input
|
|
5438
|
+
* @returns {boolean} True if schema exists in metadata
|
|
5439
|
+
*/
|
|
5440
|
+
supports(input) {
|
|
5441
|
+
return !!input.metadata?.schema;
|
|
5442
|
+
}
|
|
5443
|
+
/**
|
|
5444
|
+
* Validates TLV fields against the schema definition.
|
|
5445
|
+
*
|
|
5446
|
+
* **Validation Steps:**
|
|
5447
|
+
* 1. Validate the schema itself using Zod
|
|
5448
|
+
* 2. Iterate through each field definition
|
|
5449
|
+
* 3. Check required fields are present
|
|
5450
|
+
* 4. Validate size limits (maxLen)
|
|
5451
|
+
* 5. Validate type-specific rules
|
|
5452
|
+
*
|
|
5453
|
+
* @param {any} input - Standard SensorInput
|
|
5454
|
+
* @returns {{ action: 'ALLOW' } | { action: 'DENY', code: string, reason: string }} Decision
|
|
5455
|
+
*/
|
|
5456
|
+
async run(input) {
|
|
5457
|
+
const schema = input.metadata?.schema;
|
|
5458
|
+
const headerTLVs = input.headerTLVs;
|
|
5459
|
+
const bodyTLVs = input.bodyTLVs;
|
|
5460
|
+
if (!schema) {
|
|
5461
|
+
return { action: "ALLOW" };
|
|
5462
|
+
}
|
|
5463
|
+
const validatedSchema = IntentSchemaZ.safeParse(schema);
|
|
5464
|
+
if (!validatedSchema.success) {
|
|
5465
|
+
return {
|
|
5466
|
+
action: "DENY",
|
|
5467
|
+
code: "SCHEMA_INVALID",
|
|
5468
|
+
reason: `Schema validation failed: ${validatedSchema.error.message}`
|
|
5469
|
+
};
|
|
5470
|
+
}
|
|
5471
|
+
try {
|
|
5472
|
+
for (const field of schema.fields) {
|
|
5473
|
+
const scope = field.scope ?? "body";
|
|
5474
|
+
const map3 = scope === "header" ? headerTLVs : bodyTLVs;
|
|
5475
|
+
const val = map3?.get(field.tlv);
|
|
5476
|
+
if (field.required && !val) {
|
|
5477
|
+
throw new AxisError(
|
|
5478
|
+
"SCHEMA_FIELD_MISSING",
|
|
5479
|
+
`Missing required field: ${field.name} (TLV ${field.tlv})`,
|
|
5480
|
+
400
|
|
5481
|
+
);
|
|
5482
|
+
}
|
|
5483
|
+
if (!val) continue;
|
|
5484
|
+
if (typeof field.maxLen === "number" && val.length > field.maxLen) {
|
|
5485
|
+
throw new AxisError(
|
|
5486
|
+
"SCHEMA_LIMIT_EXCEEDED",
|
|
5487
|
+
`Field ${field.name} too large (${val.length} > ${field.maxLen})`,
|
|
5488
|
+
413
|
|
5489
|
+
// Payload Too Large
|
|
5490
|
+
);
|
|
5491
|
+
}
|
|
5492
|
+
switch (field.kind) {
|
|
5493
|
+
case "utf8":
|
|
5494
|
+
try {
|
|
5495
|
+
new TextDecoder("utf-8", { fatal: true }).decode(val);
|
|
5496
|
+
} catch {
|
|
5497
|
+
throw new AxisError(
|
|
5498
|
+
"SCHEMA_TYPE_MISMATCH",
|
|
5499
|
+
`Invalid UTF-8 in ${field.name}`,
|
|
5500
|
+
400
|
|
5501
|
+
);
|
|
5502
|
+
}
|
|
5503
|
+
break;
|
|
5504
|
+
case "bool":
|
|
5505
|
+
if (val.length !== 1 || val[0] !== 0 && val[0] !== 1) {
|
|
5506
|
+
throw new AxisError(
|
|
5507
|
+
"SCHEMA_TYPE_MISMATCH",
|
|
5508
|
+
`Invalid bool: ${field.name}`,
|
|
5509
|
+
400
|
|
5510
|
+
);
|
|
5511
|
+
}
|
|
5512
|
+
break;
|
|
5513
|
+
case "u64": {
|
|
5514
|
+
const x = readU64be(val);
|
|
5515
|
+
if (field.max) {
|
|
5516
|
+
const mx = BigInt(field.max);
|
|
5517
|
+
if (x > mx) {
|
|
5518
|
+
throw new AxisError(
|
|
5519
|
+
"SCHEMA_LIMIT_EXCEEDED",
|
|
5520
|
+
`u64 ${field.name} exceeds max (${x} > ${mx})`,
|
|
5521
|
+
400
|
|
5522
|
+
);
|
|
5523
|
+
}
|
|
5524
|
+
}
|
|
5525
|
+
break;
|
|
5526
|
+
}
|
|
5527
|
+
case "bytes16":
|
|
5528
|
+
if (val.length !== 16) {
|
|
5529
|
+
throw new AxisError(
|
|
5530
|
+
"SCHEMA_TYPE_MISMATCH",
|
|
5531
|
+
`bytes16 required for ${field.name}`,
|
|
5532
|
+
400
|
|
5533
|
+
);
|
|
5534
|
+
}
|
|
5535
|
+
break;
|
|
5536
|
+
case "bytes":
|
|
5537
|
+
break;
|
|
5538
|
+
case "obj":
|
|
5539
|
+
case "arr":
|
|
5540
|
+
break;
|
|
5541
|
+
default:
|
|
5542
|
+
throw new AxisError(
|
|
5543
|
+
"SCHEMA_TYPE_MISMATCH",
|
|
5544
|
+
`Unknown schema kind: ${field.kind}`,
|
|
5545
|
+
500
|
|
5546
|
+
);
|
|
5547
|
+
}
|
|
5548
|
+
}
|
|
5549
|
+
const validators = input.metadata?.validators;
|
|
5550
|
+
if (validators && validators.size > 0) {
|
|
5551
|
+
for (const field of schema.fields) {
|
|
5552
|
+
const fns = validators.get(field.tlv);
|
|
5553
|
+
if (!fns || fns.length === 0) continue;
|
|
5554
|
+
const scope = field.scope ?? "body";
|
|
5555
|
+
const map3 = scope === "header" ? headerTLVs : bodyTLVs;
|
|
5556
|
+
const val = map3?.get(field.tlv);
|
|
5557
|
+
if (!val) continue;
|
|
5558
|
+
for (const fn of fns) {
|
|
5559
|
+
const error = fn(val, field.name);
|
|
5560
|
+
if (error) {
|
|
5561
|
+
throw new AxisError(
|
|
5562
|
+
"SCHEMA_VALIDATION_FAILED",
|
|
5563
|
+
`${field.name} (TLV ${field.tlv}): ${error}`,
|
|
5564
|
+
400
|
|
5565
|
+
);
|
|
5566
|
+
}
|
|
5567
|
+
}
|
|
5568
|
+
}
|
|
5569
|
+
}
|
|
5570
|
+
} catch (err) {
|
|
5571
|
+
if (err instanceof AxisError) {
|
|
5572
|
+
return {
|
|
5573
|
+
action: "DENY",
|
|
5574
|
+
code: err.code,
|
|
5575
|
+
reason: err.message
|
|
5576
|
+
};
|
|
5577
|
+
}
|
|
5578
|
+
throw err;
|
|
5579
|
+
}
|
|
5580
|
+
return { action: "ALLOW" };
|
|
5581
|
+
}
|
|
5582
|
+
};
|
|
5583
|
+
SchemaValidationSensor = __decorateClass([
|
|
5584
|
+
Sensor(),
|
|
5585
|
+
(0, import_common25.Injectable)()
|
|
5586
|
+
], SchemaValidationSensor);
|
|
5587
|
+
|
|
5588
|
+
// src/sensors/stream-scope.sensor.ts
|
|
5589
|
+
var import_common26 = require("@nestjs/common");
|
|
5590
|
+
var StreamScopeSensor = class {
|
|
5591
|
+
constructor() {
|
|
5592
|
+
/** Sensor identifier */
|
|
5593
|
+
this.name = "StreamScopeSensor";
|
|
5594
|
+
/**
|
|
5595
|
+
* Execution order - near handler execution
|
|
5596
|
+
*
|
|
5597
|
+
* Order 200 ensures:
|
|
5598
|
+
* - All authentication complete
|
|
5599
|
+
* - All policy checks complete
|
|
5600
|
+
* - Stream-specific check right before subscription
|
|
5601
|
+
*/
|
|
5602
|
+
this.order = BAND.BUSINESS + 0;
|
|
5603
|
+
}
|
|
5604
|
+
/**
|
|
5605
|
+
* Determines if this sensor should process the given input.
|
|
5606
|
+
*
|
|
5607
|
+
* Currently processes all inputs.
|
|
5608
|
+
*
|
|
5609
|
+
* @returns {boolean} Always true
|
|
5610
|
+
*/
|
|
5611
|
+
supports() {
|
|
5612
|
+
return true;
|
|
5613
|
+
}
|
|
5614
|
+
/**
|
|
5615
|
+
* Validates stream topic access permissions.
|
|
5616
|
+
*
|
|
5617
|
+
* **Current Implementation:** Stub that always allows.
|
|
5618
|
+
*
|
|
5619
|
+
* **TODO:** Full implementation should:
|
|
5620
|
+
* 1. Check if intent is stream.subscribe or stream.publish
|
|
5621
|
+
* 2. Extract topic from body TLVs
|
|
5622
|
+
* 3. Parse topic into owner/resource pattern
|
|
5623
|
+
* 4. Look up topic ACL from database/cache
|
|
5624
|
+
* 5. Check if actor has required permission (read/write)
|
|
5625
|
+
* 6. DENY if unauthorized
|
|
5626
|
+
*
|
|
5627
|
+
* @returns {Promise<SensorDecision>} ALLOW (stub implementation)
|
|
5628
|
+
*/
|
|
5629
|
+
async run() {
|
|
5630
|
+
return { action: "ALLOW" };
|
|
5631
|
+
}
|
|
5632
|
+
};
|
|
5633
|
+
StreamScopeSensor = __decorateClass([
|
|
5634
|
+
Sensor(),
|
|
5635
|
+
(0, import_common26.Injectable)()
|
|
5636
|
+
], StreamScopeSensor);
|
|
5637
|
+
|
|
5638
|
+
// src/sensors/tlv-parse.sensor.ts
|
|
5639
|
+
var import_common27 = require("@nestjs/common");
|
|
5640
|
+
var TLVParseSensor = class {
|
|
5641
|
+
constructor() {
|
|
5642
|
+
this.name = "TLVParseSensor";
|
|
5643
|
+
this.order = BAND.CONTENT + 20;
|
|
5644
|
+
}
|
|
5645
|
+
supports(input) {
|
|
5646
|
+
return !!input.packet;
|
|
5647
|
+
}
|
|
5648
|
+
async run(input) {
|
|
5649
|
+
const packet = input.packet;
|
|
5650
|
+
if (!packet) return { action: "ALLOW" };
|
|
5651
|
+
const hdrBytes = packet.hdrBytes ?? packet.headerBytes;
|
|
5652
|
+
if (hdrBytes && hdrBytes.length > 0) {
|
|
5653
|
+
const result = this.validateCanonicalTLV(hdrBytes, "header");
|
|
5654
|
+
if (result) return result;
|
|
5655
|
+
}
|
|
5656
|
+
const bodyBytes = packet.bodyBytes ?? input.body;
|
|
5657
|
+
const bodyIsTlv = packet.flags !== void 0 ? (packet.flags & 1) !== 0 : false;
|
|
5658
|
+
const bodyProfile = input.metadata?.schema?.bodyProfile;
|
|
5659
|
+
const skipBody = bodyProfile === "RAW";
|
|
5660
|
+
if (!skipBody && bodyIsTlv && bodyBytes && bodyBytes.length > 0) {
|
|
5661
|
+
const result = this.validateCanonicalTLV(bodyBytes, "body");
|
|
5662
|
+
if (result) return result;
|
|
5663
|
+
}
|
|
5664
|
+
return { action: "ALLOW" };
|
|
5665
|
+
}
|
|
5666
|
+
/**
|
|
5667
|
+
* Validates a TLV buffer for canonical ordering, no duplicates,
|
|
5668
|
+
* valid bounds, and minimal varint encoding.
|
|
5669
|
+
*/
|
|
5670
|
+
validateCanonicalTLV(buf, section) {
|
|
5671
|
+
let offset = 0;
|
|
5672
|
+
let lastType = -1;
|
|
5673
|
+
let count = 0;
|
|
5674
|
+
const maxItems = 512;
|
|
5675
|
+
while (offset < buf.length) {
|
|
5676
|
+
if (count >= maxItems) {
|
|
5677
|
+
return {
|
|
5678
|
+
action: "DENY",
|
|
5679
|
+
code: "TLV_LIMIT",
|
|
5680
|
+
reason: `Too many TLVs in ${section}`
|
|
5681
|
+
};
|
|
5682
|
+
}
|
|
5683
|
+
let type;
|
|
5684
|
+
let typeLen;
|
|
5685
|
+
try {
|
|
5686
|
+
const r = (0, import_axis_protocol3.decodeVarint)(buf, offset);
|
|
5687
|
+
type = r.value;
|
|
5688
|
+
typeLen = r.length;
|
|
5689
|
+
} catch {
|
|
5690
|
+
return {
|
|
5691
|
+
action: "DENY",
|
|
5692
|
+
code: "TLV_PARSE_ERROR",
|
|
5693
|
+
reason: `Malformed type varint in ${section} at offset ${offset}`
|
|
5694
|
+
};
|
|
5695
|
+
}
|
|
5696
|
+
offset += typeLen;
|
|
5697
|
+
if (type <= 0) {
|
|
5698
|
+
return {
|
|
5699
|
+
action: "DENY",
|
|
5700
|
+
code: "TLV_INVALID_TAG",
|
|
5701
|
+
reason: `Invalid tag ${type} in ${section}`
|
|
5702
|
+
};
|
|
5703
|
+
}
|
|
5704
|
+
if (type <= lastType) {
|
|
5705
|
+
return {
|
|
5706
|
+
action: "DENY",
|
|
5707
|
+
code: "TLV_NOT_CANONICAL",
|
|
5708
|
+
reason: `Non-canonical tag order in ${section}: ${type} after ${lastType}`
|
|
5709
|
+
};
|
|
5710
|
+
}
|
|
5711
|
+
lastType = type;
|
|
5712
|
+
let len;
|
|
5713
|
+
let lenLen;
|
|
5714
|
+
try {
|
|
5715
|
+
const r = (0, import_axis_protocol3.decodeVarint)(buf, offset);
|
|
5716
|
+
len = r.value;
|
|
5717
|
+
lenLen = r.length;
|
|
5718
|
+
} catch {
|
|
5719
|
+
return {
|
|
5720
|
+
action: "DENY",
|
|
5721
|
+
code: "TLV_PARSE_ERROR",
|
|
5722
|
+
reason: `Malformed length varint in ${section}`
|
|
5723
|
+
};
|
|
5724
|
+
}
|
|
5725
|
+
offset += lenLen;
|
|
5726
|
+
if (offset + len > buf.length) {
|
|
5727
|
+
return {
|
|
5728
|
+
action: "DENY",
|
|
5729
|
+
code: "TLV_TRUNCATED",
|
|
5730
|
+
reason: `TLV value truncated in ${section}`
|
|
5731
|
+
};
|
|
5732
|
+
}
|
|
5733
|
+
offset += len;
|
|
5734
|
+
count++;
|
|
5735
|
+
}
|
|
5736
|
+
return null;
|
|
5737
|
+
}
|
|
5738
|
+
};
|
|
5739
|
+
TLVParseSensor = __decorateClass([
|
|
5740
|
+
Sensor(),
|
|
5741
|
+
(0, import_common27.Injectable)()
|
|
5742
|
+
], TLVParseSensor);
|
|
5743
|
+
|
|
5744
|
+
// src/sensors/varint-hardening.sensor.ts
|
|
5745
|
+
var import_common28 = require("@nestjs/common");
|
|
5746
|
+
var VarintHardeningSensor = class {
|
|
5747
|
+
constructor() {
|
|
5748
|
+
/** Sensor identifier */
|
|
5749
|
+
this.name = "VarintHardeningSensor";
|
|
5750
|
+
/**
|
|
5751
|
+
* Execution order - early detection
|
|
5752
|
+
*
|
|
5753
|
+
* Order 40 ensures:
|
|
5754
|
+
* - After protocol magic check
|
|
5755
|
+
* - Before length-based parsing
|
|
5756
|
+
*/
|
|
5757
|
+
this.order = BAND.WIRE + 35;
|
|
5758
|
+
/** Maximum allowed bytes for a single varint */
|
|
5759
|
+
this.MAX_VARINT_BYTES = 5;
|
|
5760
|
+
}
|
|
5761
|
+
/**
|
|
5762
|
+
* Determines if this sensor should process the given input.
|
|
5763
|
+
*
|
|
5764
|
+
* Requires at least 7 bytes of peeked data.
|
|
5765
|
+
*
|
|
5766
|
+
* @param {SensorInput} input - Incoming request
|
|
5767
|
+
* @returns {boolean} True if sufficient peek data
|
|
5768
|
+
*/
|
|
5769
|
+
supports(input) {
|
|
5770
|
+
return !!input.peek && input.peek.length >= 7;
|
|
5771
|
+
}
|
|
5772
|
+
/**
|
|
5773
|
+
* Validates varint lengths in frame header.
|
|
5774
|
+
*
|
|
5775
|
+
* **Processing Flow:**
|
|
5776
|
+
* 1. Skip to varint section (offset 7)
|
|
5777
|
+
* 2. Scan for continuation bytes (MSB = 1)
|
|
5778
|
+
* 3. Count consecutive continuation bytes
|
|
5779
|
+
* 4. DENY if count exceeds MAX_VARINT_BYTES
|
|
5780
|
+
*
|
|
5781
|
+
* @param {SensorInput} input - Request with peek data
|
|
5782
|
+
* @returns {Promise<SensorDecision>} ALLOW or DENY based on varint length
|
|
5783
|
+
*/
|
|
5784
|
+
async run(input) {
|
|
5785
|
+
const peek = input.peek;
|
|
5786
|
+
const offset = 7;
|
|
5787
|
+
const maxOffset = Math.min(offset + 15, peek.length);
|
|
5788
|
+
let continuationCount = 0;
|
|
5789
|
+
for (let i = offset; i < maxOffset; i++) {
|
|
5790
|
+
if ((peek[i] & 128) !== 0) {
|
|
5791
|
+
continuationCount++;
|
|
5792
|
+
if (continuationCount > this.MAX_VARINT_BYTES) {
|
|
5793
|
+
return {
|
|
5794
|
+
action: "DENY",
|
|
5795
|
+
code: "VARINT_OVERFLOW",
|
|
5796
|
+
reason: `Varint exceeds ${this.MAX_VARINT_BYTES} bytes`
|
|
5797
|
+
};
|
|
5798
|
+
}
|
|
5799
|
+
} else {
|
|
5800
|
+
continuationCount = 0;
|
|
5801
|
+
}
|
|
5802
|
+
}
|
|
5803
|
+
return { action: "ALLOW" };
|
|
5804
|
+
}
|
|
5805
|
+
};
|
|
5806
|
+
VarintHardeningSensor = __decorateClass([
|
|
5807
|
+
Sensor({ phase: "PRE_DECODE" }),
|
|
5808
|
+
(0, import_common28.Injectable)()
|
|
5809
|
+
], VarintHardeningSensor);
|
|
5810
|
+
|
|
5811
|
+
// src/utils/index.ts
|
|
5812
|
+
var utils_exports = {};
|
|
5813
|
+
__export(utils_exports, {
|
|
5814
|
+
encodeAxisTlvDto: () => encodeAxisTlvDto
|
|
5815
|
+
});
|
|
5816
|
+
|
|
5817
|
+
// src/utils/axis-tlv-codec.ts
|
|
5818
|
+
function encodeAxisTlvDto(dtoClass, data) {
|
|
5819
|
+
const schema = extractDtoSchema(dtoClass);
|
|
5820
|
+
const items = schema.fields.flatMap((field) => {
|
|
5821
|
+
const value = data[field.name];
|
|
5822
|
+
if (value === void 0 || value === null) {
|
|
5823
|
+
if (field.required) {
|
|
5824
|
+
throw new Error(`Missing required TLV response field: ${field.name}`);
|
|
5825
|
+
}
|
|
5826
|
+
return [];
|
|
5827
|
+
}
|
|
5828
|
+
return [{ type: field.tag, value: encodeField(field, value) }];
|
|
5829
|
+
});
|
|
5830
|
+
return buildTLVs(items);
|
|
5831
|
+
}
|
|
5832
|
+
function encodeField(field, value) {
|
|
5833
|
+
switch (field.kind) {
|
|
5834
|
+
case "utf8":
|
|
5835
|
+
return Buffer.from(String(value), "utf8");
|
|
5836
|
+
case "u64":
|
|
5837
|
+
return encodeU64(value);
|
|
5838
|
+
case "bytes":
|
|
5839
|
+
case "bytes16":
|
|
5840
|
+
return toBuffer(value);
|
|
5841
|
+
case "bool":
|
|
5842
|
+
return Buffer.from([value ? 1 : 0]);
|
|
5843
|
+
case "obj":
|
|
5844
|
+
case "arr":
|
|
5845
|
+
return Buffer.from(JSON.stringify(value), "utf8");
|
|
5846
|
+
default:
|
|
5847
|
+
return toBuffer(value);
|
|
5848
|
+
}
|
|
5849
|
+
}
|
|
5850
|
+
function encodeU64(value) {
|
|
5851
|
+
const encoded = Buffer.alloc(8);
|
|
5852
|
+
encoded.writeBigUInt64BE(
|
|
5853
|
+
typeof value === "bigint" ? value : BigInt(value)
|
|
5854
|
+
);
|
|
5855
|
+
return encoded;
|
|
5856
|
+
}
|
|
5857
|
+
function toBuffer(value) {
|
|
5858
|
+
if (Buffer.isBuffer(value)) {
|
|
5859
|
+
return value;
|
|
5860
|
+
}
|
|
5861
|
+
if (value instanceof Uint8Array) {
|
|
5862
|
+
return Buffer.from(value);
|
|
5863
|
+
}
|
|
5864
|
+
if (typeof value === "string") {
|
|
5865
|
+
return Buffer.from(value, "utf8");
|
|
5866
|
+
}
|
|
5867
|
+
throw new Error(`Unsupported TLV bytes value: ${typeof value}`);
|
|
5868
|
+
}
|
|
2810
5869
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2811
5870
|
0 && (module.exports = {
|
|
2812
5871
|
ATS1_HDR,
|
|
@@ -2937,34 +5996,45 @@ var DiskUploadFileStore = class {
|
|
|
2937
5996
|
buildAts1Hdr,
|
|
2938
5997
|
buildDtoDecoder,
|
|
2939
5998
|
buildPacket,
|
|
5999
|
+
buildQueueMessage,
|
|
2940
6000
|
buildReceiptHash,
|
|
2941
6001
|
buildTLVs,
|
|
6002
|
+
buildUnsignedWitness,
|
|
2942
6003
|
bytes,
|
|
2943
6004
|
canAccessResource,
|
|
2944
6005
|
canonicalJson,
|
|
2945
6006
|
canonicalJsonExcluding,
|
|
6007
|
+
canonicalizeObservation,
|
|
2946
6008
|
classifyIntent,
|
|
2947
6009
|
computeReceiptHash,
|
|
2948
6010
|
computeSignaturePayload,
|
|
6011
|
+
core,
|
|
6012
|
+
crypto,
|
|
2949
6013
|
decodeArray,
|
|
2950
6014
|
decodeAxis1Frame,
|
|
2951
6015
|
decodeFrame,
|
|
2952
6016
|
decodeObject,
|
|
6017
|
+
decodeQueueMessage,
|
|
2953
6018
|
decodeTLVs,
|
|
2954
6019
|
decodeTLVsList,
|
|
2955
6020
|
decodeVarint,
|
|
6021
|
+
decorators,
|
|
2956
6022
|
encVarint,
|
|
2957
6023
|
encodeAxis1Frame,
|
|
2958
6024
|
encodeFrame,
|
|
6025
|
+
encodeQueueMessage,
|
|
2959
6026
|
encodeTLVs,
|
|
2960
6027
|
encodeVarint,
|
|
6028
|
+
engine,
|
|
2961
6029
|
extractDtoSchema,
|
|
2962
6030
|
generateEd25519KeyPair,
|
|
2963
6031
|
getSignTarget,
|
|
2964
6032
|
hasScope,
|
|
6033
|
+
hashObservation,
|
|
2965
6034
|
isAdminOpcode,
|
|
2966
6035
|
isKnownOpcode,
|
|
2967
6036
|
isTimestampValid,
|
|
6037
|
+
loom,
|
|
2968
6038
|
nonce16,
|
|
2969
6039
|
normalizeSensorDecision,
|
|
2970
6040
|
packPasskeyLoginOptionsReq,
|
|
@@ -2972,20 +6042,28 @@ var DiskUploadFileStore = class {
|
|
|
2972
6042
|
packPasskeyLoginVerifyReq,
|
|
2973
6043
|
packPasskeyLoginVerifyRes,
|
|
2974
6044
|
packPasskeyRegisterOptionsReq,
|
|
6045
|
+
parseAutoClaimEntries,
|
|
2975
6046
|
parseScope,
|
|
6047
|
+
parseStreamEntries,
|
|
2976
6048
|
resolveTimeout,
|
|
6049
|
+
schemas,
|
|
6050
|
+
security,
|
|
2977
6051
|
sensitivityName,
|
|
6052
|
+
sensors,
|
|
2978
6053
|
sha256,
|
|
2979
6054
|
signFrame,
|
|
6055
|
+
stableJsonStringify,
|
|
2980
6056
|
tlv,
|
|
2981
6057
|
u64be,
|
|
2982
6058
|
unpackPasskeyLoginOptionsReq,
|
|
2983
6059
|
unpackPasskeyLoginVerifyReq,
|
|
2984
6060
|
unpackPasskeyRegisterOptionsReq,
|
|
2985
6061
|
utf8,
|
|
6062
|
+
utils,
|
|
2986
6063
|
validateFrameShape,
|
|
2987
6064
|
varintLength,
|
|
2988
6065
|
varintU,
|
|
2989
|
-
verifyFrameSignature
|
|
6066
|
+
verifyFrameSignature,
|
|
6067
|
+
verifyResponse
|
|
2990
6068
|
});
|
|
2991
6069
|
//# sourceMappingURL=index.js.map
|