@nextera.one/axis-server-sdk 1.2.0 → 1.3.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/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 +1506 -7
- package/dist/index.d.ts +1506 -7
- package/dist/index.js +3387 -113
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3366 -111
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -2
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
33
33
|
if (kind && result) __defProp(target, key, result);
|
|
34
34
|
return result;
|
|
35
35
|
};
|
|
36
|
+
var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
|
|
36
37
|
|
|
37
38
|
// src/index.ts
|
|
38
39
|
var index_exports = {};
|
|
@@ -41,8 +42,13 @@ __export(index_exports, {
|
|
|
41
42
|
ATS1_SCHEMA: () => ATS1_SCHEMA,
|
|
42
43
|
AXIS_MAGIC: () => import_axis_protocol2.AXIS_MAGIC,
|
|
43
44
|
AXIS_OPCODES: () => AXIS_OPCODES,
|
|
45
|
+
AXIS_UPLOAD_FILE_STORE: () => AXIS_UPLOAD_FILE_STORE,
|
|
46
|
+
AXIS_UPLOAD_RECEIPT_SIGNER: () => AXIS_UPLOAD_RECEIPT_SIGNER,
|
|
47
|
+
AXIS_UPLOAD_SESSION_STORE: () => AXIS_UPLOAD_SESSION_STORE,
|
|
44
48
|
AXIS_VERSION: () => import_axis_protocol2.AXIS_VERSION,
|
|
45
49
|
Ats1Codec: () => ats1_exports,
|
|
50
|
+
AxisFilesDownloadHandler: () => AxisFilesDownloadHandler,
|
|
51
|
+
AxisFilesFinalizeHandler: () => AxisFilesFinalizeHandler,
|
|
46
52
|
AxisFrameZ: () => AxisFrameZ,
|
|
47
53
|
AxisIdDto: () => AxisIdDto,
|
|
48
54
|
AxisPacketTags: () => T,
|
|
@@ -55,6 +61,7 @@ __export(index_exports, {
|
|
|
55
61
|
DEFAULT_CONTRACTS: () => DEFAULT_CONTRACTS,
|
|
56
62
|
DEFAULT_TIMEOUT: () => DEFAULT_TIMEOUT,
|
|
57
63
|
Decision: () => Decision,
|
|
64
|
+
DiskUploadFileStore: () => DiskUploadFileStore,
|
|
58
65
|
ERR_BAD_SIGNATURE: () => import_axis_protocol2.ERR_BAD_SIGNATURE,
|
|
59
66
|
ERR_CONTRACT_VIOLATION: () => import_axis_protocol2.ERR_CONTRACT_VIOLATION,
|
|
60
67
|
ERR_INVALID_PACKET: () => import_axis_protocol2.ERR_INVALID_PACKET,
|
|
@@ -66,14 +73,18 @@ __export(index_exports, {
|
|
|
66
73
|
FLAG_HAS_WITNESS: () => import_axis_protocol2.FLAG_HAS_WITNESS,
|
|
67
74
|
HANDLER_METADATA_KEY: () => HANDLER_METADATA_KEY,
|
|
68
75
|
Handler: () => Handler,
|
|
76
|
+
INTENT_BODY_KEY: () => INTENT_BODY_KEY,
|
|
69
77
|
INTENT_METADATA_KEY: () => INTENT_METADATA_KEY,
|
|
70
78
|
INTENT_REQUIREMENTS: () => INTENT_REQUIREMENTS,
|
|
71
79
|
INTENT_ROUTES_KEY: () => INTENT_ROUTES_KEY,
|
|
72
80
|
INTENT_SENSITIVITY_MAP: () => INTENT_SENSITIVITY_MAP,
|
|
81
|
+
INTENT_SENSORS_KEY: () => INTENT_SENSORS_KEY,
|
|
73
82
|
INTENT_TIMEOUTS: () => INTENT_TIMEOUTS,
|
|
74
83
|
Intent: () => Intent,
|
|
84
|
+
IntentBody: () => IntentBody,
|
|
75
85
|
IntentRouter: () => IntentRouter,
|
|
76
86
|
IntentSensitivity: () => IntentSensitivity,
|
|
87
|
+
IntentSensors: () => IntentSensors,
|
|
77
88
|
MAX_BODY_LEN: () => import_axis_protocol2.MAX_BODY_LEN,
|
|
78
89
|
MAX_FRAME_LEN: () => import_axis_protocol2.MAX_FRAME_LEN,
|
|
79
90
|
MAX_HDR_LEN: () => import_axis_protocol2.MAX_HDR_LEN,
|
|
@@ -164,6 +175,8 @@ __export(index_exports, {
|
|
|
164
175
|
classifyIntent: () => classifyIntent,
|
|
165
176
|
computeReceiptHash: () => computeReceiptHash,
|
|
166
177
|
computeSignaturePayload: () => computeSignaturePayload,
|
|
178
|
+
core: () => core_exports,
|
|
179
|
+
crypto: () => crypto_exports,
|
|
167
180
|
decodeArray: () => import_axis_protocol.decodeArray,
|
|
168
181
|
decodeAxis1Frame: () => decodeAxis1Frame,
|
|
169
182
|
decodeFrame: () => decodeFrame,
|
|
@@ -171,11 +184,13 @@ __export(index_exports, {
|
|
|
171
184
|
decodeTLVs: () => import_axis_protocol.decodeTLVs,
|
|
172
185
|
decodeTLVsList: () => import_axis_protocol.decodeTLVsList,
|
|
173
186
|
decodeVarint: () => import_axis_protocol3.decodeVarint,
|
|
187
|
+
decorators: () => decorators_exports,
|
|
174
188
|
encVarint: () => encVarint,
|
|
175
189
|
encodeAxis1Frame: () => encodeAxis1Frame,
|
|
176
190
|
encodeFrame: () => encodeFrame,
|
|
177
191
|
encodeTLVs: () => import_axis_protocol.encodeTLVs,
|
|
178
192
|
encodeVarint: () => import_axis_protocol3.encodeVarint,
|
|
193
|
+
engine: () => engine_exports,
|
|
179
194
|
extractDtoSchema: () => extractDtoSchema,
|
|
180
195
|
generateEd25519KeyPair: () => generateEd25519KeyPair,
|
|
181
196
|
getSignTarget: () => getSignTarget,
|
|
@@ -183,6 +198,7 @@ __export(index_exports, {
|
|
|
183
198
|
isAdminOpcode: () => isAdminOpcode,
|
|
184
199
|
isKnownOpcode: () => isKnownOpcode,
|
|
185
200
|
isTimestampValid: () => isTimestampValid,
|
|
201
|
+
loom: () => loom_exports,
|
|
186
202
|
nonce16: () => nonce16,
|
|
187
203
|
normalizeSensorDecision: () => normalizeSensorDecision,
|
|
188
204
|
packPasskeyLoginOptionsReq: () => packPasskeyLoginOptionsReq,
|
|
@@ -192,7 +208,10 @@ __export(index_exports, {
|
|
|
192
208
|
packPasskeyRegisterOptionsReq: () => packPasskeyRegisterOptionsReq,
|
|
193
209
|
parseScope: () => parseScope,
|
|
194
210
|
resolveTimeout: () => resolveTimeout,
|
|
211
|
+
schemas: () => schemas_exports,
|
|
212
|
+
security: () => security_exports,
|
|
195
213
|
sensitivityName: () => sensitivityName,
|
|
214
|
+
sensors: () => sensors_exports,
|
|
196
215
|
sha256: () => sha256,
|
|
197
216
|
signFrame: () => signFrame,
|
|
198
217
|
tlv: () => tlv,
|
|
@@ -201,6 +220,7 @@ __export(index_exports, {
|
|
|
201
220
|
unpackPasskeyLoginVerifyReq: () => unpackPasskeyLoginVerifyReq,
|
|
202
221
|
unpackPasskeyRegisterOptionsReq: () => unpackPasskeyRegisterOptionsReq,
|
|
203
222
|
utf8: () => utf8,
|
|
223
|
+
utils: () => utils_exports,
|
|
204
224
|
validateFrameShape: () => validateFrameShape,
|
|
205
225
|
varintLength: () => import_axis_protocol3.varintLength,
|
|
206
226
|
varintU: () => varintU,
|
|
@@ -245,8 +265,26 @@ function Intent(action, options) {
|
|
|
245
265
|
};
|
|
246
266
|
}
|
|
247
267
|
|
|
248
|
-
// src/decorators/
|
|
268
|
+
// src/decorators/intent-body.decorator.ts
|
|
249
269
|
var import_reflect_metadata2 = require("reflect-metadata");
|
|
270
|
+
var INTENT_BODY_KEY = "axis:intent:body";
|
|
271
|
+
function IntentBody(decoder) {
|
|
272
|
+
return (target, propertyKey) => {
|
|
273
|
+
Reflect.defineMetadata(INTENT_BODY_KEY, decoder, target, propertyKey);
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/decorators/intent-sensors.decorator.ts
|
|
278
|
+
var import_reflect_metadata3 = require("reflect-metadata");
|
|
279
|
+
var INTENT_SENSORS_KEY = "axis:intent:sensors";
|
|
280
|
+
function IntentSensors(sensors) {
|
|
281
|
+
return (target, propertyKey) => {
|
|
282
|
+
Reflect.defineMetadata(INTENT_SENSORS_KEY, sensors, target, propertyKey);
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// src/decorators/tlv-field.decorator.ts
|
|
287
|
+
var import_reflect_metadata4 = require("reflect-metadata");
|
|
250
288
|
var TLV_FIELDS_KEY = "axis:tlv:fields";
|
|
251
289
|
var TLV_VALIDATORS_KEY = "axis:tlv:validators";
|
|
252
290
|
function TlvField(tag, options) {
|
|
@@ -304,7 +342,7 @@ function TlvRange(min, max, message) {
|
|
|
304
342
|
}
|
|
305
343
|
|
|
306
344
|
// src/decorators/dto-schema.util.ts
|
|
307
|
-
var
|
|
345
|
+
var import_reflect_metadata5 = require("reflect-metadata");
|
|
308
346
|
|
|
309
347
|
// src/core/tlv.ts
|
|
310
348
|
var import_axis_protocol = require("@nextera.one/axis-protocol");
|
|
@@ -405,7 +443,7 @@ __decorateClass([
|
|
|
405
443
|
], AxisIdDto.prototype, "id", 2);
|
|
406
444
|
|
|
407
445
|
// src/base/axis-partial-type.ts
|
|
408
|
-
var
|
|
446
|
+
var import_reflect_metadata6 = require("reflect-metadata");
|
|
409
447
|
function AxisPartialType(BaseDto) {
|
|
410
448
|
class PartialDto extends BaseDto {
|
|
411
449
|
}
|
|
@@ -452,10 +490,145 @@ __decorateClass([
|
|
|
452
490
|
|
|
453
491
|
// src/engine/intent.router.ts
|
|
454
492
|
var import_common2 = require("@nestjs/common");
|
|
493
|
+
|
|
494
|
+
// src/sensor/axis-sensor.ts
|
|
495
|
+
var Decision = /* @__PURE__ */ ((Decision2) => {
|
|
496
|
+
Decision2["ALLOW"] = "ALLOW";
|
|
497
|
+
Decision2["DENY"] = "DENY";
|
|
498
|
+
Decision2["THROTTLE"] = "THROTTLE";
|
|
499
|
+
Decision2["FLAG"] = "FLAG";
|
|
500
|
+
return Decision2;
|
|
501
|
+
})(Decision || {});
|
|
502
|
+
function normalizeSensorDecision(sensorDecision) {
|
|
503
|
+
if ("action" in sensorDecision) {
|
|
504
|
+
switch (sensorDecision.action) {
|
|
505
|
+
case "ALLOW":
|
|
506
|
+
return {
|
|
507
|
+
allow: true,
|
|
508
|
+
riskScore: 0,
|
|
509
|
+
reasons: [],
|
|
510
|
+
meta: sensorDecision.meta
|
|
511
|
+
};
|
|
512
|
+
case "DENY":
|
|
513
|
+
return {
|
|
514
|
+
allow: false,
|
|
515
|
+
riskScore: 100,
|
|
516
|
+
reasons: [sensorDecision.code, sensorDecision.reason].filter(
|
|
517
|
+
Boolean
|
|
518
|
+
),
|
|
519
|
+
meta: sensorDecision.meta,
|
|
520
|
+
retryAfterMs: sensorDecision.retryAfterMs
|
|
521
|
+
};
|
|
522
|
+
case "THROTTLE":
|
|
523
|
+
return {
|
|
524
|
+
allow: false,
|
|
525
|
+
riskScore: 50,
|
|
526
|
+
reasons: ["RATE_LIMIT"],
|
|
527
|
+
retryAfterMs: sensorDecision.retryAfterMs,
|
|
528
|
+
meta: sensorDecision.meta
|
|
529
|
+
};
|
|
530
|
+
case "FLAG":
|
|
531
|
+
return {
|
|
532
|
+
allow: true,
|
|
533
|
+
riskScore: sensorDecision.scoreDelta,
|
|
534
|
+
reasons: sensorDecision.reasons,
|
|
535
|
+
meta: sensorDecision.meta
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return {
|
|
540
|
+
allow: sensorDecision.allow,
|
|
541
|
+
riskScore: sensorDecision.riskScore,
|
|
542
|
+
reasons: sensorDecision.reasons,
|
|
543
|
+
tags: sensorDecision.tags,
|
|
544
|
+
meta: sensorDecision.meta,
|
|
545
|
+
tighten: sensorDecision.tighten,
|
|
546
|
+
retryAfterMs: sensorDecision.retryAfterMs
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
var SensorDecisions = {
|
|
550
|
+
allow(meta, tags) {
|
|
551
|
+
return {
|
|
552
|
+
decision: "ALLOW" /* ALLOW */,
|
|
553
|
+
allow: true,
|
|
554
|
+
riskScore: 0,
|
|
555
|
+
reasons: [],
|
|
556
|
+
tags,
|
|
557
|
+
meta
|
|
558
|
+
};
|
|
559
|
+
},
|
|
560
|
+
deny(code, reason, meta) {
|
|
561
|
+
return {
|
|
562
|
+
decision: "DENY" /* DENY */,
|
|
563
|
+
allow: false,
|
|
564
|
+
riskScore: 100,
|
|
565
|
+
code,
|
|
566
|
+
reasons: [code, reason].filter(Boolean),
|
|
567
|
+
meta
|
|
568
|
+
};
|
|
569
|
+
},
|
|
570
|
+
throttle(retryAfterMs, meta) {
|
|
571
|
+
return {
|
|
572
|
+
decision: "THROTTLE" /* THROTTLE */,
|
|
573
|
+
allow: false,
|
|
574
|
+
riskScore: 50,
|
|
575
|
+
retryAfterMs,
|
|
576
|
+
code: "RATE_LIMIT",
|
|
577
|
+
reasons: ["RATE_LIMIT"],
|
|
578
|
+
meta
|
|
579
|
+
};
|
|
580
|
+
},
|
|
581
|
+
flag(scoreDelta, reasons, meta) {
|
|
582
|
+
return {
|
|
583
|
+
decision: "FLAG" /* FLAG */,
|
|
584
|
+
allow: true,
|
|
585
|
+
riskScore: scoreDelta,
|
|
586
|
+
scoreDelta,
|
|
587
|
+
reasons,
|
|
588
|
+
meta
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
// src/engine/intent.router.ts
|
|
455
594
|
var IntentRouter = class {
|
|
456
|
-
constructor() {
|
|
595
|
+
constructor(moduleRef) {
|
|
596
|
+
this.moduleRef = moduleRef;
|
|
597
|
+
this.logger = new import_common2.Logger(IntentRouter.name);
|
|
457
598
|
/** Internal registry of dynamic intent handlers */
|
|
458
599
|
this.handlers = /* @__PURE__ */ new Map();
|
|
600
|
+
/** Per-intent sensor classes (resolved at call time) */
|
|
601
|
+
this.intentSensors = /* @__PURE__ */ new Map();
|
|
602
|
+
/** Per-intent body decoders */
|
|
603
|
+
this.intentDecoders = /* @__PURE__ */ new Map();
|
|
604
|
+
/** Per-intent TLV schemas */
|
|
605
|
+
this.intentSchemas = /* @__PURE__ */ new Map();
|
|
606
|
+
/** Per-intent custom validators */
|
|
607
|
+
this.intentValidators = /* @__PURE__ */ new Map();
|
|
608
|
+
/** Per-intent operation kind */
|
|
609
|
+
this.intentKinds = /* @__PURE__ */ new Map();
|
|
610
|
+
}
|
|
611
|
+
getSchema(intent) {
|
|
612
|
+
return this.intentSchemas.get(intent);
|
|
613
|
+
}
|
|
614
|
+
getValidators(intent) {
|
|
615
|
+
return this.intentValidators.get(intent);
|
|
616
|
+
}
|
|
617
|
+
has(intent) {
|
|
618
|
+
return this.handlers.has(intent) || IntentRouter.BUILTIN_INTENTS.has(intent);
|
|
619
|
+
}
|
|
620
|
+
getRegisteredIntents() {
|
|
621
|
+
return [...IntentRouter.BUILTIN_INTENTS, ...this.handlers.keys()];
|
|
622
|
+
}
|
|
623
|
+
getIntentEntry(intent) {
|
|
624
|
+
if (!this.has(intent)) return null;
|
|
625
|
+
return {
|
|
626
|
+
schema: this.intentSchemas.get(intent),
|
|
627
|
+
validators: this.intentValidators.get(intent),
|
|
628
|
+
hasSensors: this.intentSensors.has(intent),
|
|
629
|
+
builtin: IntentRouter.BUILTIN_INTENTS.has(intent),
|
|
630
|
+
kind: this.intentKinds.get(intent)
|
|
631
|
+
};
|
|
459
632
|
}
|
|
460
633
|
/**
|
|
461
634
|
* Registers a handler for a specific intent.
|
|
@@ -491,6 +664,16 @@ var IntentRouter = class {
|
|
|
491
664
|
} else {
|
|
492
665
|
this.register(intentName, fn);
|
|
493
666
|
}
|
|
667
|
+
this.registerIntentMeta(intentName, Object.getPrototypeOf(instance), String(route.methodName));
|
|
668
|
+
}
|
|
669
|
+
const proto = Object.getPrototypeOf(instance);
|
|
670
|
+
for (const key of Object.getOwnPropertyNames(proto)) {
|
|
671
|
+
const meta = Reflect.getMetadata(INTENT_METADATA_KEY, proto, key);
|
|
672
|
+
if (!meta?.intent) continue;
|
|
673
|
+
if (!this.handlers.has(meta.intent)) {
|
|
674
|
+
this.register(meta.intent, instance[key].bind(instance));
|
|
675
|
+
}
|
|
676
|
+
this.registerIntentMeta(meta.intent, proto, key);
|
|
494
677
|
}
|
|
495
678
|
}
|
|
496
679
|
/**
|
|
@@ -514,6 +697,7 @@ var IntentRouter = class {
|
|
|
514
697
|
intent = new TextDecoder().decode(intentBytes);
|
|
515
698
|
let effect;
|
|
516
699
|
if (intent === "system.ping" || intent === "public.ping") {
|
|
700
|
+
this.logger.debug("PING received");
|
|
517
701
|
effect = {
|
|
518
702
|
ok: true,
|
|
519
703
|
effect: "pong",
|
|
@@ -554,6 +738,7 @@ var IntentRouter = class {
|
|
|
554
738
|
if (!innerIntent) {
|
|
555
739
|
throw new Error("INTENT.EXEC missing inner intent");
|
|
556
740
|
}
|
|
741
|
+
this.logger.debug(`EXEC: routing to inner intent '${innerIntent}'`);
|
|
557
742
|
const innerFrame = {
|
|
558
743
|
...frame,
|
|
559
744
|
headers: new Map(frame.headers),
|
|
@@ -569,8 +754,23 @@ var IntentRouter = class {
|
|
|
569
754
|
if (!handler) {
|
|
570
755
|
throw new Error(`Intent not found: ${intent}`);
|
|
571
756
|
}
|
|
757
|
+
const sensorClasses = this.intentSensors.get(intent);
|
|
758
|
+
if (sensorClasses && sensorClasses.length > 0) {
|
|
759
|
+
await this.runIntentSensors(sensorClasses, intent, frame);
|
|
760
|
+
}
|
|
761
|
+
const decoder = this.intentDecoders.get(intent);
|
|
762
|
+
let decodedBody = frame.body;
|
|
763
|
+
if (decoder) {
|
|
764
|
+
try {
|
|
765
|
+
decodedBody = decoder(Buffer.from(frame.body));
|
|
766
|
+
} catch (decodeErr) {
|
|
767
|
+
throw new Error(
|
|
768
|
+
`IntentBody decode failed for ${intent}: ${decodeErr.message}`
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
572
772
|
if (typeof handler === "function") {
|
|
573
|
-
const resultBody = await handler(frame.body, frame.headers);
|
|
773
|
+
const resultBody = decoder ? await handler(decodedBody, frame.headers) : await handler(frame.body, frame.headers);
|
|
574
774
|
effect = {
|
|
575
775
|
ok: true,
|
|
576
776
|
effect: "complete",
|
|
@@ -580,10 +780,7 @@ var IntentRouter = class {
|
|
|
580
780
|
if (typeof handler.handle === "function") {
|
|
581
781
|
effect = await handler.handle(frame);
|
|
582
782
|
} else if (typeof handler.execute === "function") {
|
|
583
|
-
const bodyRes = await handler.execute(
|
|
584
|
-
frame.body,
|
|
585
|
-
frame.headers
|
|
586
|
-
);
|
|
783
|
+
const bodyRes = decoder ? await handler.execute(decodedBody, frame.headers) : await handler.execute(frame.body, frame.headers);
|
|
587
784
|
effect = {
|
|
588
785
|
ok: true,
|
|
589
786
|
effect: "complete",
|
|
@@ -596,20 +793,130 @@ var IntentRouter = class {
|
|
|
596
793
|
}
|
|
597
794
|
}
|
|
598
795
|
}
|
|
599
|
-
this.
|
|
796
|
+
this.logIntent(intent, start, true);
|
|
600
797
|
return effect;
|
|
601
798
|
} catch (e) {
|
|
602
|
-
|
|
799
|
+
this.logIntent(intent, start, false, e.message);
|
|
603
800
|
throw e;
|
|
604
801
|
}
|
|
605
802
|
}
|
|
606
|
-
|
|
803
|
+
logIntent(intent, start, ok, error) {
|
|
607
804
|
const diff = process.hrtime(start);
|
|
608
|
-
|
|
805
|
+
const ms = (diff[0] * 1e3 + diff[1] / 1e6).toFixed(2);
|
|
806
|
+
if (ok) {
|
|
807
|
+
this.logger.debug(`${intent} completed in ${ms}ms`);
|
|
808
|
+
} else {
|
|
809
|
+
this.logger.warn(`${intent} failed in ${ms}ms - ${error}`);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
registerIntentMeta(intent, proto, methodName) {
|
|
813
|
+
const decoder = Reflect.getMetadata(INTENT_BODY_KEY, proto, methodName);
|
|
814
|
+
if (decoder) {
|
|
815
|
+
this.intentDecoders.set(intent, decoder);
|
|
816
|
+
}
|
|
817
|
+
const sensors = Reflect.getMetadata(INTENT_SENSORS_KEY, proto, methodName);
|
|
818
|
+
if (sensors && Array.isArray(sensors) && sensors.length > 0) {
|
|
819
|
+
this.intentSensors.set(intent, sensors);
|
|
820
|
+
}
|
|
821
|
+
const meta = Reflect.getMetadata(INTENT_METADATA_KEY, proto, methodName);
|
|
822
|
+
if (meta) {
|
|
823
|
+
this.storeSchema(meta);
|
|
824
|
+
if (meta.kind) {
|
|
825
|
+
this.intentKinds.set(intent, meta.kind);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
async runIntentSensors(sensorClasses, intent, frame) {
|
|
830
|
+
if (!this.moduleRef) return;
|
|
831
|
+
for (const SensorClass of sensorClasses) {
|
|
832
|
+
let sensor;
|
|
833
|
+
try {
|
|
834
|
+
sensor = this.moduleRef.get(SensorClass, { strict: false });
|
|
835
|
+
} catch {
|
|
836
|
+
this.logger.warn(
|
|
837
|
+
`@IntentSensors: could not resolve ${SensorClass.name} for ${intent}`
|
|
838
|
+
);
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
const sensorInput = {
|
|
842
|
+
rawBytes: frame.body,
|
|
843
|
+
intent,
|
|
844
|
+
body: frame.body,
|
|
845
|
+
headerTLVs: frame.headers,
|
|
846
|
+
metadata: { phase: "intent", intent }
|
|
847
|
+
};
|
|
848
|
+
if (sensor.supports && !sensor.supports(sensorInput)) continue;
|
|
849
|
+
const decision = normalizeSensorDecision(await sensor.run(sensorInput));
|
|
850
|
+
if (!decision.allow) {
|
|
851
|
+
const reason = decision.reasons[0] || `${sensor.name}:DENIED`;
|
|
852
|
+
this.logger.warn(
|
|
853
|
+
`Intent sensor ${sensor.name} denied ${intent}: ${reason}`
|
|
854
|
+
);
|
|
855
|
+
throw new Error(`SENSOR_DENY:${reason}`);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
storeSchema(meta) {
|
|
860
|
+
if (meta.dto) {
|
|
861
|
+
if (meta.tlv && meta.tlv.length > 0) {
|
|
862
|
+
this.logger.warn(
|
|
863
|
+
`${meta.intent}: both 'dto' and 'tlv' specified - using dto, ignoring tlv`
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
const extracted = extractDtoSchema(meta.dto);
|
|
867
|
+
const schema2 = {
|
|
868
|
+
intent: meta.intent,
|
|
869
|
+
version: 1,
|
|
870
|
+
bodyProfile: meta.bodyProfile || "TLV_MAP",
|
|
871
|
+
fields: extracted.fields.map((f) => ({
|
|
872
|
+
name: f.name,
|
|
873
|
+
tlv: f.tag,
|
|
874
|
+
kind: f.kind,
|
|
875
|
+
required: f.required,
|
|
876
|
+
maxLen: f.maxLen,
|
|
877
|
+
max: f.max,
|
|
878
|
+
scope: f.scope
|
|
879
|
+
}))
|
|
880
|
+
};
|
|
881
|
+
this.intentSchemas.set(meta.intent, schema2);
|
|
882
|
+
if (extracted.validators.size > 0) {
|
|
883
|
+
this.intentValidators.set(meta.intent, extracted.validators);
|
|
884
|
+
}
|
|
885
|
+
if (!this.intentDecoders.has(meta.intent)) {
|
|
886
|
+
this.intentDecoders.set(meta.intent, buildDtoDecoder(meta.dto));
|
|
887
|
+
}
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
if (!meta.tlv || meta.tlv.length === 0) return;
|
|
891
|
+
const schema = {
|
|
892
|
+
intent: meta.intent,
|
|
893
|
+
version: 1,
|
|
894
|
+
bodyProfile: meta.bodyProfile || "TLV_MAP",
|
|
895
|
+
fields: meta.tlv.map((f) => ({
|
|
896
|
+
name: f.name,
|
|
897
|
+
tlv: f.tag,
|
|
898
|
+
kind: f.kind,
|
|
899
|
+
required: f.required,
|
|
900
|
+
maxLen: f.maxLen,
|
|
901
|
+
max: f.max,
|
|
902
|
+
scope: f.scope
|
|
903
|
+
}))
|
|
904
|
+
};
|
|
905
|
+
this.intentSchemas.set(meta.intent, schema);
|
|
609
906
|
}
|
|
610
907
|
};
|
|
908
|
+
/** Intents handled inline in route() — not in `handlers` map */
|
|
909
|
+
IntentRouter.BUILTIN_INTENTS = /* @__PURE__ */ new Set([
|
|
910
|
+
"system.ping",
|
|
911
|
+
"public.ping",
|
|
912
|
+
"system.time",
|
|
913
|
+
"system.echo",
|
|
914
|
+
"INTENT.EXEC",
|
|
915
|
+
"axis.intent.exec"
|
|
916
|
+
]);
|
|
611
917
|
IntentRouter = __decorateClass([
|
|
612
|
-
(0, import_common2.Injectable)()
|
|
918
|
+
(0, import_common2.Injectable)(),
|
|
919
|
+
__decorateParam(0, (0, import_common2.Optional)())
|
|
613
920
|
], IntentRouter);
|
|
614
921
|
|
|
615
922
|
// src/core/constants.ts
|
|
@@ -1985,105 +2292,6 @@ function buildPacket(hdr, body, sig, flags = 0) {
|
|
|
1985
2292
|
};
|
|
1986
2293
|
}
|
|
1987
2294
|
|
|
1988
|
-
// src/sensor/axis-sensor.ts
|
|
1989
|
-
var Decision = /* @__PURE__ */ ((Decision2) => {
|
|
1990
|
-
Decision2["ALLOW"] = "ALLOW";
|
|
1991
|
-
Decision2["DENY"] = "DENY";
|
|
1992
|
-
Decision2["THROTTLE"] = "THROTTLE";
|
|
1993
|
-
Decision2["FLAG"] = "FLAG";
|
|
1994
|
-
return Decision2;
|
|
1995
|
-
})(Decision || {});
|
|
1996
|
-
function normalizeSensorDecision(sensorDecision) {
|
|
1997
|
-
if ("action" in sensorDecision) {
|
|
1998
|
-
switch (sensorDecision.action) {
|
|
1999
|
-
case "ALLOW":
|
|
2000
|
-
return {
|
|
2001
|
-
allow: true,
|
|
2002
|
-
riskScore: 0,
|
|
2003
|
-
reasons: [],
|
|
2004
|
-
meta: sensorDecision.meta
|
|
2005
|
-
};
|
|
2006
|
-
case "DENY":
|
|
2007
|
-
return {
|
|
2008
|
-
allow: false,
|
|
2009
|
-
riskScore: 100,
|
|
2010
|
-
reasons: [sensorDecision.code, sensorDecision.reason].filter(
|
|
2011
|
-
Boolean
|
|
2012
|
-
),
|
|
2013
|
-
meta: sensorDecision.meta,
|
|
2014
|
-
retryAfterMs: sensorDecision.retryAfterMs
|
|
2015
|
-
};
|
|
2016
|
-
case "THROTTLE":
|
|
2017
|
-
return {
|
|
2018
|
-
allow: false,
|
|
2019
|
-
riskScore: 50,
|
|
2020
|
-
reasons: ["RATE_LIMIT"],
|
|
2021
|
-
retryAfterMs: sensorDecision.retryAfterMs,
|
|
2022
|
-
meta: sensorDecision.meta
|
|
2023
|
-
};
|
|
2024
|
-
case "FLAG":
|
|
2025
|
-
return {
|
|
2026
|
-
allow: true,
|
|
2027
|
-
riskScore: sensorDecision.scoreDelta,
|
|
2028
|
-
reasons: sensorDecision.reasons,
|
|
2029
|
-
meta: sensorDecision.meta
|
|
2030
|
-
};
|
|
2031
|
-
}
|
|
2032
|
-
}
|
|
2033
|
-
return {
|
|
2034
|
-
allow: sensorDecision.allow,
|
|
2035
|
-
riskScore: sensorDecision.riskScore,
|
|
2036
|
-
reasons: sensorDecision.reasons,
|
|
2037
|
-
tags: sensorDecision.tags,
|
|
2038
|
-
meta: sensorDecision.meta,
|
|
2039
|
-
tighten: sensorDecision.tighten,
|
|
2040
|
-
retryAfterMs: sensorDecision.retryAfterMs
|
|
2041
|
-
};
|
|
2042
|
-
}
|
|
2043
|
-
var SensorDecisions = {
|
|
2044
|
-
allow(meta, tags) {
|
|
2045
|
-
return {
|
|
2046
|
-
decision: "ALLOW" /* ALLOW */,
|
|
2047
|
-
allow: true,
|
|
2048
|
-
riskScore: 0,
|
|
2049
|
-
reasons: [],
|
|
2050
|
-
tags,
|
|
2051
|
-
meta
|
|
2052
|
-
};
|
|
2053
|
-
},
|
|
2054
|
-
deny(code, reason, meta) {
|
|
2055
|
-
return {
|
|
2056
|
-
decision: "DENY" /* DENY */,
|
|
2057
|
-
allow: false,
|
|
2058
|
-
riskScore: 100,
|
|
2059
|
-
code,
|
|
2060
|
-
reasons: [code, reason].filter(Boolean),
|
|
2061
|
-
meta
|
|
2062
|
-
};
|
|
2063
|
-
},
|
|
2064
|
-
throttle(retryAfterMs, meta) {
|
|
2065
|
-
return {
|
|
2066
|
-
decision: "THROTTLE" /* THROTTLE */,
|
|
2067
|
-
allow: false,
|
|
2068
|
-
riskScore: 50,
|
|
2069
|
-
retryAfterMs,
|
|
2070
|
-
code: "RATE_LIMIT",
|
|
2071
|
-
reasons: ["RATE_LIMIT"],
|
|
2072
|
-
meta
|
|
2073
|
-
};
|
|
2074
|
-
},
|
|
2075
|
-
flag(scoreDelta, reasons, meta) {
|
|
2076
|
-
return {
|
|
2077
|
-
decision: "FLAG" /* FLAG */,
|
|
2078
|
-
allow: true,
|
|
2079
|
-
riskScore: scoreDelta,
|
|
2080
|
-
scoreDelta,
|
|
2081
|
-
reasons,
|
|
2082
|
-
meta
|
|
2083
|
-
};
|
|
2084
|
-
}
|
|
2085
|
-
};
|
|
2086
|
-
|
|
2087
2295
|
// src/security/scopes.ts
|
|
2088
2296
|
function hasScope(scopes, required) {
|
|
2089
2297
|
if (!Array.isArray(scopes) || scopes.length === 0) {
|
|
@@ -2381,14 +2589,3066 @@ function isTimestampValid(ts, skewSeconds = 120) {
|
|
|
2381
2589
|
const diff = Math.abs(now - ts);
|
|
2382
2590
|
return diff <= skewSeconds;
|
|
2383
2591
|
}
|
|
2592
|
+
|
|
2593
|
+
// src/upload/axis-files.handlers.ts
|
|
2594
|
+
var import_common3 = require("@nestjs/common");
|
|
2595
|
+
var crypto2 = __toESM(require("crypto"));
|
|
2596
|
+
|
|
2597
|
+
// src/upload/upload.tokens.ts
|
|
2598
|
+
var AXIS_UPLOAD_SESSION_STORE = "AXIS_UPLOAD_SESSION_STORE";
|
|
2599
|
+
var AXIS_UPLOAD_FILE_STORE = "AXIS_UPLOAD_FILE_STORE";
|
|
2600
|
+
var AXIS_UPLOAD_RECEIPT_SIGNER = "AXIS_UPLOAD_RECEIPT_SIGNER";
|
|
2601
|
+
|
|
2602
|
+
// src/upload/axis-files.handlers.ts
|
|
2603
|
+
var AxisFilesDownloadHandler = class {
|
|
2604
|
+
constructor(sessions, files) {
|
|
2605
|
+
this.sessions = sessions;
|
|
2606
|
+
this.files = files;
|
|
2607
|
+
this.logger = new import_common3.Logger(AxisFilesDownloadHandler.name);
|
|
2608
|
+
this.name = "axis.files.download";
|
|
2609
|
+
this.open = true;
|
|
2610
|
+
this.description = "File download handler";
|
|
2611
|
+
}
|
|
2612
|
+
async execute(body, headers) {
|
|
2613
|
+
const h = headers;
|
|
2614
|
+
if (!h) throw new Error("MISSING_HEADERS");
|
|
2615
|
+
const uploadIdBytes = h.get(20);
|
|
2616
|
+
if (!uploadIdBytes) throw new Error("MISSING_UPLOAD_ID");
|
|
2617
|
+
const uploadId = new TextDecoder().decode(uploadIdBytes);
|
|
2618
|
+
let rangeStart = 0;
|
|
2619
|
+
let rangeLen = -1;
|
|
2620
|
+
const startBytes = h.get(21);
|
|
2621
|
+
if (startBytes) {
|
|
2622
|
+
const { value } = (0, import_axis_protocol3.decodeVarint)(startBytes);
|
|
2623
|
+
rangeStart = value;
|
|
2624
|
+
}
|
|
2625
|
+
const lenBytes = h.get(22);
|
|
2626
|
+
if (lenBytes) {
|
|
2627
|
+
const { value } = (0, import_axis_protocol3.decodeVarint)(lenBytes);
|
|
2628
|
+
rangeLen = value;
|
|
2629
|
+
}
|
|
2630
|
+
const session = await this.sessions.findByFileId(uploadId);
|
|
2631
|
+
if (!session) {
|
|
2632
|
+
throw new Error(`SESSION_NOT_FOUND: ${uploadId}`);
|
|
2633
|
+
}
|
|
2634
|
+
if (session.status !== "COMPLETE") {
|
|
2635
|
+
throw new Error(`FILE_NOT_READY: Status is ${session.status}`);
|
|
2636
|
+
}
|
|
2637
|
+
const stat = await this.files.statFinal(
|
|
2638
|
+
uploadId,
|
|
2639
|
+
session.filename
|
|
2640
|
+
);
|
|
2641
|
+
const fileSize = stat.size;
|
|
2642
|
+
if (rangeStart < 0) rangeStart = 0;
|
|
2643
|
+
if (rangeStart >= fileSize) throw new Error("RANGE_OUT_OF_BOUNDS");
|
|
2644
|
+
let end = fileSize;
|
|
2645
|
+
if (rangeLen >= 0) {
|
|
2646
|
+
end = Math.min(rangeStart + rangeLen, fileSize);
|
|
2647
|
+
}
|
|
2648
|
+
const actualLen = end - rangeStart;
|
|
2649
|
+
const buffer = await this.files.readFinalRange(
|
|
2650
|
+
uploadId,
|
|
2651
|
+
session.filename,
|
|
2652
|
+
rangeStart,
|
|
2653
|
+
actualLen
|
|
2654
|
+
);
|
|
2655
|
+
const responseHeaders = /* @__PURE__ */ new Map();
|
|
2656
|
+
responseHeaders.set(30, (0, import_axis_protocol3.encodeVarint)(fileSize));
|
|
2657
|
+
responseHeaders.set(31, (0, import_axis_protocol3.encodeVarint)(rangeStart));
|
|
2658
|
+
responseHeaders.set(32, (0, import_axis_protocol3.encodeVarint)(actualLen));
|
|
2659
|
+
return {
|
|
2660
|
+
ok: true,
|
|
2661
|
+
effect: "FILE_PART",
|
|
2662
|
+
body: buffer,
|
|
2663
|
+
headers: responseHeaders
|
|
2664
|
+
};
|
|
2665
|
+
}
|
|
2666
|
+
};
|
|
2667
|
+
__decorateClass([
|
|
2668
|
+
Intent("file.download", { absolute: true, kind: "read" })
|
|
2669
|
+
], AxisFilesDownloadHandler.prototype, "execute", 1);
|
|
2670
|
+
AxisFilesDownloadHandler = __decorateClass([
|
|
2671
|
+
Handler("axis.files.download"),
|
|
2672
|
+
(0, import_common3.Injectable)(),
|
|
2673
|
+
__decorateParam(0, (0, import_common3.Inject)(AXIS_UPLOAD_SESSION_STORE)),
|
|
2674
|
+
__decorateParam(1, (0, import_common3.Inject)(AXIS_UPLOAD_FILE_STORE))
|
|
2675
|
+
], AxisFilesDownloadHandler);
|
|
2676
|
+
var AxisFilesFinalizeHandler = class {
|
|
2677
|
+
constructor(sessions, files, keyring) {
|
|
2678
|
+
this.sessions = sessions;
|
|
2679
|
+
this.files = files;
|
|
2680
|
+
this.keyring = keyring;
|
|
2681
|
+
this.logger = new import_common3.Logger(AxisFilesFinalizeHandler.name);
|
|
2682
|
+
this.name = "axis.files.finalize";
|
|
2683
|
+
this.open = false;
|
|
2684
|
+
this.description = "File upload finalization handler";
|
|
2685
|
+
}
|
|
2686
|
+
async execute(body, headers) {
|
|
2687
|
+
const bodyStr = new TextDecoder().decode(body);
|
|
2688
|
+
const req = JSON.parse(bodyStr);
|
|
2689
|
+
const { fileId, expectedHash } = req;
|
|
2690
|
+
if (!fileId) throw new Error("MISSING_FILE_ID");
|
|
2691
|
+
const session = await this.sessions.findByFileId(fileId);
|
|
2692
|
+
if (!session) throw new Error("SESSION_NOT_FOUND");
|
|
2693
|
+
if (!await this.files.hasTemp(fileId)) {
|
|
2694
|
+
throw new Error("CHUNKS_NOT_FOUND");
|
|
2695
|
+
}
|
|
2696
|
+
const hash = crypto2.createHash("sha256");
|
|
2697
|
+
const rs = this.files.createTempReadStream(fileId);
|
|
2698
|
+
for await (const chunk of rs) {
|
|
2699
|
+
hash.update(chunk);
|
|
2700
|
+
}
|
|
2701
|
+
const finalHash = hash.digest("hex");
|
|
2702
|
+
if (expectedHash && finalHash !== expectedHash) {
|
|
2703
|
+
throw new Error("HASH_MISMATCH");
|
|
2704
|
+
}
|
|
2705
|
+
const finalPath = await this.files.moveTempToFinal(
|
|
2706
|
+
fileId,
|
|
2707
|
+
session.filename
|
|
2708
|
+
);
|
|
2709
|
+
await this.sessions.updateStatus(fileId, "COMPLETE", null);
|
|
2710
|
+
if (!this.keyring) {
|
|
2711
|
+
this.logger.warn("Receipt signer not configured; returning unsigned receipt");
|
|
2712
|
+
return {
|
|
2713
|
+
ok: true,
|
|
2714
|
+
effect: "FILE_FINALIZED",
|
|
2715
|
+
body: new TextEncoder().encode(
|
|
2716
|
+
JSON.stringify({
|
|
2717
|
+
uploadId: fileId,
|
|
2718
|
+
sha256_final: finalHash,
|
|
2719
|
+
totalSize: session.totalSize,
|
|
2720
|
+
tsMs: Date.now(),
|
|
2721
|
+
path: finalPath
|
|
2722
|
+
})
|
|
2723
|
+
)
|
|
2724
|
+
};
|
|
2725
|
+
}
|
|
2726
|
+
const receiptData = {
|
|
2727
|
+
uploadId: fileId,
|
|
2728
|
+
sha256_final: finalHash,
|
|
2729
|
+
totalSize: session.totalSize,
|
|
2730
|
+
tsMs: Date.now()
|
|
2731
|
+
};
|
|
2732
|
+
const receiptJson = JSON.stringify(receiptData);
|
|
2733
|
+
const receiptBody = new TextEncoder().encode(receiptJson);
|
|
2734
|
+
const SIG_PRESENT = 1;
|
|
2735
|
+
const responseFrame = {
|
|
2736
|
+
flags: SIG_PRESENT,
|
|
2737
|
+
headers: /* @__PURE__ */ new Map(),
|
|
2738
|
+
body: receiptBody,
|
|
2739
|
+
sig: new Uint8Array(0)
|
|
2740
|
+
};
|
|
2741
|
+
const signTarget = getSignTarget(responseFrame);
|
|
2742
|
+
const { sig, kid } = this.keyring.signActive(signTarget);
|
|
2743
|
+
responseFrame.sig = sig;
|
|
2744
|
+
return {
|
|
2745
|
+
ok: true,
|
|
2746
|
+
effect: "FILE_FINALIZED",
|
|
2747
|
+
data: encodeFrame(responseFrame),
|
|
2748
|
+
headers: /* @__PURE__ */ new Map([[1, new TextEncoder().encode(kid)]])
|
|
2749
|
+
};
|
|
2750
|
+
}
|
|
2751
|
+
};
|
|
2752
|
+
__decorateClass([
|
|
2753
|
+
Intent("file.finalize", { absolute: true, kind: "action" })
|
|
2754
|
+
], AxisFilesFinalizeHandler.prototype, "execute", 1);
|
|
2755
|
+
AxisFilesFinalizeHandler = __decorateClass([
|
|
2756
|
+
Handler("axis.files.finalize"),
|
|
2757
|
+
(0, import_common3.Injectable)(),
|
|
2758
|
+
__decorateParam(0, (0, import_common3.Inject)(AXIS_UPLOAD_SESSION_STORE)),
|
|
2759
|
+
__decorateParam(1, (0, import_common3.Inject)(AXIS_UPLOAD_FILE_STORE)),
|
|
2760
|
+
__decorateParam(2, (0, import_common3.Optional)()),
|
|
2761
|
+
__decorateParam(2, (0, import_common3.Inject)(AXIS_UPLOAD_RECEIPT_SIGNER))
|
|
2762
|
+
], AxisFilesFinalizeHandler);
|
|
2763
|
+
|
|
2764
|
+
// src/upload/disk-upload-file.store.ts
|
|
2765
|
+
var fs = __toESM(require("fs"));
|
|
2766
|
+
var path = __toESM(require("path"));
|
|
2767
|
+
var DiskUploadFileStore = class {
|
|
2768
|
+
constructor(options) {
|
|
2769
|
+
this.uploadDir = options.uploadDir;
|
|
2770
|
+
this.chunkDir = options.chunkDir;
|
|
2771
|
+
}
|
|
2772
|
+
getFinalPath(fileId, filename) {
|
|
2773
|
+
const safeFilename = filename ? path.basename(filename) : fileId;
|
|
2774
|
+
return path.join(this.uploadDir, safeFilename);
|
|
2775
|
+
}
|
|
2776
|
+
getTempPath(fileId) {
|
|
2777
|
+
const safeId = path.basename(fileId);
|
|
2778
|
+
return path.join(this.chunkDir, safeId);
|
|
2779
|
+
}
|
|
2780
|
+
async statFinal(fileId, filename) {
|
|
2781
|
+
const finalPath = this.getFinalPath(fileId, filename);
|
|
2782
|
+
if (!fs.existsSync(finalPath)) {
|
|
2783
|
+
throw new Error("FILE_MISSING_ON_DISK");
|
|
2784
|
+
}
|
|
2785
|
+
const stat = fs.statSync(finalPath);
|
|
2786
|
+
return { path: finalPath, size: stat.size };
|
|
2787
|
+
}
|
|
2788
|
+
async readFinalRange(fileId, filename, start, length) {
|
|
2789
|
+
const finalPath = this.getFinalPath(fileId, filename);
|
|
2790
|
+
const buffer = Buffer.alloc(length);
|
|
2791
|
+
const fd = fs.openSync(finalPath, "r");
|
|
2792
|
+
try {
|
|
2793
|
+
fs.readSync(fd, buffer, 0, length, start);
|
|
2794
|
+
} finally {
|
|
2795
|
+
fs.closeSync(fd);
|
|
2796
|
+
}
|
|
2797
|
+
return buffer;
|
|
2798
|
+
}
|
|
2799
|
+
async hasTemp(fileId) {
|
|
2800
|
+
const tempPath = this.getTempPath(fileId);
|
|
2801
|
+
return fs.existsSync(tempPath);
|
|
2802
|
+
}
|
|
2803
|
+
async moveTempToFinal(fileId, filename) {
|
|
2804
|
+
const tempPath = this.getTempPath(fileId);
|
|
2805
|
+
const finalPath = this.getFinalPath(fileId, filename);
|
|
2806
|
+
try {
|
|
2807
|
+
await fs.promises.rename(tempPath, finalPath);
|
|
2808
|
+
} catch {
|
|
2809
|
+
await fs.promises.copyFile(tempPath, finalPath);
|
|
2810
|
+
await fs.promises.unlink(tempPath);
|
|
2811
|
+
}
|
|
2812
|
+
return finalPath;
|
|
2813
|
+
}
|
|
2814
|
+
createTempReadStream(fileId) {
|
|
2815
|
+
const tempPath = this.getTempPath(fileId);
|
|
2816
|
+
return fs.createReadStream(tempPath);
|
|
2817
|
+
}
|
|
2818
|
+
};
|
|
2819
|
+
|
|
2820
|
+
// src/core/index.ts
|
|
2821
|
+
var core_exports = {};
|
|
2822
|
+
__export(core_exports, {
|
|
2823
|
+
AXIS_MAGIC: () => import_axis_protocol2.AXIS_MAGIC,
|
|
2824
|
+
AXIS_VERSION: () => import_axis_protocol2.AXIS_VERSION,
|
|
2825
|
+
AxisError: () => AxisError,
|
|
2826
|
+
AxisFrameZ: () => AxisFrameZ,
|
|
2827
|
+
BodyProfile: () => import_axis_protocol2.BodyProfile,
|
|
2828
|
+
ERR_BAD_SIGNATURE: () => import_axis_protocol2.ERR_BAD_SIGNATURE,
|
|
2829
|
+
ERR_CONTRACT_VIOLATION: () => import_axis_protocol2.ERR_CONTRACT_VIOLATION,
|
|
2830
|
+
ERR_INVALID_PACKET: () => import_axis_protocol2.ERR_INVALID_PACKET,
|
|
2831
|
+
ERR_REPLAY_DETECTED: () => import_axis_protocol2.ERR_REPLAY_DETECTED,
|
|
2832
|
+
FLAG_BODY_TLV: () => import_axis_protocol2.FLAG_BODY_TLV,
|
|
2833
|
+
FLAG_CHAIN_REQ: () => import_axis_protocol2.FLAG_CHAIN_REQ,
|
|
2834
|
+
FLAG_HAS_WITNESS: () => import_axis_protocol2.FLAG_HAS_WITNESS,
|
|
2835
|
+
MAX_BODY_LEN: () => import_axis_protocol2.MAX_BODY_LEN,
|
|
2836
|
+
MAX_FRAME_LEN: () => import_axis_protocol2.MAX_FRAME_LEN,
|
|
2837
|
+
MAX_HDR_LEN: () => import_axis_protocol2.MAX_HDR_LEN,
|
|
2838
|
+
MAX_SIG_LEN: () => import_axis_protocol2.MAX_SIG_LEN,
|
|
2839
|
+
NCERT_ALG: () => import_axis_protocol2.NCERT_ALG,
|
|
2840
|
+
NCERT_EXP: () => import_axis_protocol2.NCERT_EXP,
|
|
2841
|
+
NCERT_ISSUER_KID: () => import_axis_protocol2.NCERT_ISSUER_KID,
|
|
2842
|
+
NCERT_KID: () => import_axis_protocol2.NCERT_KID,
|
|
2843
|
+
NCERT_NBF: () => import_axis_protocol2.NCERT_NBF,
|
|
2844
|
+
NCERT_NODE_ID: () => import_axis_protocol2.NCERT_NODE_ID,
|
|
2845
|
+
NCERT_PAYLOAD: () => import_axis_protocol2.NCERT_PAYLOAD,
|
|
2846
|
+
NCERT_PUB: () => import_axis_protocol2.NCERT_PUB,
|
|
2847
|
+
NCERT_SCOPE: () => import_axis_protocol2.NCERT_SCOPE,
|
|
2848
|
+
NCERT_SIG: () => import_axis_protocol2.NCERT_SIG,
|
|
2849
|
+
PROOF_CAPSULE: () => import_axis_protocol2.PROOF_CAPSULE,
|
|
2850
|
+
PROOF_JWT: () => import_axis_protocol2.PROOF_JWT,
|
|
2851
|
+
PROOF_LOOM: () => import_axis_protocol2.PROOF_LOOM,
|
|
2852
|
+
PROOF_MTLS: () => import_axis_protocol2.PROOF_MTLS,
|
|
2853
|
+
PROOF_NONE: () => import_axis_protocol2.PROOF_NONE,
|
|
2854
|
+
PROOF_WITNESS: () => import_axis_protocol2.PROOF_WITNESS,
|
|
2855
|
+
ProofType: () => import_axis_protocol2.ProofType,
|
|
2856
|
+
TLV: () => import_axis_protocol.TLV,
|
|
2857
|
+
TLV_ACTOR_ID: () => import_axis_protocol2.TLV_ACTOR_ID,
|
|
2858
|
+
TLV_AUD: () => import_axis_protocol2.TLV_AUD,
|
|
2859
|
+
TLV_BODY_ARR: () => import_axis_protocol2.TLV_BODY_ARR,
|
|
2860
|
+
TLV_BODY_OBJ: () => import_axis_protocol2.TLV_BODY_OBJ,
|
|
2861
|
+
TLV_CAPSULE: () => import_axis_protocol2.TLV_CAPSULE,
|
|
2862
|
+
TLV_EFFECT: () => import_axis_protocol2.TLV_EFFECT,
|
|
2863
|
+
TLV_ERROR_CODE: () => import_axis_protocol2.TLV_ERROR_CODE,
|
|
2864
|
+
TLV_ERROR_MSG: () => import_axis_protocol2.TLV_ERROR_MSG,
|
|
2865
|
+
TLV_INDEX: () => import_axis_protocol2.TLV_INDEX,
|
|
2866
|
+
TLV_INTENT: () => import_axis_protocol2.TLV_INTENT,
|
|
2867
|
+
TLV_KID: () => import_axis_protocol2.TLV_KID,
|
|
2868
|
+
TLV_LOOM_PRESENCE_ID: () => import_axis_protocol2.TLV_LOOM_PRESENCE_ID,
|
|
2869
|
+
TLV_LOOM_THREAD_HASH: () => import_axis_protocol2.TLV_LOOM_THREAD_HASH,
|
|
2870
|
+
TLV_LOOM_WRIT: () => import_axis_protocol2.TLV_LOOM_WRIT,
|
|
2871
|
+
TLV_NODE: () => import_axis_protocol2.TLV_NODE,
|
|
2872
|
+
TLV_NODE_CERT_HASH: () => import_axis_protocol2.TLV_NODE_CERT_HASH,
|
|
2873
|
+
TLV_NODE_KID: () => import_axis_protocol2.TLV_NODE_KID,
|
|
2874
|
+
TLV_NONCE: () => import_axis_protocol2.TLV_NONCE,
|
|
2875
|
+
TLV_OFFSET: () => import_axis_protocol2.TLV_OFFSET,
|
|
2876
|
+
TLV_OK: () => import_axis_protocol2.TLV_OK,
|
|
2877
|
+
TLV_PID: () => import_axis_protocol2.TLV_PID,
|
|
2878
|
+
TLV_PREV_HASH: () => import_axis_protocol2.TLV_PREV_HASH,
|
|
2879
|
+
TLV_PROOF_REF: () => import_axis_protocol2.TLV_PROOF_REF,
|
|
2880
|
+
TLV_PROOF_TYPE: () => import_axis_protocol2.TLV_PROOF_TYPE,
|
|
2881
|
+
TLV_REALM: () => import_axis_protocol2.TLV_REALM,
|
|
2882
|
+
TLV_RECEIPT_HASH: () => import_axis_protocol2.TLV_RECEIPT_HASH,
|
|
2883
|
+
TLV_RID: () => import_axis_protocol2.TLV_RID,
|
|
2884
|
+
TLV_SHA256_CHUNK: () => import_axis_protocol2.TLV_SHA256_CHUNK,
|
|
2885
|
+
TLV_TRACE_ID: () => import_axis_protocol2.TLV_TRACE_ID,
|
|
2886
|
+
TLV_TS: () => import_axis_protocol2.TLV_TS,
|
|
2887
|
+
TLV_UPLOAD_ID: () => import_axis_protocol2.TLV_UPLOAD_ID,
|
|
2888
|
+
computeReceiptHash: () => computeReceiptHash,
|
|
2889
|
+
computeSignaturePayload: () => computeSignaturePayload,
|
|
2890
|
+
decodeArray: () => import_axis_protocol.decodeArray,
|
|
2891
|
+
decodeFrame: () => decodeFrame,
|
|
2892
|
+
decodeObject: () => import_axis_protocol.decodeObject,
|
|
2893
|
+
decodeTLVs: () => import_axis_protocol.decodeTLVs,
|
|
2894
|
+
decodeTLVsList: () => import_axis_protocol.decodeTLVsList,
|
|
2895
|
+
decodeVarint: () => import_axis_protocol3.decodeVarint,
|
|
2896
|
+
encodeFrame: () => encodeFrame,
|
|
2897
|
+
encodeTLVs: () => import_axis_protocol.encodeTLVs,
|
|
2898
|
+
encodeVarint: () => import_axis_protocol3.encodeVarint,
|
|
2899
|
+
generateEd25519KeyPair: () => generateEd25519KeyPair,
|
|
2900
|
+
getSignTarget: () => getSignTarget,
|
|
2901
|
+
sha256: () => sha256,
|
|
2902
|
+
signFrame: () => signFrame,
|
|
2903
|
+
varintLength: () => import_axis_protocol3.varintLength,
|
|
2904
|
+
verifyFrameSignature: () => verifyFrameSignature
|
|
2905
|
+
});
|
|
2906
|
+
|
|
2907
|
+
// src/core/axis-error.ts
|
|
2908
|
+
var AxisError = class extends Error {
|
|
2909
|
+
constructor(code, message, httpStatus = 400, details) {
|
|
2910
|
+
super(message);
|
|
2911
|
+
this.code = code;
|
|
2912
|
+
this.httpStatus = httpStatus;
|
|
2913
|
+
this.details = details;
|
|
2914
|
+
this.name = "AxisError";
|
|
2915
|
+
}
|
|
2916
|
+
};
|
|
2917
|
+
|
|
2918
|
+
// src/crypto/index.ts
|
|
2919
|
+
var crypto_exports = {};
|
|
2920
|
+
__export(crypto_exports, {
|
|
2921
|
+
ProofVerificationService: () => ProofVerificationService,
|
|
2922
|
+
b64urlDecode: () => b64urlDecode,
|
|
2923
|
+
b64urlDecodeString: () => b64urlDecodeString,
|
|
2924
|
+
b64urlEncode: () => b64urlEncode,
|
|
2925
|
+
b64urlEncodeString: () => b64urlEncodeString,
|
|
2926
|
+
canonicalJson: () => canonicalJson,
|
|
2927
|
+
canonicalJsonExcluding: () => canonicalJsonExcluding
|
|
2928
|
+
});
|
|
2929
|
+
|
|
2930
|
+
// src/crypto/proof-verification.service.ts
|
|
2931
|
+
var import_common4 = require("@nestjs/common");
|
|
2932
|
+
var crypto3 = __toESM(require("crypto"));
|
|
2933
|
+
var nacl = __toESM(require("tweetnacl"));
|
|
2934
|
+
var ProofVerificationService = class {
|
|
2935
|
+
constructor() {
|
|
2936
|
+
this.logger = new import_common4.Logger(ProofVerificationService.name);
|
|
2937
|
+
// Cache of registered device public keys (deviceId -> pubKey)
|
|
2938
|
+
this.deviceKeys = /* @__PURE__ */ new Map();
|
|
2939
|
+
// Cache of trusted mTLS certificate fingerprints
|
|
2940
|
+
this.trustedCerts = /* @__PURE__ */ new Map();
|
|
2941
|
+
}
|
|
2942
|
+
/**
|
|
2943
|
+
* Verifies an authentication proof based on its type.
|
|
2944
|
+
*
|
|
2945
|
+
* **Supported Types:**
|
|
2946
|
+
* - 1 (CAPSULE): Delegated to `verifyCapsuleProof`
|
|
2947
|
+
* - 2 (JWT): Verified by `verifyJWTProof`
|
|
2948
|
+
* - 3 (MTLS_ID): Verified by `verifyMTLSProof`
|
|
2949
|
+
* - 4 (DEVICE_SE): Verified by `verifyDeviceSEProof`
|
|
2950
|
+
*
|
|
2951
|
+
* @param {ProofType} proofType - The numeric AXIS proof type
|
|
2952
|
+
* @param {Uint8Array} proofRef - The binary reference or token for the proof
|
|
2953
|
+
* @param {Object} context - Additional metadata required for specific proof types
|
|
2954
|
+
* @param {Uint8Array} [context.signTarget] - The canonical bytes that were signed (for Ed25519)
|
|
2955
|
+
* @param {Uint8Array} [context.signature] - The signature to verify (for Ed25519)
|
|
2956
|
+
* @param {MTLSContext} [context.mtls] - mTLS certificate data
|
|
2957
|
+
* @param {DeviceSEContext} [context.deviceSE] - Device Secure Element information
|
|
2958
|
+
* @returns {Promise<ProofVerificationResult>} The outcome of the verification
|
|
2959
|
+
*/
|
|
2960
|
+
async verifyProof(proofType, proofRef, context) {
|
|
2961
|
+
switch (proofType) {
|
|
2962
|
+
case 1:
|
|
2963
|
+
return this.verifyCapsuleProof(proofRef);
|
|
2964
|
+
case 2:
|
|
2965
|
+
return this.verifyJWTProof(proofRef);
|
|
2966
|
+
case 3:
|
|
2967
|
+
return this.verifyMTLSProof(context.mtls);
|
|
2968
|
+
case 4:
|
|
2969
|
+
return this.verifyDeviceSEProof(
|
|
2970
|
+
context.signTarget,
|
|
2971
|
+
context.signature,
|
|
2972
|
+
context.deviceSE
|
|
2973
|
+
);
|
|
2974
|
+
default:
|
|
2975
|
+
return { valid: false, error: `Unknown proof type: ${proofType}` };
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
/**
|
|
2979
|
+
* Verify CAPSULE proof (delegated to CapsuleService)
|
|
2980
|
+
*/
|
|
2981
|
+
async verifyCapsuleProof(proofRef) {
|
|
2982
|
+
const capsuleId = new TextDecoder().decode(proofRef);
|
|
2983
|
+
return {
|
|
2984
|
+
valid: true,
|
|
2985
|
+
metadata: { capsuleId, requiresCapsuleValidation: true }
|
|
2986
|
+
};
|
|
2987
|
+
}
|
|
2988
|
+
/**
|
|
2989
|
+
* Verifies a JSON Web Token (JWT) proof.
|
|
2990
|
+
*
|
|
2991
|
+
* **Validation Logic:**
|
|
2992
|
+
* 1. Decodes the token string.
|
|
2993
|
+
* 2. Checks for valid 3-part JWT structure.
|
|
2994
|
+
* 3. Validates `exp` (expiration) and `nbf` (not before) claims.
|
|
2995
|
+
* 4. Extracts `actor_id` or `sub` as the identity.
|
|
2996
|
+
*
|
|
2997
|
+
* @param {Uint8Array} proofRef - Binary representation of the JWT string
|
|
2998
|
+
* @returns {Promise<ProofVerificationResult>} Result including the actor identifier
|
|
2999
|
+
*/
|
|
3000
|
+
async verifyJWTProof(proofRef) {
|
|
3001
|
+
try {
|
|
3002
|
+
const token = new TextDecoder().decode(proofRef);
|
|
3003
|
+
const parts = token.split(".");
|
|
3004
|
+
if (parts.length !== 3) {
|
|
3005
|
+
return { valid: false, error: "Invalid JWT format" };
|
|
3006
|
+
}
|
|
3007
|
+
const header = JSON.parse(Buffer.from(parts[0], "base64url").toString());
|
|
3008
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
3009
|
+
if (payload.exp && Date.now() / 1e3 > payload.exp) {
|
|
3010
|
+
return { valid: false, error: "JWT expired" };
|
|
3011
|
+
}
|
|
3012
|
+
if (payload.nbf && Date.now() / 1e3 < payload.nbf) {
|
|
3013
|
+
return { valid: false, error: "JWT not yet valid" };
|
|
3014
|
+
}
|
|
3015
|
+
return {
|
|
3016
|
+
valid: true,
|
|
3017
|
+
actorId: payload.sub || payload.actor_id,
|
|
3018
|
+
metadata: { iss: payload.iss, scope: payload.scope }
|
|
3019
|
+
};
|
|
3020
|
+
} catch (e) {
|
|
3021
|
+
const message = e instanceof Error ? e.message : "Unknown error";
|
|
3022
|
+
return { valid: false, error: `JWT parse error: ${message}` };
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
/**
|
|
3026
|
+
* Verify mTLS client certificate proof
|
|
3027
|
+
*/
|
|
3028
|
+
async verifyMTLSProof(mtls) {
|
|
3029
|
+
if (!mtls) {
|
|
3030
|
+
return { valid: false, error: "No mTLS context provided" };
|
|
3031
|
+
}
|
|
3032
|
+
if (!mtls.verified) {
|
|
3033
|
+
return { valid: false, error: "mTLS not verified by TLS terminator" };
|
|
3034
|
+
}
|
|
3035
|
+
if (mtls.clientCertFingerprint) {
|
|
3036
|
+
const trusted = this.trustedCerts.get(mtls.clientCertFingerprint);
|
|
3037
|
+
if (trusted) {
|
|
3038
|
+
return {
|
|
3039
|
+
valid: true,
|
|
3040
|
+
actorId: trusted.actorId,
|
|
3041
|
+
metadata: {
|
|
3042
|
+
fingerprint: mtls.clientCertFingerprint,
|
|
3043
|
+
subject: mtls.clientCertSubject
|
|
3044
|
+
}
|
|
3045
|
+
};
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
if (mtls.clientCertSubject) {
|
|
3049
|
+
const cnMatch = mtls.clientCertSubject.match(/CN=([^,]+)/);
|
|
3050
|
+
if (cnMatch) {
|
|
3051
|
+
return {
|
|
3052
|
+
valid: true,
|
|
3053
|
+
actorId: cnMatch[1],
|
|
3054
|
+
metadata: {
|
|
3055
|
+
subject: mtls.clientCertSubject,
|
|
3056
|
+
issuer: mtls.clientCertIssuer
|
|
3057
|
+
}
|
|
3058
|
+
};
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
return { valid: false, error: "Could not extract actor from certificate" };
|
|
3062
|
+
}
|
|
3063
|
+
/**
|
|
3064
|
+
* Verify Device Secure Element signature
|
|
3065
|
+
*/
|
|
3066
|
+
async verifyDeviceSEProof(signTarget, signature, deviceSE) {
|
|
3067
|
+
if (!deviceSE || !signTarget || !signature) {
|
|
3068
|
+
return { valid: false, error: "Missing Device SE context" };
|
|
3069
|
+
}
|
|
3070
|
+
let publicKey = deviceSE.publicKey;
|
|
3071
|
+
const registeredKey = this.deviceKeys.get(deviceSE.deviceId);
|
|
3072
|
+
if (registeredKey) {
|
|
3073
|
+
publicKey = registeredKey;
|
|
3074
|
+
}
|
|
3075
|
+
if (!publicKey || publicKey.length !== 32) {
|
|
3076
|
+
return {
|
|
3077
|
+
valid: false,
|
|
3078
|
+
error: "Invalid or unregistered device public key"
|
|
3079
|
+
};
|
|
3080
|
+
}
|
|
3081
|
+
try {
|
|
3082
|
+
const valid = nacl.sign.detached.verify(signTarget, signature, publicKey);
|
|
3083
|
+
if (!valid) {
|
|
3084
|
+
return { valid: false, error: "Device signature verification failed" };
|
|
3085
|
+
}
|
|
3086
|
+
return {
|
|
3087
|
+
valid: true,
|
|
3088
|
+
actorId: deviceSE.deviceId,
|
|
3089
|
+
metadata: { deviceId: deviceSE.deviceId, proofType: "DEVICE_SE" }
|
|
3090
|
+
};
|
|
3091
|
+
} catch (e) {
|
|
3092
|
+
const message = e instanceof Error ? e.message : "Unknown error";
|
|
3093
|
+
return {
|
|
3094
|
+
valid: false,
|
|
3095
|
+
error: `Signature verification error: ${message}`
|
|
3096
|
+
};
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
/**
|
|
3100
|
+
* Registers a public key for a trusted device.
|
|
3101
|
+
* This key will be used for future `DEVICE_SE` proof verifications.
|
|
3102
|
+
*
|
|
3103
|
+
* @param {string} deviceId - Unique identifier for the device
|
|
3104
|
+
* @param {Uint8Array} publicKey - 32-byte Ed25519 public key
|
|
3105
|
+
* @throws {Error} If the public key is not 32 bytes
|
|
3106
|
+
*/
|
|
3107
|
+
registerDeviceKey(deviceId, publicKey) {
|
|
3108
|
+
if (publicKey.length !== 32) {
|
|
3109
|
+
throw new Error("Device public key must be 32 bytes (Ed25519)");
|
|
3110
|
+
}
|
|
3111
|
+
this.deviceKeys.set(deviceId, publicKey);
|
|
3112
|
+
this.logger.log(`Registered device key for ${deviceId}`);
|
|
3113
|
+
}
|
|
3114
|
+
/**
|
|
3115
|
+
* Unregister a device
|
|
3116
|
+
*/
|
|
3117
|
+
unregisterDevice(deviceId) {
|
|
3118
|
+
return this.deviceKeys.delete(deviceId);
|
|
3119
|
+
}
|
|
3120
|
+
/**
|
|
3121
|
+
* Registers a trusted mTLS certificate fingerprint and associates it with an actor.
|
|
3122
|
+
*
|
|
3123
|
+
* @param {string} fingerprint - SHA-256 fingerprint of the client certificate
|
|
3124
|
+
* @param {string} actorId - The actor to associate with this certificate
|
|
3125
|
+
*/
|
|
3126
|
+
registerMTLSCert(fingerprint, actorId) {
|
|
3127
|
+
this.trustedCerts.set(fingerprint, { actorId, issuedAt: Date.now() });
|
|
3128
|
+
this.logger.log(`Registered mTLS cert ${fingerprint} for actor ${actorId}`);
|
|
3129
|
+
}
|
|
3130
|
+
/**
|
|
3131
|
+
* Revoke an mTLS certificate
|
|
3132
|
+
*/
|
|
3133
|
+
revokeMTLSCert(fingerprint) {
|
|
3134
|
+
return this.trustedCerts.delete(fingerprint);
|
|
3135
|
+
}
|
|
3136
|
+
/**
|
|
3137
|
+
* Calculate certificate fingerprint (SHA-256)
|
|
3138
|
+
*/
|
|
3139
|
+
static calculateFingerprint(certPem) {
|
|
3140
|
+
const der = Buffer.from(
|
|
3141
|
+
certPem.replace(/-----BEGIN CERTIFICATE-----/, "").replace(/-----END CERTIFICATE-----/, "").replace(/\s/g, ""),
|
|
3142
|
+
"base64"
|
|
3143
|
+
);
|
|
3144
|
+
return crypto3.createHash("sha256").update(der).digest("hex");
|
|
3145
|
+
}
|
|
3146
|
+
};
|
|
3147
|
+
ProofVerificationService = __decorateClass([
|
|
3148
|
+
(0, import_common4.Injectable)()
|
|
3149
|
+
], ProofVerificationService);
|
|
3150
|
+
|
|
3151
|
+
// src/decorators/index.ts
|
|
3152
|
+
var decorators_exports = {};
|
|
3153
|
+
__export(decorators_exports, {
|
|
3154
|
+
AxisContext: () => AxisContext,
|
|
3155
|
+
AxisDemoPubkey: () => AxisDemoPubkey,
|
|
3156
|
+
AxisFrame: () => AxisFrame3,
|
|
3157
|
+
AxisIp: () => AxisIp,
|
|
3158
|
+
AxisRaw: () => AxisRaw,
|
|
3159
|
+
HANDLER_METADATA_KEY: () => HANDLER_METADATA_KEY,
|
|
3160
|
+
Handler: () => Handler,
|
|
3161
|
+
INTENT_BODY_KEY: () => INTENT_BODY_KEY,
|
|
3162
|
+
INTENT_METADATA_KEY: () => INTENT_METADATA_KEY,
|
|
3163
|
+
INTENT_ROUTES_KEY: () => INTENT_ROUTES_KEY,
|
|
3164
|
+
INTENT_SENSORS_KEY: () => INTENT_SENSORS_KEY,
|
|
3165
|
+
Intent: () => Intent,
|
|
3166
|
+
IntentBody: () => IntentBody,
|
|
3167
|
+
IntentSensors: () => IntentSensors,
|
|
3168
|
+
SENSOR_METADATA_KEY: () => SENSOR_METADATA_KEY,
|
|
3169
|
+
Sensor: () => Sensor,
|
|
3170
|
+
TLV_FIELDS_KEY: () => TLV_FIELDS_KEY,
|
|
3171
|
+
TLV_VALIDATORS_KEY: () => TLV_VALIDATORS_KEY,
|
|
3172
|
+
TlvEnum: () => TlvEnum,
|
|
3173
|
+
TlvField: () => TlvField,
|
|
3174
|
+
TlvMinLen: () => TlvMinLen,
|
|
3175
|
+
TlvRange: () => TlvRange,
|
|
3176
|
+
TlvUtf8Pattern: () => TlvUtf8Pattern,
|
|
3177
|
+
TlvValidate: () => TlvValidate,
|
|
3178
|
+
buildDtoDecoder: () => buildDtoDecoder,
|
|
3179
|
+
extractDtoSchema: () => extractDtoSchema
|
|
3180
|
+
});
|
|
3181
|
+
|
|
3182
|
+
// src/decorators/axis-request.decorator.ts
|
|
3183
|
+
var import_common5 = require("@nestjs/common");
|
|
3184
|
+
function resolveIp(req) {
|
|
3185
|
+
return req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.headers["x-real-ip"] || req.socket.remoteAddress || void 0;
|
|
3186
|
+
}
|
|
3187
|
+
var AxisRaw = (0, import_common5.createParamDecorator)(
|
|
3188
|
+
(_data, ctx) => {
|
|
3189
|
+
const req = ctx.switchToHttp().getRequest();
|
|
3190
|
+
return req.body;
|
|
3191
|
+
}
|
|
3192
|
+
);
|
|
3193
|
+
var AxisIp = (0, import_common5.createParamDecorator)(
|
|
3194
|
+
(_data, ctx) => {
|
|
3195
|
+
const req = ctx.switchToHttp().getRequest();
|
|
3196
|
+
return resolveIp(req);
|
|
3197
|
+
}
|
|
3198
|
+
);
|
|
3199
|
+
var AxisContext = (0, import_common5.createParamDecorator)(
|
|
3200
|
+
(_data, ctx) => {
|
|
3201
|
+
const req = ctx.switchToHttp().getRequest();
|
|
3202
|
+
const axisData = req.axis || {};
|
|
3203
|
+
return {
|
|
3204
|
+
raw: req.body,
|
|
3205
|
+
ip: resolveIp(req),
|
|
3206
|
+
preDecodeInput: axisData.preDecodeInput,
|
|
3207
|
+
frameBytesCount: axisData.frameBytesCount || 0
|
|
3208
|
+
};
|
|
3209
|
+
}
|
|
3210
|
+
);
|
|
3211
|
+
var AxisDemoPubkey = (0, import_common5.createParamDecorator)(
|
|
3212
|
+
(_data, ctx) => {
|
|
3213
|
+
if (process.env.NODE_ENV !== "development") return void 0;
|
|
3214
|
+
const req = ctx.switchToHttp().getRequest();
|
|
3215
|
+
return req.headers["x-demo-pubkey"];
|
|
3216
|
+
}
|
|
3217
|
+
);
|
|
3218
|
+
var AxisFrame3 = (0, import_common5.createParamDecorator)(
|
|
3219
|
+
(_data, ctx) => {
|
|
3220
|
+
const req = ctx.switchToHttp().getRequest();
|
|
3221
|
+
const decoded = req.axisDecoded;
|
|
3222
|
+
if (!decoded) {
|
|
3223
|
+
throw new Error(
|
|
3224
|
+
"@AxisFrame() requires AxisDecodeInterceptor on the route. Add @UseInterceptors(AxisDecodeInterceptor) to use this decorator."
|
|
3225
|
+
);
|
|
3226
|
+
}
|
|
3227
|
+
return decoded;
|
|
3228
|
+
}
|
|
3229
|
+
);
|
|
3230
|
+
|
|
3231
|
+
// src/decorators/sensor.decorator.ts
|
|
3232
|
+
var import_common6 = require("@nestjs/common");
|
|
3233
|
+
var SENSOR_METADATA_KEY = "axis:sensor";
|
|
3234
|
+
function Sensor(options) {
|
|
3235
|
+
return (0, import_common6.SetMetadata)(SENSOR_METADATA_KEY, options ?? true);
|
|
3236
|
+
}
|
|
3237
|
+
|
|
3238
|
+
// src/engine/index.ts
|
|
3239
|
+
var engine_exports = {};
|
|
3240
|
+
__export(engine_exports, {
|
|
3241
|
+
BAND: () => BAND,
|
|
3242
|
+
HandlerDiscoveryService: () => HandlerDiscoveryService,
|
|
3243
|
+
IntentRouter: () => IntentRouter,
|
|
3244
|
+
PRE_DECODE_BOUNDARY: () => PRE_DECODE_BOUNDARY,
|
|
3245
|
+
SensorDiscoveryService: () => SensorDiscoveryService,
|
|
3246
|
+
SensorRegistry: () => SensorRegistry,
|
|
3247
|
+
createObservation: () => createObservation,
|
|
3248
|
+
endStage: () => endStage,
|
|
3249
|
+
finalizeObservation: () => finalizeObservation,
|
|
3250
|
+
recordSensor: () => recordSensor,
|
|
3251
|
+
startStage: () => startStage
|
|
3252
|
+
});
|
|
3253
|
+
|
|
3254
|
+
// src/engine/axis-observation.ts
|
|
3255
|
+
var import_crypto4 = require("crypto");
|
|
3256
|
+
function createObservation(transport, ip) {
|
|
3257
|
+
return {
|
|
3258
|
+
id: (0, import_crypto4.randomBytes)(16).toString("hex"),
|
|
3259
|
+
startMs: Date.now(),
|
|
3260
|
+
transport,
|
|
3261
|
+
ip,
|
|
3262
|
+
stages: [],
|
|
3263
|
+
sensors: [],
|
|
3264
|
+
facts: {}
|
|
3265
|
+
};
|
|
3266
|
+
}
|
|
3267
|
+
function startStage(obs, name) {
|
|
3268
|
+
const stage = { name, status: "ok", startMs: Date.now() };
|
|
3269
|
+
obs.stages.push(stage);
|
|
3270
|
+
return stage;
|
|
3271
|
+
}
|
|
3272
|
+
function endStage(stage, status = "ok", reason, code) {
|
|
3273
|
+
stage.endMs = Date.now();
|
|
3274
|
+
stage.durationMs = stage.endMs - stage.startMs;
|
|
3275
|
+
stage.status = status;
|
|
3276
|
+
if (reason) stage.reason = reason;
|
|
3277
|
+
if (code) stage.code = code;
|
|
3278
|
+
}
|
|
3279
|
+
function recordSensor(obs, name, allowed, riskScore, durationMs, reasons, code) {
|
|
3280
|
+
obs.sensors.push({ name, allowed, riskScore, durationMs, reasons, code });
|
|
3281
|
+
}
|
|
3282
|
+
function finalizeObservation(obs, decision, statusCode, resultCode) {
|
|
3283
|
+
obs.endMs = Date.now();
|
|
3284
|
+
obs.durationMs = obs.endMs - obs.startMs;
|
|
3285
|
+
obs.decision = decision;
|
|
3286
|
+
obs.statusCode = statusCode;
|
|
3287
|
+
if (resultCode) obs.resultCode = resultCode;
|
|
3288
|
+
}
|
|
3289
|
+
|
|
3290
|
+
// src/engine/handler-discovery.service.ts
|
|
3291
|
+
var import_common7 = require("@nestjs/common");
|
|
3292
|
+
var HandlerDiscoveryService = class {
|
|
3293
|
+
constructor(discovery, scanner, router) {
|
|
3294
|
+
this.discovery = discovery;
|
|
3295
|
+
this.scanner = scanner;
|
|
3296
|
+
this.router = router;
|
|
3297
|
+
this.logger = new import_common7.Logger(HandlerDiscoveryService.name);
|
|
3298
|
+
}
|
|
3299
|
+
onModuleInit() {
|
|
3300
|
+
const providers = this.discovery.getProviders();
|
|
3301
|
+
let totalIntents = 0;
|
|
3302
|
+
for (const wrapper of providers) {
|
|
3303
|
+
const { instance, metatype } = wrapper;
|
|
3304
|
+
if (!instance || !metatype) continue;
|
|
3305
|
+
const handlerMeta = Reflect.getMetadata(HANDLER_METADATA_KEY, metatype);
|
|
3306
|
+
if (!handlerMeta) continue;
|
|
3307
|
+
const handlerName = handlerMeta.intent || metatype.name;
|
|
3308
|
+
const proto = Object.getPrototypeOf(instance);
|
|
3309
|
+
const methods = this.scanner.getAllMethodNames(proto);
|
|
3310
|
+
let registered = 0;
|
|
3311
|
+
for (const methodName of methods) {
|
|
3312
|
+
const meta = Reflect.getMetadata(
|
|
3313
|
+
INTENT_METADATA_KEY,
|
|
3314
|
+
proto,
|
|
3315
|
+
methodName
|
|
3316
|
+
);
|
|
3317
|
+
if (!meta?.intent) continue;
|
|
3318
|
+
if (!this.router.has(meta.intent)) {
|
|
3319
|
+
this.router.register(
|
|
3320
|
+
meta.intent,
|
|
3321
|
+
instance[methodName].bind(instance)
|
|
3322
|
+
);
|
|
3323
|
+
registered++;
|
|
3324
|
+
totalIntents++;
|
|
3325
|
+
}
|
|
3326
|
+
this.router.registerIntentMeta(meta.intent, proto, methodName);
|
|
3327
|
+
}
|
|
3328
|
+
if (registered > 0) {
|
|
3329
|
+
this.logger.log(
|
|
3330
|
+
`Auto-registered ${registered} intents from ${handlerName}`
|
|
3331
|
+
);
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
this.logger.log(
|
|
3335
|
+
`Handler discovery complete: ${totalIntents} intents auto-registered`
|
|
3336
|
+
);
|
|
3337
|
+
}
|
|
3338
|
+
};
|
|
3339
|
+
HandlerDiscoveryService = __decorateClass([
|
|
3340
|
+
(0, import_common7.Injectable)()
|
|
3341
|
+
], HandlerDiscoveryService);
|
|
3342
|
+
|
|
3343
|
+
// src/engine/sensor-bands.ts
|
|
3344
|
+
var BAND = {
|
|
3345
|
+
/** Pre-decode: raw byte validation, geo, budget, magic */
|
|
3346
|
+
WIRE: 0,
|
|
3347
|
+
/** Post-decode: identity resolution, capsule, proof */
|
|
3348
|
+
IDENTITY: 40,
|
|
3349
|
+
/** Post-decode: authorization, signature, rate limiting */
|
|
3350
|
+
POLICY: 90,
|
|
3351
|
+
/** Post-decode: content validation, TLV, schema, files */
|
|
3352
|
+
CONTENT: 140,
|
|
3353
|
+
/** Post-decode: business logic sensors, streams, WS */
|
|
3354
|
+
BUSINESS: 200,
|
|
3355
|
+
/** Post-decode: audit, logging (always last) */
|
|
3356
|
+
AUDIT: 900
|
|
3357
|
+
};
|
|
3358
|
+
var PRE_DECODE_BOUNDARY = 40;
|
|
3359
|
+
|
|
3360
|
+
// src/engine/sensor-discovery.service.ts
|
|
3361
|
+
var import_common8 = require("@nestjs/common");
|
|
3362
|
+
var SensorDiscoveryService = class {
|
|
3363
|
+
constructor(discovery, reflector, registry) {
|
|
3364
|
+
this.discovery = discovery;
|
|
3365
|
+
this.reflector = reflector;
|
|
3366
|
+
this.registry = registry;
|
|
3367
|
+
this.logger = new import_common8.Logger(SensorDiscoveryService.name);
|
|
3368
|
+
}
|
|
3369
|
+
onApplicationBootstrap() {
|
|
3370
|
+
const providers = this.discovery.getProviders();
|
|
3371
|
+
let count = 0;
|
|
3372
|
+
for (const wrapper of providers) {
|
|
3373
|
+
const { instance } = wrapper;
|
|
3374
|
+
if (!instance || !instance.constructor) continue;
|
|
3375
|
+
const meta = this.reflector.get(
|
|
3376
|
+
SENSOR_METADATA_KEY,
|
|
3377
|
+
instance.constructor
|
|
3378
|
+
);
|
|
3379
|
+
if (!meta) continue;
|
|
3380
|
+
const sensor = instance;
|
|
3381
|
+
if (!sensor.name || sensor.order === void 0) {
|
|
3382
|
+
this.logger.warn(
|
|
3383
|
+
`@Sensor() on ${instance.constructor.name} missing name or order \u2014 skipped`
|
|
3384
|
+
);
|
|
3385
|
+
continue;
|
|
3386
|
+
}
|
|
3387
|
+
if (!sensor.phase) {
|
|
3388
|
+
const decoratorPhase = meta !== true ? meta.phase : void 0;
|
|
3389
|
+
sensor.phase = decoratorPhase ?? (sensor.order < PRE_DECODE_BOUNDARY ? "PRE_DECODE" : "POST_DECODE");
|
|
3390
|
+
}
|
|
3391
|
+
this.registry.register(sensor);
|
|
3392
|
+
count++;
|
|
3393
|
+
}
|
|
3394
|
+
this.logger.log(`Auto-registered ${count} sensors via @Sensor()`);
|
|
3395
|
+
}
|
|
3396
|
+
};
|
|
3397
|
+
SensorDiscoveryService = __decorateClass([
|
|
3398
|
+
(0, import_common8.Injectable)()
|
|
3399
|
+
], SensorDiscoveryService);
|
|
3400
|
+
|
|
3401
|
+
// src/engine/registry/sensor.registry.ts
|
|
3402
|
+
var import_common9 = require("@nestjs/common");
|
|
3403
|
+
var SensorRegistry = class {
|
|
3404
|
+
constructor(configService) {
|
|
3405
|
+
this.configService = configService;
|
|
3406
|
+
this.sensors = [];
|
|
3407
|
+
this.logger = new import_common9.Logger(SensorRegistry.name);
|
|
3408
|
+
}
|
|
3409
|
+
/**
|
|
3410
|
+
* Registers a new sensor in the registry.
|
|
3411
|
+
*
|
|
3412
|
+
* Validates that:
|
|
3413
|
+
* - AxisSensor has a unique name
|
|
3414
|
+
* - AxisSensor has an order field
|
|
3415
|
+
* - Pre-decode sensors have order < 40
|
|
3416
|
+
* - Post-decode sensors have order >= 40
|
|
3417
|
+
*
|
|
3418
|
+
* @param {AxisSensor} sensor - The sensor instance to register
|
|
3419
|
+
* @throws Error if validation fails
|
|
3420
|
+
*/
|
|
3421
|
+
register(sensor) {
|
|
3422
|
+
if (!sensor.name) {
|
|
3423
|
+
throw new Error("AxisSensor must have a name");
|
|
3424
|
+
}
|
|
3425
|
+
const enabledSensorsStr = this.configService.get("ENABLED_SENSORS");
|
|
3426
|
+
const disabledSensorsStr = this.configService.get("DISABLED_SENSORS");
|
|
3427
|
+
const enabledSensors = enabledSensorsStr ? enabledSensorsStr.split(",").map((s) => s.trim()) : null;
|
|
3428
|
+
const disabledSensors = disabledSensorsStr ? disabledSensorsStr.split(",").map((s) => s.trim()) : [];
|
|
3429
|
+
if (enabledSensors && !enabledSensors.includes(sensor.name)) {
|
|
3430
|
+
this.logger.log(`Skipping disabled sensor (not in ENABLED_SENSORS): ${sensor.name}`);
|
|
3431
|
+
return;
|
|
3432
|
+
}
|
|
3433
|
+
if (disabledSensors.includes(sensor.name)) {
|
|
3434
|
+
this.logger.log(`Skipping disabled sensor (in DISABLED_SENSORS): ${sensor.name}`);
|
|
3435
|
+
return;
|
|
3436
|
+
}
|
|
3437
|
+
if (sensor.order === void 0) {
|
|
3438
|
+
throw new Error(`AxisSensor "${sensor.name}" must have an order field`);
|
|
3439
|
+
}
|
|
3440
|
+
const isPreDecodeSensor = this.isPreDecodeSensor(sensor);
|
|
3441
|
+
const isPostDecodeSensor = this.isPostDecodeSensor(sensor);
|
|
3442
|
+
if (isPreDecodeSensor && sensor.order >= 40) {
|
|
3443
|
+
this.logger.warn(
|
|
3444
|
+
`AxisSensor "${sensor.name}" is marked as PRE_DECODE but has order ${sensor.order} (should be < 40)`
|
|
3445
|
+
);
|
|
3446
|
+
}
|
|
3447
|
+
if (isPostDecodeSensor && sensor.order < 40) {
|
|
3448
|
+
this.logger.warn(
|
|
3449
|
+
`AxisSensor "${sensor.name}" is marked as POST_DECODE but has order ${sensor.order} (should be >= 40)`
|
|
3450
|
+
);
|
|
3451
|
+
}
|
|
3452
|
+
this.sensors.push(sensor);
|
|
3453
|
+
const phaseLabel = typeof sensor.phase === "string" ? sensor.phase : sensor.phase?.phase || "UNKNOWN";
|
|
3454
|
+
this.logger.debug(
|
|
3455
|
+
`Registered sensor: ${sensor.name} (order: ${sensor.order}, phase: ${phaseLabel})`
|
|
3456
|
+
);
|
|
3457
|
+
}
|
|
3458
|
+
/**
|
|
3459
|
+
* Returns all registered sensors, sorted by their execution order.
|
|
3460
|
+
*
|
|
3461
|
+
* @returns {AxisSensor[]} A sorted array of sensors
|
|
3462
|
+
*/
|
|
3463
|
+
list() {
|
|
3464
|
+
return [...this.sensors].sort(
|
|
3465
|
+
(a, b) => (a.order ?? 999) - (b.order ?? 999)
|
|
3466
|
+
);
|
|
3467
|
+
}
|
|
3468
|
+
/**
|
|
3469
|
+
* Returns only pre-decode sensors (order < 40).
|
|
3470
|
+
* These sensors run in middleware on raw bytes before frame decoding.
|
|
3471
|
+
*
|
|
3472
|
+
* @returns {AxisPreSensor[]} Pre-decode sensors sorted by order
|
|
3473
|
+
*/
|
|
3474
|
+
getPreDecodeSensors() {
|
|
3475
|
+
return this.list().filter((s) => (s.order ?? 999) < 40);
|
|
3476
|
+
}
|
|
3477
|
+
/**
|
|
3478
|
+
* Returns only post-decode sensors (order >= 40).
|
|
3479
|
+
* These sensors run in the controller on fully decoded frames.
|
|
3480
|
+
*
|
|
3481
|
+
* @returns {AxisPostSensor[]} Post-decode sensors sorted by order
|
|
3482
|
+
*/
|
|
3483
|
+
getPostDecodeSensors() {
|
|
3484
|
+
return this.list().filter(
|
|
3485
|
+
(s) => (s.order ?? 999) >= 40
|
|
3486
|
+
);
|
|
3487
|
+
}
|
|
3488
|
+
/**
|
|
3489
|
+
* Helper: Check if a sensor is a pre-decode sensor.
|
|
3490
|
+
*
|
|
3491
|
+
* @private
|
|
3492
|
+
* @param {AxisSensor} sensor - The sensor to check
|
|
3493
|
+
* @returns {boolean} True if sensor is pre-decode
|
|
3494
|
+
*/
|
|
3495
|
+
isPreDecodeSensor(sensor) {
|
|
3496
|
+
const phase = typeof sensor.phase === "string" ? sensor.phase : sensor.phase?.phase;
|
|
3497
|
+
return phase === "PRE_DECODE" || (sensor.order ?? 999) < 40;
|
|
3498
|
+
}
|
|
3499
|
+
/**
|
|
3500
|
+
* Helper: Check if a sensor is a post-decode sensor.
|
|
3501
|
+
*
|
|
3502
|
+
* @private
|
|
3503
|
+
* @param {AxisSensor} sensor - The sensor to check
|
|
3504
|
+
* @returns {boolean} True if sensor is post-decode
|
|
3505
|
+
*/
|
|
3506
|
+
isPostDecodeSensor(sensor) {
|
|
3507
|
+
const phase = typeof sensor.phase === "string" ? sensor.phase : sensor.phase?.phase;
|
|
3508
|
+
return phase === "POST_DECODE" || (sensor.order ?? 999) >= 40;
|
|
3509
|
+
}
|
|
3510
|
+
/**
|
|
3511
|
+
* Returns sensor count by phase.
|
|
3512
|
+
* Useful for diagnostics and monitoring.
|
|
3513
|
+
*
|
|
3514
|
+
* @returns {{preDecodeCount: number, postDecodeCount: number}}
|
|
3515
|
+
*/
|
|
3516
|
+
getSensorCountByPhase() {
|
|
3517
|
+
return {
|
|
3518
|
+
preDecodeCount: this.getPreDecodeSensors().length,
|
|
3519
|
+
postDecodeCount: this.getPostDecodeSensors().length
|
|
3520
|
+
};
|
|
3521
|
+
}
|
|
3522
|
+
/**
|
|
3523
|
+
* Clears all registered sensors.
|
|
3524
|
+
* Useful for testing.
|
|
3525
|
+
*
|
|
3526
|
+
* @internal
|
|
3527
|
+
*/
|
|
3528
|
+
clear() {
|
|
3529
|
+
this.sensors = [];
|
|
3530
|
+
}
|
|
3531
|
+
};
|
|
3532
|
+
SensorRegistry = __decorateClass([
|
|
3533
|
+
(0, import_common9.Injectable)()
|
|
3534
|
+
], SensorRegistry);
|
|
3535
|
+
|
|
3536
|
+
// src/loom/index.ts
|
|
3537
|
+
var loom_exports = {};
|
|
3538
|
+
__export(loom_exports, {
|
|
3539
|
+
PROOF_LOOM: () => import_axis_protocol2.PROOF_LOOM,
|
|
3540
|
+
TLV_PRESENCE_ID: () => import_axis_protocol2.TLV_LOOM_PRESENCE_ID,
|
|
3541
|
+
TLV_THREAD_HASH: () => import_axis_protocol2.TLV_LOOM_THREAD_HASH,
|
|
3542
|
+
TLV_WRIT: () => import_axis_protocol2.TLV_LOOM_WRIT,
|
|
3543
|
+
canonicalizeGrant: () => canonicalizeGrant,
|
|
3544
|
+
canonicalizeWrit: () => canonicalizeWrit,
|
|
3545
|
+
deriveAnchorReflection: () => deriveAnchorReflection
|
|
3546
|
+
});
|
|
3547
|
+
|
|
3548
|
+
// src/loom/loom.types.ts
|
|
3549
|
+
function deriveAnchorReflection(softid, context = "openlogs", scope = "loom") {
|
|
3550
|
+
return `ar:${context}:${scope}:${softid}`;
|
|
3551
|
+
}
|
|
3552
|
+
function canonicalizeWrit(writ) {
|
|
3553
|
+
const ordered = {
|
|
3554
|
+
head: { tid: writ.head.tid, seq: writ.head.seq },
|
|
3555
|
+
body: {
|
|
3556
|
+
who: writ.body.who,
|
|
3557
|
+
act: writ.body.act,
|
|
3558
|
+
res: writ.body.res,
|
|
3559
|
+
law: writ.body.law
|
|
3560
|
+
},
|
|
3561
|
+
meta: { iat: writ.meta.iat, exp: writ.meta.exp, prev: writ.meta.prev }
|
|
3562
|
+
};
|
|
3563
|
+
return JSON.stringify(ordered);
|
|
3564
|
+
}
|
|
3565
|
+
function canonicalizeGrant(grant) {
|
|
3566
|
+
const ordered = {
|
|
3567
|
+
grant_id: grant.grant_id,
|
|
3568
|
+
issuer: grant.issuer,
|
|
3569
|
+
subject: grant.subject,
|
|
3570
|
+
grant_type: grant.grant_type,
|
|
3571
|
+
caps: grant.caps,
|
|
3572
|
+
meta: grant.meta
|
|
3573
|
+
};
|
|
3574
|
+
return JSON.stringify(ordered);
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3577
|
+
// src/schemas/index.ts
|
|
3578
|
+
var schemas_exports = {};
|
|
3579
|
+
__export(schemas_exports, {
|
|
3580
|
+
AccessProfileZ: () => AccessProfileZ,
|
|
3581
|
+
AxisContextZ: () => AxisContextZ,
|
|
3582
|
+
AxisErrorZ: () => AxisErrorZ,
|
|
3583
|
+
BodyBudgetInputZ: () => BodyBudgetInputZ,
|
|
3584
|
+
BodyBudgetPolicyZ: () => BodyBudgetPolicyZ,
|
|
3585
|
+
BodyProfile: () => BodyProfile2,
|
|
3586
|
+
BodyProfileValidator: () => BodyProfileValidator,
|
|
3587
|
+
BodyProfileZ: () => BodyProfileZ,
|
|
3588
|
+
CapsuleClaimsZ: () => CapsuleClaimsZ,
|
|
3589
|
+
CapsuleValidationResultZ: () => CapsuleValidationResultZ,
|
|
3590
|
+
CapsuleVerifyResultZ: () => CapsuleVerifyResultZ,
|
|
3591
|
+
CapsuleVerifySensorInputZ: () => CapsuleVerifySensorInputZ,
|
|
3592
|
+
CapsuleZ: () => CapsuleZ,
|
|
3593
|
+
ChunkHashInputZ: () => ChunkHashInputZ,
|
|
3594
|
+
CountryBlockDecisionZ: () => CountryBlockDecisionZ,
|
|
3595
|
+
CountryBlockSensorInputZ: () => CountryBlockSensorInputZ,
|
|
3596
|
+
EntropySensorInputZ: () => EntropySensorInputZ,
|
|
3597
|
+
ExecutionMetricsZ: () => ExecutionMetricsZ,
|
|
3598
|
+
IPReputationInputZ: () => IPReputationInputZ,
|
|
3599
|
+
IPReputationZ: () => IPReputationZ,
|
|
3600
|
+
IntentPolicyDecisionZ: () => IntentPolicyDecisionZ,
|
|
3601
|
+
IntentPolicySensorInputZ: () => IntentPolicySensorInputZ,
|
|
3602
|
+
IntentPolicyZ: () => IntentPolicyZ,
|
|
3603
|
+
IntentSchemaZ: () => IntentSchemaZ,
|
|
3604
|
+
PassportZ: () => PassportZ,
|
|
3605
|
+
ProofKindZ: () => ProofKindZ,
|
|
3606
|
+
ProofPresenceInputZ: () => ProofPresenceInputZ,
|
|
3607
|
+
ProofType: () => ProofType2,
|
|
3608
|
+
ProtocolStrictInputZ: () => ProtocolStrictInputZ,
|
|
3609
|
+
RateLimitConfigZ: () => RateLimitConfigZ,
|
|
3610
|
+
RateLimitInputZ: () => RateLimitInputZ,
|
|
3611
|
+
RateLimitProfileZ: () => RateLimitProfileZ,
|
|
3612
|
+
ScanBurstDecisionZ: () => ScanBurstDecisionZ,
|
|
3613
|
+
ScanBurstSensorInputZ: () => ScanBurstSensorInputZ,
|
|
3614
|
+
SchemaFieldKindZ: () => SchemaFieldKindZ,
|
|
3615
|
+
SchemaFieldZ: () => SchemaFieldZ,
|
|
3616
|
+
ScopeZ: () => ScopeZ,
|
|
3617
|
+
SensitivityLevelZ: () => SensitivityLevelZ,
|
|
3618
|
+
SensorChainInputZ: () => SensorChainInputZ,
|
|
3619
|
+
SensorDecisionWithMetadataZ: () => SensorDecisionWithMetadataZ,
|
|
3620
|
+
SensorDecisionZ: () => SensorDecisionZ,
|
|
3621
|
+
SensorResultZ: () => SensorResultZ,
|
|
3622
|
+
UploadSessionZ: () => UploadSessionZ,
|
|
3623
|
+
UploadStatusZ: () => UploadStatusZ,
|
|
3624
|
+
WsHandshakeDecisionZ: () => WsHandshakeDecisionZ,
|
|
3625
|
+
WsHandshakeInputZ: () => WsHandshakeInputZ
|
|
3626
|
+
});
|
|
3627
|
+
|
|
3628
|
+
// src/schemas/axis-schemas.ts
|
|
3629
|
+
var z2 = __toESM(require("zod"));
|
|
3630
|
+
var SensorDecisionZ = z2.union([
|
|
3631
|
+
z2.object({ action: z2.literal("ALLOW"), meta: z2.any().optional() }),
|
|
3632
|
+
z2.object({
|
|
3633
|
+
action: z2.literal("DENY"),
|
|
3634
|
+
code: z2.string(),
|
|
3635
|
+
reason: z2.string().optional(),
|
|
3636
|
+
meta: z2.any().optional()
|
|
3637
|
+
})
|
|
3638
|
+
]);
|
|
3639
|
+
var SensorDecisionWithMetadataZ = z2.union([
|
|
3640
|
+
z2.object({ action: z2.literal("ALLOW"), meta: z2.any().optional() }),
|
|
3641
|
+
z2.object({
|
|
3642
|
+
action: z2.literal("DENY"),
|
|
3643
|
+
code: z2.string(),
|
|
3644
|
+
reason: z2.string().optional(),
|
|
3645
|
+
retryAfterMs: z2.number().int().positive().optional(),
|
|
3646
|
+
meta: z2.any().optional()
|
|
3647
|
+
})
|
|
3648
|
+
]);
|
|
3649
|
+
var CountryBlockSensorInputZ = z2.object({
|
|
3650
|
+
ip: z2.string().min(1),
|
|
3651
|
+
country: z2.string().length(2).toUpperCase().optional()
|
|
3652
|
+
});
|
|
3653
|
+
var CountryBlockDecisionZ = SensorDecisionZ;
|
|
3654
|
+
var ScanBurstSensorInputZ = z2.object({
|
|
3655
|
+
ip: z2.string().min(1),
|
|
3656
|
+
isFailure: z2.boolean().optional()
|
|
3657
|
+
});
|
|
3658
|
+
var ScanBurstDecisionZ = SensorDecisionWithMetadataZ;
|
|
3659
|
+
var ProofKindZ = z2.enum([
|
|
3660
|
+
"NONE",
|
|
3661
|
+
"CAPSULE",
|
|
3662
|
+
"PASSPORT",
|
|
3663
|
+
"MTLS",
|
|
3664
|
+
"JWT"
|
|
3665
|
+
]);
|
|
3666
|
+
var AccessProfileZ = z2.enum(["PUBLIC", "PARTNER", "INTERNAL", "NODE"]);
|
|
3667
|
+
var ProofPresenceInputZ = z2.object({
|
|
3668
|
+
profile: AccessProfileZ,
|
|
3669
|
+
visibility: z2.enum(["PUBLIC", "GUARDED"]),
|
|
3670
|
+
requiredProof: z2.array(ProofKindZ).min(1),
|
|
3671
|
+
hasCapsule: z2.boolean(),
|
|
3672
|
+
hasPassportSignature: z2.boolean(),
|
|
3673
|
+
intent: z2.string().min(1)
|
|
3674
|
+
});
|
|
3675
|
+
var SensitivityLevelZ = z2.enum(["LOW", "MEDIUM", "HIGH", "CRITICAL"]);
|
|
3676
|
+
var IntentPolicyZ = z2.object({
|
|
3677
|
+
intent: z2.string().min(1),
|
|
3678
|
+
sensitivity: SensitivityLevelZ,
|
|
3679
|
+
maxFrameBytes: z2.number().int().positive(),
|
|
3680
|
+
maxHeaderBytes: z2.number().int().positive(),
|
|
3681
|
+
maxBodyBytes: z2.number().int().positive(),
|
|
3682
|
+
maxSigBytes: z2.number().int().positive().optional(),
|
|
3683
|
+
rateLimitPerMinute: z2.number().int().positive().optional(),
|
|
3684
|
+
rateLimitPerHour: z2.number().int().positive().optional(),
|
|
3685
|
+
requiresSignature: z2.boolean(),
|
|
3686
|
+
requiresCapsule: z2.boolean(),
|
|
3687
|
+
timeoutMs: z2.number().int().positive()
|
|
3688
|
+
});
|
|
3689
|
+
var IntentPolicySensorInputZ = z2.object({
|
|
3690
|
+
frame: AxisFrameZ,
|
|
3691
|
+
intent: z2.string().min(1),
|
|
3692
|
+
rawFrameSize: z2.number().int().positive()
|
|
3693
|
+
});
|
|
3694
|
+
var IntentPolicyDecisionZ = z2.union([
|
|
3695
|
+
z2.object({
|
|
3696
|
+
action: z2.literal("ALLOW"),
|
|
3697
|
+
policy: IntentPolicyZ
|
|
3698
|
+
}),
|
|
3699
|
+
z2.object({
|
|
3700
|
+
action: z2.literal("DENY"),
|
|
3701
|
+
reason: z2.string()
|
|
3702
|
+
})
|
|
3703
|
+
]);
|
|
3704
|
+
var CapsuleClaimsZ = z2.object({
|
|
3705
|
+
capsuleId: z2.string().min(8),
|
|
3706
|
+
allowIntents: z2.array(z2.string()).min(1),
|
|
3707
|
+
limits: z2.object({
|
|
3708
|
+
maxBodyBytes: z2.number().int().positive().optional()
|
|
3709
|
+
}).optional(),
|
|
3710
|
+
scopes: z2.record(z2.string(), z2.any()).optional()
|
|
3711
|
+
});
|
|
3712
|
+
var CapsuleZ = z2.object({
|
|
3713
|
+
id: z2.string(),
|
|
3714
|
+
claims: CapsuleClaimsZ,
|
|
3715
|
+
issuedAt: z2.number().int(),
|
|
3716
|
+
expiresAt: z2.number().int(),
|
|
3717
|
+
tier: z2.enum(["FREE", "STANDARD", "PREMIUM"])
|
|
3718
|
+
});
|
|
3719
|
+
var CapsuleValidationResultZ = z2.object({
|
|
3720
|
+
valid: z2.boolean(),
|
|
3721
|
+
capsule: CapsuleZ.optional(),
|
|
3722
|
+
reason: z2.string().optional(),
|
|
3723
|
+
requiresStepUp: z2.boolean().optional()
|
|
3724
|
+
});
|
|
3725
|
+
var CapsuleVerifySensorInputZ = z2.object({
|
|
3726
|
+
headers: z2.map(
|
|
3727
|
+
z2.number(),
|
|
3728
|
+
z2.custom((v) => v instanceof Uint8Array)
|
|
3729
|
+
),
|
|
3730
|
+
intent: z2.string().min(1),
|
|
3731
|
+
ctx: z2.any()
|
|
3732
|
+
// AxisContext - avoid circular dependency
|
|
3733
|
+
});
|
|
3734
|
+
var CapsuleVerifyResultZ = z2.object({
|
|
3735
|
+
ok: z2.literal(true),
|
|
3736
|
+
capsule: CapsuleZ
|
|
3737
|
+
});
|
|
3738
|
+
var RateLimitProfileZ = z2.enum([
|
|
3739
|
+
"PUBLIC",
|
|
3740
|
+
"PARTNER",
|
|
3741
|
+
"INTERNAL",
|
|
3742
|
+
"NODE"
|
|
3743
|
+
]);
|
|
3744
|
+
var RateLimitInputZ = z2.object({
|
|
3745
|
+
ip: z2.string().min(1),
|
|
3746
|
+
userAgent: z2.string().optional(),
|
|
3747
|
+
actorId: z2.string().optional(),
|
|
3748
|
+
capsuleId: z2.string().optional(),
|
|
3749
|
+
intent: z2.string().min(1),
|
|
3750
|
+
profile: RateLimitProfileZ
|
|
3751
|
+
});
|
|
3752
|
+
var RateLimitConfigZ = z2.object({
|
|
3753
|
+
windowSec: z2.number().int().positive(),
|
|
3754
|
+
max: z2.number().int().positive(),
|
|
3755
|
+
key: z2.enum(["ip_fingerprint", "actor_capsule"])
|
|
3756
|
+
});
|
|
3757
|
+
var SensorResultZ = z2.object({
|
|
3758
|
+
ok: z2.literal(true)
|
|
3759
|
+
});
|
|
3760
|
+
var PassportZ = z2.object({
|
|
3761
|
+
id: z2.string(),
|
|
3762
|
+
public_key: z2.custom((v) => Buffer.isBuffer(v)),
|
|
3763
|
+
status: z2.enum(["ACTIVE", "REVOKED", "EXPIRED", "PENDING"]),
|
|
3764
|
+
issuedAt: z2.number().int(),
|
|
3765
|
+
expiresAt: z2.number().int().optional()
|
|
3766
|
+
});
|
|
3767
|
+
var ExecutionMetricsZ = z2.object({
|
|
3768
|
+
dbWrites: z2.number().int(),
|
|
3769
|
+
dbReads: z2.number().int(),
|
|
3770
|
+
externalCalls: z2.number().int(),
|
|
3771
|
+
elapsedMs: z2.number().int().optional()
|
|
3772
|
+
});
|
|
3773
|
+
var SensorChainInputZ = z2.object({
|
|
3774
|
+
ip: z2.string().min(1),
|
|
3775
|
+
path: z2.string().min(1),
|
|
3776
|
+
contentLength: z2.number().int().nonnegative(),
|
|
3777
|
+
peek: z2.instanceof(Uint8Array),
|
|
3778
|
+
country: z2.string().optional()
|
|
3779
|
+
});
|
|
3780
|
+
var EntropySensorInputZ = z2.object({
|
|
3781
|
+
pid: z2.custom((v) => Buffer.isBuffer(v)).optional(),
|
|
3782
|
+
nonce: z2.custom((v) => Buffer.isBuffer(v)).optional(),
|
|
3783
|
+
ip: z2.string().min(1)
|
|
3784
|
+
});
|
|
3785
|
+
var ProtocolStrictInputZ = z2.object({
|
|
3786
|
+
rawBytes: z2.union([z2.custom((v) => Buffer.isBuffer(v)), z2.instanceof(Uint8Array)]).optional(),
|
|
3787
|
+
ip: z2.string().min(1),
|
|
3788
|
+
path: z2.string().min(1),
|
|
3789
|
+
contentLength: z2.number().int().nonnegative(),
|
|
3790
|
+
peek: z2.instanceof(Uint8Array),
|
|
3791
|
+
country: z2.string().optional(),
|
|
3792
|
+
contentType: z2.string().optional()
|
|
3793
|
+
});
|
|
3794
|
+
var SchemaFieldKindZ = z2.enum([
|
|
3795
|
+
"utf8",
|
|
3796
|
+
"u64",
|
|
3797
|
+
"bytes",
|
|
3798
|
+
"bytes16",
|
|
3799
|
+
"bool",
|
|
3800
|
+
"obj",
|
|
3801
|
+
"arr"
|
|
3802
|
+
]);
|
|
3803
|
+
var ScopeZ = z2.enum(["header", "body"]);
|
|
3804
|
+
var SchemaFieldZ = z2.object({
|
|
3805
|
+
name: z2.string().min(1),
|
|
3806
|
+
tlv: z2.number().int().positive(),
|
|
3807
|
+
kind: SchemaFieldKindZ,
|
|
3808
|
+
required: z2.boolean().optional(),
|
|
3809
|
+
maxLen: z2.number().int().positive().optional(),
|
|
3810
|
+
max: z2.string().optional(),
|
|
3811
|
+
scope: ScopeZ.optional()
|
|
3812
|
+
});
|
|
3813
|
+
var BodyProfileZ = z2.enum(["TLV_MAP", "RAW", "TLV_OBJ", "TLV_ARR"]);
|
|
3814
|
+
var IntentSchemaZ = z2.object({
|
|
3815
|
+
intent: z2.string().min(1),
|
|
3816
|
+
version: z2.number().int().positive(),
|
|
3817
|
+
bodyProfile: BodyProfileZ,
|
|
3818
|
+
fields: z2.array(SchemaFieldZ).min(1)
|
|
3819
|
+
});
|
|
3820
|
+
var WsHandshakeInputZ = z2.object({
|
|
3821
|
+
clientId: z2.string().min(1),
|
|
3822
|
+
isWs: z2.boolean(),
|
|
3823
|
+
ip: z2.string().min(1)
|
|
3824
|
+
});
|
|
3825
|
+
var WsHandshakeDecisionZ = z2.union([
|
|
3826
|
+
z2.object({ action: z2.literal("ALLOW") }),
|
|
3827
|
+
z2.object({ action: z2.literal("DENY"), code: z2.string() })
|
|
3828
|
+
]);
|
|
3829
|
+
var IPReputationInputZ = z2.object({
|
|
3830
|
+
ip: z2.string().min(1)
|
|
3831
|
+
});
|
|
3832
|
+
var IPReputationZ = z2.object({
|
|
3833
|
+
score: z2.number().min(-100).max(100),
|
|
3834
|
+
lastUpdated: z2.number().int(),
|
|
3835
|
+
totalRequests: z2.number().int().nonnegative(),
|
|
3836
|
+
failedRequests: z2.number().int().nonnegative(),
|
|
3837
|
+
blockedRequests: z2.number().int().nonnegative(),
|
|
3838
|
+
tags: z2.array(z2.string())
|
|
3839
|
+
});
|
|
3840
|
+
var UploadStatusZ = z2.enum([
|
|
3841
|
+
"INIT",
|
|
3842
|
+
"UPLOADING",
|
|
3843
|
+
"FINALIZING",
|
|
3844
|
+
"DONE",
|
|
3845
|
+
"ABORTED"
|
|
3846
|
+
]);
|
|
3847
|
+
var UploadSessionZ = z2.object({
|
|
3848
|
+
uploadIdHex: z2.string().min(1),
|
|
3849
|
+
fileName: z2.string().min(1),
|
|
3850
|
+
totalSize: z2.number().int().positive(),
|
|
3851
|
+
chunkSize: z2.number().int().positive(),
|
|
3852
|
+
totalChunks: z2.number().int().positive(),
|
|
3853
|
+
receivedCount: z2.number().int().nonnegative(),
|
|
3854
|
+
status: UploadStatusZ
|
|
3855
|
+
});
|
|
3856
|
+
var BodyBudgetInputZ = z2.object({
|
|
3857
|
+
intent: z2.string().min(1),
|
|
3858
|
+
headerLen: z2.number().int().nonnegative(),
|
|
3859
|
+
bodyLen: z2.number().int().nonnegative()
|
|
3860
|
+
});
|
|
3861
|
+
var BodyBudgetPolicyZ = z2.object({
|
|
3862
|
+
maxHeaderBytes: z2.number().int().positive(),
|
|
3863
|
+
maxBodyBytes: z2.number().int().positive()
|
|
3864
|
+
});
|
|
3865
|
+
var ChunkHashInputZ = z2.object({
|
|
3866
|
+
headerTLVs: z2.any(),
|
|
3867
|
+
// Map<number, Uint8Array> - flexible validation for compatibility
|
|
3868
|
+
bodyBytes: z2.any(),
|
|
3869
|
+
// Uint8Array - flexible validation for compatibility
|
|
3870
|
+
intent: z2.string().min(1)
|
|
3871
|
+
});
|
|
3872
|
+
var ProofType2 = /* @__PURE__ */ ((ProofType3) => {
|
|
3873
|
+
ProofType3[ProofType3["CAPSULE"] = 1] = "CAPSULE";
|
|
3874
|
+
ProofType3[ProofType3["JWT"] = 2] = "JWT";
|
|
3875
|
+
ProofType3[ProofType3["MTLS_ID"] = 3] = "MTLS_ID";
|
|
3876
|
+
ProofType3[ProofType3["DEVICE_SE"] = 4] = "DEVICE_SE";
|
|
3877
|
+
ProofType3[ProofType3["WITNESS_SIG"] = 5] = "WITNESS_SIG";
|
|
3878
|
+
return ProofType3;
|
|
3879
|
+
})(ProofType2 || {});
|
|
3880
|
+
var AxisContextZ = z2.object({
|
|
3881
|
+
pid: z2.custom((v) => Buffer.isBuffer(v)),
|
|
3882
|
+
// Process ID
|
|
3883
|
+
ts: z2.bigint(),
|
|
3884
|
+
// Timestamp
|
|
3885
|
+
intent: z2.string().min(1),
|
|
3886
|
+
actorId: z2.custom((v) => Buffer.isBuffer(v)),
|
|
3887
|
+
proofType: z2.enum(ProofType2),
|
|
3888
|
+
proofRef: z2.custom((v) => Buffer.isBuffer(v)),
|
|
3889
|
+
nonce: z2.custom((v) => Buffer.isBuffer(v)),
|
|
3890
|
+
ip: z2.string().min(1),
|
|
3891
|
+
nodeCertHash: z2.string().optional(),
|
|
3892
|
+
capsule: CapsuleZ.optional(),
|
|
3893
|
+
passport: PassportZ.optional(),
|
|
3894
|
+
meter: z2.any().optional()
|
|
3895
|
+
// ExecutionMeter instance - any to avoid circular dependency and allow class instance
|
|
3896
|
+
});
|
|
3897
|
+
var AxisErrorZ = z2.object({
|
|
3898
|
+
code: z2.string(),
|
|
3899
|
+
message: z2.string(),
|
|
3900
|
+
httpStatus: z2.number().int()
|
|
3901
|
+
});
|
|
3902
|
+
|
|
3903
|
+
// src/schemas/body-profile.validator.ts
|
|
3904
|
+
var import_common10 = require("@nestjs/common");
|
|
3905
|
+
var BodyProfile2 = /* @__PURE__ */ ((BodyProfile3) => {
|
|
3906
|
+
BodyProfile3[BodyProfile3["RAW"] = 0] = "RAW";
|
|
3907
|
+
BodyProfile3[BodyProfile3["TLV_MAP"] = 1] = "TLV_MAP";
|
|
3908
|
+
BodyProfile3[BodyProfile3["OBJ"] = 2] = "OBJ";
|
|
3909
|
+
BodyProfile3[BodyProfile3["ARR"] = 3] = "ARR";
|
|
3910
|
+
return BodyProfile3;
|
|
3911
|
+
})(BodyProfile2 || {});
|
|
3912
|
+
var BodyProfileValidator = class {
|
|
3913
|
+
constructor() {
|
|
3914
|
+
this.logger = new import_common10.Logger(BodyProfileValidator.name);
|
|
3915
|
+
}
|
|
3916
|
+
/**
|
|
3917
|
+
* Validate body matches declared profile
|
|
3918
|
+
*/
|
|
3919
|
+
validate(body, profile) {
|
|
3920
|
+
switch (profile) {
|
|
3921
|
+
case 0 /* RAW */:
|
|
3922
|
+
return this.validateRaw(body);
|
|
3923
|
+
case 1 /* TLV_MAP */:
|
|
3924
|
+
return this.validateTlvMap(body);
|
|
3925
|
+
case 2 /* OBJ */:
|
|
3926
|
+
return this.validateObj(body);
|
|
3927
|
+
case 3 /* ARR */:
|
|
3928
|
+
return this.validateArr(body);
|
|
3929
|
+
default:
|
|
3930
|
+
return {
|
|
3931
|
+
valid: false,
|
|
3932
|
+
error: `Unknown body profile: ${profile}`,
|
|
3933
|
+
profile
|
|
3934
|
+
};
|
|
3935
|
+
}
|
|
3936
|
+
}
|
|
3937
|
+
/**
|
|
3938
|
+
* RAW profile - no validation, any bytes accepted
|
|
3939
|
+
*/
|
|
3940
|
+
validateRaw(body) {
|
|
3941
|
+
return {
|
|
3942
|
+
valid: true,
|
|
3943
|
+
profile: 0 /* RAW */
|
|
3944
|
+
};
|
|
3945
|
+
}
|
|
3946
|
+
/**
|
|
3947
|
+
* TLV_MAP profile - flat TLV list (no nested structures)
|
|
3948
|
+
*/
|
|
3949
|
+
validateTlvMap(body) {
|
|
3950
|
+
try {
|
|
3951
|
+
const tlvs = (0, import_axis_protocol.decodeTLVsList)(body);
|
|
3952
|
+
for (const tlv2 of tlvs) {
|
|
3953
|
+
if (tlv2.type === 254 || tlv2.type === 255) {
|
|
3954
|
+
return {
|
|
3955
|
+
valid: false,
|
|
3956
|
+
error: "TLV_MAP profile cannot contain nested OBJ/ARR types",
|
|
3957
|
+
profile: 1 /* TLV_MAP */
|
|
3958
|
+
};
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3961
|
+
return {
|
|
3962
|
+
valid: true,
|
|
3963
|
+
profile: 1 /* TLV_MAP */
|
|
3964
|
+
};
|
|
3965
|
+
} catch (error) {
|
|
3966
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3967
|
+
return {
|
|
3968
|
+
valid: false,
|
|
3969
|
+
error: `TLV_MAP decode failed: ${message}`,
|
|
3970
|
+
profile: 1 /* TLV_MAP */
|
|
3971
|
+
};
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
/**
|
|
3975
|
+
* OBJ profile - must be valid nested object
|
|
3976
|
+
*/
|
|
3977
|
+
validateObj(body) {
|
|
3978
|
+
try {
|
|
3979
|
+
const tlvs = (0, import_axis_protocol.decodeTLVsList)(body);
|
|
3980
|
+
const hasObj = tlvs.some((t) => t.type === 254);
|
|
3981
|
+
if (!hasObj && tlvs.length > 0) {
|
|
3982
|
+
return {
|
|
3983
|
+
valid: false,
|
|
3984
|
+
error: "OBJ profile must contain OBJ type (254)",
|
|
3985
|
+
profile: 2 /* OBJ */
|
|
3986
|
+
};
|
|
3987
|
+
}
|
|
3988
|
+
return {
|
|
3989
|
+
valid: true,
|
|
3990
|
+
profile: 2 /* OBJ */
|
|
3991
|
+
};
|
|
3992
|
+
} catch (error) {
|
|
3993
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3994
|
+
return {
|
|
3995
|
+
valid: false,
|
|
3996
|
+
error: `OBJ decode failed: ${message}`,
|
|
3997
|
+
profile: 2 /* OBJ */
|
|
3998
|
+
};
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
/**
|
|
4002
|
+
* ARR profile - must be valid array
|
|
4003
|
+
*/
|
|
4004
|
+
validateArr(body) {
|
|
4005
|
+
try {
|
|
4006
|
+
const tlvs = (0, import_axis_protocol.decodeTLVsList)(body);
|
|
4007
|
+
const hasArr = tlvs.some((t) => t.type === 255);
|
|
4008
|
+
if (!hasArr && tlvs.length > 0) {
|
|
4009
|
+
return {
|
|
4010
|
+
valid: false,
|
|
4011
|
+
error: "ARR profile must contain ARR type (255)",
|
|
4012
|
+
profile: 3 /* ARR */
|
|
4013
|
+
};
|
|
4014
|
+
}
|
|
4015
|
+
return {
|
|
4016
|
+
valid: true,
|
|
4017
|
+
profile: 3 /* ARR */
|
|
4018
|
+
};
|
|
4019
|
+
} catch (error) {
|
|
4020
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4021
|
+
return {
|
|
4022
|
+
valid: false,
|
|
4023
|
+
error: `ARR decode failed: ${message}`,
|
|
4024
|
+
profile: 3 /* ARR */
|
|
4025
|
+
};
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
};
|
|
4029
|
+
BodyProfileValidator = __decorateClass([
|
|
4030
|
+
(0, import_common10.Injectable)()
|
|
4031
|
+
], BodyProfileValidator);
|
|
4032
|
+
|
|
4033
|
+
// src/security/index.ts
|
|
4034
|
+
var security_exports = {};
|
|
4035
|
+
__export(security_exports, {
|
|
4036
|
+
CAPABILITIES: () => CAPABILITIES,
|
|
4037
|
+
INTENT_REQUIREMENTS: () => INTENT_REQUIREMENTS,
|
|
4038
|
+
PROOF_CAPABILITIES: () => PROOF_CAPABILITIES,
|
|
4039
|
+
canAccessResource: () => canAccessResource,
|
|
4040
|
+
hasScope: () => hasScope,
|
|
4041
|
+
parseScope: () => parseScope
|
|
4042
|
+
});
|
|
4043
|
+
|
|
4044
|
+
// src/sensors/index.ts
|
|
4045
|
+
var sensors_exports = {};
|
|
4046
|
+
__export(sensors_exports, {
|
|
4047
|
+
AccessProfileResolverSensor: () => AccessProfileResolverSensor,
|
|
4048
|
+
BodyBudgetSensor: () => BodyBudgetSensor,
|
|
4049
|
+
CapabilityEnforcementSensor: () => CapabilityEnforcementSensor,
|
|
4050
|
+
ChunkHashSensor: () => ChunkHashSensor,
|
|
4051
|
+
EntropySensor: () => EntropySensor,
|
|
4052
|
+
ExecutionTimeoutSensor: () => ExecutionTimeoutSensor,
|
|
4053
|
+
FrameBudgetSensor: () => FrameBudgetSensor,
|
|
4054
|
+
FrameHeaderSanitySensor: () => FrameHeaderSanitySensor,
|
|
4055
|
+
HeaderTLVLimitSensor: () => HeaderTLVLimitSensor,
|
|
4056
|
+
IntentAllowlistSensor: () => IntentAllowlistSensor,
|
|
4057
|
+
IntentRegistrySensor: () => IntentRegistrySensor,
|
|
4058
|
+
ProofPresenceSensor: () => ProofPresenceSensor,
|
|
4059
|
+
ProtocolStrictSensor: () => ProtocolStrictSensor,
|
|
4060
|
+
ReceiptPolicySensor: () => ReceiptPolicySensor,
|
|
4061
|
+
SchemaValidationSensor: () => SchemaValidationSensor,
|
|
4062
|
+
StreamScopeSensor: () => StreamScopeSensor,
|
|
4063
|
+
TLVParseSensor: () => TLVParseSensor,
|
|
4064
|
+
VarintHardeningSensor: () => VarintHardeningSensor
|
|
4065
|
+
});
|
|
4066
|
+
|
|
4067
|
+
// src/sensors/access-profile-resolver.sensor.ts
|
|
4068
|
+
var import_common11 = require("@nestjs/common");
|
|
4069
|
+
var AccessProfileResolverSensor = class {
|
|
4070
|
+
constructor() {
|
|
4071
|
+
/** AxisSensor identifier */
|
|
4072
|
+
this.name = "AccessProfileResolverSensor";
|
|
4073
|
+
/**
|
|
4074
|
+
* Execution order - runs early to establish the access profile
|
|
4075
|
+
* for downstream sensors.
|
|
4076
|
+
*/
|
|
4077
|
+
this.order = BAND.IDENTITY + 10;
|
|
4078
|
+
}
|
|
4079
|
+
supports() {
|
|
4080
|
+
return true;
|
|
4081
|
+
}
|
|
4082
|
+
async run(input) {
|
|
4083
|
+
const hasCapsule = !!input.metadata?.capsuleId;
|
|
4084
|
+
const hasPassport = !!input.metadata?.passportSig;
|
|
4085
|
+
const hasMTLS = !!input.metadata?.mtlsId;
|
|
4086
|
+
const profile = hasCapsule || hasPassport || hasMTLS ? "GUARDED" : "PUBLIC";
|
|
4087
|
+
if (!input.metadata) input.metadata = {};
|
|
4088
|
+
input.metadata.profile = profile;
|
|
4089
|
+
return { action: "ALLOW" };
|
|
4090
|
+
}
|
|
4091
|
+
};
|
|
4092
|
+
AccessProfileResolverSensor = __decorateClass([
|
|
4093
|
+
Sensor(),
|
|
4094
|
+
(0, import_common11.Injectable)()
|
|
4095
|
+
], AccessProfileResolverSensor);
|
|
4096
|
+
|
|
4097
|
+
// src/sensors/body-budget.sensor.ts
|
|
4098
|
+
var import_common12 = require("@nestjs/common");
|
|
4099
|
+
var BodyBudgetSensor = class {
|
|
4100
|
+
constructor() {
|
|
4101
|
+
/** AxisSensor identifier */
|
|
4102
|
+
this.name = "BodyBudgetSensor";
|
|
4103
|
+
/**
|
|
4104
|
+
* Execution order - after authentication
|
|
4105
|
+
*
|
|
4106
|
+
* Order 150 ensures:
|
|
4107
|
+
* - Authentication complete
|
|
4108
|
+
* - Runs before full body read
|
|
4109
|
+
* - Before schema validation (170)
|
|
4110
|
+
*/
|
|
4111
|
+
this.order = BAND.CONTENT + 10;
|
|
4112
|
+
}
|
|
4113
|
+
/**
|
|
4114
|
+
* Determines if this sensor should process the given input.
|
|
4115
|
+
*
|
|
4116
|
+
* Requires at least 8 bytes of peeked data to read headers.
|
|
4117
|
+
*
|
|
4118
|
+
* @param {SensorInput} input - Incoming request
|
|
4119
|
+
* @returns {boolean} True if sufficient peek data available
|
|
4120
|
+
*/
|
|
4121
|
+
supports(input) {
|
|
4122
|
+
return !!input.peek && input.peek.length >= 8;
|
|
4123
|
+
}
|
|
4124
|
+
/**
|
|
4125
|
+
* Validates header and body lengths against configured limits.
|
|
4126
|
+
*
|
|
4127
|
+
* **Frame Parsing:**
|
|
4128
|
+
* - Skip magic (5 bytes)
|
|
4129
|
+
* - Skip version (1 byte)
|
|
4130
|
+
* - Skip flags (1 byte)
|
|
4131
|
+
* - Read HDR_LEN varint
|
|
4132
|
+
* - Read BODY_LEN varint
|
|
4133
|
+
* - Compare against MAX_HDR_LEN and MAX_BODY_LEN
|
|
4134
|
+
*
|
|
4135
|
+
* @param {SensorInput} input - Request with peek data
|
|
4136
|
+
* @returns {Promise<SensorDecision>} ALLOW or DENY based on size limits
|
|
4137
|
+
*/
|
|
4138
|
+
async run(input) {
|
|
4139
|
+
const { peek } = input;
|
|
4140
|
+
if (!peek || peek.length < 8) {
|
|
4141
|
+
return { action: "ALLOW" };
|
|
4142
|
+
}
|
|
4143
|
+
try {
|
|
4144
|
+
let offset = 5;
|
|
4145
|
+
offset += 1;
|
|
4146
|
+
offset += 1;
|
|
4147
|
+
const { value: hdrLen, length: hdrBytes } = (0, import_axis_protocol3.decodeVarint)(peek, offset);
|
|
4148
|
+
offset += hdrBytes;
|
|
4149
|
+
const { value: bodyLen } = (0, import_axis_protocol3.decodeVarint)(peek, offset);
|
|
4150
|
+
if (hdrLen > import_axis_protocol2.MAX_HDR_LEN) {
|
|
4151
|
+
return {
|
|
4152
|
+
action: "DENY",
|
|
4153
|
+
code: "HEADER_TOO_LARGE",
|
|
4154
|
+
reason: `Header size ${hdrLen} exceeds limit ${import_axis_protocol2.MAX_HDR_LEN}`
|
|
4155
|
+
};
|
|
4156
|
+
}
|
|
4157
|
+
if (bodyLen > import_axis_protocol2.MAX_BODY_LEN) {
|
|
4158
|
+
return {
|
|
4159
|
+
action: "DENY",
|
|
4160
|
+
code: "BODY_TOO_LARGE",
|
|
4161
|
+
reason: `Body size ${bodyLen} exceeds limit ${import_axis_protocol2.MAX_BODY_LEN}`
|
|
4162
|
+
};
|
|
4163
|
+
}
|
|
4164
|
+
return { action: "ALLOW" };
|
|
4165
|
+
} catch (e) {
|
|
4166
|
+
return { action: "ALLOW" };
|
|
4167
|
+
}
|
|
4168
|
+
}
|
|
4169
|
+
};
|
|
4170
|
+
BodyBudgetSensor = __decorateClass([
|
|
4171
|
+
Sensor(),
|
|
4172
|
+
(0, import_common12.Injectable)()
|
|
4173
|
+
], BodyBudgetSensor);
|
|
4174
|
+
|
|
4175
|
+
// src/sensors/capability-enforcement.sensor.ts
|
|
4176
|
+
var import_common13 = require("@nestjs/common");
|
|
4177
|
+
var CapabilityEnforcementSensor = class {
|
|
4178
|
+
constructor() {
|
|
4179
|
+
this.logger = new import_common13.Logger(CapabilityEnforcementSensor.name);
|
|
4180
|
+
/** AxisSensor identifier for logging and registry */
|
|
4181
|
+
this.name = "CapabilityEnforcementSensor";
|
|
4182
|
+
/**
|
|
4183
|
+
* Execution order - runs after authentication
|
|
4184
|
+
*
|
|
4185
|
+
* Order 100 ensures:
|
|
4186
|
+
* - Capsule is verified (CapsuleVerifySensor @ 80)
|
|
4187
|
+
* - Signature is verified (SigVerifySensor @ 90)
|
|
4188
|
+
* - We know the proof type for capability lookup
|
|
4189
|
+
*/
|
|
4190
|
+
this.order = BAND.POLICY + 10;
|
|
4191
|
+
}
|
|
4192
|
+
/**
|
|
4193
|
+
* Determines if this sensor should process the given input.
|
|
4194
|
+
*
|
|
4195
|
+
* Only activates when an intent is present.
|
|
4196
|
+
*
|
|
4197
|
+
* @param {SensorInput} input - Incoming AXIS request
|
|
4198
|
+
* @returns {boolean} True if intent is present
|
|
4199
|
+
*/
|
|
4200
|
+
supports(input) {
|
|
4201
|
+
return !!input.intent;
|
|
4202
|
+
}
|
|
4203
|
+
/**
|
|
4204
|
+
* Enforces capability requirements for the requested intent.
|
|
4205
|
+
*
|
|
4206
|
+
* **Processing Flow:**
|
|
4207
|
+
* 1. Extract proof type from packet (default: 0/NONE)
|
|
4208
|
+
* 2. Look up capabilities granted by this proof type
|
|
4209
|
+
* 3. Look up capabilities required by the intent
|
|
4210
|
+
* 4. If no requirements, ALLOW
|
|
4211
|
+
* 5. Check if all required capabilities are granted
|
|
4212
|
+
* 6. If missing capabilities, DENY with details
|
|
4213
|
+
* 7. Otherwise, ALLOW
|
|
4214
|
+
*
|
|
4215
|
+
* @param {SensorInput} input - Request with intent and packet
|
|
4216
|
+
* @returns {Promise<SensorDecision>} ALLOW or DENY based on capabilities
|
|
4217
|
+
*/
|
|
4218
|
+
async run(input) {
|
|
4219
|
+
const { intent, packet } = input;
|
|
4220
|
+
if (!intent) {
|
|
4221
|
+
return { action: "ALLOW" };
|
|
4222
|
+
}
|
|
4223
|
+
const proofType = packet?.proofType ?? 0;
|
|
4224
|
+
const grantedCapabilities = PROOF_CAPABILITIES[proofType] || [];
|
|
4225
|
+
const requiredCapabilities = this.getRequiredCapabilities(intent);
|
|
4226
|
+
if (requiredCapabilities.length === 0) {
|
|
4227
|
+
return { action: "ALLOW" };
|
|
4228
|
+
}
|
|
4229
|
+
const missingCapabilities = requiredCapabilities.filter(
|
|
4230
|
+
(cap) => !grantedCapabilities.includes(cap)
|
|
4231
|
+
);
|
|
4232
|
+
if (missingCapabilities.length > 0) {
|
|
4233
|
+
this.logger.warn(
|
|
4234
|
+
`Capability denied for ${intent}: missing ${missingCapabilities.join(", ")} (has: ${grantedCapabilities.join(", ")})`
|
|
4235
|
+
);
|
|
4236
|
+
return {
|
|
4237
|
+
action: "DENY",
|
|
4238
|
+
code: "CAPABILITY_DENIED",
|
|
4239
|
+
reason: `Missing capabilities: ${missingCapabilities.join(", ")}`
|
|
4240
|
+
};
|
|
4241
|
+
}
|
|
4242
|
+
return { action: "ALLOW" };
|
|
4243
|
+
}
|
|
4244
|
+
/**
|
|
4245
|
+
* Gets required capabilities for an intent.
|
|
4246
|
+
*
|
|
4247
|
+
* **Lookup Strategy:**
|
|
4248
|
+
* 1. Check for exact intent match
|
|
4249
|
+
* 2. Check for prefix pattern match (*.suffix)
|
|
4250
|
+
* 3. Default to 'execute' for unknown intents
|
|
4251
|
+
*
|
|
4252
|
+
* @private
|
|
4253
|
+
* @param {string} intent - Intent name to look up
|
|
4254
|
+
* @returns {Capability[]} Array of required capabilities
|
|
4255
|
+
*/
|
|
4256
|
+
getRequiredCapabilities(intent) {
|
|
4257
|
+
if (INTENT_REQUIREMENTS[intent]) {
|
|
4258
|
+
return INTENT_REQUIREMENTS[intent];
|
|
4259
|
+
}
|
|
4260
|
+
for (const [pattern, caps] of Object.entries(INTENT_REQUIREMENTS)) {
|
|
4261
|
+
if (pattern.endsWith(".*")) {
|
|
4262
|
+
const prefix = pattern.slice(0, -1);
|
|
4263
|
+
if (intent.startsWith(prefix)) {
|
|
4264
|
+
return caps;
|
|
4265
|
+
}
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
4268
|
+
return ["execute"];
|
|
4269
|
+
}
|
|
4270
|
+
};
|
|
4271
|
+
CapabilityEnforcementSensor = __decorateClass([
|
|
4272
|
+
Sensor(),
|
|
4273
|
+
(0, import_common13.Injectable)()
|
|
4274
|
+
], CapabilityEnforcementSensor);
|
|
4275
|
+
|
|
4276
|
+
// src/sensors/chunk-hash.sensor.ts
|
|
4277
|
+
var import_common14 = require("@nestjs/common");
|
|
4278
|
+
var import_crypto5 = require("crypto");
|
|
4279
|
+
var ChunkHashSensor = class {
|
|
4280
|
+
constructor() {
|
|
4281
|
+
/** Sensor identifier */
|
|
4282
|
+
this.name = "ChunkHashSensor";
|
|
4283
|
+
/**
|
|
4284
|
+
* Execution order - after session validation
|
|
4285
|
+
*
|
|
4286
|
+
* Order 190 ensures:
|
|
4287
|
+
* - Session validated (180)
|
|
4288
|
+
* - Chunk parameters verified
|
|
4289
|
+
* - Hash check before storage
|
|
4290
|
+
*/
|
|
4291
|
+
this.order = BAND.CONTENT + 50;
|
|
4292
|
+
}
|
|
4293
|
+
/**
|
|
4294
|
+
* Determines if this sensor should process the given input.
|
|
4295
|
+
*
|
|
4296
|
+
* Only processes file.chunk intents.
|
|
4297
|
+
*
|
|
4298
|
+
* @param {SensorInput} input - Incoming request
|
|
4299
|
+
* @returns {boolean} True if intent is 'file.chunk'
|
|
4300
|
+
*/
|
|
4301
|
+
supports(input) {
|
|
4302
|
+
return input.intent === "file.chunk";
|
|
4303
|
+
}
|
|
4304
|
+
/**
|
|
4305
|
+
* Validates chunk data against declared SHA-256 hash.
|
|
4306
|
+
*
|
|
4307
|
+
* **Processing Flow:**
|
|
4308
|
+
* 1. Check for required headerTLVs and body
|
|
4309
|
+
* 2. Extract expected hash from TLV 73
|
|
4310
|
+
* 3. Verify hash is exactly 32 bytes
|
|
4311
|
+
* 4. Compute SHA-256 of body
|
|
4312
|
+
* 5. Compare bytes (timing-safe)
|
|
4313
|
+
* 6. DENY on mismatch
|
|
4314
|
+
*
|
|
4315
|
+
* @param {SensorInput} input - Request with chunk body
|
|
4316
|
+
* @returns {Promise<SensorDecision>} ALLOW if hash matches, DENY otherwise
|
|
4317
|
+
*/
|
|
4318
|
+
async run(input) {
|
|
4319
|
+
const headerTLVs = input.headerTLVs;
|
|
4320
|
+
const bodyBytes = input.body;
|
|
4321
|
+
if (!headerTLVs || !bodyBytes) {
|
|
4322
|
+
return {
|
|
4323
|
+
action: "DENY",
|
|
4324
|
+
code: "SENSOR_INVALID_INPUT",
|
|
4325
|
+
reason: "Missing headerTLVs or body"
|
|
4326
|
+
};
|
|
4327
|
+
}
|
|
4328
|
+
const TLV_SHA256_CHUNK2 = 73;
|
|
4329
|
+
const expected = headerTLVs.get(TLV_SHA256_CHUNK2);
|
|
4330
|
+
if (!expected || expected.length !== 32) {
|
|
4331
|
+
return {
|
|
4332
|
+
action: "DENY",
|
|
4333
|
+
code: "FILE_CHUNK_HASH_MISSING",
|
|
4334
|
+
reason: "Missing sha256Chunk TLV in header"
|
|
4335
|
+
};
|
|
4336
|
+
}
|
|
4337
|
+
const actual = (0, import_crypto5.createHash)("sha256").update(bodyBytes).digest();
|
|
4338
|
+
if (!Buffer.from(actual).equals(Buffer.from(expected))) {
|
|
4339
|
+
return {
|
|
4340
|
+
action: "DENY",
|
|
4341
|
+
code: "FILE_CHUNK_HASH_MISMATCH",
|
|
4342
|
+
reason: "Chunk hash mismatch - data corrupted"
|
|
4343
|
+
};
|
|
4344
|
+
}
|
|
4345
|
+
return { action: "ALLOW" };
|
|
4346
|
+
}
|
|
4347
|
+
};
|
|
4348
|
+
ChunkHashSensor = __decorateClass([
|
|
4349
|
+
Sensor(),
|
|
4350
|
+
(0, import_common14.Injectable)()
|
|
4351
|
+
], ChunkHashSensor);
|
|
4352
|
+
|
|
4353
|
+
// src/sensors/entropy.sensor.ts
|
|
4354
|
+
var import_common15 = require("@nestjs/common");
|
|
4355
|
+
var crypto4 = __toESM(require("crypto"));
|
|
4356
|
+
var EntropySensor = class {
|
|
4357
|
+
constructor() {
|
|
4358
|
+
this.logger = new import_common15.Logger(EntropySensor.name);
|
|
4359
|
+
/**
|
|
4360
|
+
* Minimum acceptable entropy in bits per byte.
|
|
4361
|
+
*
|
|
4362
|
+
* 3.0 bits/byte is a conservative threshold:
|
|
4363
|
+
* - Random data: ~7.9 bits/byte
|
|
4364
|
+
* - English text: ~4.5 bits/byte
|
|
4365
|
+
* - Sequential data: ~0-2 bits/byte
|
|
4366
|
+
*/
|
|
4367
|
+
this.MIN_ENTROPY_THRESHOLD = 3;
|
|
4368
|
+
/** AxisSensor identifier */
|
|
4369
|
+
this.name = "EntropySensor";
|
|
4370
|
+
/**
|
|
4371
|
+
* Execution order - anomaly detection phase
|
|
4372
|
+
*
|
|
4373
|
+
* Order 130 ensures:
|
|
4374
|
+
* - Replay protection done (120)
|
|
4375
|
+
* - Runs before expensive policy lookups
|
|
4376
|
+
*/
|
|
4377
|
+
this.order = BAND.POLICY + 35;
|
|
4378
|
+
}
|
|
4379
|
+
/**
|
|
4380
|
+
* Calculates Shannon entropy of a byte array.
|
|
4381
|
+
*
|
|
4382
|
+
* **Algorithm:**
|
|
4383
|
+
* 1. Count frequency of each byte value (0-255)
|
|
4384
|
+
* 2. Calculate probability p = count / total
|
|
4385
|
+
* 3. Sum: -Σ(p * log2(p))
|
|
4386
|
+
*
|
|
4387
|
+
* @private
|
|
4388
|
+
* @param {Uint8Array} data - Bytes to analyze
|
|
4389
|
+
* @returns {number} Entropy in bits per byte (0-8 scale)
|
|
4390
|
+
*/
|
|
4391
|
+
calculateEntropy(data) {
|
|
4392
|
+
if (data.length === 0) return 0;
|
|
4393
|
+
const freq = /* @__PURE__ */ new Map();
|
|
4394
|
+
for (const byte of data) {
|
|
4395
|
+
freq.set(byte, (freq.get(byte) || 0) + 1);
|
|
4396
|
+
}
|
|
4397
|
+
let entropy = 0;
|
|
4398
|
+
const len = data.length;
|
|
4399
|
+
for (const count of freq.values()) {
|
|
4400
|
+
const p = count / len;
|
|
4401
|
+
entropy -= p * Math.log2(p);
|
|
4402
|
+
}
|
|
4403
|
+
return entropy;
|
|
4404
|
+
}
|
|
4405
|
+
/**
|
|
4406
|
+
* Checks for sequential patterns in data.
|
|
4407
|
+
*
|
|
4408
|
+
* Detects sequences like [1,2,3,4...] or [10,9,8,7...].
|
|
4409
|
+
* More than 50% sequential is considered suspicious.
|
|
4410
|
+
*
|
|
4411
|
+
* @private
|
|
4412
|
+
* @param {Uint8Array} data - Bytes to analyze
|
|
4413
|
+
* @returns {boolean} True if sequential pattern detected
|
|
4414
|
+
*/
|
|
4415
|
+
hasSequentialPattern(data) {
|
|
4416
|
+
if (data.length < 4) return false;
|
|
4417
|
+
let ascending = 0;
|
|
4418
|
+
let descending = 0;
|
|
4419
|
+
for (let i = 1; i < data.length; i++) {
|
|
4420
|
+
if (data[i] === data[i - 1] + 1) ascending++;
|
|
4421
|
+
if (data[i] === data[i - 1] - 1) descending++;
|
|
4422
|
+
}
|
|
4423
|
+
return ascending > data.length / 2 || descending > data.length / 2;
|
|
4424
|
+
}
|
|
4425
|
+
/**
|
|
4426
|
+
* Checks for repeated patterns in data.
|
|
4427
|
+
*
|
|
4428
|
+
* Detects patterns like [0xAB, 0xCD, 0xAB, 0xCD...].
|
|
4429
|
+
* Checks for 2, 4, and 8 byte repeating patterns.
|
|
4430
|
+
*
|
|
4431
|
+
* @private
|
|
4432
|
+
* @param {Uint8Array} data - Bytes to analyze
|
|
4433
|
+
* @returns {boolean} True if repeated pattern detected
|
|
4434
|
+
*/
|
|
4435
|
+
hasRepeatedPattern(data) {
|
|
4436
|
+
if (data.length < 8) return false;
|
|
4437
|
+
for (const patternLen of [2, 4, 8]) {
|
|
4438
|
+
if (data.length % patternLen !== 0) continue;
|
|
4439
|
+
let matches = 0;
|
|
4440
|
+
for (let i = patternLen; i < data.length; i++) {
|
|
4441
|
+
if (data[i] === data[i % patternLen]) matches++;
|
|
4442
|
+
}
|
|
4443
|
+
if (matches > (data.length - patternLen) * 0.9) {
|
|
4444
|
+
return true;
|
|
4445
|
+
}
|
|
4446
|
+
}
|
|
4447
|
+
return false;
|
|
4448
|
+
}
|
|
4449
|
+
/**
|
|
4450
|
+
* Analyzes entropy of PID and nonce in request headers.
|
|
4451
|
+
*
|
|
4452
|
+
* **Processing Flow:**
|
|
4453
|
+
* 1. Extract PID and nonce from header TLVs
|
|
4454
|
+
* 2. Calculate entropy for each
|
|
4455
|
+
* 3. Check for sequential patterns
|
|
4456
|
+
* 4. Check for repeated patterns
|
|
4457
|
+
* 5. Accumulate issues and score delta
|
|
4458
|
+
* 6. Return FLAG if issues found, ALLOW otherwise
|
|
4459
|
+
*
|
|
4460
|
+
* @param {SensorInput} input - Request with header TLVs
|
|
4461
|
+
* @returns {Promise<SensorDecision>} ALLOW or FLAG based on entropy analysis
|
|
4462
|
+
*/
|
|
4463
|
+
async run(input) {
|
|
4464
|
+
const headers = input.headerTLVs;
|
|
4465
|
+
if (!headers) {
|
|
4466
|
+
return { action: "ALLOW" };
|
|
4467
|
+
}
|
|
4468
|
+
const pid = headers.get(import_axis_protocol2.TLV_PID);
|
|
4469
|
+
const nonce = headers.get(import_axis_protocol2.TLV_NONCE);
|
|
4470
|
+
const issues = [];
|
|
4471
|
+
let totalDelta = 0;
|
|
4472
|
+
if (pid && pid.length > 0) {
|
|
4473
|
+
const pidEntropy = this.calculateEntropy(pid);
|
|
4474
|
+
if (pidEntropy < this.MIN_ENTROPY_THRESHOLD) {
|
|
4475
|
+
issues.push(`pid_low_entropy:${pidEntropy.toFixed(2)}`);
|
|
4476
|
+
totalDelta -= 3;
|
|
4477
|
+
}
|
|
4478
|
+
if (this.hasSequentialPattern(pid)) {
|
|
4479
|
+
issues.push("pid_sequential");
|
|
4480
|
+
totalDelta -= 5;
|
|
4481
|
+
}
|
|
4482
|
+
if (this.hasRepeatedPattern(pid)) {
|
|
4483
|
+
issues.push("pid_repeated");
|
|
4484
|
+
totalDelta -= 5;
|
|
4485
|
+
}
|
|
4486
|
+
}
|
|
4487
|
+
if (nonce && nonce.length > 0) {
|
|
4488
|
+
const nonceEntropy = this.calculateEntropy(nonce);
|
|
4489
|
+
if (nonceEntropy < this.MIN_ENTROPY_THRESHOLD) {
|
|
4490
|
+
issues.push(`nonce_low_entropy:${nonceEntropy.toFixed(2)}`);
|
|
4491
|
+
totalDelta -= 3;
|
|
4492
|
+
}
|
|
4493
|
+
if (this.hasSequentialPattern(nonce)) {
|
|
4494
|
+
issues.push("nonce_sequential");
|
|
4495
|
+
totalDelta -= 5;
|
|
4496
|
+
}
|
|
4497
|
+
if (this.hasRepeatedPattern(nonce)) {
|
|
4498
|
+
issues.push("nonce_repeated");
|
|
4499
|
+
totalDelta -= 5;
|
|
4500
|
+
}
|
|
4501
|
+
}
|
|
4502
|
+
if (issues.length > 0) {
|
|
4503
|
+
this.logger.warn(`Entropy issues from ${input.ip}: ${issues.join(", ")}`);
|
|
4504
|
+
return {
|
|
4505
|
+
action: "FLAG",
|
|
4506
|
+
scoreDelta: totalDelta,
|
|
4507
|
+
reasons: issues
|
|
4508
|
+
};
|
|
4509
|
+
}
|
|
4510
|
+
return { action: "ALLOW" };
|
|
4511
|
+
}
|
|
4512
|
+
/**
|
|
4513
|
+
* Generates cryptographically secure random bytes.
|
|
4514
|
+
*
|
|
4515
|
+
* Utility method for SDK/client code to ensure proper entropy.
|
|
4516
|
+
* Uses Node.js crypto.randomBytes for secure PRNG.
|
|
4517
|
+
*
|
|
4518
|
+
* @static
|
|
4519
|
+
* @param {number} length - Number of random bytes
|
|
4520
|
+
* @returns {Uint8Array} Cryptographically secure random bytes
|
|
4521
|
+
*/
|
|
4522
|
+
static generateSecureRandom(length) {
|
|
4523
|
+
return new Uint8Array(crypto4.randomBytes(length));
|
|
4524
|
+
}
|
|
4525
|
+
};
|
|
4526
|
+
EntropySensor = __decorateClass([
|
|
4527
|
+
Sensor(),
|
|
4528
|
+
(0, import_common15.Injectable)()
|
|
4529
|
+
], EntropySensor);
|
|
4530
|
+
|
|
4531
|
+
// src/sensors/execution-timeout.sensor.ts
|
|
4532
|
+
var import_common16 = require("@nestjs/common");
|
|
4533
|
+
var ExecutionTimeoutSensor = class {
|
|
4534
|
+
constructor() {
|
|
4535
|
+
this.logger = new import_common16.Logger(ExecutionTimeoutSensor.name);
|
|
4536
|
+
/** AxisSensor identifier */
|
|
4537
|
+
this.name = "ExecutionTimeoutSensor";
|
|
4538
|
+
/**
|
|
4539
|
+
* Execution order - late, near handler execution
|
|
4540
|
+
*
|
|
4541
|
+
* Order 210 ensures:
|
|
4542
|
+
* - All validation complete
|
|
4543
|
+
* - Deadline set just before handler
|
|
4544
|
+
*/
|
|
4545
|
+
this.order = BAND.BUSINESS + 10;
|
|
4546
|
+
}
|
|
4547
|
+
/**
|
|
4548
|
+
* Determines if this sensor should process the given input.
|
|
4549
|
+
*
|
|
4550
|
+
* @param {SensorInput} input - Incoming request
|
|
4551
|
+
* @returns {boolean} True if intent is present
|
|
4552
|
+
*/
|
|
4553
|
+
supports(input) {
|
|
4554
|
+
return !!input.intent;
|
|
4555
|
+
}
|
|
4556
|
+
/**
|
|
4557
|
+
* Sets execution deadline in the request context.
|
|
4558
|
+
*
|
|
4559
|
+
* **Processing Flow:**
|
|
4560
|
+
* 1. Look up timeout for intent
|
|
4561
|
+
* 2. Calculate absolute deadline
|
|
4562
|
+
* 3. Store in context for handler use
|
|
4563
|
+
* 4. Return ALLOW
|
|
4564
|
+
*
|
|
4565
|
+
* @param {SensorInput} input - Request with intent
|
|
4566
|
+
* @returns {Promise<SensorDecision>} Always ALLOW
|
|
4567
|
+
*/
|
|
4568
|
+
async run(input) {
|
|
4569
|
+
const { intent, context } = input;
|
|
4570
|
+
if (!intent) {
|
|
4571
|
+
return { action: "ALLOW" };
|
|
4572
|
+
}
|
|
4573
|
+
const timeout = resolveTimeout(intent);
|
|
4574
|
+
const deadline = Date.now() + timeout;
|
|
4575
|
+
if (context) {
|
|
4576
|
+
context.deadline = deadline;
|
|
4577
|
+
context.timeoutMs = timeout;
|
|
4578
|
+
}
|
|
4579
|
+
this.logger.debug(
|
|
4580
|
+
`Set ${timeout}ms timeout for ${intent} (deadline: ${new Date(deadline).toISOString()})`
|
|
4581
|
+
);
|
|
4582
|
+
return { action: "ALLOW" };
|
|
4583
|
+
}
|
|
4584
|
+
/**
|
|
4585
|
+
* Checks if a deadline has been exceeded.
|
|
4586
|
+
*
|
|
4587
|
+
* Utility method for handler code.
|
|
4588
|
+
*
|
|
4589
|
+
* @static
|
|
4590
|
+
* @param {object} ctx - Context with deadline
|
|
4591
|
+
* @returns {boolean} True if deadline passed
|
|
4592
|
+
*/
|
|
4593
|
+
static isExpired(ctx) {
|
|
4594
|
+
if (!ctx.deadline) return false;
|
|
4595
|
+
return Date.now() > ctx.deadline;
|
|
4596
|
+
}
|
|
4597
|
+
/**
|
|
4598
|
+
* Gets remaining time until deadline.
|
|
4599
|
+
*
|
|
4600
|
+
* Utility method for handler code.
|
|
4601
|
+
*
|
|
4602
|
+
* @static
|
|
4603
|
+
* @param {object} ctx - Context with deadline
|
|
4604
|
+
* @returns {number} Remaining milliseconds (0 if expired, Infinity if no deadline)
|
|
4605
|
+
*/
|
|
4606
|
+
static getRemainingMs(ctx) {
|
|
4607
|
+
if (!ctx.deadline) return Infinity;
|
|
4608
|
+
return Math.max(0, ctx.deadline - Date.now());
|
|
4609
|
+
}
|
|
4610
|
+
};
|
|
4611
|
+
ExecutionTimeoutSensor = __decorateClass([
|
|
4612
|
+
Sensor(),
|
|
4613
|
+
(0, import_common16.Injectable)()
|
|
4614
|
+
], ExecutionTimeoutSensor);
|
|
4615
|
+
|
|
4616
|
+
// src/sensors/frame-budget.sensor.ts
|
|
4617
|
+
var import_common17 = require("@nestjs/common");
|
|
4618
|
+
var FrameBudgetSensor = class {
|
|
4619
|
+
constructor(config) {
|
|
4620
|
+
this.config = config;
|
|
4621
|
+
/** AxisSensor identifier for logging and registry */
|
|
4622
|
+
this.name = "FrameBudgetSensor";
|
|
4623
|
+
/**
|
|
4624
|
+
* Execution order - runs after protocol validation
|
|
4625
|
+
*
|
|
4626
|
+
* Order 20 ensures:
|
|
4627
|
+
* - Protocol is valid (ProtocolStrictSensor @ 10)
|
|
4628
|
+
* - Size checked before expensive processing
|
|
4629
|
+
*/
|
|
4630
|
+
this.order = BAND.WIRE + 20;
|
|
4631
|
+
}
|
|
4632
|
+
/**
|
|
4633
|
+
* Determines if this sensor should process the given input.
|
|
4634
|
+
*
|
|
4635
|
+
* Only activates when Content-Length header is available.
|
|
4636
|
+
* WebSocket frames may not have Content-Length; they use different size tracking.
|
|
4637
|
+
*
|
|
4638
|
+
* @param {SensorInput} input - Incoming AXIS request
|
|
4639
|
+
* @returns {boolean} True if Content-Length is present
|
|
4640
|
+
*/
|
|
4641
|
+
supports(input) {
|
|
4642
|
+
return typeof input.contentLength === "number";
|
|
4643
|
+
}
|
|
4644
|
+
/**
|
|
4645
|
+
* Validates frame size against configured limits.
|
|
4646
|
+
*
|
|
4647
|
+
* **Current Implementation:** Stub that always allows.
|
|
4648
|
+
*
|
|
4649
|
+
* **TODO:** Full implementation should:
|
|
4650
|
+
* 1. Load intent policy for the request
|
|
4651
|
+
* 2. Get maxFrameBytes from policy
|
|
4652
|
+
* 3. Compare against contentLength
|
|
4653
|
+
* 4. DENY if exceeded
|
|
4654
|
+
*
|
|
4655
|
+
* @param {SensorInput} input - Request with contentLength
|
|
4656
|
+
* @returns {Promise<SensorDecision>} ALLOW or DENY based on size
|
|
4657
|
+
*/
|
|
4658
|
+
async run(input) {
|
|
4659
|
+
const maxBytes = this.config.get("AXIS_MAX_FRAME_SIZE") || 50 * 1024 * 1024;
|
|
4660
|
+
const contentLength = input.contentLength;
|
|
4661
|
+
if (typeof contentLength !== "number") {
|
|
4662
|
+
return { action: "ALLOW" };
|
|
4663
|
+
}
|
|
4664
|
+
if (contentLength > maxBytes) {
|
|
4665
|
+
return {
|
|
4666
|
+
action: "DENY",
|
|
4667
|
+
code: "FRAME_TOO_LARGE",
|
|
4668
|
+
reason: `Frame size ${contentLength} exceeds limit ${maxBytes}`
|
|
4669
|
+
};
|
|
4670
|
+
}
|
|
4671
|
+
return { action: "ALLOW" };
|
|
4672
|
+
}
|
|
4673
|
+
};
|
|
4674
|
+
FrameBudgetSensor = __decorateClass([
|
|
4675
|
+
Sensor({ phase: "PRE_DECODE" }),
|
|
4676
|
+
(0, import_common17.Injectable)()
|
|
4677
|
+
], FrameBudgetSensor);
|
|
4678
|
+
|
|
4679
|
+
// src/sensors/frame-header-sanity.sensor.ts
|
|
4680
|
+
var import_common18 = require("@nestjs/common");
|
|
4681
|
+
var FrameHeaderSanitySensor = class {
|
|
4682
|
+
constructor() {
|
|
4683
|
+
this.name = "FrameHeaderSanitySensor";
|
|
4684
|
+
this.order = BAND.WIRE + 30;
|
|
4685
|
+
}
|
|
4686
|
+
supports(input) {
|
|
4687
|
+
return !!input.peek && input.peek.length >= 7;
|
|
4688
|
+
}
|
|
4689
|
+
async run(input) {
|
|
4690
|
+
const peek = input.peek;
|
|
4691
|
+
const contentLen = input.contentLength || 0;
|
|
4692
|
+
if (peek.length < 5 || !this.bufferEqual(peek.slice(0, 5), import_axis_protocol2.AXIS_MAGIC)) {
|
|
4693
|
+
return {
|
|
4694
|
+
action: "DENY",
|
|
4695
|
+
code: "INVALID_MAGIC",
|
|
4696
|
+
reason: "Frame magic is not AXIS1"
|
|
4697
|
+
};
|
|
4698
|
+
}
|
|
4699
|
+
if (peek[5] !== import_axis_protocol2.AXIS_VERSION) {
|
|
4700
|
+
return {
|
|
4701
|
+
action: "DENY",
|
|
4702
|
+
code: "UNSUPPORTED_VERSION",
|
|
4703
|
+
reason: `Unsupported version: ${peek[5]}`
|
|
4704
|
+
};
|
|
4705
|
+
}
|
|
4706
|
+
if (contentLen > import_axis_protocol2.MAX_FRAME_LEN) {
|
|
4707
|
+
return {
|
|
4708
|
+
action: "DENY",
|
|
4709
|
+
code: "FRAME_TOO_LARGE",
|
|
4710
|
+
reason: `Frame size ${contentLen} exceeds max ${import_axis_protocol2.MAX_FRAME_LEN}`
|
|
4711
|
+
};
|
|
4712
|
+
}
|
|
4713
|
+
return { action: "ALLOW" };
|
|
4714
|
+
}
|
|
4715
|
+
bufferEqual(a, b) {
|
|
4716
|
+
if (a.length !== b.length) return false;
|
|
4717
|
+
for (let i = 0; i < a.length; i++) {
|
|
4718
|
+
if (a[i] !== b[i]) return false;
|
|
4719
|
+
}
|
|
4720
|
+
return true;
|
|
4721
|
+
}
|
|
4722
|
+
};
|
|
4723
|
+
FrameHeaderSanitySensor = __decorateClass([
|
|
4724
|
+
(0, import_common18.Injectable)(),
|
|
4725
|
+
Sensor({ phase: "PRE_DECODE" })
|
|
4726
|
+
], FrameHeaderSanitySensor);
|
|
4727
|
+
|
|
4728
|
+
// src/sensors/header-tlv-limit.sensor.ts
|
|
4729
|
+
var import_common19 = require("@nestjs/common");
|
|
4730
|
+
var HeaderTLVLimitSensor = class {
|
|
4731
|
+
constructor() {
|
|
4732
|
+
this.name = "HeaderTLVLimitSensor";
|
|
4733
|
+
this.order = BAND.CONTENT + 0;
|
|
4734
|
+
this.MAX_TLVS = 64;
|
|
4735
|
+
}
|
|
4736
|
+
supports(input) {
|
|
4737
|
+
return !!input.headerTLVs || !!input.packet;
|
|
4738
|
+
}
|
|
4739
|
+
async run(input) {
|
|
4740
|
+
if (input.headerTLVs && input.headerTLVs.size > this.MAX_TLVS) {
|
|
4741
|
+
return {
|
|
4742
|
+
action: "DENY",
|
|
4743
|
+
code: "TOO_MANY_TLVS",
|
|
4744
|
+
reason: `Header TLVs (${input.headerTLVs.size}) exceed max (${this.MAX_TLVS})`
|
|
4745
|
+
};
|
|
4746
|
+
}
|
|
4747
|
+
if (input.packet && input.packet.headerBytes) {
|
|
4748
|
+
const hdrLen = input.packet.headerBytes.length;
|
|
4749
|
+
if (hdrLen > import_axis_protocol2.MAX_HDR_LEN) {
|
|
4750
|
+
return {
|
|
4751
|
+
action: "DENY",
|
|
4752
|
+
code: "HEADER_TOO_LARGE",
|
|
4753
|
+
reason: `Header size ${hdrLen} exceeds max ${import_axis_protocol2.MAX_HDR_LEN}`
|
|
4754
|
+
};
|
|
4755
|
+
}
|
|
4756
|
+
}
|
|
4757
|
+
return { action: "ALLOW" };
|
|
4758
|
+
}
|
|
4759
|
+
};
|
|
4760
|
+
HeaderTLVLimitSensor = __decorateClass([
|
|
4761
|
+
(0, import_common19.Injectable)(),
|
|
4762
|
+
Sensor()
|
|
4763
|
+
], HeaderTLVLimitSensor);
|
|
4764
|
+
|
|
4765
|
+
// src/sensors/intent-allowlist.sensor.ts
|
|
4766
|
+
var import_common20 = require("@nestjs/common");
|
|
4767
|
+
var PUBLIC_INTENT_ALLOWLIST = [
|
|
4768
|
+
"public.",
|
|
4769
|
+
"schema.",
|
|
4770
|
+
"catalog.",
|
|
4771
|
+
"health.",
|
|
4772
|
+
"system."
|
|
4773
|
+
];
|
|
4774
|
+
var IntentAllowlistSensor = class {
|
|
4775
|
+
constructor() {
|
|
4776
|
+
this.name = "IntentAllowlistSensor";
|
|
4777
|
+
this.order = BAND.IDENTITY + 20;
|
|
4778
|
+
}
|
|
4779
|
+
supports(input) {
|
|
4780
|
+
return !!input.intent;
|
|
4781
|
+
}
|
|
4782
|
+
async run(input) {
|
|
4783
|
+
const profile = input.metadata?.profile || "PUBLIC";
|
|
4784
|
+
const intent = input.intent || "";
|
|
4785
|
+
if (profile === "PUBLIC") {
|
|
4786
|
+
const isAllowed = PUBLIC_INTENT_ALLOWLIST.some(
|
|
4787
|
+
(prefix) => intent.startsWith(prefix)
|
|
4788
|
+
);
|
|
4789
|
+
if (!isAllowed) {
|
|
4790
|
+
return {
|
|
4791
|
+
action: "DENY",
|
|
4792
|
+
code: "INTENT_NOT_ALLOWED",
|
|
4793
|
+
reason: `Intent '${intent}' not in public allowlist`
|
|
4794
|
+
};
|
|
4795
|
+
}
|
|
4796
|
+
}
|
|
4797
|
+
return { action: "ALLOW" };
|
|
4798
|
+
}
|
|
4799
|
+
};
|
|
4800
|
+
IntentAllowlistSensor = __decorateClass([
|
|
4801
|
+
(0, import_common20.Injectable)(),
|
|
4802
|
+
Sensor()
|
|
4803
|
+
], IntentAllowlistSensor);
|
|
4804
|
+
|
|
4805
|
+
// src/sensors/intent-registry.sensor.ts
|
|
4806
|
+
var import_common21 = require("@nestjs/common");
|
|
4807
|
+
var IntentRegistrySensor = class {
|
|
4808
|
+
constructor(router) {
|
|
4809
|
+
this.router = router;
|
|
4810
|
+
this.name = "IntentRegistrySensor";
|
|
4811
|
+
this.order = BAND.IDENTITY + 25;
|
|
4812
|
+
}
|
|
4813
|
+
supports(input) {
|
|
4814
|
+
return !!input.intent;
|
|
4815
|
+
}
|
|
4816
|
+
async run(input) {
|
|
4817
|
+
const intent = input.intent;
|
|
4818
|
+
if (this.router.has(intent)) {
|
|
4819
|
+
return { action: "ALLOW" };
|
|
4820
|
+
}
|
|
4821
|
+
return {
|
|
4822
|
+
action: "DENY",
|
|
4823
|
+
code: "INTENT_NOT_REGISTERED",
|
|
4824
|
+
reason: `Intent '${intent}' is not registered`
|
|
4825
|
+
};
|
|
4826
|
+
}
|
|
4827
|
+
};
|
|
4828
|
+
IntentRegistrySensor = __decorateClass([
|
|
4829
|
+
(0, import_common21.Injectable)(),
|
|
4830
|
+
Sensor({ phase: "POST_DECODE" })
|
|
4831
|
+
], IntentRegistrySensor);
|
|
4832
|
+
|
|
4833
|
+
// src/sensors/proof-presence.sensor.ts
|
|
4834
|
+
var import_common22 = require("@nestjs/common");
|
|
4835
|
+
var ProofPresenceSensor = class {
|
|
4836
|
+
constructor() {
|
|
4837
|
+
this.name = "ProofPresenceSensor";
|
|
4838
|
+
this.order = BAND.IDENTITY + 30;
|
|
4839
|
+
}
|
|
4840
|
+
supports(input) {
|
|
4841
|
+
return !!input.profile && !!input.visibility;
|
|
4842
|
+
}
|
|
4843
|
+
async run(input) {
|
|
4844
|
+
const validatedInput = ProofPresenceInputZ.safeParse(input);
|
|
4845
|
+
if (!validatedInput.success) {
|
|
4846
|
+
throw new AxisError(
|
|
4847
|
+
"SENSOR_INVALID_INPUT",
|
|
4848
|
+
`Input validation failed: ${validatedInput.error.message}`,
|
|
4849
|
+
400
|
|
4850
|
+
);
|
|
4851
|
+
}
|
|
4852
|
+
const {
|
|
4853
|
+
visibility,
|
|
4854
|
+
requiredProof,
|
|
4855
|
+
hasCapsule,
|
|
4856
|
+
hasPassportSignature,
|
|
4857
|
+
profile,
|
|
4858
|
+
intent
|
|
4859
|
+
} = validatedInput.data;
|
|
4860
|
+
if (visibility === "PUBLIC") {
|
|
4861
|
+
return { action: "ALLOW" };
|
|
4862
|
+
}
|
|
4863
|
+
if (requiredProof.includes("NONE")) {
|
|
4864
|
+
return { action: "ALLOW" };
|
|
4865
|
+
}
|
|
4866
|
+
const hasCapsuleProof = requiredProof.includes("CAPSULE") && hasCapsule;
|
|
4867
|
+
const hasPassportProof = requiredProof.includes("PASSPORT") && hasPassportSignature;
|
|
4868
|
+
const hasNodeProof = requiredProof.includes("MTLS") && profile === "NODE";
|
|
4869
|
+
const satisfied = hasCapsuleProof || hasPassportProof || hasNodeProof;
|
|
4870
|
+
if (!satisfied) {
|
|
4871
|
+
throw new AxisError(
|
|
4872
|
+
"SENSOR_PROOF_REQUIRED",
|
|
4873
|
+
`Proof required for guarded intent: ${intent}`,
|
|
4874
|
+
403
|
|
4875
|
+
);
|
|
4876
|
+
}
|
|
4877
|
+
return { action: "ALLOW" };
|
|
4878
|
+
}
|
|
4879
|
+
};
|
|
4880
|
+
ProofPresenceSensor = __decorateClass([
|
|
4881
|
+
Sensor(),
|
|
4882
|
+
(0, import_common22.Injectable)()
|
|
4883
|
+
], ProofPresenceSensor);
|
|
4884
|
+
|
|
4885
|
+
// src/sensors/protocol-strict.sensor.ts
|
|
4886
|
+
var import_common23 = require("@nestjs/common");
|
|
4887
|
+
var VALID_FLAGS = [
|
|
4888
|
+
0,
|
|
4889
|
+
// No flags
|
|
4890
|
+
import_axis_protocol2.FLAG_BODY_TLV,
|
|
4891
|
+
// Body contains TLVs
|
|
4892
|
+
import_axis_protocol2.FLAG_CHAIN_REQ,
|
|
4893
|
+
// Requires receipt chaining
|
|
4894
|
+
import_axis_protocol2.FLAG_HAS_WITNESS,
|
|
4895
|
+
// Has witness signatures
|
|
4896
|
+
import_axis_protocol2.FLAG_BODY_TLV | import_axis_protocol2.FLAG_CHAIN_REQ,
|
|
4897
|
+
import_axis_protocol2.FLAG_BODY_TLV | import_axis_protocol2.FLAG_HAS_WITNESS,
|
|
4898
|
+
import_axis_protocol2.FLAG_CHAIN_REQ | import_axis_protocol2.FLAG_HAS_WITNESS,
|
|
4899
|
+
import_axis_protocol2.FLAG_BODY_TLV | import_axis_protocol2.FLAG_CHAIN_REQ | import_axis_protocol2.FLAG_HAS_WITNESS
|
|
4900
|
+
];
|
|
4901
|
+
var ProtocolStrictSensor = class {
|
|
4902
|
+
constructor(config) {
|
|
4903
|
+
this.config = config;
|
|
4904
|
+
this.logger = new import_common23.Logger(ProtocolStrictSensor.name);
|
|
4905
|
+
/** Sensor identifier for logging and registry */
|
|
4906
|
+
this.name = "ProtocolStrictSensor";
|
|
4907
|
+
/**
|
|
4908
|
+
* Execution order - FIRST sensor in the chain
|
|
4909
|
+
*
|
|
4910
|
+
* Order 10 ensures:
|
|
4911
|
+
* - Runs before any other processing
|
|
4912
|
+
* - Invalid frames rejected immediately
|
|
4913
|
+
* - Protects all downstream sensors from malformed input
|
|
4914
|
+
*/
|
|
4915
|
+
this.order = BAND.WIRE + 10;
|
|
4916
|
+
this.protocolMagic = import_axis_protocol2.AXIS_MAGIC;
|
|
4917
|
+
this.protocolVersion = import_axis_protocol2.AXIS_VERSION;
|
|
4918
|
+
}
|
|
4919
|
+
/**
|
|
4920
|
+
* Static validation for streaming middleware (Fast Check)
|
|
4921
|
+
*/
|
|
4922
|
+
static validateMagic(chunk, expected) {
|
|
4923
|
+
if (chunk.length < expected.length) return { valid: true };
|
|
4924
|
+
const actual = chunk.subarray(0, expected.length);
|
|
4925
|
+
const valid = Buffer.from(actual).equals(Buffer.from(expected));
|
|
4926
|
+
return {
|
|
4927
|
+
valid,
|
|
4928
|
+
actual: valid ? void 0 : new TextDecoder().decode(actual)
|
|
4929
|
+
};
|
|
4930
|
+
}
|
|
4931
|
+
static validateVersion(version, expected) {
|
|
4932
|
+
return version === expected;
|
|
4933
|
+
}
|
|
4934
|
+
/**
|
|
4935
|
+
* Lifecycle hook: Registers this sensor in the chain on module initialization.
|
|
4936
|
+
*/
|
|
4937
|
+
onModuleInit() {
|
|
4938
|
+
const magicStr = this.config.get("AXIS_PROTOCOL_MAGIC");
|
|
4939
|
+
this.protocolMagic = magicStr ? Buffer.from(magicStr, "ascii") : import_axis_protocol2.AXIS_MAGIC;
|
|
4940
|
+
this.protocolVersion = this.config.get("AXIS_PROTOCOL_VERSION") || import_axis_protocol2.AXIS_VERSION;
|
|
4941
|
+
}
|
|
4942
|
+
/**
|
|
4943
|
+
* Evaluate protocol strictness
|
|
4944
|
+
*/
|
|
4945
|
+
async run(input) {
|
|
4946
|
+
const validatedInput = ProtocolStrictInputZ.safeParse(input);
|
|
4947
|
+
if (!validatedInput.success) {
|
|
4948
|
+
this.logger.error(
|
|
4949
|
+
`Invalid input: ${validatedInput.error.message}`,
|
|
4950
|
+
validatedInput.error.issues
|
|
4951
|
+
);
|
|
4952
|
+
return {
|
|
4953
|
+
action: "DENY",
|
|
4954
|
+
code: "INVALID_INPUT",
|
|
4955
|
+
reason: "Protocol validation input failed"
|
|
4956
|
+
};
|
|
4957
|
+
}
|
|
4958
|
+
const { contentType, peek } = validatedInput.data;
|
|
4959
|
+
const issues = [];
|
|
4960
|
+
if (peek.length >= 8) {
|
|
4961
|
+
const hex = Buffer.from(peek.subarray(0, 10)).toString("hex");
|
|
4962
|
+
this.logger.debug(`Raw Frame Header (Hex): ${hex} (IP: ${input.ip})`);
|
|
4963
|
+
}
|
|
4964
|
+
if (contentType !== void 0) {
|
|
4965
|
+
if (!this.isValidContentType(contentType)) {
|
|
4966
|
+
issues.push(`invalid_content_type:${contentType}`);
|
|
4967
|
+
}
|
|
4968
|
+
}
|
|
4969
|
+
if (peek.length < 9) {
|
|
4970
|
+
return {
|
|
4971
|
+
action: "DENY",
|
|
4972
|
+
code: "FRAME_TOO_SHORT",
|
|
4973
|
+
reason: "Frame too short for protocol header"
|
|
4974
|
+
};
|
|
4975
|
+
}
|
|
4976
|
+
const magicCheck = ProtocolStrictSensor.validateMagic(
|
|
4977
|
+
peek,
|
|
4978
|
+
this.protocolMagic
|
|
4979
|
+
);
|
|
4980
|
+
if (!magicCheck.valid) {
|
|
4981
|
+
return {
|
|
4982
|
+
action: "DENY",
|
|
4983
|
+
code: "INVALID_MAGIC",
|
|
4984
|
+
reason: `Expected ${new TextDecoder().decode(this.protocolMagic)} magic, got ${magicCheck.actual}`
|
|
4985
|
+
};
|
|
4986
|
+
}
|
|
4987
|
+
const version = peek[5];
|
|
4988
|
+
if (!ProtocolStrictSensor.validateVersion(version, this.protocolVersion)) {
|
|
4989
|
+
issues.push(`unsupported_version:${version}`);
|
|
4990
|
+
}
|
|
4991
|
+
const flags = peek[6];
|
|
4992
|
+
if (!this.isValidFlags(flags)) {
|
|
4993
|
+
issues.push(`invalid_flags:0x${flags.toString(16)}`);
|
|
4994
|
+
}
|
|
4995
|
+
if (peek.length >= 10) {
|
|
4996
|
+
const lengthCheck = this.checkVarintEncoding(peek.subarray(7));
|
|
4997
|
+
if (!lengthCheck.valid) {
|
|
4998
|
+
issues.push(`non_minimal_varint:${lengthCheck.reason}`);
|
|
4999
|
+
}
|
|
5000
|
+
}
|
|
5001
|
+
if (peek.length >= 20) {
|
|
5002
|
+
const tlvCheck = this.checkTLVOrdering(peek);
|
|
5003
|
+
if (!tlvCheck.valid) {
|
|
5004
|
+
issues.push(`tlv_not_canonical:${tlvCheck.reason}`);
|
|
5005
|
+
}
|
|
5006
|
+
const hasClientVersion = await this.checkForClientVersion(peek);
|
|
5007
|
+
if (!hasClientVersion) {
|
|
5008
|
+
issues.push("missing_client_version");
|
|
5009
|
+
}
|
|
5010
|
+
}
|
|
5011
|
+
if (issues.length > 0) {
|
|
5012
|
+
const critical = issues.some(
|
|
5013
|
+
(i) => i.startsWith("invalid_magic") || i.startsWith("unsupported_version")
|
|
5014
|
+
);
|
|
5015
|
+
if (critical) {
|
|
5016
|
+
return {
|
|
5017
|
+
action: "DENY",
|
|
5018
|
+
code: "PROTOCOL_VIOLATION",
|
|
5019
|
+
reason: issues.join(", ")
|
|
5020
|
+
};
|
|
5021
|
+
}
|
|
5022
|
+
this.logger.warn(
|
|
5023
|
+
`Protocol issues from ${input.ip}: ${issues.join(", ")}`
|
|
5024
|
+
);
|
|
5025
|
+
return {
|
|
5026
|
+
action: "FLAG",
|
|
5027
|
+
scoreDelta: -issues.length * 2,
|
|
5028
|
+
reasons: issues
|
|
5029
|
+
};
|
|
5030
|
+
}
|
|
5031
|
+
return { action: "ALLOW" };
|
|
5032
|
+
}
|
|
5033
|
+
/**
|
|
5034
|
+
* Compare two buffers for equality
|
|
5035
|
+
*/
|
|
5036
|
+
buffersEqual(a, b) {
|
|
5037
|
+
if (a.length !== b.length) return false;
|
|
5038
|
+
for (let i = 0; i < a.length; i++) {
|
|
5039
|
+
if (a[i] !== b[i]) return false;
|
|
5040
|
+
}
|
|
5041
|
+
return true;
|
|
5042
|
+
}
|
|
5043
|
+
/**
|
|
5044
|
+
* Check if Content-Type is valid for AXIS
|
|
5045
|
+
*/
|
|
5046
|
+
isValidContentType(contentType) {
|
|
5047
|
+
const valid = [
|
|
5048
|
+
"application/axis-bin",
|
|
5049
|
+
"application/octet-stream",
|
|
5050
|
+
"application/x-axis"
|
|
5051
|
+
];
|
|
5052
|
+
return valid.some((v) => contentType.toLowerCase().includes(v));
|
|
5053
|
+
}
|
|
5054
|
+
/**
|
|
5055
|
+
* Check if flags are a valid combination
|
|
5056
|
+
*/
|
|
5057
|
+
isValidFlags(flags) {
|
|
5058
|
+
return VALID_FLAGS.includes(flags);
|
|
5059
|
+
}
|
|
5060
|
+
/**
|
|
5061
|
+
* Check varint encoding is minimal (no leading zeros)
|
|
5062
|
+
*/
|
|
5063
|
+
checkVarintEncoding(data) {
|
|
5064
|
+
try {
|
|
5065
|
+
const { value, length: bytesRead } = (0, import_axis_protocol3.decodeVarint)(data, 0);
|
|
5066
|
+
if (value < 128 && bytesRead > 1) {
|
|
5067
|
+
return { valid: false, reason: "non-minimal-small-value" };
|
|
5068
|
+
}
|
|
5069
|
+
if (value < 16384 && bytesRead > 2) {
|
|
5070
|
+
return { valid: false, reason: "non-minimal-medium-value" };
|
|
5071
|
+
}
|
|
5072
|
+
return { valid: true };
|
|
5073
|
+
} catch {
|
|
5074
|
+
return { valid: false, reason: "varint-decode-error" };
|
|
5075
|
+
}
|
|
5076
|
+
}
|
|
5077
|
+
/**
|
|
5078
|
+
* Check TLV ordering is canonical (sorted by type, no duplicates)
|
|
5079
|
+
*/
|
|
5080
|
+
checkTLVOrdering(data) {
|
|
5081
|
+
try {
|
|
5082
|
+
let offset = 7;
|
|
5083
|
+
const { value: hdrLen, length: hdrBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
|
|
5084
|
+
offset += hdrBytes;
|
|
5085
|
+
const { length: bodyBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
|
|
5086
|
+
offset += bodyBytes;
|
|
5087
|
+
const { length: sigBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
|
|
5088
|
+
offset += sigBytes;
|
|
5089
|
+
const hdrStart = offset;
|
|
5090
|
+
const hdrEnd = hdrStart + Number(hdrLen);
|
|
5091
|
+
if (hdrEnd > data.length) {
|
|
5092
|
+
return { valid: true };
|
|
5093
|
+
}
|
|
5094
|
+
let lastType = -1;
|
|
5095
|
+
let pos = hdrStart;
|
|
5096
|
+
while (pos < hdrEnd && pos < data.length - 2) {
|
|
5097
|
+
const { value: type, length: typeBytes } = (0, import_axis_protocol3.decodeVarint)(data, pos);
|
|
5098
|
+
pos += typeBytes;
|
|
5099
|
+
if (pos >= hdrEnd) break;
|
|
5100
|
+
const { value: len, length: lenBytes } = (0, import_axis_protocol3.decodeVarint)(data, pos);
|
|
5101
|
+
pos += lenBytes;
|
|
5102
|
+
if (Number(type) <= lastType) {
|
|
5103
|
+
return {
|
|
5104
|
+
valid: false,
|
|
5105
|
+
reason: `type-${type}-after-${lastType}`
|
|
5106
|
+
};
|
|
5107
|
+
}
|
|
5108
|
+
lastType = Number(type);
|
|
5109
|
+
pos += Number(len);
|
|
5110
|
+
}
|
|
5111
|
+
return { valid: true };
|
|
5112
|
+
} catch {
|
|
5113
|
+
return { valid: true };
|
|
5114
|
+
}
|
|
5115
|
+
}
|
|
5116
|
+
/**
|
|
5117
|
+
* Check if TLV 100 (Client Version) exists in the headers
|
|
5118
|
+
*/
|
|
5119
|
+
async checkForClientVersion(data) {
|
|
5120
|
+
try {
|
|
5121
|
+
let offset = 7;
|
|
5122
|
+
const { value: hdrLen, length: hdrBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
|
|
5123
|
+
offset += hdrBytes;
|
|
5124
|
+
const { length: bodyBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
|
|
5125
|
+
offset += bodyBytes;
|
|
5126
|
+
const { length: sigBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
|
|
5127
|
+
offset += sigBytes;
|
|
5128
|
+
const hdrEnd = offset + Number(hdrLen);
|
|
5129
|
+
let pos = offset;
|
|
5130
|
+
while (pos < hdrEnd && pos < data.length) {
|
|
5131
|
+
const { value: type, length: typeBytes } = (0, import_axis_protocol3.decodeVarint)(data, pos);
|
|
5132
|
+
pos += typeBytes;
|
|
5133
|
+
const { length: lenBytes } = (0, import_axis_protocol3.decodeVarint)(data, pos);
|
|
5134
|
+
pos += lenBytes;
|
|
5135
|
+
const { value: valLen, length: valLenBytes } = (0, import_axis_protocol3.decodeVarint)(
|
|
5136
|
+
data,
|
|
5137
|
+
pos - lenBytes
|
|
5138
|
+
);
|
|
5139
|
+
}
|
|
5140
|
+
pos = offset;
|
|
5141
|
+
while (pos < hdrEnd && pos < data.length) {
|
|
5142
|
+
const t = (0, import_axis_protocol3.decodeVarint)(data, pos);
|
|
5143
|
+
pos += t.length;
|
|
5144
|
+
const l = (0, import_axis_protocol3.decodeVarint)(data, pos);
|
|
5145
|
+
pos += l.length;
|
|
5146
|
+
if (t.value === 100) return true;
|
|
5147
|
+
pos += Number(l.value);
|
|
5148
|
+
}
|
|
5149
|
+
return false;
|
|
5150
|
+
} catch {
|
|
5151
|
+
return false;
|
|
5152
|
+
}
|
|
5153
|
+
}
|
|
5154
|
+
};
|
|
5155
|
+
ProtocolStrictSensor = __decorateClass([
|
|
5156
|
+
Sensor({ phase: "PRE_DECODE" }),
|
|
5157
|
+
(0, import_common23.Injectable)()
|
|
5158
|
+
], ProtocolStrictSensor);
|
|
5159
|
+
|
|
5160
|
+
// src/sensors/receipt-policy.sensor.ts
|
|
5161
|
+
var import_common24 = require("@nestjs/common");
|
|
5162
|
+
var ReceiptPolicySensor = class {
|
|
5163
|
+
constructor() {
|
|
5164
|
+
this.name = "ReceiptPolicySensor";
|
|
5165
|
+
this.order = BAND.BUSINESS + 20;
|
|
5166
|
+
}
|
|
5167
|
+
supports() {
|
|
5168
|
+
return true;
|
|
5169
|
+
}
|
|
5170
|
+
async run() {
|
|
5171
|
+
return { action: "ALLOW" };
|
|
5172
|
+
}
|
|
5173
|
+
};
|
|
5174
|
+
ReceiptPolicySensor = __decorateClass([
|
|
5175
|
+
(0, import_common24.Injectable)(),
|
|
5176
|
+
Sensor()
|
|
5177
|
+
], ReceiptPolicySensor);
|
|
5178
|
+
|
|
5179
|
+
// src/sensors/schema-validation.sensor.ts
|
|
5180
|
+
var import_common25 = require("@nestjs/common");
|
|
5181
|
+
function readU64be(b) {
|
|
5182
|
+
if (b.length !== 8)
|
|
5183
|
+
throw new AxisError("SCHEMA_TYPE_MISMATCH", "u64 must be 8 bytes", 400);
|
|
5184
|
+
let x = 0n;
|
|
5185
|
+
for (const by of b) x = x << 8n | BigInt(by);
|
|
5186
|
+
return x;
|
|
5187
|
+
}
|
|
5188
|
+
var SchemaValidationSensor = class {
|
|
5189
|
+
constructor() {
|
|
5190
|
+
/** Sensor identifier for logging and registry */
|
|
5191
|
+
this.name = "SchemaValidationSensor";
|
|
5192
|
+
/**
|
|
5193
|
+
* Execution order - runs late in the pipeline
|
|
5194
|
+
*
|
|
5195
|
+
* Order 170 ensures:
|
|
5196
|
+
* - All authentication complete
|
|
5197
|
+
* - All policy checks complete
|
|
5198
|
+
* - Data validated before handler execution
|
|
5199
|
+
*/
|
|
5200
|
+
this.order = BAND.CONTENT + 35;
|
|
5201
|
+
}
|
|
5202
|
+
/**
|
|
5203
|
+
* Determines if this sensor should process the given input.
|
|
5204
|
+
*
|
|
5205
|
+
* Only activates when a schema is provided for the intent (post-decode phase).
|
|
5206
|
+
*
|
|
5207
|
+
* @param {any} input - Sensor input
|
|
5208
|
+
* @returns {boolean} True if schema exists in metadata
|
|
5209
|
+
*/
|
|
5210
|
+
supports(input) {
|
|
5211
|
+
return !!input.metadata?.schema;
|
|
5212
|
+
}
|
|
5213
|
+
/**
|
|
5214
|
+
* Validates TLV fields against the schema definition.
|
|
5215
|
+
*
|
|
5216
|
+
* **Validation Steps:**
|
|
5217
|
+
* 1. Validate the schema itself using Zod
|
|
5218
|
+
* 2. Iterate through each field definition
|
|
5219
|
+
* 3. Check required fields are present
|
|
5220
|
+
* 4. Validate size limits (maxLen)
|
|
5221
|
+
* 5. Validate type-specific rules
|
|
5222
|
+
*
|
|
5223
|
+
* @param {any} input - Standard SensorInput
|
|
5224
|
+
* @returns {{ action: 'ALLOW' } | { action: 'DENY', code: string, reason: string }} Decision
|
|
5225
|
+
*/
|
|
5226
|
+
async run(input) {
|
|
5227
|
+
const schema = input.metadata?.schema;
|
|
5228
|
+
const headerTLVs = input.headerTLVs;
|
|
5229
|
+
const bodyTLVs = input.bodyTLVs;
|
|
5230
|
+
if (!schema) {
|
|
5231
|
+
return { action: "ALLOW" };
|
|
5232
|
+
}
|
|
5233
|
+
const validatedSchema = IntentSchemaZ.safeParse(schema);
|
|
5234
|
+
if (!validatedSchema.success) {
|
|
5235
|
+
return {
|
|
5236
|
+
action: "DENY",
|
|
5237
|
+
code: "SCHEMA_INVALID",
|
|
5238
|
+
reason: `Schema validation failed: ${validatedSchema.error.message}`
|
|
5239
|
+
};
|
|
5240
|
+
}
|
|
5241
|
+
try {
|
|
5242
|
+
for (const field of schema.fields) {
|
|
5243
|
+
const scope = field.scope ?? "body";
|
|
5244
|
+
const map3 = scope === "header" ? headerTLVs : bodyTLVs;
|
|
5245
|
+
const val = map3?.get(field.tlv);
|
|
5246
|
+
if (field.required && !val) {
|
|
5247
|
+
throw new AxisError(
|
|
5248
|
+
"SCHEMA_FIELD_MISSING",
|
|
5249
|
+
`Missing required field: ${field.name} (TLV ${field.tlv})`,
|
|
5250
|
+
400
|
|
5251
|
+
);
|
|
5252
|
+
}
|
|
5253
|
+
if (!val) continue;
|
|
5254
|
+
if (typeof field.maxLen === "number" && val.length > field.maxLen) {
|
|
5255
|
+
throw new AxisError(
|
|
5256
|
+
"SCHEMA_LIMIT_EXCEEDED",
|
|
5257
|
+
`Field ${field.name} too large (${val.length} > ${field.maxLen})`,
|
|
5258
|
+
413
|
|
5259
|
+
// Payload Too Large
|
|
5260
|
+
);
|
|
5261
|
+
}
|
|
5262
|
+
switch (field.kind) {
|
|
5263
|
+
case "utf8":
|
|
5264
|
+
try {
|
|
5265
|
+
new TextDecoder("utf-8", { fatal: true }).decode(val);
|
|
5266
|
+
} catch {
|
|
5267
|
+
throw new AxisError(
|
|
5268
|
+
"SCHEMA_TYPE_MISMATCH",
|
|
5269
|
+
`Invalid UTF-8 in ${field.name}`,
|
|
5270
|
+
400
|
|
5271
|
+
);
|
|
5272
|
+
}
|
|
5273
|
+
break;
|
|
5274
|
+
case "bool":
|
|
5275
|
+
if (val.length !== 1 || val[0] !== 0 && val[0] !== 1) {
|
|
5276
|
+
throw new AxisError(
|
|
5277
|
+
"SCHEMA_TYPE_MISMATCH",
|
|
5278
|
+
`Invalid bool: ${field.name}`,
|
|
5279
|
+
400
|
|
5280
|
+
);
|
|
5281
|
+
}
|
|
5282
|
+
break;
|
|
5283
|
+
case "u64": {
|
|
5284
|
+
const x = readU64be(val);
|
|
5285
|
+
if (field.max) {
|
|
5286
|
+
const mx = BigInt(field.max);
|
|
5287
|
+
if (x > mx) {
|
|
5288
|
+
throw new AxisError(
|
|
5289
|
+
"SCHEMA_LIMIT_EXCEEDED",
|
|
5290
|
+
`u64 ${field.name} exceeds max (${x} > ${mx})`,
|
|
5291
|
+
400
|
|
5292
|
+
);
|
|
5293
|
+
}
|
|
5294
|
+
}
|
|
5295
|
+
break;
|
|
5296
|
+
}
|
|
5297
|
+
case "bytes16":
|
|
5298
|
+
if (val.length !== 16) {
|
|
5299
|
+
throw new AxisError(
|
|
5300
|
+
"SCHEMA_TYPE_MISMATCH",
|
|
5301
|
+
`bytes16 required for ${field.name}`,
|
|
5302
|
+
400
|
|
5303
|
+
);
|
|
5304
|
+
}
|
|
5305
|
+
break;
|
|
5306
|
+
case "bytes":
|
|
5307
|
+
break;
|
|
5308
|
+
case "obj":
|
|
5309
|
+
case "arr":
|
|
5310
|
+
break;
|
|
5311
|
+
default:
|
|
5312
|
+
throw new AxisError(
|
|
5313
|
+
"SCHEMA_TYPE_MISMATCH",
|
|
5314
|
+
`Unknown schema kind: ${field.kind}`,
|
|
5315
|
+
500
|
|
5316
|
+
);
|
|
5317
|
+
}
|
|
5318
|
+
}
|
|
5319
|
+
const validators = input.metadata?.validators;
|
|
5320
|
+
if (validators && validators.size > 0) {
|
|
5321
|
+
for (const field of schema.fields) {
|
|
5322
|
+
const fns = validators.get(field.tlv);
|
|
5323
|
+
if (!fns || fns.length === 0) continue;
|
|
5324
|
+
const scope = field.scope ?? "body";
|
|
5325
|
+
const map3 = scope === "header" ? headerTLVs : bodyTLVs;
|
|
5326
|
+
const val = map3?.get(field.tlv);
|
|
5327
|
+
if (!val) continue;
|
|
5328
|
+
for (const fn of fns) {
|
|
5329
|
+
const error = fn(val, field.name);
|
|
5330
|
+
if (error) {
|
|
5331
|
+
throw new AxisError(
|
|
5332
|
+
"SCHEMA_VALIDATION_FAILED",
|
|
5333
|
+
`${field.name} (TLV ${field.tlv}): ${error}`,
|
|
5334
|
+
400
|
|
5335
|
+
);
|
|
5336
|
+
}
|
|
5337
|
+
}
|
|
5338
|
+
}
|
|
5339
|
+
}
|
|
5340
|
+
} catch (err) {
|
|
5341
|
+
if (err instanceof AxisError) {
|
|
5342
|
+
return {
|
|
5343
|
+
action: "DENY",
|
|
5344
|
+
code: err.code,
|
|
5345
|
+
reason: err.message
|
|
5346
|
+
};
|
|
5347
|
+
}
|
|
5348
|
+
throw err;
|
|
5349
|
+
}
|
|
5350
|
+
return { action: "ALLOW" };
|
|
5351
|
+
}
|
|
5352
|
+
};
|
|
5353
|
+
SchemaValidationSensor = __decorateClass([
|
|
5354
|
+
Sensor(),
|
|
5355
|
+
(0, import_common25.Injectable)()
|
|
5356
|
+
], SchemaValidationSensor);
|
|
5357
|
+
|
|
5358
|
+
// src/sensors/stream-scope.sensor.ts
|
|
5359
|
+
var import_common26 = require("@nestjs/common");
|
|
5360
|
+
var StreamScopeSensor = class {
|
|
5361
|
+
constructor() {
|
|
5362
|
+
/** Sensor identifier */
|
|
5363
|
+
this.name = "StreamScopeSensor";
|
|
5364
|
+
/**
|
|
5365
|
+
* Execution order - near handler execution
|
|
5366
|
+
*
|
|
5367
|
+
* Order 200 ensures:
|
|
5368
|
+
* - All authentication complete
|
|
5369
|
+
* - All policy checks complete
|
|
5370
|
+
* - Stream-specific check right before subscription
|
|
5371
|
+
*/
|
|
5372
|
+
this.order = BAND.BUSINESS + 0;
|
|
5373
|
+
}
|
|
5374
|
+
/**
|
|
5375
|
+
* Determines if this sensor should process the given input.
|
|
5376
|
+
*
|
|
5377
|
+
* Currently processes all inputs.
|
|
5378
|
+
*
|
|
5379
|
+
* @returns {boolean} Always true
|
|
5380
|
+
*/
|
|
5381
|
+
supports() {
|
|
5382
|
+
return true;
|
|
5383
|
+
}
|
|
5384
|
+
/**
|
|
5385
|
+
* Validates stream topic access permissions.
|
|
5386
|
+
*
|
|
5387
|
+
* **Current Implementation:** Stub that always allows.
|
|
5388
|
+
*
|
|
5389
|
+
* **TODO:** Full implementation should:
|
|
5390
|
+
* 1. Check if intent is stream.subscribe or stream.publish
|
|
5391
|
+
* 2. Extract topic from body TLVs
|
|
5392
|
+
* 3. Parse topic into owner/resource pattern
|
|
5393
|
+
* 4. Look up topic ACL from database/cache
|
|
5394
|
+
* 5. Check if actor has required permission (read/write)
|
|
5395
|
+
* 6. DENY if unauthorized
|
|
5396
|
+
*
|
|
5397
|
+
* @returns {Promise<SensorDecision>} ALLOW (stub implementation)
|
|
5398
|
+
*/
|
|
5399
|
+
async run() {
|
|
5400
|
+
return { action: "ALLOW" };
|
|
5401
|
+
}
|
|
5402
|
+
};
|
|
5403
|
+
StreamScopeSensor = __decorateClass([
|
|
5404
|
+
Sensor(),
|
|
5405
|
+
(0, import_common26.Injectable)()
|
|
5406
|
+
], StreamScopeSensor);
|
|
5407
|
+
|
|
5408
|
+
// src/sensors/tlv-parse.sensor.ts
|
|
5409
|
+
var import_common27 = require("@nestjs/common");
|
|
5410
|
+
var TLVParseSensor = class {
|
|
5411
|
+
constructor() {
|
|
5412
|
+
this.name = "TLVParseSensor";
|
|
5413
|
+
this.order = BAND.CONTENT + 20;
|
|
5414
|
+
}
|
|
5415
|
+
supports(input) {
|
|
5416
|
+
return !!input.packet;
|
|
5417
|
+
}
|
|
5418
|
+
async run(input) {
|
|
5419
|
+
const packet = input.packet;
|
|
5420
|
+
if (!packet) return { action: "ALLOW" };
|
|
5421
|
+
const hdrBytes = packet.hdrBytes ?? packet.headerBytes;
|
|
5422
|
+
if (hdrBytes && hdrBytes.length > 0) {
|
|
5423
|
+
const result = this.validateCanonicalTLV(hdrBytes, "header");
|
|
5424
|
+
if (result) return result;
|
|
5425
|
+
}
|
|
5426
|
+
const bodyBytes = packet.bodyBytes ?? input.body;
|
|
5427
|
+
const bodyIsTlv = packet.flags !== void 0 ? (packet.flags & 1) !== 0 : false;
|
|
5428
|
+
const bodyProfile = input.metadata?.schema?.bodyProfile;
|
|
5429
|
+
const skipBody = bodyProfile === "RAW";
|
|
5430
|
+
if (!skipBody && bodyIsTlv && bodyBytes && bodyBytes.length > 0) {
|
|
5431
|
+
const result = this.validateCanonicalTLV(bodyBytes, "body");
|
|
5432
|
+
if (result) return result;
|
|
5433
|
+
}
|
|
5434
|
+
return { action: "ALLOW" };
|
|
5435
|
+
}
|
|
5436
|
+
/**
|
|
5437
|
+
* Validates a TLV buffer for canonical ordering, no duplicates,
|
|
5438
|
+
* valid bounds, and minimal varint encoding.
|
|
5439
|
+
*/
|
|
5440
|
+
validateCanonicalTLV(buf, section) {
|
|
5441
|
+
let offset = 0;
|
|
5442
|
+
let lastType = -1;
|
|
5443
|
+
let count = 0;
|
|
5444
|
+
const maxItems = 512;
|
|
5445
|
+
while (offset < buf.length) {
|
|
5446
|
+
if (count >= maxItems) {
|
|
5447
|
+
return {
|
|
5448
|
+
action: "DENY",
|
|
5449
|
+
code: "TLV_LIMIT",
|
|
5450
|
+
reason: `Too many TLVs in ${section}`
|
|
5451
|
+
};
|
|
5452
|
+
}
|
|
5453
|
+
let type;
|
|
5454
|
+
let typeLen;
|
|
5455
|
+
try {
|
|
5456
|
+
const r = (0, import_axis_protocol3.decodeVarint)(buf, offset);
|
|
5457
|
+
type = r.value;
|
|
5458
|
+
typeLen = r.length;
|
|
5459
|
+
} catch {
|
|
5460
|
+
return {
|
|
5461
|
+
action: "DENY",
|
|
5462
|
+
code: "TLV_PARSE_ERROR",
|
|
5463
|
+
reason: `Malformed type varint in ${section} at offset ${offset}`
|
|
5464
|
+
};
|
|
5465
|
+
}
|
|
5466
|
+
offset += typeLen;
|
|
5467
|
+
if (type <= 0) {
|
|
5468
|
+
return {
|
|
5469
|
+
action: "DENY",
|
|
5470
|
+
code: "TLV_INVALID_TAG",
|
|
5471
|
+
reason: `Invalid tag ${type} in ${section}`
|
|
5472
|
+
};
|
|
5473
|
+
}
|
|
5474
|
+
if (type <= lastType) {
|
|
5475
|
+
return {
|
|
5476
|
+
action: "DENY",
|
|
5477
|
+
code: "TLV_NOT_CANONICAL",
|
|
5478
|
+
reason: `Non-canonical tag order in ${section}: ${type} after ${lastType}`
|
|
5479
|
+
};
|
|
5480
|
+
}
|
|
5481
|
+
lastType = type;
|
|
5482
|
+
let len;
|
|
5483
|
+
let lenLen;
|
|
5484
|
+
try {
|
|
5485
|
+
const r = (0, import_axis_protocol3.decodeVarint)(buf, offset);
|
|
5486
|
+
len = r.value;
|
|
5487
|
+
lenLen = r.length;
|
|
5488
|
+
} catch {
|
|
5489
|
+
return {
|
|
5490
|
+
action: "DENY",
|
|
5491
|
+
code: "TLV_PARSE_ERROR",
|
|
5492
|
+
reason: `Malformed length varint in ${section}`
|
|
5493
|
+
};
|
|
5494
|
+
}
|
|
5495
|
+
offset += lenLen;
|
|
5496
|
+
if (offset + len > buf.length) {
|
|
5497
|
+
return {
|
|
5498
|
+
action: "DENY",
|
|
5499
|
+
code: "TLV_TRUNCATED",
|
|
5500
|
+
reason: `TLV value truncated in ${section}`
|
|
5501
|
+
};
|
|
5502
|
+
}
|
|
5503
|
+
offset += len;
|
|
5504
|
+
count++;
|
|
5505
|
+
}
|
|
5506
|
+
return null;
|
|
5507
|
+
}
|
|
5508
|
+
};
|
|
5509
|
+
TLVParseSensor = __decorateClass([
|
|
5510
|
+
Sensor(),
|
|
5511
|
+
(0, import_common27.Injectable)()
|
|
5512
|
+
], TLVParseSensor);
|
|
5513
|
+
|
|
5514
|
+
// src/sensors/varint-hardening.sensor.ts
|
|
5515
|
+
var import_common28 = require("@nestjs/common");
|
|
5516
|
+
var VarintHardeningSensor = class {
|
|
5517
|
+
constructor() {
|
|
5518
|
+
/** Sensor identifier */
|
|
5519
|
+
this.name = "VarintHardeningSensor";
|
|
5520
|
+
/**
|
|
5521
|
+
* Execution order - early detection
|
|
5522
|
+
*
|
|
5523
|
+
* Order 40 ensures:
|
|
5524
|
+
* - After protocol magic check
|
|
5525
|
+
* - Before length-based parsing
|
|
5526
|
+
*/
|
|
5527
|
+
this.order = BAND.WIRE + 35;
|
|
5528
|
+
/** Maximum allowed bytes for a single varint */
|
|
5529
|
+
this.MAX_VARINT_BYTES = 5;
|
|
5530
|
+
}
|
|
5531
|
+
/**
|
|
5532
|
+
* Determines if this sensor should process the given input.
|
|
5533
|
+
*
|
|
5534
|
+
* Requires at least 7 bytes of peeked data.
|
|
5535
|
+
*
|
|
5536
|
+
* @param {SensorInput} input - Incoming request
|
|
5537
|
+
* @returns {boolean} True if sufficient peek data
|
|
5538
|
+
*/
|
|
5539
|
+
supports(input) {
|
|
5540
|
+
return !!input.peek && input.peek.length >= 7;
|
|
5541
|
+
}
|
|
5542
|
+
/**
|
|
5543
|
+
* Validates varint lengths in frame header.
|
|
5544
|
+
*
|
|
5545
|
+
* **Processing Flow:**
|
|
5546
|
+
* 1. Skip to varint section (offset 7)
|
|
5547
|
+
* 2. Scan for continuation bytes (MSB = 1)
|
|
5548
|
+
* 3. Count consecutive continuation bytes
|
|
5549
|
+
* 4. DENY if count exceeds MAX_VARINT_BYTES
|
|
5550
|
+
*
|
|
5551
|
+
* @param {SensorInput} input - Request with peek data
|
|
5552
|
+
* @returns {Promise<SensorDecision>} ALLOW or DENY based on varint length
|
|
5553
|
+
*/
|
|
5554
|
+
async run(input) {
|
|
5555
|
+
const peek = input.peek;
|
|
5556
|
+
const offset = 7;
|
|
5557
|
+
const maxOffset = Math.min(offset + 15, peek.length);
|
|
5558
|
+
let continuationCount = 0;
|
|
5559
|
+
for (let i = offset; i < maxOffset; i++) {
|
|
5560
|
+
if ((peek[i] & 128) !== 0) {
|
|
5561
|
+
continuationCount++;
|
|
5562
|
+
if (continuationCount > this.MAX_VARINT_BYTES) {
|
|
5563
|
+
return {
|
|
5564
|
+
action: "DENY",
|
|
5565
|
+
code: "VARINT_OVERFLOW",
|
|
5566
|
+
reason: `Varint exceeds ${this.MAX_VARINT_BYTES} bytes`
|
|
5567
|
+
};
|
|
5568
|
+
}
|
|
5569
|
+
} else {
|
|
5570
|
+
continuationCount = 0;
|
|
5571
|
+
}
|
|
5572
|
+
}
|
|
5573
|
+
return { action: "ALLOW" };
|
|
5574
|
+
}
|
|
5575
|
+
};
|
|
5576
|
+
VarintHardeningSensor = __decorateClass([
|
|
5577
|
+
Sensor({ phase: "PRE_DECODE" }),
|
|
5578
|
+
(0, import_common28.Injectable)()
|
|
5579
|
+
], VarintHardeningSensor);
|
|
5580
|
+
|
|
5581
|
+
// src/utils/index.ts
|
|
5582
|
+
var utils_exports = {};
|
|
5583
|
+
__export(utils_exports, {
|
|
5584
|
+
encodeAxisTlvDto: () => encodeAxisTlvDto
|
|
5585
|
+
});
|
|
5586
|
+
|
|
5587
|
+
// src/utils/axis-tlv-codec.ts
|
|
5588
|
+
function encodeAxisTlvDto(dtoClass, data) {
|
|
5589
|
+
const schema = extractDtoSchema(dtoClass);
|
|
5590
|
+
const items = schema.fields.flatMap((field) => {
|
|
5591
|
+
const value = data[field.name];
|
|
5592
|
+
if (value === void 0 || value === null) {
|
|
5593
|
+
if (field.required) {
|
|
5594
|
+
throw new Error(`Missing required TLV response field: ${field.name}`);
|
|
5595
|
+
}
|
|
5596
|
+
return [];
|
|
5597
|
+
}
|
|
5598
|
+
return [{ type: field.tag, value: encodeField(field, value) }];
|
|
5599
|
+
});
|
|
5600
|
+
return buildTLVs(items);
|
|
5601
|
+
}
|
|
5602
|
+
function encodeField(field, value) {
|
|
5603
|
+
switch (field.kind) {
|
|
5604
|
+
case "utf8":
|
|
5605
|
+
return Buffer.from(String(value), "utf8");
|
|
5606
|
+
case "u64":
|
|
5607
|
+
return encodeU64(value);
|
|
5608
|
+
case "bytes":
|
|
5609
|
+
case "bytes16":
|
|
5610
|
+
return toBuffer(value);
|
|
5611
|
+
case "bool":
|
|
5612
|
+
return Buffer.from([value ? 1 : 0]);
|
|
5613
|
+
case "obj":
|
|
5614
|
+
case "arr":
|
|
5615
|
+
return Buffer.from(JSON.stringify(value), "utf8");
|
|
5616
|
+
default:
|
|
5617
|
+
return toBuffer(value);
|
|
5618
|
+
}
|
|
5619
|
+
}
|
|
5620
|
+
function encodeU64(value) {
|
|
5621
|
+
const encoded = Buffer.alloc(8);
|
|
5622
|
+
encoded.writeBigUInt64BE(
|
|
5623
|
+
typeof value === "bigint" ? value : BigInt(value)
|
|
5624
|
+
);
|
|
5625
|
+
return encoded;
|
|
5626
|
+
}
|
|
5627
|
+
function toBuffer(value) {
|
|
5628
|
+
if (Buffer.isBuffer(value)) {
|
|
5629
|
+
return value;
|
|
5630
|
+
}
|
|
5631
|
+
if (value instanceof Uint8Array) {
|
|
5632
|
+
return Buffer.from(value);
|
|
5633
|
+
}
|
|
5634
|
+
if (typeof value === "string") {
|
|
5635
|
+
return Buffer.from(value, "utf8");
|
|
5636
|
+
}
|
|
5637
|
+
throw new Error(`Unsupported TLV bytes value: ${typeof value}`);
|
|
5638
|
+
}
|
|
2384
5639
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2385
5640
|
0 && (module.exports = {
|
|
2386
5641
|
ATS1_HDR,
|
|
2387
5642
|
ATS1_SCHEMA,
|
|
2388
5643
|
AXIS_MAGIC,
|
|
2389
5644
|
AXIS_OPCODES,
|
|
5645
|
+
AXIS_UPLOAD_FILE_STORE,
|
|
5646
|
+
AXIS_UPLOAD_RECEIPT_SIGNER,
|
|
5647
|
+
AXIS_UPLOAD_SESSION_STORE,
|
|
2390
5648
|
AXIS_VERSION,
|
|
2391
5649
|
Ats1Codec,
|
|
5650
|
+
AxisFilesDownloadHandler,
|
|
5651
|
+
AxisFilesFinalizeHandler,
|
|
2392
5652
|
AxisFrameZ,
|
|
2393
5653
|
AxisIdDto,
|
|
2394
5654
|
AxisPacketTags,
|
|
@@ -2401,6 +5661,7 @@ function isTimestampValid(ts, skewSeconds = 120) {
|
|
|
2401
5661
|
DEFAULT_CONTRACTS,
|
|
2402
5662
|
DEFAULT_TIMEOUT,
|
|
2403
5663
|
Decision,
|
|
5664
|
+
DiskUploadFileStore,
|
|
2404
5665
|
ERR_BAD_SIGNATURE,
|
|
2405
5666
|
ERR_CONTRACT_VIOLATION,
|
|
2406
5667
|
ERR_INVALID_PACKET,
|
|
@@ -2412,14 +5673,18 @@ function isTimestampValid(ts, skewSeconds = 120) {
|
|
|
2412
5673
|
FLAG_HAS_WITNESS,
|
|
2413
5674
|
HANDLER_METADATA_KEY,
|
|
2414
5675
|
Handler,
|
|
5676
|
+
INTENT_BODY_KEY,
|
|
2415
5677
|
INTENT_METADATA_KEY,
|
|
2416
5678
|
INTENT_REQUIREMENTS,
|
|
2417
5679
|
INTENT_ROUTES_KEY,
|
|
2418
5680
|
INTENT_SENSITIVITY_MAP,
|
|
5681
|
+
INTENT_SENSORS_KEY,
|
|
2419
5682
|
INTENT_TIMEOUTS,
|
|
2420
5683
|
Intent,
|
|
5684
|
+
IntentBody,
|
|
2421
5685
|
IntentRouter,
|
|
2422
5686
|
IntentSensitivity,
|
|
5687
|
+
IntentSensors,
|
|
2423
5688
|
MAX_BODY_LEN,
|
|
2424
5689
|
MAX_FRAME_LEN,
|
|
2425
5690
|
MAX_HDR_LEN,
|
|
@@ -2510,6 +5775,8 @@ function isTimestampValid(ts, skewSeconds = 120) {
|
|
|
2510
5775
|
classifyIntent,
|
|
2511
5776
|
computeReceiptHash,
|
|
2512
5777
|
computeSignaturePayload,
|
|
5778
|
+
core,
|
|
5779
|
+
crypto,
|
|
2513
5780
|
decodeArray,
|
|
2514
5781
|
decodeAxis1Frame,
|
|
2515
5782
|
decodeFrame,
|
|
@@ -2517,11 +5784,13 @@ function isTimestampValid(ts, skewSeconds = 120) {
|
|
|
2517
5784
|
decodeTLVs,
|
|
2518
5785
|
decodeTLVsList,
|
|
2519
5786
|
decodeVarint,
|
|
5787
|
+
decorators,
|
|
2520
5788
|
encVarint,
|
|
2521
5789
|
encodeAxis1Frame,
|
|
2522
5790
|
encodeFrame,
|
|
2523
5791
|
encodeTLVs,
|
|
2524
5792
|
encodeVarint,
|
|
5793
|
+
engine,
|
|
2525
5794
|
extractDtoSchema,
|
|
2526
5795
|
generateEd25519KeyPair,
|
|
2527
5796
|
getSignTarget,
|
|
@@ -2529,6 +5798,7 @@ function isTimestampValid(ts, skewSeconds = 120) {
|
|
|
2529
5798
|
isAdminOpcode,
|
|
2530
5799
|
isKnownOpcode,
|
|
2531
5800
|
isTimestampValid,
|
|
5801
|
+
loom,
|
|
2532
5802
|
nonce16,
|
|
2533
5803
|
normalizeSensorDecision,
|
|
2534
5804
|
packPasskeyLoginOptionsReq,
|
|
@@ -2538,7 +5808,10 @@ function isTimestampValid(ts, skewSeconds = 120) {
|
|
|
2538
5808
|
packPasskeyRegisterOptionsReq,
|
|
2539
5809
|
parseScope,
|
|
2540
5810
|
resolveTimeout,
|
|
5811
|
+
schemas,
|
|
5812
|
+
security,
|
|
2541
5813
|
sensitivityName,
|
|
5814
|
+
sensors,
|
|
2542
5815
|
sha256,
|
|
2543
5816
|
signFrame,
|
|
2544
5817
|
tlv,
|
|
@@ -2547,6 +5820,7 @@ function isTimestampValid(ts, skewSeconds = 120) {
|
|
|
2547
5820
|
unpackPasskeyLoginVerifyReq,
|
|
2548
5821
|
unpackPasskeyRegisterOptionsReq,
|
|
2549
5822
|
utf8,
|
|
5823
|
+
utils,
|
|
2550
5824
|
validateFrameShape,
|
|
2551
5825
|
varintLength,
|
|
2552
5826
|
varintU,
|