@agirails/sdk 2.0.1-beta → 2.0.2
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/LICENSE +190 -0
- package/README.md +116 -108
- package/bin/actp +10 -0
- package/dist/ACTPClient.d.ts +456 -33
- package/dist/ACTPClient.d.ts.map +1 -1
- package/dist/ACTPClient.js +477 -93
- package/dist/ACTPClient.js.map +1 -1
- package/dist/abi/AgentRegistry.json +782 -0
- package/dist/abi/EscrowVault.json +106 -38
- package/dist/abi/IdentityRegistry.json +316 -0
- package/dist/adapters/BaseAdapter.d.ts +231 -0
- package/dist/adapters/BaseAdapter.d.ts.map +1 -0
- package/dist/adapters/BaseAdapter.js +393 -0
- package/dist/adapters/BaseAdapter.js.map +1 -0
- package/dist/adapters/BeginnerAdapter.d.ts +152 -0
- package/dist/adapters/BeginnerAdapter.d.ts.map +1 -0
- package/dist/adapters/BeginnerAdapter.js +168 -0
- package/dist/adapters/BeginnerAdapter.js.map +1 -0
- package/dist/adapters/IntermediateAdapter.d.ts +211 -0
- package/dist/adapters/IntermediateAdapter.d.ts.map +1 -0
- package/dist/adapters/IntermediateAdapter.js +260 -0
- package/dist/adapters/IntermediateAdapter.js.map +1 -0
- package/dist/adapters/index.d.ts +15 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +26 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/builders/DeliveryProofBuilder.d.ts +60 -1
- package/dist/builders/DeliveryProofBuilder.d.ts.map +1 -1
- package/dist/builders/DeliveryProofBuilder.js +81 -5
- package/dist/builders/DeliveryProofBuilder.js.map +1 -1
- package/dist/builders/QuoteBuilder.d.ts +101 -0
- package/dist/builders/QuoteBuilder.d.ts.map +1 -1
- package/dist/builders/QuoteBuilder.js +120 -3
- package/dist/builders/QuoteBuilder.js.map +1 -1
- package/dist/builders/index.d.ts +4 -0
- package/dist/builders/index.d.ts.map +1 -1
- package/dist/builders/index.js +4 -0
- package/dist/builders/index.js.map +1 -1
- package/dist/cli/commands/balance.d.ts +13 -0
- package/dist/cli/commands/balance.d.ts.map +1 -0
- package/dist/cli/commands/balance.js +89 -0
- package/dist/cli/commands/balance.js.map +1 -0
- package/dist/cli/commands/batch.d.ts +24 -0
- package/dist/cli/commands/batch.d.ts.map +1 -0
- package/dist/cli/commands/batch.js +424 -0
- package/dist/cli/commands/batch.js.map +1 -0
- package/dist/cli/commands/config.d.ts +13 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +192 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/init.d.ts +19 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +143 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/mint.d.ts +13 -0
- package/dist/cli/commands/mint.d.ts.map +1 -0
- package/dist/cli/commands/mint.js +91 -0
- package/dist/cli/commands/mint.js.map +1 -0
- package/dist/cli/commands/pay.d.ts +18 -0
- package/dist/cli/commands/pay.d.ts.map +1 -0
- package/dist/cli/commands/pay.js +87 -0
- package/dist/cli/commands/pay.js.map +1 -0
- package/dist/cli/commands/simulate.d.ts +32 -0
- package/dist/cli/commands/simulate.d.ts.map +1 -0
- package/dist/cli/commands/simulate.js +290 -0
- package/dist/cli/commands/simulate.js.map +1 -0
- package/dist/cli/commands/time.d.ts +29 -0
- package/dist/cli/commands/time.d.ts.map +1 -0
- package/dist/cli/commands/time.js +252 -0
- package/dist/cli/commands/time.js.map +1 -0
- package/dist/cli/commands/tx.d.ts +16 -0
- package/dist/cli/commands/tx.d.ts.map +1 -0
- package/dist/cli/commands/tx.js +379 -0
- package/dist/cli/commands/tx.js.map +1 -0
- package/dist/cli/commands/watch.d.ts +20 -0
- package/dist/cli/commands/watch.d.ts.map +1 -0
- package/dist/cli/commands/watch.js +160 -0
- package/dist/cli/commands/watch.js.map +1 -0
- package/dist/cli/index.d.ts +17 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +104 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/utils/client.d.ts +70 -0
- package/dist/cli/utils/client.d.ts.map +1 -0
- package/dist/cli/utils/client.js +240 -0
- package/dist/cli/utils/client.js.map +1 -0
- package/dist/cli/utils/config.d.ts +91 -0
- package/dist/cli/utils/config.d.ts.map +1 -0
- package/dist/cli/utils/config.js +240 -0
- package/dist/cli/utils/config.js.map +1 -0
- package/dist/cli/utils/output.d.ts +174 -0
- package/dist/cli/utils/output.d.ts.map +1 -0
- package/dist/cli/utils/output.js +380 -0
- package/dist/cli/utils/output.js.map +1 -0
- package/dist/config/networks.d.ts +28 -0
- package/dist/config/networks.d.ts.map +1 -1
- package/dist/config/networks.js +60 -12
- package/dist/config/networks.js.map +1 -1
- package/dist/errors/index.d.ts +165 -2
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +260 -2
- package/dist/errors/index.js.map +1 -1
- package/dist/index.d.ts +61 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +141 -36
- package/dist/index.js.map +1 -1
- package/dist/level0/Provider.d.ts +106 -0
- package/dist/level0/Provider.d.ts.map +1 -0
- package/dist/level0/Provider.js +10 -0
- package/dist/level0/Provider.js.map +1 -0
- package/dist/level0/ServiceDirectory.d.ts +74 -0
- package/dist/level0/ServiceDirectory.d.ts.map +1 -0
- package/dist/level0/ServiceDirectory.js +122 -0
- package/dist/level0/ServiceDirectory.js.map +1 -0
- package/dist/level0/index.d.ts +10 -0
- package/dist/level0/index.d.ts.map +1 -0
- package/dist/level0/index.js +15 -0
- package/dist/level0/index.js.map +1 -0
- package/dist/level0/provide.d.ts +51 -0
- package/dist/level0/provide.d.ts.map +1 -0
- package/dist/level0/provide.js +113 -0
- package/dist/level0/provide.js.map +1 -0
- package/dist/level0/request.d.ts +53 -0
- package/dist/level0/request.d.ts.map +1 -0
- package/dist/level0/request.js +462 -0
- package/dist/level0/request.js.map +1 -0
- package/dist/level1/Agent.d.ts +472 -0
- package/dist/level1/Agent.d.ts.map +1 -0
- package/dist/level1/Agent.js +1091 -0
- package/dist/level1/Agent.js.map +1 -0
- package/dist/level1/index.d.ts +10 -0
- package/dist/level1/index.d.ts.map +1 -0
- package/dist/level1/index.js +30 -0
- package/dist/level1/index.js.map +1 -0
- package/dist/level1/pricing/PriceCalculator.d.ts +62 -0
- package/dist/level1/pricing/PriceCalculator.d.ts.map +1 -0
- package/dist/level1/pricing/PriceCalculator.js +237 -0
- package/dist/level1/pricing/PriceCalculator.js.map +1 -0
- package/dist/level1/pricing/PricingStrategy.d.ts +179 -0
- package/dist/level1/pricing/PricingStrategy.d.ts.map +1 -0
- package/dist/level1/pricing/PricingStrategy.js +11 -0
- package/dist/level1/pricing/PricingStrategy.js.map +1 -0
- package/dist/level1/types/Job.d.ts +166 -0
- package/dist/level1/types/Job.d.ts.map +1 -0
- package/dist/level1/types/Job.js +11 -0
- package/dist/level1/types/Job.js.map +1 -0
- package/dist/level1/types/Options.d.ts +258 -0
- package/dist/level1/types/Options.d.ts.map +1 -0
- package/dist/level1/types/Options.js +8 -0
- package/dist/level1/types/Options.js.map +1 -0
- package/dist/level1/types/index.d.ts +8 -0
- package/dist/level1/types/index.d.ts.map +1 -0
- package/dist/level1/types/index.js +8 -0
- package/dist/level1/types/index.js.map +1 -0
- package/dist/protocol/ACTPKernel.d.ts +229 -2
- package/dist/protocol/ACTPKernel.d.ts.map +1 -1
- package/dist/protocol/ACTPKernel.js +367 -33
- package/dist/protocol/ACTPKernel.js.map +1 -1
- package/dist/protocol/AgentRegistry.d.ts +177 -0
- package/dist/protocol/AgentRegistry.d.ts.map +1 -0
- package/dist/protocol/AgentRegistry.js +449 -0
- package/dist/protocol/AgentRegistry.js.map +1 -0
- package/dist/protocol/DIDManager.d.ts +289 -0
- package/dist/protocol/DIDManager.d.ts.map +1 -0
- package/dist/protocol/DIDManager.js +481 -0
- package/dist/protocol/DIDManager.js.map +1 -0
- package/dist/protocol/DIDResolver.d.ts +236 -0
- package/dist/protocol/DIDResolver.d.ts.map +1 -0
- package/dist/protocol/DIDResolver.js +495 -0
- package/dist/protocol/DIDResolver.js.map +1 -0
- package/dist/protocol/EASHelper.d.ts +57 -2
- package/dist/protocol/EASHelper.d.ts.map +1 -1
- package/dist/protocol/EASHelper.js +230 -37
- package/dist/protocol/EASHelper.js.map +1 -1
- package/dist/protocol/EscrowVault.d.ts +93 -2
- package/dist/protocol/EscrowVault.d.ts.map +1 -1
- package/dist/protocol/EscrowVault.js +122 -33
- package/dist/protocol/EscrowVault.js.map +1 -1
- package/dist/protocol/EventMonitor.d.ts +45 -1
- package/dist/protocol/EventMonitor.d.ts.map +1 -1
- package/dist/protocol/EventMonitor.js +64 -8
- package/dist/protocol/EventMonitor.js.map +1 -1
- package/dist/protocol/MessageSigner.d.ts +116 -2
- package/dist/protocol/MessageSigner.d.ts.map +1 -1
- package/dist/protocol/MessageSigner.js +215 -9
- package/dist/protocol/MessageSigner.js.map +1 -1
- package/dist/protocol/ProofGenerator.d.ts +93 -0
- package/dist/protocol/ProofGenerator.d.ts.map +1 -1
- package/dist/protocol/ProofGenerator.js +194 -9
- package/dist/protocol/ProofGenerator.js.map +1 -1
- package/dist/protocol/QuoteBuilder.d.ts +8 -0
- package/dist/protocol/QuoteBuilder.d.ts.map +1 -1
- package/dist/protocol/QuoteBuilder.js +8 -0
- package/dist/protocol/QuoteBuilder.js.map +1 -1
- package/dist/runtime/BlockchainRuntime.d.ts +360 -0
- package/dist/runtime/BlockchainRuntime.d.ts.map +1 -0
- package/dist/runtime/BlockchainRuntime.js +767 -0
- package/dist/runtime/BlockchainRuntime.js.map +1 -0
- package/dist/runtime/IACTPRuntime.d.ts +271 -0
- package/dist/runtime/IACTPRuntime.d.ts.map +1 -0
- package/dist/runtime/IACTPRuntime.js +15 -0
- package/dist/runtime/IACTPRuntime.js.map +1 -0
- package/dist/runtime/MockRuntime.d.ts +445 -0
- package/dist/runtime/MockRuntime.d.ts.map +1 -0
- package/dist/runtime/MockRuntime.js +1065 -0
- package/dist/runtime/MockRuntime.js.map +1 -0
- package/dist/runtime/MockStateManager.d.ts +233 -0
- package/dist/runtime/MockStateManager.d.ts.map +1 -0
- package/dist/runtime/MockStateManager.js +533 -0
- package/dist/runtime/MockStateManager.js.map +1 -0
- package/dist/runtime/index.d.ts +14 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +42 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/types/MockState.d.ts +167 -0
- package/dist/runtime/types/MockState.d.ts.map +1 -0
- package/dist/runtime/types/MockState.js +43 -0
- package/dist/runtime/types/MockState.js.map +1 -0
- package/dist/types/agent.d.ts +76 -0
- package/dist/types/agent.d.ts.map +1 -0
- package/dist/types/agent.js +8 -0
- package/dist/types/agent.js.map +1 -0
- package/dist/types/did.d.ts +192 -0
- package/dist/types/did.d.ts.map +1 -0
- package/dist/types/did.js +38 -0
- package/dist/types/did.js.map +1 -0
- package/dist/types/eip712.d.ts +34 -0
- package/dist/types/eip712.d.ts.map +1 -1
- package/dist/types/eip712.js +31 -5
- package/dist/types/eip712.js.map +1 -1
- package/dist/types/escrow.d.ts +17 -10
- package/dist/types/escrow.d.ts.map +1 -1
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +8 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/message.d.ts +32 -0
- package/dist/types/message.d.ts.map +1 -1
- package/dist/types/message.js +4 -0
- package/dist/types/message.js.map +1 -1
- package/dist/types/state.d.ts +28 -0
- package/dist/types/state.d.ts.map +1 -1
- package/dist/types/state.js +37 -6
- package/dist/types/state.js.map +1 -1
- package/dist/types/transaction.d.ts +17 -0
- package/dist/types/transaction.d.ts.map +1 -1
- package/dist/utils/ErrorRecoveryGuide.d.ts +125 -0
- package/dist/utils/ErrorRecoveryGuide.d.ts.map +1 -0
- package/dist/utils/ErrorRecoveryGuide.js +579 -0
- package/dist/utils/ErrorRecoveryGuide.js.map +1 -0
- package/dist/utils/Helpers.d.ts +453 -0
- package/dist/utils/Helpers.d.ts.map +1 -0
- package/dist/utils/Helpers.js +623 -0
- package/dist/utils/Helpers.js.map +1 -0
- package/dist/utils/IPFSClient.d.ts +113 -0
- package/dist/utils/IPFSClient.d.ts.map +1 -1
- package/dist/utils/IPFSClient.js +128 -7
- package/dist/utils/IPFSClient.js.map +1 -1
- package/dist/utils/Logger.d.ts +195 -0
- package/dist/utils/Logger.d.ts.map +1 -0
- package/dist/utils/Logger.js +382 -0
- package/dist/utils/Logger.js.map +1 -0
- package/dist/utils/NonceManager.d.ts +234 -1
- package/dist/utils/NonceManager.d.ts.map +1 -1
- package/dist/utils/NonceManager.js +372 -7
- package/dist/utils/NonceManager.js.map +1 -1
- package/dist/utils/RateLimiter.d.ts +253 -0
- package/dist/utils/RateLimiter.d.ts.map +1 -0
- package/dist/utils/RateLimiter.js +424 -0
- package/dist/utils/RateLimiter.js.map +1 -0
- package/dist/utils/ReceivedNonceTracker.d.ts +175 -0
- package/dist/utils/ReceivedNonceTracker.d.ts.map +1 -1
- package/dist/utils/ReceivedNonceTracker.js +261 -5
- package/dist/utils/ReceivedNonceTracker.js.map +1 -1
- package/dist/utils/SDKLifecycle.d.ts +156 -0
- package/dist/utils/SDKLifecycle.d.ts.map +1 -0
- package/dist/utils/SDKLifecycle.js +347 -0
- package/dist/utils/SDKLifecycle.js.map +1 -0
- package/dist/utils/SecureNonce.d.ts +57 -0
- package/dist/utils/SecureNonce.d.ts.map +1 -0
- package/dist/utils/SecureNonce.js +80 -0
- package/dist/utils/SecureNonce.js.map +1 -0
- package/dist/utils/Semaphore.d.ts +123 -0
- package/dist/utils/Semaphore.d.ts.map +1 -0
- package/dist/utils/Semaphore.js +247 -0
- package/dist/utils/Semaphore.js.map +1 -0
- package/dist/utils/UsedAttestationTracker.d.ts +167 -0
- package/dist/utils/UsedAttestationTracker.d.ts.map +1 -0
- package/dist/utils/UsedAttestationTracker.js +309 -0
- package/dist/utils/UsedAttestationTracker.js.map +1 -0
- package/dist/utils/canonicalJson.d.ts +22 -0
- package/dist/utils/canonicalJson.d.ts.map +1 -1
- package/dist/utils/canonicalJson.js +26 -3
- package/dist/utils/canonicalJson.js.map +1 -1
- package/dist/utils/computeTypeHash.d.ts +14 -0
- package/dist/utils/computeTypeHash.d.ts.map +1 -1
- package/dist/utils/computeTypeHash.js +19 -2
- package/dist/utils/computeTypeHash.js.map +1 -1
- package/dist/utils/fsSafe.d.ts +14 -0
- package/dist/utils/fsSafe.d.ts.map +1 -0
- package/dist/utils/fsSafe.js +89 -0
- package/dist/utils/fsSafe.js.map +1 -0
- package/dist/utils/index.d.ts +15 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +51 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/security.d.ts +147 -0
- package/dist/utils/security.d.ts.map +1 -0
- package/dist/utils/security.js +391 -0
- package/dist/utils/security.js.map +1 -0
- package/dist/utils/validation.d.ts +40 -0
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +184 -7
- package/dist/utils/validation.js.map +1 -1
- package/package.json +54 -37
- package/src/ACTPClient.ts +692 -178
- package/src/abi/AgentRegistry.json +782 -0
- package/src/abi/EscrowVault.json +106 -38
- package/src/abi/IdentityRegistry.json +316 -0
- package/src/adapters/BaseAdapter.ts +473 -0
- package/src/adapters/BeginnerAdapter.ts +232 -0
- package/src/adapters/IntermediateAdapter.ts +316 -0
- package/src/adapters/index.ts +25 -0
- package/src/builders/DeliveryProofBuilder.ts +3 -2
- package/src/cli/commands/balance.ts +110 -0
- package/src/cli/commands/batch.ts +487 -0
- package/src/cli/commands/config.ts +231 -0
- package/src/cli/commands/init.ts +161 -0
- package/src/cli/commands/mint.ts +116 -0
- package/src/cli/commands/pay.ts +113 -0
- package/src/cli/commands/simulate.ts +345 -0
- package/src/cli/commands/time.ts +303 -0
- package/src/cli/commands/tx.ts +448 -0
- package/src/cli/commands/watch.ts +211 -0
- package/src/cli/index.ts +116 -0
- package/src/cli/utils/client.ts +249 -0
- package/src/cli/utils/config.ts +282 -0
- package/src/cli/utils/output.ts +465 -0
- package/src/config/networks.ts +32 -9
- package/src/errors/index.ts +298 -1
- package/src/index.ts +207 -71
- package/src/level0/Provider.ts +117 -0
- package/src/level0/ServiceDirectory.ts +131 -0
- package/src/level0/index.ts +10 -0
- package/src/level0/provide.ts +131 -0
- package/src/level0/request.ts +494 -0
- package/src/level1/Agent.ts +1432 -0
- package/src/level1/index.ts +10 -0
- package/src/level1/pricing/PriceCalculator.ts +255 -0
- package/src/level1/pricing/PricingStrategy.ts +198 -0
- package/src/level1/types/Job.ts +179 -0
- package/src/level1/types/Options.ts +291 -0
- package/src/level1/types/index.ts +8 -0
- package/src/protocol/ACTPKernel.ts +175 -23
- package/src/protocol/AgentRegistry.ts +559 -0
- package/src/protocol/DIDManager.ts +629 -0
- package/src/protocol/DIDResolver.ts +554 -0
- package/src/protocol/EASHelper.ts +230 -46
- package/src/protocol/EscrowVault.ts +68 -50
- package/src/protocol/EventMonitor.ts +44 -15
- package/src/protocol/MessageSigner.ts +193 -13
- package/src/protocol/ProofGenerator.ts +223 -4
- package/src/runtime/BlockchainRuntime.ts +993 -0
- package/src/runtime/IACTPRuntime.ts +284 -0
- package/src/runtime/MockRuntime.ts +1244 -0
- package/src/runtime/MockStateManager.ts +576 -0
- package/src/runtime/index.ts +25 -0
- package/src/runtime/types/MockState.ts +227 -0
- package/src/types/agent.ts +79 -0
- package/src/types/did.ts +223 -0
- package/src/types/escrow.ts +12 -11
- package/src/types/index.ts +5 -1
- package/src/types/state.ts +12 -3
- package/src/types/transaction.ts +4 -1
- package/src/utils/ErrorRecoveryGuide.ts +675 -0
- package/src/utils/Helpers.ts +688 -0
- package/src/utils/IPFSClient.ts +122 -5
- package/src/utils/Logger.ts +484 -0
- package/src/utils/NonceManager.ts +305 -8
- package/src/utils/RateLimiter.ts +534 -0
- package/src/utils/ReceivedNonceTracker.ts +170 -0
- package/src/utils/SDKLifecycle.ts +416 -0
- package/src/utils/SecureNonce.ts +78 -0
- package/src/utils/Semaphore.ts +276 -0
- package/src/utils/UsedAttestationTracker.ts +387 -0
- package/src/utils/fsSafe.ts +75 -0
- package/src/utils/index.ts +80 -0
- package/src/utils/security.ts +418 -0
- package/src/utils/validation.ts +164 -0
- package/src/__tests__/ProofGenerator.test.ts +0 -124
- package/src/__tests__/QuoteBuilder.test.ts +0 -516
- package/src/__tests__/StateMachine.test.ts +0 -82
- package/src/__tests__/builders/DeliveryProofBuilder.test.ts +0 -581
- package/src/__tests__/integration/ACTPClient.test.ts +0 -263
- package/src/__tests__/integration.test.ts +0 -289
- package/src/__tests__/protocol/EASHelper.test.ts +0 -472
- package/src/__tests__/protocol/EventMonitor.test.ts +0 -382
- package/src/__tests__/security/ACTPKernel.security.test.ts +0 -1167
- package/src/__tests__/security/EscrowVault.security.test.ts +0 -570
- package/src/__tests__/security/MessageSigner.security.test.ts +0 -286
- package/src/__tests__/security/NonceReplay.security.test.ts +0 -501
- package/src/__tests__/security/validation.security.test.ts +0 -376
- package/src/__tests__/utils/IPFSClient.test.ts +0 -262
- package/src/__tests__/utils/NonceManager.test.ts +0 -205
- package/src/__tests__/utils/canonicalJson.test.ts +0 -153
|
@@ -32,15 +32,79 @@ interface SignerWithTypedData extends Signer {
|
|
|
32
32
|
* Reference: Yellow Paper §11.4.2
|
|
33
33
|
*
|
|
34
34
|
* V4 Security Enhancement: Optional nonce replay protection via ReceivedNonceTracker
|
|
35
|
+
*
|
|
36
|
+
* IMPORTANT: Use MessageSigner.create() factory method to ensure domain is initialized.
|
|
35
37
|
*/
|
|
36
38
|
export class MessageSigner {
|
|
37
39
|
private domain: EIP712Domain | null = null;
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
/**
|
|
42
|
+
* SECURITY FIX (H-5): Private constructor - MUST use MessageSigner.create() factory method
|
|
43
|
+
*
|
|
44
|
+
* This ensures EIP-712 domain is ALWAYS initialized before use (prevents race conditions).
|
|
45
|
+
* Direct construction would allow calling sign/verify without domain initialization.
|
|
46
|
+
*/
|
|
47
|
+
private constructor(
|
|
40
48
|
private readonly signer: Signer,
|
|
41
49
|
private readonly nonceTracker?: IReceivedNonceTracker
|
|
42
50
|
) {}
|
|
43
51
|
|
|
52
|
+
/**
|
|
53
|
+
* SECURITY FIX (H-4): Factory method to create MessageSigner with guaranteed domain initialization
|
|
54
|
+
*
|
|
55
|
+
* This factory ensures the EIP-712 domain is always properly initialized before use.
|
|
56
|
+
* Prevents the common bug of calling sign/verify without initializing domain first.
|
|
57
|
+
*
|
|
58
|
+
* @param signer - Ethers signer for signing messages
|
|
59
|
+
* @param kernelAddress - Address of ACTP Kernel contract (for domain separation)
|
|
60
|
+
* @param options - Optional configuration (chainId, nonceTracker)
|
|
61
|
+
* @returns Promise resolving to initialized MessageSigner
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* const messageSigner = await MessageSigner.create(
|
|
66
|
+
* signer,
|
|
67
|
+
* KERNEL_ADDRESS,
|
|
68
|
+
* { chainId: 84532 }
|
|
69
|
+
* );
|
|
70
|
+
* const signature = await messageSigner.signMessage(message);
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
static async create(
|
|
74
|
+
signer: Signer,
|
|
75
|
+
kernelAddress: string,
|
|
76
|
+
options?: {
|
|
77
|
+
chainId?: number;
|
|
78
|
+
nonceTracker?: IReceivedNonceTracker;
|
|
79
|
+
}
|
|
80
|
+
): Promise<MessageSigner> {
|
|
81
|
+
const messageSigner = new MessageSigner(signer, options?.nonceTracker);
|
|
82
|
+
await messageSigner.initDomain(kernelAddress, options?.chainId);
|
|
83
|
+
return messageSigner;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check if domain is initialized
|
|
88
|
+
* @returns true if domain has been initialized
|
|
89
|
+
*/
|
|
90
|
+
isDomainInitialized(): boolean {
|
|
91
|
+
return this.domain !== null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get the current domain (throws if not initialized)
|
|
96
|
+
* @returns Current EIP-712 domain
|
|
97
|
+
* @throws Error if domain not initialized
|
|
98
|
+
*/
|
|
99
|
+
getDomain(): EIP712Domain {
|
|
100
|
+
if (!this.domain) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
'Domain not initialized. Use MessageSigner.create() factory or call initDomain() first.'
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
return this.domain;
|
|
106
|
+
}
|
|
107
|
+
|
|
44
108
|
/**
|
|
45
109
|
* Initialize EIP-712 domain (must be called before signing)
|
|
46
110
|
* @param kernelAddress - Address of ACTP Kernel contract
|
|
@@ -67,8 +131,10 @@ export class MessageSigner {
|
|
|
67
131
|
}
|
|
68
132
|
}
|
|
69
133
|
|
|
134
|
+
// SECURITY FIX (H-6): Standardize domain name to 'AGIRAILS' for brand consistency
|
|
135
|
+
// Note: This change requires coordination with any existing signed messages
|
|
70
136
|
this.domain = {
|
|
71
|
-
name: '
|
|
137
|
+
name: 'AGIRAILS',
|
|
72
138
|
version: '1.0',
|
|
73
139
|
chainId: resolvedChainId,
|
|
74
140
|
verifyingContract: kernelAddress
|
|
@@ -78,17 +144,55 @@ export class MessageSigner {
|
|
|
78
144
|
/**
|
|
79
145
|
* Sign ACTP message using EIP-712 typed data
|
|
80
146
|
* Uses ECDSA (secp256k1) with domain separation per Yellow Paper §11.4.2
|
|
81
|
-
*
|
|
147
|
+
*
|
|
148
|
+
* SECURITY FIX (H-3): Validates nonce format and warns about sequential nonces
|
|
149
|
+
*
|
|
82
150
|
* Generic ACTPMessage format (backward compatible).
|
|
83
151
|
* For strict typed AIP messages, use signQuoteRequest/signQuoteResponse/signDeliveryProof
|
|
84
152
|
*/
|
|
85
153
|
async signMessage(message: ACTPMessage): Promise<string> {
|
|
86
154
|
if (!this.domain) {
|
|
87
|
-
throw new Error(
|
|
155
|
+
throw new Error(
|
|
156
|
+
'Domain not initialized. Use MessageSigner.create() factory or call initDomain() first.'
|
|
157
|
+
);
|
|
88
158
|
}
|
|
89
159
|
|
|
90
160
|
const { type, version, from, to, timestamp, nonce, signature, ...payload } = message;
|
|
91
161
|
|
|
162
|
+
// SECURITY FIX (H-3): Validate nonce format (must be bytes32)
|
|
163
|
+
if (!nonce || !/^0x[a-fA-F0-9]{64}$/.test(nonce)) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Invalid nonce format: "${nonce}". ` +
|
|
166
|
+
`Nonce MUST be a bytes32 hex string (0x + 64 hex chars). ` +
|
|
167
|
+
`Use SecureNonce.generateSecureNonce() to generate cryptographically secure nonces. ` +
|
|
168
|
+
`Never use sequential integers (1, 2, 3...) or timestamps as nonces.`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// SECURITY FIX (H-3): Warn about sequential nonces (low entropy)
|
|
173
|
+
// Sequential nonces like 0x0000...0001, 0x0000...0002 are weak
|
|
174
|
+
// Check if nonce has low entropy (e.g., last 8 bytes are zero, or all same digits)
|
|
175
|
+
const nonceValue = BigInt(nonce);
|
|
176
|
+
if (nonceValue < 0xFFFFFFFFn) {
|
|
177
|
+
// Nonce is suspiciously small (< 4 billion = likely sequential)
|
|
178
|
+
console.warn(
|
|
179
|
+
`[SECURITY WARNING] Nonce ${nonce} appears to be sequential (value < 2^32). ` +
|
|
180
|
+
`This makes replay attacks easier. ` +
|
|
181
|
+
`Use SecureNonce.generateSecureNonce() for cryptographically secure random nonces.`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Check if nonce has all same digits (e.g., 0x111...111 or 0x000...000)
|
|
186
|
+
const hexDigits = nonce.slice(2); // Remove '0x'
|
|
187
|
+
const firstDigit = hexDigits[0];
|
|
188
|
+
if (hexDigits.split('').every(d => d === firstDigit)) {
|
|
189
|
+
console.warn(
|
|
190
|
+
`[SECURITY WARNING] Nonce ${nonce} has low entropy (all digits are '${firstDigit}'). ` +
|
|
191
|
+
`This is NOT cryptographically secure. ` +
|
|
192
|
+
`Use SecureNonce.generateSecureNonce() instead.`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
92
196
|
// Generic ACTPMessage with payload encoding (backward compatible)
|
|
93
197
|
const abiCoder = AbiCoder.defaultAbiCoder();
|
|
94
198
|
const payloadBytes = abiCoder.encode(
|
|
@@ -121,7 +225,9 @@ export class MessageSigner {
|
|
|
121
225
|
*/
|
|
122
226
|
async signQuoteRequest(data: QuoteRequestData): Promise<string> {
|
|
123
227
|
if (!this.domain) {
|
|
124
|
-
throw new Error(
|
|
228
|
+
throw new Error(
|
|
229
|
+
'Domain not initialized. Use MessageSigner.create() factory or call initDomain() first.'
|
|
230
|
+
);
|
|
125
231
|
}
|
|
126
232
|
|
|
127
233
|
const messageTypes = getMessageTypes('quote.request');
|
|
@@ -134,7 +240,9 @@ export class MessageSigner {
|
|
|
134
240
|
*/
|
|
135
241
|
async signQuoteResponse(data: QuoteResponseData): Promise<string> {
|
|
136
242
|
if (!this.domain) {
|
|
137
|
-
throw new Error(
|
|
243
|
+
throw new Error(
|
|
244
|
+
'Domain not initialized. Use MessageSigner.create() factory or call initDomain() first.'
|
|
245
|
+
);
|
|
138
246
|
}
|
|
139
247
|
|
|
140
248
|
const messageTypes = getMessageTypes('quote.response');
|
|
@@ -147,7 +255,9 @@ export class MessageSigner {
|
|
|
147
255
|
*/
|
|
148
256
|
async signDeliveryProof(data: DeliveryProofData): Promise<string> {
|
|
149
257
|
if (!this.domain) {
|
|
150
|
-
throw new Error(
|
|
258
|
+
throw new Error(
|
|
259
|
+
'Domain not initialized. Use MessageSigner.create() factory or call initDomain() first.'
|
|
260
|
+
);
|
|
151
261
|
}
|
|
152
262
|
|
|
153
263
|
const messageTypes = getMessageTypes('delivery.proof');
|
|
@@ -171,7 +281,9 @@ export class MessageSigner {
|
|
|
171
281
|
*/
|
|
172
282
|
async verifySignature(message: ACTPMessage, signature: string): Promise<boolean> {
|
|
173
283
|
if (!this.domain) {
|
|
174
|
-
throw new Error(
|
|
284
|
+
throw new Error(
|
|
285
|
+
'Domain not initialized. Use MessageSigner.create() factory or call initDomain() first.'
|
|
286
|
+
);
|
|
175
287
|
}
|
|
176
288
|
|
|
177
289
|
const { type, version, from, to, timestamp, nonce, signature: _, ...payload } = message;
|
|
@@ -308,29 +420,97 @@ export class MessageSigner {
|
|
|
308
420
|
|
|
309
421
|
/**
|
|
310
422
|
* Convert DID to Ethereum address
|
|
311
|
-
*
|
|
423
|
+
*
|
|
424
|
+
* SECURITY FIX (DID-FORMAT): Handles both DID formats:
|
|
425
|
+
* - Legacy: did:ethr:<address>
|
|
426
|
+
* - Canonical (EIP-3770): did:ethr:<chainId>:<address>
|
|
427
|
+
*
|
|
428
|
+
* Examples:
|
|
429
|
+
* - "did:ethr:0x1234...abcd" → "0x1234...abcd"
|
|
430
|
+
* - "did:ethr:84532:0x1234...abcd" → "0x1234...abcd"
|
|
431
|
+
* - "0x1234...abcd" → "0x1234...abcd" (raw address passthrough)
|
|
312
432
|
*/
|
|
313
433
|
private didToAddress(did: string): string {
|
|
314
|
-
|
|
315
|
-
|
|
434
|
+
// Check for DID format first
|
|
435
|
+
const DID_PREFIX = 'did:ethr:';
|
|
436
|
+
if (did.startsWith(DID_PREFIX)) {
|
|
437
|
+
const remainder = did.slice(DID_PREFIX.length);
|
|
438
|
+
|
|
439
|
+
// Check if it's canonical format: did:ethr:<chainId>:<address>
|
|
440
|
+
// chainId is numeric, address starts with 0x
|
|
441
|
+
const parts = remainder.split(':');
|
|
442
|
+
|
|
443
|
+
if (parts.length === 2) {
|
|
444
|
+
// Canonical format: did:ethr:<chainId>:<address>
|
|
445
|
+
const [chainIdStr, address] = parts;
|
|
446
|
+
const chainId = parseInt(chainIdStr, 10);
|
|
447
|
+
|
|
448
|
+
if (isNaN(chainId)) {
|
|
449
|
+
throw new Error(
|
|
450
|
+
`Invalid DID format: ${did}. ` +
|
|
451
|
+
`Expected did:ethr:<chainId>:<address> but chainId "${chainIdStr}" is not a number.`
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (!ethers.isAddress(address)) {
|
|
456
|
+
throw new Error(
|
|
457
|
+
`Invalid DID format: ${did}. ` +
|
|
458
|
+
`Expected did:ethr:<chainId>:<address> but "${address}" is not a valid Ethereum address.`
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// SECURITY: Optionally validate chainId matches domain chainId
|
|
463
|
+
// This prevents cross-chain replay attacks where a message signed for one chain
|
|
464
|
+
// is replayed on another. For now, we just extract the address but log a warning.
|
|
465
|
+
if (this.domain && this.domain.chainId !== chainId) {
|
|
466
|
+
console.warn(
|
|
467
|
+
`[SECURITY WARNING] DID chainId (${chainId}) does not match domain chainId (${this.domain.chainId}). ` +
|
|
468
|
+
`This could indicate a cross-chain replay attempt. DID: ${did}`
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return address;
|
|
473
|
+
} else if (parts.length === 1 && ethers.isAddress(parts[0])) {
|
|
474
|
+
// Legacy format: did:ethr:<address>
|
|
475
|
+
return parts[0];
|
|
476
|
+
} else {
|
|
477
|
+
throw new Error(
|
|
478
|
+
`Invalid DID format: ${did}. ` +
|
|
479
|
+
`Expected did:ethr:<address> or did:ethr:<chainId>:<address>.`
|
|
480
|
+
);
|
|
481
|
+
}
|
|
316
482
|
}
|
|
317
483
|
|
|
318
|
-
// If already an address, return as-is
|
|
484
|
+
// If already an address (raw 0x format), return as-is
|
|
319
485
|
if (ethers.isAddress(did)) {
|
|
320
486
|
return did;
|
|
321
487
|
}
|
|
322
488
|
|
|
323
|
-
throw new Error(
|
|
489
|
+
throw new Error(
|
|
490
|
+
`Invalid DID format: ${did}. ` +
|
|
491
|
+
`Expected Ethereum address (0x...) or DID (did:ethr:...).`
|
|
492
|
+
);
|
|
324
493
|
}
|
|
325
494
|
|
|
326
495
|
/**
|
|
327
496
|
* Convert Ethereum address to DID
|
|
497
|
+
*
|
|
498
|
+
* SECURITY FIX (DID-FORMAT): Now generates canonical DID format
|
|
499
|
+
* with chainId when domain is initialized: did:ethr:<chainId>:<address>
|
|
500
|
+
*
|
|
501
|
+
* Falls back to legacy format if domain not initialized.
|
|
328
502
|
*/
|
|
329
503
|
addressToDID(address: string): string {
|
|
330
504
|
if (!ethers.isAddress(address)) {
|
|
331
505
|
throw new Error(`Invalid Ethereum address: ${address}`);
|
|
332
506
|
}
|
|
333
507
|
|
|
508
|
+
// Use canonical format with chainId if domain is initialized
|
|
509
|
+
if (this.domain && this.domain.chainId) {
|
|
510
|
+
return `did:ethr:${this.domain.chainId}:${address}`;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Fallback to legacy format (backward compatible)
|
|
334
514
|
return `did:ethr:${address}`;
|
|
335
515
|
}
|
|
336
516
|
}
|
|
@@ -2,11 +2,83 @@ import { keccak256, toUtf8Bytes, AbiCoder, BytesLike } from 'ethers';
|
|
|
2
2
|
import { DeliveryProof } from '../types';
|
|
3
3
|
import { DeliveryProofData, deliveryProofDataFromProof } from '../types/eip712';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* SECURITY FIX (MEDIUM-2): URL validation configuration for SSRF prevention
|
|
7
|
+
*/
|
|
8
|
+
export interface URLValidationConfig {
|
|
9
|
+
/**
|
|
10
|
+
* Allowed URL protocols (default: ['https:'])
|
|
11
|
+
* Set to ['https:', 'http:'] to allow HTTP in development
|
|
12
|
+
*/
|
|
13
|
+
allowedProtocols?: string[];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Allow localhost URLs (default: false in production, true in dev)
|
|
17
|
+
*/
|
|
18
|
+
allowLocalhost?: boolean;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Maximum response size in bytes (default: 10MB)
|
|
22
|
+
*/
|
|
23
|
+
maxSize?: number;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Request timeout in milliseconds (default: 30000)
|
|
27
|
+
*/
|
|
28
|
+
timeout?: number;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Blocked hostnames (e.g., internal services)
|
|
32
|
+
*/
|
|
33
|
+
blockedHosts?: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Default URL validation config - SECURE by default
|
|
38
|
+
*/
|
|
39
|
+
const DEFAULT_URL_CONFIG: Required<URLValidationConfig> = {
|
|
40
|
+
allowedProtocols: ['https:'],
|
|
41
|
+
allowLocalhost: false,
|
|
42
|
+
maxSize: 10 * 1024 * 1024, // 10MB
|
|
43
|
+
timeout: 30000, // 30 seconds
|
|
44
|
+
blockedHosts: [
|
|
45
|
+
'metadata.google.internal',
|
|
46
|
+
'169.254.169.254', // AWS/GCP metadata
|
|
47
|
+
'metadata.aws.internal',
|
|
48
|
+
'localhost',
|
|
49
|
+
'127.0.0.1',
|
|
50
|
+
'0.0.0.0',
|
|
51
|
+
'[::1]',
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
|
|
5
55
|
/**
|
|
6
56
|
* ProofGenerator - Content hashing and delivery proofs
|
|
7
57
|
* Reference: Yellow Paper §11.4.1
|
|
58
|
+
*
|
|
59
|
+
* SECURITY FIX (MEDIUM-2): Now includes URL validation for SSRF prevention
|
|
8
60
|
*/
|
|
9
61
|
export class ProofGenerator {
|
|
62
|
+
private readonly urlConfig: Required<URLValidationConfig>;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Create ProofGenerator with optional URL validation config
|
|
66
|
+
*
|
|
67
|
+
* @param urlConfig - URL validation configuration for hashFromUrl()
|
|
68
|
+
*/
|
|
69
|
+
constructor(urlConfig?: URLValidationConfig) {
|
|
70
|
+
this.urlConfig = {
|
|
71
|
+
...DEFAULT_URL_CONFIG,
|
|
72
|
+
...urlConfig,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// If localhost is explicitly allowed, remove from blocked hosts
|
|
76
|
+
if (urlConfig?.allowLocalhost) {
|
|
77
|
+
this.urlConfig.blockedHosts = this.urlConfig.blockedHosts.filter(
|
|
78
|
+
(h) => !['localhost', '127.0.0.1', '0.0.0.0', '[::1]'].includes(h)
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
10
82
|
/**
|
|
11
83
|
* Hash deliverable content
|
|
12
84
|
* Uses Keccak256 per Yellow Paper §11.4.1
|
|
@@ -104,16 +176,163 @@ export class ProofGenerator {
|
|
|
104
176
|
|
|
105
177
|
/**
|
|
106
178
|
* Generate content hash from URL (for IPFS/Arweave)
|
|
179
|
+
*
|
|
180
|
+
* SECURITY FIX (MEDIUM-2): Now includes:
|
|
181
|
+
* - Protocol validation (HTTPS only by default)
|
|
182
|
+
* - Hostname blocklist (prevents SSRF to internal services)
|
|
183
|
+
* - Size limits (prevents DoS via large responses)
|
|
184
|
+
* - Request timeout
|
|
185
|
+
*
|
|
186
|
+
* @param url - URL to fetch content from
|
|
187
|
+
* @returns Keccak256 hash of content
|
|
188
|
+
* @throws Error if URL is blocked, too large, or fetch fails
|
|
107
189
|
*/
|
|
108
190
|
async hashFromUrl(url: string): Promise<string> {
|
|
191
|
+
// SECURITY FIX (MEDIUM-2): Validate URL before fetching
|
|
192
|
+
this.validateUrl(url);
|
|
193
|
+
|
|
109
194
|
// In browser/Node.js environment with fetch
|
|
110
195
|
try {
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
|
|
196
|
+
// Create AbortController for timeout
|
|
197
|
+
const controller = new AbortController();
|
|
198
|
+
const timeoutId = setTimeout(() => controller.abort(), this.urlConfig.timeout);
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
const response = await fetch(url, {
|
|
202
|
+
signal: controller.signal,
|
|
203
|
+
// Prevent following redirects to blocked hosts
|
|
204
|
+
redirect: 'follow',
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
clearTimeout(timeoutId);
|
|
208
|
+
|
|
209
|
+
if (!response.ok) {
|
|
210
|
+
throw new Error(`HTTP error: ${response.status} ${response.statusText}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// SECURITY FIX (MEDIUM-2): Check Content-Length header first
|
|
214
|
+
const contentLength = response.headers.get('content-length');
|
|
215
|
+
if (contentLength) {
|
|
216
|
+
const size = parseInt(contentLength, 10);
|
|
217
|
+
if (size > this.urlConfig.maxSize) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
`Content too large: ${size} bytes exceeds maximum of ${this.urlConfig.maxSize} bytes`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// SECURITY FIX (MEDIUM-2): Read response with size limit
|
|
225
|
+
const chunks: Uint8Array[] = [];
|
|
226
|
+
let totalSize = 0;
|
|
227
|
+
const reader = response.body?.getReader();
|
|
228
|
+
|
|
229
|
+
if (!reader) {
|
|
230
|
+
throw new Error('Response body is not readable');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
while (true) {
|
|
234
|
+
const { done, value } = await reader.read();
|
|
235
|
+
|
|
236
|
+
if (done) break;
|
|
237
|
+
|
|
238
|
+
totalSize += value.length;
|
|
239
|
+
|
|
240
|
+
// Check size limit during streaming
|
|
241
|
+
if (totalSize > this.urlConfig.maxSize) {
|
|
242
|
+
reader.cancel();
|
|
243
|
+
throw new Error(
|
|
244
|
+
`Content too large: ${totalSize}+ bytes exceeds maximum of ${this.urlConfig.maxSize} bytes`
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
chunks.push(value);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Concatenate chunks
|
|
252
|
+
const buffer = Buffer.concat(chunks.map(c => Buffer.from(c)));
|
|
253
|
+
return this.hashContent(buffer);
|
|
254
|
+
} finally {
|
|
255
|
+
clearTimeout(timeoutId);
|
|
256
|
+
}
|
|
115
257
|
} catch (error) {
|
|
258
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
259
|
+
throw new Error(`Request timed out after ${this.urlConfig.timeout}ms for ${url}`);
|
|
260
|
+
}
|
|
116
261
|
throw new Error(`Failed to fetch content from ${url}: ${error}`);
|
|
117
262
|
}
|
|
118
263
|
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* SECURITY FIX (MEDIUM-2): Validate URL against security rules
|
|
267
|
+
*
|
|
268
|
+
* @param url - URL to validate
|
|
269
|
+
* @throws Error if URL is not allowed
|
|
270
|
+
*/
|
|
271
|
+
private validateUrl(url: string): void {
|
|
272
|
+
let parsed: URL;
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
parsed = new URL(url);
|
|
276
|
+
} catch {
|
|
277
|
+
throw new Error(`Invalid URL: ${url}`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Check protocol
|
|
281
|
+
if (!this.urlConfig.allowedProtocols.includes(parsed.protocol)) {
|
|
282
|
+
throw new Error(
|
|
283
|
+
`URL protocol "${parsed.protocol}" not allowed. ` +
|
|
284
|
+
`Allowed protocols: ${this.urlConfig.allowedProtocols.join(', ')}`
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Check blocked hosts
|
|
289
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
290
|
+
if (this.urlConfig.blockedHosts.includes(hostname)) {
|
|
291
|
+
throw new Error(
|
|
292
|
+
`URL hostname "${hostname}" is blocked for security reasons. ` +
|
|
293
|
+
`This prevents SSRF attacks to internal services.`
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Check for private IP ranges (additional SSRF protection)
|
|
298
|
+
if (this.isPrivateIP(hostname)) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
`URL hostname "${hostname}" resolves to a private IP address. ` +
|
|
301
|
+
`This is blocked for security reasons (SSRF prevention).`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Check if hostname is a private IP address
|
|
308
|
+
*
|
|
309
|
+
* @param hostname - Hostname to check
|
|
310
|
+
* @returns true if hostname is a private IP
|
|
311
|
+
*/
|
|
312
|
+
private isPrivateIP(hostname: string): boolean {
|
|
313
|
+
// IPv4 private ranges
|
|
314
|
+
const ipv4PrivateRanges = [
|
|
315
|
+
/^10\./, // 10.0.0.0 - 10.255.255.255
|
|
316
|
+
/^172\.(1[6-9]|2[0-9]|3[0-1])\./, // 172.16.0.0 - 172.31.255.255
|
|
317
|
+
/^192\.168\./, // 192.168.0.0 - 192.168.255.255
|
|
318
|
+
/^127\./, // 127.0.0.0 - 127.255.255.255 (loopback)
|
|
319
|
+
/^169\.254\./, // 169.254.0.0 - 169.254.255.255 (link-local)
|
|
320
|
+
/^0\./, // 0.0.0.0/8
|
|
321
|
+
];
|
|
322
|
+
|
|
323
|
+
for (const range of ipv4PrivateRanges) {
|
|
324
|
+
if (range.test(hostname)) {
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Get the URL validation config (for testing/inspection)
|
|
334
|
+
*/
|
|
335
|
+
getUrlConfig(): Required<URLValidationConfig> {
|
|
336
|
+
return { ...this.urlConfig };
|
|
337
|
+
}
|
|
119
338
|
}
|