@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/bin/generate-keys.d.mts +2 -0
- package/dist/bin/generate-keys.d.ts +2 -0
- package/dist/bin/generate-keys.js +159 -0
- package/dist/bin/generate-keys.js.map +1 -0
- package/dist/bin/generate-keys.mjs +169 -0
- package/dist/bin/generate-keys.mjs.map +1 -0
- package/dist/core/index.d.mts +2 -25
- package/dist/core/index.d.ts +2 -25
- package/dist/core/index.js +13 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +12 -0
- package/dist/core/index.mjs.map +1 -1
- package/dist/index-B5xzROld.d.mts +122 -0
- package/dist/index-B5xzROld.d.ts +122 -0
- package/dist/index.d.mts +1380 -10
- package/dist/index.d.ts +1380 -10
- package/dist/index.js +2838 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2829 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +14 -2
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,
|