@nextera.one/axis-server-sdk 1.2.1 → 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/index.js CHANGED
@@ -175,6 +175,8 @@ __export(index_exports, {
175
175
  classifyIntent: () => classifyIntent,
176
176
  computeReceiptHash: () => computeReceiptHash,
177
177
  computeSignaturePayload: () => computeSignaturePayload,
178
+ core: () => core_exports,
179
+ crypto: () => crypto_exports,
178
180
  decodeArray: () => import_axis_protocol.decodeArray,
179
181
  decodeAxis1Frame: () => decodeAxis1Frame,
180
182
  decodeFrame: () => decodeFrame,
@@ -182,11 +184,13 @@ __export(index_exports, {
182
184
  decodeTLVs: () => import_axis_protocol.decodeTLVs,
183
185
  decodeTLVsList: () => import_axis_protocol.decodeTLVsList,
184
186
  decodeVarint: () => import_axis_protocol3.decodeVarint,
187
+ decorators: () => decorators_exports,
185
188
  encVarint: () => encVarint,
186
189
  encodeAxis1Frame: () => encodeAxis1Frame,
187
190
  encodeFrame: () => encodeFrame,
188
191
  encodeTLVs: () => import_axis_protocol.encodeTLVs,
189
192
  encodeVarint: () => import_axis_protocol3.encodeVarint,
193
+ engine: () => engine_exports,
190
194
  extractDtoSchema: () => extractDtoSchema,
191
195
  generateEd25519KeyPair: () => generateEd25519KeyPair,
192
196
  getSignTarget: () => getSignTarget,
@@ -194,6 +198,7 @@ __export(index_exports, {
194
198
  isAdminOpcode: () => isAdminOpcode,
195
199
  isKnownOpcode: () => isKnownOpcode,
196
200
  isTimestampValid: () => isTimestampValid,
201
+ loom: () => loom_exports,
197
202
  nonce16: () => nonce16,
198
203
  normalizeSensorDecision: () => normalizeSensorDecision,
199
204
  packPasskeyLoginOptionsReq: () => packPasskeyLoginOptionsReq,
@@ -203,7 +208,10 @@ __export(index_exports, {
203
208
  packPasskeyRegisterOptionsReq: () => packPasskeyRegisterOptionsReq,
204
209
  parseScope: () => parseScope,
205
210
  resolveTimeout: () => resolveTimeout,
211
+ schemas: () => schemas_exports,
212
+ security: () => security_exports,
206
213
  sensitivityName: () => sensitivityName,
214
+ sensors: () => sensors_exports,
207
215
  sha256: () => sha256,
208
216
  signFrame: () => signFrame,
209
217
  tlv: () => tlv,
@@ -212,6 +220,7 @@ __export(index_exports, {
212
220
  unpackPasskeyLoginVerifyReq: () => unpackPasskeyLoginVerifyReq,
213
221
  unpackPasskeyRegisterOptionsReq: () => unpackPasskeyRegisterOptionsReq,
214
222
  utf8: () => utf8,
223
+ utils: () => utils_exports,
215
224
  validateFrameShape: () => validateFrameShape,
216
225
  varintLength: () => import_axis_protocol3.varintLength,
217
226
  varintU: () => varintU,
@@ -2807,6 +2816,2826 @@ var DiskUploadFileStore = class {
2807
2816
  return fs.createReadStream(tempPath);
2808
2817
  }
2809
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
+ }
2810
5639
  // Annotate the CommonJS export names for ESM import in node:
2811
5640
  0 && (module.exports = {
2812
5641
  ATS1_HDR,
@@ -2946,6 +5775,8 @@ var DiskUploadFileStore = class {
2946
5775
  classifyIntent,
2947
5776
  computeReceiptHash,
2948
5777
  computeSignaturePayload,
5778
+ core,
5779
+ crypto,
2949
5780
  decodeArray,
2950
5781
  decodeAxis1Frame,
2951
5782
  decodeFrame,
@@ -2953,11 +5784,13 @@ var DiskUploadFileStore = class {
2953
5784
  decodeTLVs,
2954
5785
  decodeTLVsList,
2955
5786
  decodeVarint,
5787
+ decorators,
2956
5788
  encVarint,
2957
5789
  encodeAxis1Frame,
2958
5790
  encodeFrame,
2959
5791
  encodeTLVs,
2960
5792
  encodeVarint,
5793
+ engine,
2961
5794
  extractDtoSchema,
2962
5795
  generateEd25519KeyPair,
2963
5796
  getSignTarget,
@@ -2965,6 +5798,7 @@ var DiskUploadFileStore = class {
2965
5798
  isAdminOpcode,
2966
5799
  isKnownOpcode,
2967
5800
  isTimestampValid,
5801
+ loom,
2968
5802
  nonce16,
2969
5803
  normalizeSensorDecision,
2970
5804
  packPasskeyLoginOptionsReq,
@@ -2974,7 +5808,10 @@ var DiskUploadFileStore = class {
2974
5808
  packPasskeyRegisterOptionsReq,
2975
5809
  parseScope,
2976
5810
  resolveTimeout,
5811
+ schemas,
5812
+ security,
2977
5813
  sensitivityName,
5814
+ sensors,
2978
5815
  sha256,
2979
5816
  signFrame,
2980
5817
  tlv,
@@ -2983,6 +5820,7 @@ var DiskUploadFileStore = class {
2983
5820
  unpackPasskeyLoginVerifyReq,
2984
5821
  unpackPasskeyRegisterOptionsReq,
2985
5822
  utf8,
5823
+ utils,
2986
5824
  validateFrameShape,
2987
5825
  varintLength,
2988
5826
  varintU,