@agirails/sdk 2.5.3 → 2.5.5
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/ACTPClient.d.ts +18 -0
- package/dist/ACTPClient.d.ts.map +1 -1
- package/dist/ACTPClient.js +72 -23
- package/dist/ACTPClient.js.map +1 -1
- package/dist/adapters/BasicAdapter.d.ts +15 -0
- package/dist/adapters/BasicAdapter.d.ts.map +1 -1
- package/dist/adapters/BasicAdapter.js +33 -4
- package/dist/adapters/BasicAdapter.js.map +1 -1
- package/dist/adapters/StandardAdapter.d.ts +20 -3
- package/dist/adapters/StandardAdapter.d.ts.map +1 -1
- package/dist/adapters/StandardAdapter.js +90 -12
- package/dist/adapters/StandardAdapter.js.map +1 -1
- package/dist/cli/commands/publish.js +16 -4
- package/dist/cli/commands/publish.js.map +1 -1
- package/dist/cli/commands/register.js +16 -4
- package/dist/cli/commands/register.js.map +1 -1
- package/dist/cli/commands/tx.js +31 -3
- package/dist/cli/commands/tx.js.map +1 -1
- package/dist/config/networks.d.ts +10 -2
- package/dist/config/networks.d.ts.map +1 -1
- package/dist/config/networks.js +31 -22
- package/dist/config/networks.js.map +1 -1
- package/dist/level0/request.d.ts.map +1 -1
- package/dist/level0/request.js +2 -1
- package/dist/level0/request.js.map +1 -1
- package/dist/runtime/BlockchainRuntime.d.ts.map +1 -1
- package/dist/runtime/BlockchainRuntime.js +11 -5
- package/dist/runtime/BlockchainRuntime.js.map +1 -1
- package/dist/utils/IPFSClient.d.ts +3 -1
- package/dist/utils/IPFSClient.d.ts.map +1 -1
- package/dist/utils/IPFSClient.js +27 -7
- package/dist/utils/IPFSClient.js.map +1 -1
- package/dist/wallet/AutoWalletProvider.d.ts +11 -1
- package/dist/wallet/AutoWalletProvider.d.ts.map +1 -1
- package/dist/wallet/AutoWalletProvider.js +84 -19
- package/dist/wallet/AutoWalletProvider.js.map +1 -1
- package/dist/wallet/IWalletProvider.d.ts +34 -0
- package/dist/wallet/IWalletProvider.d.ts.map +1 -1
- package/dist/wallet/SmartWalletRouter.d.ts +128 -0
- package/dist/wallet/SmartWalletRouter.d.ts.map +1 -0
- package/dist/wallet/SmartWalletRouter.js +248 -0
- package/dist/wallet/SmartWalletRouter.js.map +1 -0
- package/dist/wallet/aa/DualNonceManager.d.ts +26 -1
- package/dist/wallet/aa/DualNonceManager.d.ts.map +1 -1
- package/dist/wallet/aa/DualNonceManager.js +140 -6
- package/dist/wallet/aa/DualNonceManager.js.map +1 -1
- package/package.json +3 -6
- package/src/ACTPClient.ts +0 -1579
- package/src/abi/ACTPKernel.json +0 -1356
- package/src/abi/AgentRegistry.json +0 -915
- package/src/abi/ERC20.json +0 -40
- package/src/abi/EscrowVault.json +0 -134
- package/src/abi/IdentityRegistry.json +0 -316
- package/src/adapters/AdapterRegistry.ts +0 -173
- package/src/adapters/AdapterRouter.ts +0 -416
- package/src/adapters/BaseAdapter.ts +0 -498
- package/src/adapters/BasicAdapter.ts +0 -514
- package/src/adapters/IAdapter.ts +0 -292
- package/src/adapters/StandardAdapter.ts +0 -555
- package/src/adapters/X402Adapter.ts +0 -731
- package/src/adapters/index.ts +0 -60
- package/src/builders/DeliveryProofBuilder.ts +0 -327
- package/src/builders/QuoteBuilder.ts +0 -483
- package/src/builders/index.ts +0 -17
- package/src/cli/commands/balance.ts +0 -110
- package/src/cli/commands/batch.ts +0 -487
- package/src/cli/commands/config.ts +0 -231
- package/src/cli/commands/deploy-check.ts +0 -364
- package/src/cli/commands/deploy-env.ts +0 -120
- package/src/cli/commands/diff.ts +0 -141
- package/src/cli/commands/init.ts +0 -469
- package/src/cli/commands/mint.ts +0 -116
- package/src/cli/commands/pay.ts +0 -113
- package/src/cli/commands/publish.ts +0 -475
- package/src/cli/commands/pull.ts +0 -124
- package/src/cli/commands/register.ts +0 -247
- package/src/cli/commands/simulate.ts +0 -345
- package/src/cli/commands/time.ts +0 -302
- package/src/cli/commands/tx.ts +0 -448
- package/src/cli/commands/watch.ts +0 -211
- package/src/cli/index.ts +0 -134
- package/src/cli/utils/client.ts +0 -252
- package/src/cli/utils/config.ts +0 -389
- package/src/cli/utils/output.ts +0 -465
- package/src/cli/utils/wallet.ts +0 -109
- package/src/config/agirailsmd.ts +0 -262
- package/src/config/networks.ts +0 -275
- package/src/config/pendingPublish.ts +0 -237
- package/src/config/publishPipeline.ts +0 -359
- package/src/config/syncOperations.ts +0 -279
- package/src/erc8004/ERC8004Bridge.ts +0 -462
- package/src/erc8004/ReputationReporter.ts +0 -468
- package/src/erc8004/index.ts +0 -61
- package/src/errors/index.ts +0 -427
- package/src/index.ts +0 -364
- package/src/level0/Provider.ts +0 -117
- package/src/level0/ServiceDirectory.ts +0 -131
- package/src/level0/index.ts +0 -10
- package/src/level0/provide.ts +0 -132
- package/src/level0/request.ts +0 -432
- package/src/level1/Agent.ts +0 -1426
- package/src/level1/index.ts +0 -10
- package/src/level1/pricing/PriceCalculator.ts +0 -255
- package/src/level1/pricing/PricingStrategy.ts +0 -198
- package/src/level1/types/Job.ts +0 -179
- package/src/level1/types/Options.ts +0 -291
- package/src/level1/types/index.ts +0 -8
- package/src/protocol/ACTPKernel.ts +0 -808
- package/src/protocol/AgentRegistry.ts +0 -559
- package/src/protocol/DIDManager.ts +0 -629
- package/src/protocol/DIDResolver.ts +0 -554
- package/src/protocol/EASHelper.ts +0 -378
- package/src/protocol/EscrowVault.ts +0 -255
- package/src/protocol/EventMonitor.ts +0 -204
- package/src/protocol/MessageSigner.ts +0 -510
- package/src/protocol/ProofGenerator.ts +0 -339
- package/src/protocol/QuoteBuilder.ts +0 -15
- package/src/registry/AgentRegistryClient.ts +0 -202
- package/src/runtime/BlockchainRuntime.ts +0 -1015
- package/src/runtime/IACTPRuntime.ts +0 -306
- package/src/runtime/MockRuntime.ts +0 -1298
- package/src/runtime/MockStateManager.ts +0 -577
- package/src/runtime/index.ts +0 -25
- package/src/runtime/types/MockState.ts +0 -237
- package/src/storage/ArchiveBundleBuilder.ts +0 -561
- package/src/storage/ArweaveClient.ts +0 -946
- package/src/storage/FilebaseClient.ts +0 -790
- package/src/storage/index.ts +0 -96
- package/src/storage/types.ts +0 -348
- package/src/types/adapter.ts +0 -310
- package/src/types/agent.ts +0 -79
- package/src/types/did.ts +0 -223
- package/src/types/eip712.ts +0 -175
- package/src/types/erc8004.ts +0 -293
- package/src/types/escrow.ts +0 -27
- package/src/types/index.ts +0 -17
- package/src/types/message.ts +0 -145
- package/src/types/state.ts +0 -87
- package/src/types/transaction.ts +0 -69
- package/src/types/x402.ts +0 -251
- package/src/utils/ErrorRecoveryGuide.ts +0 -676
- package/src/utils/Helpers.ts +0 -688
- package/src/utils/IPFSClient.ts +0 -368
- package/src/utils/Logger.ts +0 -484
- package/src/utils/NonceManager.ts +0 -591
- package/src/utils/RateLimiter.ts +0 -534
- package/src/utils/ReceivedNonceTracker.ts +0 -567
- package/src/utils/SDKLifecycle.ts +0 -416
- package/src/utils/SecureNonce.ts +0 -78
- package/src/utils/Semaphore.ts +0 -276
- package/src/utils/UsedAttestationTracker.ts +0 -385
- package/src/utils/canonicalJson.ts +0 -38
- package/src/utils/circuitBreaker.ts +0 -324
- package/src/utils/computeTypeHash.ts +0 -48
- package/src/utils/fsSafe.ts +0 -80
- package/src/utils/index.ts +0 -80
- package/src/utils/retry.ts +0 -364
- package/src/utils/security.ts +0 -418
- package/src/utils/validation.ts +0 -540
- package/src/wallet/AutoWalletProvider.ts +0 -299
- package/src/wallet/EOAWalletProvider.ts +0 -69
- package/src/wallet/IWalletProvider.ts +0 -135
- package/src/wallet/aa/BundlerClient.ts +0 -274
- package/src/wallet/aa/DualNonceManager.ts +0 -173
- package/src/wallet/aa/PaymasterClient.ts +0 -174
- package/src/wallet/aa/TransactionBatcher.ts +0 -353
- package/src/wallet/aa/UserOpBuilder.ts +0 -246
- package/src/wallet/aa/constants.ts +0 -60
- package/src/wallet/keystore.ts +0 -240
package/src/utils/validation.ts
DELETED
|
@@ -1,540 +0,0 @@
|
|
|
1
|
-
import { isAddress, getAddress } from 'ethers';
|
|
2
|
-
import {
|
|
3
|
-
InvalidAddressError,
|
|
4
|
-
InvalidAmountError,
|
|
5
|
-
ValidationError,
|
|
6
|
-
InvalidCIDError,
|
|
7
|
-
InvalidArweaveTxIdError
|
|
8
|
-
} from '../errors';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Input validation utilities
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
// ============================================================================
|
|
15
|
-
// Shared Validation Patterns (AIP-7 Storage)
|
|
16
|
-
// ============================================================================
|
|
17
|
-
|
|
18
|
-
/** Ethereum address validation pattern */
|
|
19
|
-
export const ADDRESS_PATTERN = /^0x[a-fA-F0-9]{40}$/;
|
|
20
|
-
|
|
21
|
-
/** Transaction ID (bytes32) validation pattern */
|
|
22
|
-
export const TX_ID_PATTERN = /^0x[a-fA-F0-9]{64}$/;
|
|
23
|
-
|
|
24
|
-
/** Hash (bytes32) validation pattern */
|
|
25
|
-
export const HASH_PATTERN = /^0x[a-fA-F0-9]{64}$/;
|
|
26
|
-
|
|
27
|
-
/** Signature (65 bytes = 130 hex chars) validation pattern */
|
|
28
|
-
export const SIGNATURE_PATTERN = /^0x[a-fA-F0-9]{130}$/;
|
|
29
|
-
|
|
30
|
-
/** CID validation pattern (CIDv0 or CIDv1) */
|
|
31
|
-
export const CID_PATTERN = /^(Qm[1-9A-HJ-NP-Za-km-z]{44}|b[a-z2-7]{58,})$/;
|
|
32
|
-
|
|
33
|
-
/** Arweave TX ID pattern (43 characters, base64url) */
|
|
34
|
-
export const ARWEAVE_TX_ID_PATTERN = /^[a-zA-Z0-9_-]{43}$/;
|
|
35
|
-
|
|
36
|
-
/** Semver pattern for version validation */
|
|
37
|
-
export const SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
38
|
-
|
|
39
|
-
// ============================================================================
|
|
40
|
-
// Gateway URL Whitelist (SSRF Protection - P0-1)
|
|
41
|
-
// ============================================================================
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Allowed IPFS gateway domains
|
|
45
|
-
* Only whitelisted gateways are allowed for downloads
|
|
46
|
-
*/
|
|
47
|
-
export const ALLOWED_IPFS_GATEWAYS = [
|
|
48
|
-
'ipfs.filebase.io',
|
|
49
|
-
'gateway.pinata.cloud',
|
|
50
|
-
'cloudflare-ipfs.com',
|
|
51
|
-
'ipfs.io',
|
|
52
|
-
'dweb.link',
|
|
53
|
-
'w3s.link',
|
|
54
|
-
'nftstorage.link'
|
|
55
|
-
] as const;
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Allowed Arweave gateway domains
|
|
59
|
-
* Only whitelisted gateways are allowed for downloads
|
|
60
|
-
*/
|
|
61
|
-
export const ALLOWED_ARWEAVE_GATEWAYS = [
|
|
62
|
-
'arweave.net',
|
|
63
|
-
'gateway.irys.xyz',
|
|
64
|
-
'arweave.dev'
|
|
65
|
-
] as const;
|
|
66
|
-
|
|
67
|
-
// ============================================================================
|
|
68
|
-
// Validation Functions
|
|
69
|
-
// ============================================================================
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Validate Ethereum address
|
|
73
|
-
*/
|
|
74
|
-
export function validateAddress(address: string, fieldName: string = 'address'): void {
|
|
75
|
-
if (!address || !isAddress(address)) {
|
|
76
|
-
throw new InvalidAddressError(address);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (address === getAddress('0x0000000000000000000000000000000000000000')) {
|
|
80
|
-
throw new ValidationError(fieldName, 'Address cannot be zero address');
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Validate amount (must be > 0)
|
|
86
|
-
*/
|
|
87
|
-
export function validateAmount(amount: bigint, _fieldName: string = 'amount'): void {
|
|
88
|
-
// Handle null/undefined before calling toString()
|
|
89
|
-
if (!amount) {
|
|
90
|
-
throw new InvalidAmountError(String(amount)); // Convert safely to string
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (amount <= 0n) {
|
|
94
|
-
throw new InvalidAmountError(amount.toString());
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Validate deadline (must be future timestamp)
|
|
100
|
-
*/
|
|
101
|
-
export function validateDeadline(deadline: number, fieldName: string = 'deadline'): void {
|
|
102
|
-
const now = Math.floor(Date.now() / 1000);
|
|
103
|
-
|
|
104
|
-
if (deadline <= now) {
|
|
105
|
-
throw new ValidationError(
|
|
106
|
-
fieldName,
|
|
107
|
-
`Deadline must be in the future (now: ${now}, deadline: ${deadline})`
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Validate dispute window (max 30 days per spec)
|
|
114
|
-
*/
|
|
115
|
-
export function validateDisputeWindow(
|
|
116
|
-
disputeWindow: number,
|
|
117
|
-
fieldName: string = 'disputeWindow'
|
|
118
|
-
): void {
|
|
119
|
-
const MAX_DISPUTE_WINDOW = 30 * 24 * 60 * 60; // 30 days in seconds
|
|
120
|
-
|
|
121
|
-
if (disputeWindow < 0) {
|
|
122
|
-
throw new ValidationError(fieldName, 'Dispute window cannot be negative');
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (disputeWindow > MAX_DISPUTE_WINDOW) {
|
|
126
|
-
throw new ValidationError(
|
|
127
|
-
fieldName,
|
|
128
|
-
`Dispute window exceeds maximum (${MAX_DISPUTE_WINDOW}s = 30 days)`
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Validate transaction ID format
|
|
135
|
-
*/
|
|
136
|
-
export function validateTxId(txId: string, fieldName: string = 'txId'): void {
|
|
137
|
-
if (!txId || !txId.match(/^0x[a-fA-F0-9]{64}$/)) {
|
|
138
|
-
throw new ValidationError(fieldName, 'Invalid transaction ID format (expected bytes32)');
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Check if IP address is private/local (SSRF protection)
|
|
144
|
-
*
|
|
145
|
-
* SECURITY FIX (H-1): Comprehensive private IP detection
|
|
146
|
-
* - IPv4: 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16
|
|
147
|
-
* - IPv6: ::1, fc00::/7, fd00::/8, fe80::/10
|
|
148
|
-
* - IPv4-mapped IPv6: ::ffff:127.0.0.0/8, ::ffff:10.0.0.0/8, etc.
|
|
149
|
-
*
|
|
150
|
-
* @param ip - IP address (v4 or v6, no brackets)
|
|
151
|
-
* @returns true if IP is private/local
|
|
152
|
-
*/
|
|
153
|
-
function isPrivateIP(ip: string): boolean {
|
|
154
|
-
// Remove IPv6 brackets if present
|
|
155
|
-
const cleanIP = ip.replace(/^\[|\]$/g, '');
|
|
156
|
-
|
|
157
|
-
// IPv4 patterns
|
|
158
|
-
const ipv4PrivatePatterns = [
|
|
159
|
-
/^127\./, // Loopback
|
|
160
|
-
/^10\./, // Private class A
|
|
161
|
-
/^172\.(1[6-9]|2\d|3[01])\./, // Private class B (172.16-172.31)
|
|
162
|
-
/^192\.168\./, // Private class C
|
|
163
|
-
/^169\.254\./, // Link-local / AWS metadata
|
|
164
|
-
/^0\./, // Invalid source
|
|
165
|
-
/^localhost$/i // Localhost hostname
|
|
166
|
-
];
|
|
167
|
-
|
|
168
|
-
for (const pattern of ipv4PrivatePatterns) {
|
|
169
|
-
if (pattern.test(cleanIP)) {
|
|
170
|
-
return true;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// IPv6 patterns (without brackets)
|
|
175
|
-
// Includes both standard ::ffff: and alternative ::ffff:0: notation (NEW-2 fix)
|
|
176
|
-
const ipv6PrivatePatterns = [
|
|
177
|
-
/^::1$/, // IPv6 loopback
|
|
178
|
-
/^::ffff:127\./, // IPv4-mapped localhost
|
|
179
|
-
/^::ffff:0:127\./, // Alternative IPv4-mapped localhost
|
|
180
|
-
/^::ffff:10\./, // IPv4-mapped private 10.x
|
|
181
|
-
/^::ffff:0:10\./, // Alternative IPv4-mapped private 10.x
|
|
182
|
-
/^::ffff:192\.168\./, // IPv4-mapped private 192.168.x
|
|
183
|
-
/^::ffff:0:192\.168\./, // Alternative IPv4-mapped private 192.168.x
|
|
184
|
-
/^::ffff:172\.(1[6-9]|2\d|3[01])\./, // IPv4-mapped private 172.16-31.x
|
|
185
|
-
/^::ffff:0:172\.(1[6-9]|2\d|3[01])\./,// Alternative IPv4-mapped private 172.16-31.x
|
|
186
|
-
/^::ffff:169\.254\./, // IPv4-mapped link-local (CRITICAL: AWS metadata)
|
|
187
|
-
/^::ffff:0:169\.254\./, // Alternative IPv4-mapped link-local
|
|
188
|
-
/^fc00:/i, // IPv6 ULA fc00::/7
|
|
189
|
-
/^fd/i, // IPv6 ULA fd00::/8
|
|
190
|
-
/^fe80:/i // IPv6 link-local fe80::/10
|
|
191
|
-
];
|
|
192
|
-
|
|
193
|
-
for (const pattern of ipv6PrivatePatterns) {
|
|
194
|
-
if (pattern.test(cleanIP)) {
|
|
195
|
-
return true;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return false;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Validate endpoint URL (for AgentRegistry)
|
|
204
|
-
*
|
|
205
|
-
* SECURITY FIX (H-1): Enhanced SSRF protection with DNS resolution
|
|
206
|
-
*
|
|
207
|
-
* Security checks:
|
|
208
|
-
* - Valid URL format
|
|
209
|
-
* - HTTPS or IPFS protocols only
|
|
210
|
-
* - Maximum length 256 characters
|
|
211
|
-
* - DNS resolution check (hostname → IP validation)
|
|
212
|
-
* - No private/local IP addresses (SSRF protection)
|
|
213
|
-
* - Blocks AWS metadata endpoint (169.254.169.254)
|
|
214
|
-
* - Fail-secure: if DNS lookup fails, reject
|
|
215
|
-
*
|
|
216
|
-
* **CRITICAL**: This function is now ASYNC due to DNS resolution.
|
|
217
|
-
* All callers MUST await this function.
|
|
218
|
-
*
|
|
219
|
-
* @param endpoint - URL to validate
|
|
220
|
-
* @param fieldName - Field name for error messages
|
|
221
|
-
* @throws {ValidationError} If endpoint is invalid or points to private IP
|
|
222
|
-
*/
|
|
223
|
-
export async function validateEndpointURL(endpoint: string, fieldName: string = 'endpoint'): Promise<void> {
|
|
224
|
-
if (!endpoint || endpoint.length === 0) {
|
|
225
|
-
throw new ValidationError(fieldName, 'Endpoint is required');
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const MAX_LENGTH = 256;
|
|
229
|
-
if (endpoint.length > MAX_LENGTH) {
|
|
230
|
-
throw new ValidationError(fieldName, `Endpoint exceeds maximum length (${MAX_LENGTH})`);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
let parsedUrl: URL;
|
|
234
|
-
try {
|
|
235
|
-
parsedUrl = new URL(endpoint);
|
|
236
|
-
} catch (e) {
|
|
237
|
-
throw new ValidationError(fieldName, 'Endpoint must be a valid URL');
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const allowedProtocols = ['https:', 'ipfs:'];
|
|
241
|
-
if (!allowedProtocols.includes(parsedUrl.protocol)) {
|
|
242
|
-
throw new ValidationError(
|
|
243
|
-
fieldName,
|
|
244
|
-
`Endpoint protocol must be one of: ${allowedProtocols.join(', ')}`
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// SECURITY FIX (H-1): First check hostname syntax
|
|
249
|
-
// URL().hostname strips brackets from IPv6 addresses
|
|
250
|
-
const hostname = parsedUrl.hostname;
|
|
251
|
-
|
|
252
|
-
// Check if hostname itself looks like a private IP (bypass DNS for direct IPs)
|
|
253
|
-
if (isPrivateIP(hostname)) {
|
|
254
|
-
throw new ValidationError(
|
|
255
|
-
fieldName,
|
|
256
|
-
`Endpoint hostname "${hostname}" is a private/local address (SSRF protection)`
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// SECURITY FIX (H-1): DNS resolution check
|
|
261
|
-
// Resolve hostname to IP address(es) and validate each resolved IP
|
|
262
|
-
// This prevents DNS rebinding attacks where hostname resolves to private IP
|
|
263
|
-
if (parsedUrl.protocol === 'https:') {
|
|
264
|
-
try {
|
|
265
|
-
// Dynamic import for Node.js dns module (not available in browser)
|
|
266
|
-
// If running in browser, skip DNS check (browsers have their own SSRF protection)
|
|
267
|
-
const dns = await import('dns').catch(() => null);
|
|
268
|
-
|
|
269
|
-
if (dns) {
|
|
270
|
-
// Resolve hostname to ALL IP addresses and validate each (prevents AAAA/A bypass)
|
|
271
|
-
const results = await dns.promises.lookup(hostname, { all: true });
|
|
272
|
-
|
|
273
|
-
for (const { address, family } of results) {
|
|
274
|
-
// Validate resolved IP is not private
|
|
275
|
-
if (isPrivateIP(address)) {
|
|
276
|
-
throw new ValidationError(
|
|
277
|
-
fieldName,
|
|
278
|
-
`Endpoint hostname "${hostname}" resolves to private IP address ${address} (SSRF protection). ` +
|
|
279
|
-
`This could be an attempt to access internal services. ` +
|
|
280
|
-
`IP family: IPv${family}`
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// SECURITY FIX (H-1): CRITICAL - Block AWS metadata endpoint explicitly
|
|
285
|
-
if (address === '169.254.169.254') {
|
|
286
|
-
throw new ValidationError(
|
|
287
|
-
fieldName,
|
|
288
|
-
`Endpoint resolves to AWS metadata endpoint (169.254.169.254). ` +
|
|
289
|
-
`This is blocked for security reasons (credential theft prevention).`
|
|
290
|
-
);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
} catch (error: any) {
|
|
295
|
-
// SECURITY FIX (H-1): Fail-secure - if DNS lookup fails, reject
|
|
296
|
-
// Don't allow requests to unresolvable hostnames (could be DNS rebinding setup)
|
|
297
|
-
if (error instanceof ValidationError) {
|
|
298
|
-
throw error; // Re-throw validation errors
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
throw new ValidationError(
|
|
302
|
-
fieldName,
|
|
303
|
-
`Failed to resolve hostname "${hostname}": ${error.message}. ` +
|
|
304
|
-
`DNS resolution is required for SSRF protection (fail-secure mode).`
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// IPFS endpoints skip DNS check (no DNS resolution for IPFS CIDs)
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// ============================================================================
|
|
313
|
-
// Storage Validation Functions (AIP-7)
|
|
314
|
-
// ============================================================================
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Validate IPFS CID format
|
|
318
|
-
*
|
|
319
|
-
* @param cid - IPFS CID to validate
|
|
320
|
-
* @param fieldName - Field name for error messages
|
|
321
|
-
* @throws {InvalidCIDError} If CID is invalid
|
|
322
|
-
*/
|
|
323
|
-
export function validateCID(cid: string, _fieldName: string = 'cid'): void {
|
|
324
|
-
if (!cid || typeof cid !== 'string') {
|
|
325
|
-
throw new InvalidCIDError(String(cid), 'CID is required');
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
if (!CID_PATTERN.test(cid)) {
|
|
329
|
-
throw new InvalidCIDError(cid, 'Invalid CID format (expected CIDv0 Qm... or CIDv1 bafy...)');
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Validate Arweave transaction ID format
|
|
335
|
-
*
|
|
336
|
-
* @param txId - Arweave TX ID to validate
|
|
337
|
-
* @param fieldName - Field name for error messages
|
|
338
|
-
* @throws {InvalidArweaveTxIdError} If TX ID is invalid
|
|
339
|
-
*/
|
|
340
|
-
export function validateArweaveTxId(txId: string, _fieldName: string = 'txId'): void {
|
|
341
|
-
if (!txId || typeof txId !== 'string') {
|
|
342
|
-
throw new InvalidArweaveTxIdError(String(txId), 'TX ID is required');
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
if (!ARWEAVE_TX_ID_PATTERN.test(txId)) {
|
|
346
|
-
throw new InvalidArweaveTxIdError(
|
|
347
|
-
txId,
|
|
348
|
-
'Invalid format (expected 43 character base64url string)'
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* Validate gateway URL against whitelist (SSRF Protection - P0-1)
|
|
355
|
-
*
|
|
356
|
-
* SECURITY FIX: Only allow downloads from whitelisted gateway domains.
|
|
357
|
-
* This prevents SSRF attacks where attacker controls the gateway URL.
|
|
358
|
-
*
|
|
359
|
-
* @param url - Full gateway URL to validate
|
|
360
|
-
* @param allowedGateways - List of allowed gateway domains
|
|
361
|
-
* @param fieldName - Field name for error messages
|
|
362
|
-
* @throws {ValidationError} If gateway is not whitelisted
|
|
363
|
-
*/
|
|
364
|
-
export function validateGatewayURL(
|
|
365
|
-
url: string,
|
|
366
|
-
allowedGateways: readonly string[],
|
|
367
|
-
fieldName: string = 'gatewayUrl'
|
|
368
|
-
): void {
|
|
369
|
-
if (!url || typeof url !== 'string') {
|
|
370
|
-
throw new ValidationError(fieldName, 'Gateway URL is required');
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
let parsedUrl: URL;
|
|
374
|
-
try {
|
|
375
|
-
parsedUrl = new URL(url);
|
|
376
|
-
} catch {
|
|
377
|
-
throw new ValidationError(fieldName, 'Invalid URL format');
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Must be HTTPS
|
|
381
|
-
if (parsedUrl.protocol !== 'https:') {
|
|
382
|
-
throw new ValidationError(fieldName, 'Gateway URL must use HTTPS');
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// NEW-3: Validate port (must be 443 or default, prevents port bypass attacks)
|
|
386
|
-
const port = parsedUrl.port;
|
|
387
|
-
if (port && !['443', ''].includes(port)) {
|
|
388
|
-
throw new ValidationError(
|
|
389
|
-
fieldName,
|
|
390
|
-
`Gateway URL must use standard HTTPS port (443). Found port: ${port}. ` +
|
|
391
|
-
`Non-standard ports may bypass whitelist intent.`
|
|
392
|
-
);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// Check hostname against whitelist
|
|
396
|
-
const hostname = parsedUrl.hostname.toLowerCase();
|
|
397
|
-
const isAllowed = allowedGateways.some(gateway =>
|
|
398
|
-
hostname === gateway.toLowerCase() ||
|
|
399
|
-
hostname.endsWith('.' + gateway.toLowerCase())
|
|
400
|
-
);
|
|
401
|
-
|
|
402
|
-
if (!isAllowed) {
|
|
403
|
-
throw new ValidationError(
|
|
404
|
-
fieldName,
|
|
405
|
-
`Gateway "${hostname}" is not in the allowed list. ` +
|
|
406
|
-
`Allowed gateways: ${allowedGateways.join(', ')}. ` +
|
|
407
|
-
`This restriction prevents SSRF attacks.`
|
|
408
|
-
);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Validate semver version string
|
|
414
|
-
*
|
|
415
|
-
* @param version - Version string to validate
|
|
416
|
-
* @param fieldName - Field name for error messages
|
|
417
|
-
* @throws {ValidationError} If version is invalid
|
|
418
|
-
*/
|
|
419
|
-
export function validateSemver(version: string, fieldName: string = 'version'): void {
|
|
420
|
-
if (!version || typeof version !== 'string') {
|
|
421
|
-
throw new ValidationError(fieldName, 'Version is required');
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (!SEMVER_PATTERN.test(version)) {
|
|
425
|
-
throw new ValidationError(fieldName, 'Must be semver format (e.g., 1.0.0)');
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* Validate hash (bytes32) format
|
|
431
|
-
*
|
|
432
|
-
* @param hash - Hash to validate
|
|
433
|
-
* @param fieldName - Field name for error messages
|
|
434
|
-
* @throws {ValidationError} If hash is invalid
|
|
435
|
-
*/
|
|
436
|
-
export function validateHash(hash: string, fieldName: string = 'hash'): void {
|
|
437
|
-
if (!hash || typeof hash !== 'string') {
|
|
438
|
-
throw new ValidationError(fieldName, 'Hash is required');
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
if (!HASH_PATTERN.test(hash)) {
|
|
442
|
-
throw new ValidationError(fieldName, 'Invalid hash format (expected bytes32 hex string)');
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
/**
|
|
447
|
-
* Validate signature format (65 bytes)
|
|
448
|
-
*
|
|
449
|
-
* @param signature - Signature to validate
|
|
450
|
-
* @param fieldName - Field name for error messages
|
|
451
|
-
* @throws {ValidationError} If signature is invalid
|
|
452
|
-
*/
|
|
453
|
-
export function validateSignature(signature: string, fieldName: string = 'signature'): void {
|
|
454
|
-
if (!signature || typeof signature !== 'string') {
|
|
455
|
-
throw new ValidationError(fieldName, 'Signature is required');
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
if (!SIGNATURE_PATTERN.test(signature)) {
|
|
459
|
-
throw new ValidationError(
|
|
460
|
-
fieldName,
|
|
461
|
-
'Invalid signature format (expected 65 bytes = 0x + 130 hex chars)'
|
|
462
|
-
);
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// ============================================================================
|
|
467
|
-
// Error Sanitization (P0-2)
|
|
468
|
-
// ============================================================================
|
|
469
|
-
|
|
470
|
-
/**
|
|
471
|
-
* Sanitize error messages to remove sensitive data
|
|
472
|
-
*
|
|
473
|
-
* SECURITY FIX (P0-2): Removes credentials, private keys, and other
|
|
474
|
-
* sensitive data from error messages before logging/returning.
|
|
475
|
-
*
|
|
476
|
-
* @param error - Error to sanitize
|
|
477
|
-
* @returns Sanitized error message
|
|
478
|
-
*/
|
|
479
|
-
export function sanitizeErrorMessage(error: unknown): string {
|
|
480
|
-
if (!error) return 'Unknown error';
|
|
481
|
-
|
|
482
|
-
let message = '';
|
|
483
|
-
if (error instanceof Error) {
|
|
484
|
-
message = error.message;
|
|
485
|
-
} else if (typeof error === 'string') {
|
|
486
|
-
message = error;
|
|
487
|
-
} else {
|
|
488
|
-
message = String(error);
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// Patterns to redact
|
|
492
|
-
const sensitivePatterns = [
|
|
493
|
-
// Private keys (hex)
|
|
494
|
-
/0x[a-fA-F0-9]{64}/g,
|
|
495
|
-
// AWS access key IDs
|
|
496
|
-
/AKIA[0-9A-Z]{16}/g,
|
|
497
|
-
// AWS secret keys (40 chars)
|
|
498
|
-
/[a-zA-Z0-9/+=]{40}/g,
|
|
499
|
-
// Bearer tokens
|
|
500
|
-
/Bearer\s+[a-zA-Z0-9._-]+/gi,
|
|
501
|
-
// API keys (generic pattern)
|
|
502
|
-
/api[_-]?key[=:]\s*["']?[a-zA-Z0-9_-]+["']?/gi,
|
|
503
|
-
// Secret in URL query params
|
|
504
|
-
/secret[=][^&\s]+/gi,
|
|
505
|
-
// Password in URL
|
|
506
|
-
/password[=][^&\s]+/gi,
|
|
507
|
-
// Authorization headers
|
|
508
|
-
/authorization[=:]\s*["']?[^"'\s]+["']?/gi
|
|
509
|
-
];
|
|
510
|
-
|
|
511
|
-
let sanitized = message;
|
|
512
|
-
for (const pattern of sensitivePatterns) {
|
|
513
|
-
sanitized = sanitized.replace(pattern, '[REDACTED]');
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
return sanitized;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
/**
|
|
520
|
-
* Create a safe error object for external consumption
|
|
521
|
-
*
|
|
522
|
-
* SECURITY FIX (P0-2): Returns error without stack trace or sensitive details
|
|
523
|
-
*
|
|
524
|
-
* @param error - Original error
|
|
525
|
-
* @param operation - What operation failed
|
|
526
|
-
* @returns Safe error object
|
|
527
|
-
*/
|
|
528
|
-
export function createSafeError(
|
|
529
|
-
error: unknown,
|
|
530
|
-
operation: string
|
|
531
|
-
): { message: string; code: string; operation: string } {
|
|
532
|
-
const sanitizedMessage = sanitizeErrorMessage(error);
|
|
533
|
-
|
|
534
|
-
// Don't expose internal details - generic message with operation context
|
|
535
|
-
return {
|
|
536
|
-
message: `Operation failed: ${operation}. ${sanitizedMessage}`,
|
|
537
|
-
code: (error as any)?.code || 'UNKNOWN_ERROR',
|
|
538
|
-
operation
|
|
539
|
-
};
|
|
540
|
-
}
|