w3pk 0.10.0 → 0.10.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/README.md +4 -0
- package/dist/index.d.mts +44 -12
- package/dist/index.d.ts +44 -12
- package/dist/index.js +32 -30
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +33 -31
- package/dist/index.mjs.map +1 -1
- package/dist/inspect/browser.d.mts +5 -0
- package/dist/inspect/browser.d.ts +5 -0
- package/dist/inspect/browser.js +20 -3
- package/dist/inspect/browser.js.map +1 -1
- package/dist/inspect/browser.mjs +20 -3
- package/dist/inspect/browser.mjs.map +1 -1
- package/dist/inspect/index.js +20 -3
- package/dist/inspect/index.js.map +1 -1
- package/dist/inspect/index.mjs +20 -3
- package/dist/inspect/index.mjs.map +1 -1
- package/dist/inspect/node.d.mts +5 -0
- package/dist/inspect/node.d.ts +5 -0
- package/dist/inspect/node.js +13 -2
- package/dist/inspect/node.js.map +1 -1
- package/dist/inspect/node.mjs +13 -2
- package/dist/inspect/node.mjs.map +1 -1
- package/docs/API_REFERENCE.md +10 -91
- package/docs/ARCHITECTURE.md +45 -42
- package/docs/BUILD_VERIFICATION.md +3 -3
- package/docs/INTEGRATION_GUIDELINES.md +6 -9
- package/docs/SECURITY.md +155 -314
- package/package.json +1 -1
- package/dist/chainlist/index.d.mts +0 -96
- package/dist/chainlist/index.d.ts +0 -96
package/dist/inspect/node.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/inspect/node.ts"],"sourcesContent":["/**\n * Node.js Inspection Module\n *\n * Provides filesystem-based code inspection capabilities for analyzing web3 applications\n * from the server-side or during development. Scans application source code and generates\n * security reports via the Rukh API.\n *\n * @module inspect/node\n */\n\nimport { promises as fs } from 'fs';\nimport path from 'path';\n\n/**\n * Configuration options for Node.js-based application inspection\n */\nexport interface InspectOptions {\n /**\n * The root directory of the application to inspect\n * @default process.cwd()\n */\n appPath?: string;\n\n /**\n * File patterns to include (glob patterns)\n * @default ['**\\/*.ts', '**\\/*.tsx', '**\\/*.js', '**\\/*.jsx', '**\\/*.json']\n */\n includePatterns?: string[];\n\n /**\n * Directories to exclude from inspection\n * @default ['node_modules', 'dist', '.next', '.git', 'build', 'coverage']\n */\n excludeDirs?: string[];\n\n /**\n * Maximum file size in KB to include\n * @default 500\n */\n maxFileSizeKB?: number;\n\n /**\n * Focus mode: filters files to only include those relevant to specific features\n * - 'transactions': Only files with signing, transactions, or blockchain operations\n * - 'all': Include all files (default)\n * @default 'all'\n */\n focusMode?: 'transactions' | 'all';\n}\n\n/**\n * Result of the code gathering operation\n */\nexport interface InspectResult {\n /**\n * The generated markdown content with all collected code\n */\n markdown: string;\n\n /**\n * List of files that were included\n */\n includedFiles: string[];\n\n /**\n * Total size in KB of all included files\n */\n totalSizeKB: number;\n}\n\n/**\n * Checks if file content is relevant to transactions/signing operations\n *\n * Scans file content and filename for keywords related to blockchain transactions,\n * message signing, smart contracts, and web3 operations.\n *\n * @param content - The file content to analyze\n * @param filename - The name of the file being checked\n * @returns True if the file contains transaction-related code\n * @internal\n */\nfunction isTransactionRelevant(content: string, filename: string): boolean {\n const transactionKeywords = [\n // Signing operations\n 'signMessage',\n 'signTypedData',\n 'sign(',\n 'signature',\n 'personalSign',\n 'eth_sign',\n 'sendTransaction',\n\n // Transaction operations\n 'transaction',\n 'sendTx',\n 'executeTx',\n 'Contract.connect',\n 'signer.send',\n 'wallet.send',\n 'provider.send',\n 'eth_sendTransaction',\n 'eth_sendRawTransaction',\n\n // Blockchain interaction\n 'Contract(',\n 'ethers.Contract',\n 'new Contract',\n 'ContractFactory',\n 'deployContract',\n\n // W3PK specific\n 'w3pk.sign',\n 'w3pk.send',\n 'Web3Passkey',\n 'useW3PK',\n\n // Smart contract calls\n 'call(',\n 'estimateGas',\n 'gasLimit',\n 'gasPrice',\n 'maxFeePerGas',\n 'maxPriorityFeePerGas',\n\n // Web3 providers\n 'JsonRpcProvider',\n 'Web3Provider',\n 'BrowserProvider',\n\n // EIP-7702\n 'EIP7702',\n 'authorization',\n 'delegation',\n ];\n\n const lowerContent = content.toLowerCase();\n const lowerFilename = filename.toLowerCase();\n\n // Check if filename suggests transaction relevance\n if (\n lowerFilename.includes('transaction') ||\n lowerFilename.includes('sign') ||\n lowerFilename.includes('wallet') ||\n lowerFilename.includes('contract') ||\n lowerFilename.includes('blockchain') ||\n lowerFilename.includes('web3') ||\n lowerFilename.includes('ethers')\n ) {\n return true;\n }\n\n // Check content for transaction-related keywords\n return transactionKeywords.some(keyword =>\n lowerContent.includes(keyword.toLowerCase())\n );\n}\n\n/**\n * Recursively walks a directory tree and collects matching files\n *\n * Traverses the directory structure, applying filters based on file patterns,\n * size limits, and focus mode settings.\n *\n * @param dir - The directory path to walk\n * @param options - Inspection options with filters and limits\n * @param rootDir - The root directory for calculating relative paths\n * @returns Array of file objects with path, content, and relative path\n * @internal\n */\nasync function walkDirectory(\n dir: string,\n options: Required<InspectOptions>,\n rootDir: string\n): Promise<{ path: string; content: string; relativePath: string }[]> {\n const files: { path: string; content: string; relativePath: string }[] = [];\n\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n const relativePath = path.relative(rootDir, fullPath);\n\n if (entry.isDirectory()) {\n // Skip excluded directories\n if (options.excludeDirs.includes(entry.name)) {\n continue;\n }\n\n // Recursively walk subdirectory\n const subFiles = await walkDirectory(fullPath, options, rootDir);\n files.push(...subFiles);\n } else if (entry.isFile()) {\n // Check if file matches include patterns\n const ext = path.extname(entry.name);\n const shouldInclude = options.includePatterns.some(pattern => {\n // Simple pattern matching for common extensions\n if (pattern.includes('*')) {\n const patternExt = pattern.split('.').pop();\n return ext === `.${patternExt}`;\n }\n return entry.name === pattern;\n });\n\n if (!shouldInclude) {\n continue;\n }\n\n // Check file size\n const stats = await fs.stat(fullPath);\n const fileSizeKB = stats.size / 1024;\n\n if (fileSizeKB > options.maxFileSizeKB) {\n console.warn(`Skipping ${relativePath} (${fileSizeKB.toFixed(2)} KB exceeds limit)`);\n continue;\n }\n\n // Read file content\n const content = await fs.readFile(fullPath, 'utf-8');\n\n // Apply focus mode filter if enabled\n if (options.focusMode === 'transactions') {\n if (!isTransactionRelevant(content, entry.name)) {\n continue;\n }\n }\n\n files.push({ path: fullPath, content, relativePath });\n }\n }\n } catch (error) {\n console.error(`Error reading directory ${dir}:`, error);\n }\n\n return files;\n}\n\n/**\n * Gathers application code and generates a markdown document\n *\n * @param options - Configuration options for the inspection\n * @returns An object containing the markdown content and metadata\n *\n * @example\n * ```typescript\n * const result = await gatherCode({\n * appPath: '../genji-passkey',\n * includePatterns: ['**\\/*.ts', '**\\/*.tsx']\n * });\n *\n * console.log(`Collected ${result.includedFiles.length} files`);\n * console.log(`Total size: ${result.totalSizeKB} KB`);\n * ```\n */\nexport async function gatherCode(options: InspectOptions = {}): Promise<InspectResult> {\n const opts: Required<InspectOptions> = {\n appPath: options.appPath || process.cwd(),\n includePatterns: options.includePatterns || [\n '**/*.ts',\n '**/*.tsx',\n '**/*.js',\n '**/*.jsx',\n '**/*.json'\n ],\n excludeDirs: options.excludeDirs || [\n 'node_modules',\n 'dist',\n '.next',\n '.git',\n 'build',\n 'coverage',\n '.cache',\n 'out',\n '.vercel',\n '.turbo'\n ],\n maxFileSizeKB: options.maxFileSizeKB || 500,\n focusMode: options.focusMode || 'all'\n };\n\n // Resolve app path\n const appPath = path.resolve(opts.appPath);\n\n // Check if app path exists\n try {\n await fs.access(appPath);\n } catch (error) {\n throw new Error(`App path does not exist: ${appPath}`);\n }\n\n // Walk directory and collect files\n const files = await walkDirectory(appPath, opts, appPath);\n\n // Generate markdown\n let markdown = `# Application Code Inspection\\n\\n`;\n markdown += `**Inspected Path:** \\`${appPath}\\`\\n\\n`;\n markdown += `**Files Collected:** ${files.length}\\n\\n`;\n markdown += `**Focus Mode:** ${opts.focusMode}\\n\\n`;\n markdown += `**Timestamp:** ${new Date().toISOString()}\\n\\n`;\n markdown += `---\\n\\n`;\n\n if (opts.focusMode === 'transactions') {\n markdown += `## Analysis Focus\\n\\n`;\n markdown += `This inspection is focused on **transaction and signing operations**. Only files containing:\\n\\n`;\n markdown += `- Message signing (signMessage, signTypedData, etc.)\\n`;\n markdown += `- Transaction sending (sendTransaction, eth_sendTransaction, etc.)\\n`;\n markdown += `- Smart contract interactions (Contract calls, deployments)\\n`;\n markdown += `- Blockchain providers and wallets\\n`;\n markdown += `- W3PK signing/transaction methods\\n`;\n markdown += `- EIP-7702 authorization/delegation\\n\\n`;\n markdown += `---\\n\\n`;\n }\n\n // Add table of contents\n markdown += `## Table of Contents\\n\\n`;\n files.forEach(file => {\n markdown += `- [\\`${file.relativePath}\\`](#${file.relativePath.replace(/[^a-z0-9]/gi, '-').toLowerCase()})\\n`;\n });\n markdown += `\\n---\\n\\n`;\n\n // Add file contents\n markdown += `## Files\\n\\n`;\n\n for (const file of files) {\n const ext = path.extname(file.relativePath).slice(1);\n const language = ext === 'tsx' || ext === 'ts' ? 'typescript' :\n ext === 'jsx' || ext === 'js' ? 'javascript' :\n ext === 'json' ? 'json' : ext;\n\n markdown += `### \\`${file.relativePath}\\`\\n\\n`;\n markdown += '```' + language + '\\n';\n markdown += file.content;\n markdown += '\\n```\\n\\n';\n markdown += `---\\n\\n`;\n }\n\n // Calculate total size\n const totalSizeKB = files.reduce((sum, file) => {\n return sum + Buffer.byteLength(file.content, 'utf-8') / 1024;\n }, 0);\n\n return {\n markdown,\n includedFiles: files.map(f => f.relativePath),\n totalSizeKB: Math.round(totalSizeKB * 100) / 100\n };\n}\n\n/**\n * Inspects an application and returns a security report via Rukh API\n *\n * @param appPath - Path to the application to inspect\n * @param rukhUrl - The Rukh API endpoint URL\n * @param context - The context name to use (should have an instruction file with report format)\n * @param model - The AI model to use ('anthropic', 'mistral', or 'openai')\n * @param focusMode - Focus on 'transactions' or include 'all' files\n * @returns A markdown-formatted security report\n *\n * @example\n * ```typescript\n * const report = await inspect(\n * '../genji-passkey',\n * 'https://rukh.w3hc.org',\n * 'w3pk',\n * 'anthropic',\n * 'transactions'\n * );\n *\n * console.log(report);\n * // Outputs:\n * // # Genji Passkey Report\n * // ## Available Methods\n * // ### Method #1: Sign Message\n * // ...\n * ```\n */\nexport async function inspect(\n appPath: string,\n rukhUrl: string = 'https://rukh.w3hc.org',\n context: string = 'w3pk',\n model: 'anthropic' | 'mistral' | 'openai' = 'anthropic',\n focusMode: 'transactions' | 'all' = 'transactions'\n): Promise<string> {\n // Gather application code with focus mode\n const result = await gatherCode({ appPath, focusMode });\n\n console.log(`Inspected ${result.includedFiles.length} files (${result.totalSizeKB} KB) [Focus: ${focusMode}]`);\n\n // Default message that triggers the structured report\n const message = 'Analyze this application and provide a security report listing all transaction and signing methods.';\n\n // Prepare form data\n const formData = new FormData();\n formData.append('message', message);\n formData.append('model', model);\n formData.append('context', context);\n\n // Create a blob from the markdown content\n const blob = new Blob([result.markdown], { type: 'text/markdown' });\n formData.append('file', blob, 'app-code.md');\n\n // Send request to Rukh API\n const response = await fetch(`${rukhUrl}/ask`, {\n method: 'POST',\n body: formData\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Rukh API error: ${response.status} - ${error}`);\n }\n\n const data = await response.json();\n return data.response || data.message || JSON.stringify(data);\n}\n"],"mappings":"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","names":["fs","path"]}
|
|
1
|
+
{"version":3,"sources":["../../src/inspect/node.ts"],"sourcesContent":["/**\n * Node.js Inspection Module\n *\n * Provides filesystem-based code inspection capabilities for analyzing web3 applications\n * from the server-side or during development. Scans application source code and generates\n * security reports via the Rukh API.\n *\n * @module inspect/node\n */\n\nimport { promises as fs } from 'fs';\nimport path from 'path';\n\n/**\n * Configuration options for Node.js-based application inspection\n */\nexport interface InspectOptions {\n /**\n * The root directory of the application to inspect\n * @default process.cwd()\n */\n appPath?: string;\n\n /**\n * File patterns to include (glob patterns)\n * @default ['**\\/*.ts', '**\\/*.tsx', '**\\/*.js', '**\\/*.jsx', '**\\/*.json']\n */\n includePatterns?: string[];\n\n /**\n * Directories to exclude from inspection\n * @default ['node_modules', 'dist', '.next', '.git', 'build', 'coverage']\n */\n excludeDirs?: string[];\n\n /**\n * Maximum file size in KB to include\n * @default 500\n */\n maxFileSizeKB?: number;\n\n /**\n * Maximum total size in KB for all collected code (prevents exceeding API token limits)\n * @default 100\n */\n maxTotalSizeKB?: number;\n\n /**\n * Focus mode: filters files to only include those relevant to specific features\n * - 'transactions': Only files with signing, transactions, or blockchain operations\n * - 'all': Include all files (default)\n * @default 'all'\n */\n focusMode?: 'transactions' | 'all';\n}\n\n/**\n * Result of the code gathering operation\n */\nexport interface InspectResult {\n /**\n * The generated markdown content with all collected code\n */\n markdown: string;\n\n /**\n * List of files that were included\n */\n includedFiles: string[];\n\n /**\n * Total size in KB of all included files\n */\n totalSizeKB: number;\n}\n\n/**\n * Checks if file content is relevant to transactions/signing operations\n *\n * Scans file content and filename for keywords related to blockchain transactions,\n * message signing, smart contracts, and web3 operations.\n *\n * @param content - The file content to analyze\n * @param filename - The name of the file being checked\n * @returns True if the file contains transaction-related code\n * @internal\n */\nfunction isTransactionRelevant(content: string, filename: string): boolean {\n const transactionKeywords = [\n // Signing operations\n 'signMessage',\n 'signTypedData',\n 'sign(',\n 'signature',\n 'personalSign',\n 'eth_sign',\n 'sendTransaction',\n\n // Transaction operations\n 'transaction',\n 'sendTx',\n 'executeTx',\n 'Contract.connect',\n 'signer.send',\n 'wallet.send',\n 'provider.send',\n 'eth_sendTransaction',\n 'eth_sendRawTransaction',\n\n // Blockchain interaction\n 'Contract(',\n 'ethers.Contract',\n 'new Contract',\n 'ContractFactory',\n 'deployContract',\n\n // W3PK specific\n 'w3pk.sign',\n 'w3pk.send',\n 'Web3Passkey',\n 'useW3PK',\n\n // Smart contract calls\n 'call(',\n 'estimateGas',\n 'gasLimit',\n 'gasPrice',\n 'maxFeePerGas',\n 'maxPriorityFeePerGas',\n\n // Web3 providers\n 'JsonRpcProvider',\n 'Web3Provider',\n 'BrowserProvider',\n\n // EIP-7702\n 'EIP7702',\n 'authorization',\n 'delegation',\n ];\n\n const lowerContent = content.toLowerCase();\n const lowerFilename = filename.toLowerCase();\n\n // Check if filename suggests transaction relevance\n if (\n lowerFilename.includes('transaction') ||\n lowerFilename.includes('sign') ||\n lowerFilename.includes('wallet') ||\n lowerFilename.includes('contract') ||\n lowerFilename.includes('blockchain') ||\n lowerFilename.includes('web3') ||\n lowerFilename.includes('ethers')\n ) {\n return true;\n }\n\n // Check content for transaction-related keywords\n return transactionKeywords.some(keyword =>\n lowerContent.includes(keyword.toLowerCase())\n );\n}\n\n/**\n * Recursively walks a directory tree and collects matching files\n *\n * Traverses the directory structure, applying filters based on file patterns,\n * size limits, and focus mode settings.\n *\n * @param dir - The directory path to walk\n * @param options - Inspection options with filters and limits\n * @param rootDir - The root directory for calculating relative paths\n * @param cumulativeSizeKB - Tracks total size collected so far\n * @returns Array of file objects with path, content, and relative path\n * @internal\n */\nasync function walkDirectory(\n dir: string,\n options: Required<InspectOptions>,\n rootDir: string,\n cumulativeSizeKB: { value: number } = { value: 0 }\n): Promise<{ path: string; content: string; relativePath: string }[]> {\n const files: { path: string; content: string; relativePath: string }[] = [];\n\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n // Check if we've reached the total size limit\n if (cumulativeSizeKB.value >= options.maxTotalSizeKB) {\n console.warn(`Reached total size limit of ${options.maxTotalSizeKB} KB - stopping collection`);\n break;\n }\n\n const fullPath = path.join(dir, entry.name);\n const relativePath = path.relative(rootDir, fullPath);\n\n if (entry.isDirectory()) {\n // Skip excluded directories\n if (options.excludeDirs.includes(entry.name)) {\n continue;\n }\n\n // Recursively walk subdirectory\n const subFiles = await walkDirectory(fullPath, options, rootDir, cumulativeSizeKB);\n files.push(...subFiles);\n } else if (entry.isFile()) {\n // Check if file matches include patterns\n const ext = path.extname(entry.name);\n const shouldInclude = options.includePatterns.some(pattern => {\n // Simple pattern matching for common extensions\n if (pattern.includes('*')) {\n const patternExt = pattern.split('.').pop();\n return ext === `.${patternExt}`;\n }\n return entry.name === pattern;\n });\n\n if (!shouldInclude) {\n continue;\n }\n\n // Check file size\n const stats = await fs.stat(fullPath);\n const fileSizeKB = stats.size / 1024;\n\n if (fileSizeKB > options.maxFileSizeKB) {\n console.warn(`Skipping ${relativePath} (${fileSizeKB.toFixed(2)} KB exceeds limit)`);\n continue;\n }\n\n // Read file content\n const content = await fs.readFile(fullPath, 'utf-8');\n\n // Apply focus mode filter if enabled\n if (options.focusMode === 'transactions') {\n if (!isTransactionRelevant(content, entry.name)) {\n continue;\n }\n }\n\n // Calculate content size\n const contentSizeKB = Buffer.byteLength(content, 'utf-8') / 1024;\n\n // Check if adding this file would exceed the total limit\n if (cumulativeSizeKB.value + contentSizeKB > options.maxTotalSizeKB) {\n console.warn(`Skipping ${relativePath} - would exceed total size limit (${(cumulativeSizeKB.value + contentSizeKB).toFixed(2)} KB > ${options.maxTotalSizeKB} KB)`);\n break;\n }\n\n // Add file and update cumulative size\n cumulativeSizeKB.value += contentSizeKB;\n files.push({ path: fullPath, content, relativePath });\n }\n }\n } catch (error) {\n console.error(`Error reading directory ${dir}:`, error);\n }\n\n return files;\n}\n\n/**\n * Gathers application code and generates a markdown document\n *\n * @param options - Configuration options for the inspection\n * @returns An object containing the markdown content and metadata\n *\n * @example\n * ```typescript\n * const result = await gatherCode({\n * appPath: '../genji-passkey',\n * includePatterns: ['**\\/*.ts', '**\\/*.tsx']\n * });\n *\n * console.log(`Collected ${result.includedFiles.length} files`);\n * console.log(`Total size: ${result.totalSizeKB} KB`);\n * ```\n */\nexport async function gatherCode(options: InspectOptions = {}): Promise<InspectResult> {\n const opts: Required<InspectOptions> = {\n appPath: options.appPath || process.cwd(),\n includePatterns: options.includePatterns || [\n '**/*.ts',\n '**/*.tsx',\n '**/*.js',\n '**/*.jsx',\n '**/*.json'\n ],\n excludeDirs: options.excludeDirs || [\n 'node_modules',\n 'dist',\n '.next',\n '.git',\n 'build',\n 'coverage',\n '.cache',\n 'out',\n '.vercel',\n '.turbo'\n ],\n maxFileSizeKB: options.maxFileSizeKB || 500,\n maxTotalSizeKB: options.maxTotalSizeKB || 100,\n focusMode: options.focusMode || 'all'\n };\n\n // Resolve app path\n const appPath = path.resolve(opts.appPath);\n\n // Check if app path exists\n try {\n await fs.access(appPath);\n } catch (error) {\n throw new Error(`App path does not exist: ${appPath}`);\n }\n\n // Walk directory and collect files\n const files = await walkDirectory(appPath, opts, appPath);\n\n // Generate markdown\n let markdown = `# Application Code Inspection\\n\\n`;\n markdown += `**Inspected Path:** \\`${appPath}\\`\\n\\n`;\n markdown += `**Files Collected:** ${files.length}\\n\\n`;\n markdown += `**Focus Mode:** ${opts.focusMode}\\n\\n`;\n markdown += `**Timestamp:** ${new Date().toISOString()}\\n\\n`;\n markdown += `---\\n\\n`;\n\n if (opts.focusMode === 'transactions') {\n markdown += `## Analysis Focus\\n\\n`;\n markdown += `This inspection is focused on **transaction and signing operations**. Only files containing:\\n\\n`;\n markdown += `- Message signing (signMessage, signTypedData, etc.)\\n`;\n markdown += `- Transaction sending (sendTransaction, eth_sendTransaction, etc.)\\n`;\n markdown += `- Smart contract interactions (Contract calls, deployments)\\n`;\n markdown += `- Blockchain providers and wallets\\n`;\n markdown += `- W3PK signing/transaction methods\\n`;\n markdown += `- EIP-7702 authorization/delegation\\n\\n`;\n markdown += `---\\n\\n`;\n }\n\n // Add table of contents\n markdown += `## Table of Contents\\n\\n`;\n files.forEach(file => {\n markdown += `- [\\`${file.relativePath}\\`](#${file.relativePath.replace(/[^a-z0-9]/gi, '-').toLowerCase()})\\n`;\n });\n markdown += `\\n---\\n\\n`;\n\n // Add file contents\n markdown += `## Files\\n\\n`;\n\n for (const file of files) {\n const ext = path.extname(file.relativePath).slice(1);\n const language = ext === 'tsx' || ext === 'ts' ? 'typescript' :\n ext === 'jsx' || ext === 'js' ? 'javascript' :\n ext === 'json' ? 'json' : ext;\n\n markdown += `### \\`${file.relativePath}\\`\\n\\n`;\n markdown += '```' + language + '\\n';\n markdown += file.content;\n markdown += '\\n```\\n\\n';\n markdown += `---\\n\\n`;\n }\n\n // Calculate total size\n const totalSizeKB = files.reduce((sum, file) => {\n return sum + Buffer.byteLength(file.content, 'utf-8') / 1024;\n }, 0);\n\n return {\n markdown,\n includedFiles: files.map(f => f.relativePath),\n totalSizeKB: Math.round(totalSizeKB * 100) / 100\n };\n}\n\n/**\n * Inspects an application and returns a security report via Rukh API\n *\n * @param appPath - Path to the application to inspect\n * @param rukhUrl - The Rukh API endpoint URL\n * @param context - The context name to use (should have an instruction file with report format)\n * @param model - The AI model to use ('anthropic', 'mistral', or 'openai')\n * @param focusMode - Focus on 'transactions' or include 'all' files\n * @returns A markdown-formatted security report\n *\n * @example\n * ```typescript\n * const report = await inspect(\n * '../genji-passkey',\n * 'https://rukh.w3hc.org',\n * 'w3pk',\n * 'anthropic',\n * 'transactions'\n * );\n *\n * console.log(report);\n * // Outputs:\n * // # Genji Passkey Report\n * // ## Available Methods\n * // ### Method #1: Sign Message\n * // ...\n * ```\n */\nexport async function inspect(\n appPath: string,\n rukhUrl: string = 'https://rukh.w3hc.org',\n context: string = 'w3pk',\n model: 'anthropic' | 'mistral' | 'openai' = 'anthropic',\n focusMode: 'transactions' | 'all' = 'transactions'\n): Promise<string> {\n // Gather application code with focus mode\n const result = await gatherCode({ appPath, focusMode });\n\n console.log(`Inspected ${result.includedFiles.length} files (${result.totalSizeKB} KB) [Focus: ${focusMode}]`);\n\n // Default message that triggers the structured report\n const message = 'Analyze this application and provide a security report listing all transaction and signing methods.';\n\n // Prepare form data\n const formData = new FormData();\n formData.append('message', message);\n formData.append('model', model);\n formData.append('context', context);\n\n // Create a blob from the markdown content\n const blob = new Blob([result.markdown], { type: 'text/markdown' });\n formData.append('file', blob, 'app-code.md');\n\n // Send request to Rukh API\n const response = await fetch(`${rukhUrl}/ask`, {\n method: 'POST',\n body: formData\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Rukh API error: ${response.status} - ${error}`);\n }\n\n const data = await response.json();\n return data.response || data.message || JSON.stringify(data);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,gBAA+B;AAC/B,kBAAiB;AA4EjB,SAAS,sBAAsB,SAAiB,UAA2B;AACzE,QAAM,sBAAsB;AAAA;AAAA,IAE1B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,eAAe,QAAQ,YAAY;AACzC,QAAM,gBAAgB,SAAS,YAAY;AAG3C,MACE,cAAc,SAAS,aAAa,KACpC,cAAc,SAAS,MAAM,KAC7B,cAAc,SAAS,QAAQ,KAC/B,cAAc,SAAS,UAAU,KACjC,cAAc,SAAS,YAAY,KACnC,cAAc,SAAS,MAAM,KAC7B,cAAc,SAAS,QAAQ,GAC/B;AACA,WAAO;AAAA,EACT;AAGA,SAAO,oBAAoB;AAAA,IAAK,aAC9B,aAAa,SAAS,QAAQ,YAAY,CAAC;AAAA,EAC7C;AACF;AAeA,eAAe,cACb,KACA,SACA,SACA,mBAAsC,EAAE,OAAO,EAAE,GACmB;AACpE,QAAM,QAAmE,CAAC;AAE1E,MAAI;AACF,UAAM,UAAU,MAAM,UAAAA,SAAG,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAE7D,eAAW,SAAS,SAAS;AAE3B,UAAI,iBAAiB,SAAS,QAAQ,gBAAgB;AACpD,gBAAQ,KAAK,+BAA+B,QAAQ,cAAc,2BAA2B;AAC7F;AAAA,MACF;AAEA,YAAM,WAAW,YAAAC,QAAK,KAAK,KAAK,MAAM,IAAI;AAC1C,YAAM,eAAe,YAAAA,QAAK,SAAS,SAAS,QAAQ;AAEpD,UAAI,MAAM,YAAY,GAAG;AAEvB,YAAI,QAAQ,YAAY,SAAS,MAAM,IAAI,GAAG;AAC5C;AAAA,QACF;AAGA,cAAM,WAAW,MAAM,cAAc,UAAU,SAAS,SAAS,gBAAgB;AACjF,cAAM,KAAK,GAAG,QAAQ;AAAA,MACxB,WAAW,MAAM,OAAO,GAAG;AAEzB,cAAM,MAAM,YAAAA,QAAK,QAAQ,MAAM,IAAI;AACnC,cAAM,gBAAgB,QAAQ,gBAAgB,KAAK,aAAW;AAE5D,cAAI,QAAQ,SAAS,GAAG,GAAG;AACzB,kBAAM,aAAa,QAAQ,MAAM,GAAG,EAAE,IAAI;AAC1C,mBAAO,QAAQ,IAAI,UAAU;AAAA,UAC/B;AACA,iBAAO,MAAM,SAAS;AAAA,QACxB,CAAC;AAED,YAAI,CAAC,eAAe;AAClB;AAAA,QACF;AAGA,cAAM,QAAQ,MAAM,UAAAD,SAAG,KAAK,QAAQ;AACpC,cAAM,aAAa,MAAM,OAAO;AAEhC,YAAI,aAAa,QAAQ,eAAe;AACtC,kBAAQ,KAAK,YAAY,YAAY,KAAK,WAAW,QAAQ,CAAC,CAAC,oBAAoB;AACnF;AAAA,QACF;AAGA,cAAM,UAAU,MAAM,UAAAA,SAAG,SAAS,UAAU,OAAO;AAGnD,YAAI,QAAQ,cAAc,gBAAgB;AACxC,cAAI,CAAC,sBAAsB,SAAS,MAAM,IAAI,GAAG;AAC/C;AAAA,UACF;AAAA,QACF;AAGA,cAAM,gBAAgB,OAAO,WAAW,SAAS,OAAO,IAAI;AAG5D,YAAI,iBAAiB,QAAQ,gBAAgB,QAAQ,gBAAgB;AACnE,kBAAQ,KAAK,YAAY,YAAY,sCAAsC,iBAAiB,QAAQ,eAAe,QAAQ,CAAC,CAAC,SAAS,QAAQ,cAAc,MAAM;AAClK;AAAA,QACF;AAGA,yBAAiB,SAAS;AAC1B,cAAM,KAAK,EAAE,MAAM,UAAU,SAAS,aAAa,CAAC;AAAA,MACtD;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAM,2BAA2B,GAAG,KAAK,KAAK;AAAA,EACxD;AAEA,SAAO;AACT;AAmBA,eAAsB,WAAW,UAA0B,CAAC,GAA2B;AACrF,QAAM,OAAiC;AAAA,IACrC,SAAS,QAAQ,WAAW,QAAQ,IAAI;AAAA,IACxC,iBAAiB,QAAQ,mBAAmB;AAAA,MAC1C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,aAAa,QAAQ,eAAe;AAAA,MAClC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,eAAe,QAAQ,iBAAiB;AAAA,IACxC,gBAAgB,QAAQ,kBAAkB;AAAA,IAC1C,WAAW,QAAQ,aAAa;AAAA,EAClC;AAGA,QAAM,UAAU,YAAAC,QAAK,QAAQ,KAAK,OAAO;AAGzC,MAAI;AACF,UAAM,UAAAD,SAAG,OAAO,OAAO;AAAA,EACzB,SAAS,OAAO;AACd,UAAM,IAAI,MAAM,4BAA4B,OAAO,EAAE;AAAA,EACvD;AAGA,QAAM,QAAQ,MAAM,cAAc,SAAS,MAAM,OAAO;AAGxD,MAAI,WAAW;AAAA;AAAA;AACf,cAAY,yBAAyB,OAAO;AAAA;AAAA;AAC5C,cAAY,wBAAwB,MAAM,MAAM;AAAA;AAAA;AAChD,cAAY,mBAAmB,KAAK,SAAS;AAAA;AAAA;AAC7C,cAAY,mBAAkB,oBAAI,KAAK,GAAE,YAAY,CAAC;AAAA;AAAA;AACtD,cAAY;AAAA;AAAA;AAEZ,MAAI,KAAK,cAAc,gBAAgB;AACrC,gBAAY;AAAA;AAAA;AACZ,gBAAY;AAAA;AAAA;AACZ,gBAAY;AAAA;AACZ,gBAAY;AAAA;AACZ,gBAAY;AAAA;AACZ,gBAAY;AAAA;AACZ,gBAAY;AAAA;AACZ,gBAAY;AAAA;AAAA;AACZ,gBAAY;AAAA;AAAA;AAAA,EACd;AAGA,cAAY;AAAA;AAAA;AACZ,QAAM,QAAQ,UAAQ;AACpB,gBAAY,QAAQ,KAAK,YAAY,QAAQ,KAAK,aAAa,QAAQ,eAAe,GAAG,EAAE,YAAY,CAAC;AAAA;AAAA,EAC1G,CAAC;AACD,cAAY;AAAA;AAAA;AAAA;AAGZ,cAAY;AAAA;AAAA;AAEZ,aAAW,QAAQ,OAAO;AACxB,UAAM,MAAM,YAAAC,QAAK,QAAQ,KAAK,YAAY,EAAE,MAAM,CAAC;AACnD,UAAM,WAAW,QAAQ,SAAS,QAAQ,OAAO,eAChC,QAAQ,SAAS,QAAQ,OAAO,eAChC,QAAQ,SAAS,SAAS;AAE3C,gBAAY,SAAS,KAAK,YAAY;AAAA;AAAA;AACtC,gBAAY,QAAQ,WAAW;AAC/B,gBAAY,KAAK;AACjB,gBAAY;AACZ,gBAAY;AAAA;AAAA;AAAA,EACd;AAGA,QAAM,cAAc,MAAM,OAAO,CAAC,KAAK,SAAS;AAC9C,WAAO,MAAM,OAAO,WAAW,KAAK,SAAS,OAAO,IAAI;AAAA,EAC1D,GAAG,CAAC;AAEJ,SAAO;AAAA,IACL;AAAA,IACA,eAAe,MAAM,IAAI,OAAK,EAAE,YAAY;AAAA,IAC5C,aAAa,KAAK,MAAM,cAAc,GAAG,IAAI;AAAA,EAC/C;AACF;AA8BA,eAAsB,QACpB,SACA,UAAkB,yBAClB,UAAkB,QAClB,QAA4C,aAC5C,YAAoC,gBACnB;AAEjB,QAAM,SAAS,MAAM,WAAW,EAAE,SAAS,UAAU,CAAC;AAEtD,UAAQ,IAAI,aAAa,OAAO,cAAc,MAAM,WAAW,OAAO,WAAW,gBAAgB,SAAS,GAAG;AAG7G,QAAM,UAAU;AAGhB,QAAM,WAAW,IAAI,SAAS;AAC9B,WAAS,OAAO,WAAW,OAAO;AAClC,WAAS,OAAO,SAAS,KAAK;AAC9B,WAAS,OAAO,WAAW,OAAO;AAGlC,QAAM,OAAO,IAAI,KAAK,CAAC,OAAO,QAAQ,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAClE,WAAS,OAAO,QAAQ,MAAM,aAAa;AAG3C,QAAM,WAAW,MAAM,MAAM,GAAG,OAAO,QAAQ;AAAA,IAC7C,QAAQ;AAAA,IACR,MAAM;AAAA,EACR,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,QAAQ,MAAM,SAAS,KAAK;AAClC,UAAM,IAAI,MAAM,mBAAmB,SAAS,MAAM,MAAM,KAAK,EAAE;AAAA,EACjE;AAEA,QAAM,OAAO,MAAM,SAAS,KAAK;AACjC,SAAO,KAAK,YAAY,KAAK,WAAW,KAAK,UAAU,IAAI;AAC7D;","names":["fs","path"]}
|
package/dist/inspect/node.mjs
CHANGED
|
@@ -57,18 +57,22 @@ function isTransactionRelevant(content, filename) {
|
|
|
57
57
|
(keyword) => lowerContent.includes(keyword.toLowerCase())
|
|
58
58
|
);
|
|
59
59
|
}
|
|
60
|
-
async function walkDirectory(dir, options, rootDir) {
|
|
60
|
+
async function walkDirectory(dir, options, rootDir, cumulativeSizeKB = { value: 0 }) {
|
|
61
61
|
const files = [];
|
|
62
62
|
try {
|
|
63
63
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
64
64
|
for (const entry of entries) {
|
|
65
|
+
if (cumulativeSizeKB.value >= options.maxTotalSizeKB) {
|
|
66
|
+
console.warn(`Reached total size limit of ${options.maxTotalSizeKB} KB - stopping collection`);
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
65
69
|
const fullPath = path.join(dir, entry.name);
|
|
66
70
|
const relativePath = path.relative(rootDir, fullPath);
|
|
67
71
|
if (entry.isDirectory()) {
|
|
68
72
|
if (options.excludeDirs.includes(entry.name)) {
|
|
69
73
|
continue;
|
|
70
74
|
}
|
|
71
|
-
const subFiles = await walkDirectory(fullPath, options, rootDir);
|
|
75
|
+
const subFiles = await walkDirectory(fullPath, options, rootDir, cumulativeSizeKB);
|
|
72
76
|
files.push(...subFiles);
|
|
73
77
|
} else if (entry.isFile()) {
|
|
74
78
|
const ext = path.extname(entry.name);
|
|
@@ -94,6 +98,12 @@ async function walkDirectory(dir, options, rootDir) {
|
|
|
94
98
|
continue;
|
|
95
99
|
}
|
|
96
100
|
}
|
|
101
|
+
const contentSizeKB = Buffer.byteLength(content, "utf-8") / 1024;
|
|
102
|
+
if (cumulativeSizeKB.value + contentSizeKB > options.maxTotalSizeKB) {
|
|
103
|
+
console.warn(`Skipping ${relativePath} - would exceed total size limit (${(cumulativeSizeKB.value + contentSizeKB).toFixed(2)} KB > ${options.maxTotalSizeKB} KB)`);
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
cumulativeSizeKB.value += contentSizeKB;
|
|
97
107
|
files.push({ path: fullPath, content, relativePath });
|
|
98
108
|
}
|
|
99
109
|
}
|
|
@@ -125,6 +135,7 @@ async function gatherCode(options = {}) {
|
|
|
125
135
|
".turbo"
|
|
126
136
|
],
|
|
127
137
|
maxFileSizeKB: options.maxFileSizeKB || 500,
|
|
138
|
+
maxTotalSizeKB: options.maxTotalSizeKB || 100,
|
|
128
139
|
focusMode: options.focusMode || "all"
|
|
129
140
|
};
|
|
130
141
|
const appPath = path.resolve(opts.appPath);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/inspect/node.ts"],"sourcesContent":["/**\n * Node.js Inspection Module\n *\n * Provides filesystem-based code inspection capabilities for analyzing web3 applications\n * from the server-side or during development. Scans application source code and generates\n * security reports via the Rukh API.\n *\n * @module inspect/node\n */\n\nimport { promises as fs } from 'fs';\nimport path from 'path';\n\n/**\n * Configuration options for Node.js-based application inspection\n */\nexport interface InspectOptions {\n /**\n * The root directory of the application to inspect\n * @default process.cwd()\n */\n appPath?: string;\n\n /**\n * File patterns to include (glob patterns)\n * @default ['**\\/*.ts', '**\\/*.tsx', '**\\/*.js', '**\\/*.jsx', '**\\/*.json']\n */\n includePatterns?: string[];\n\n /**\n * Directories to exclude from inspection\n * @default ['node_modules', 'dist', '.next', '.git', 'build', 'coverage']\n */\n excludeDirs?: string[];\n\n /**\n * Maximum file size in KB to include\n * @default 500\n */\n maxFileSizeKB?: number;\n\n /**\n * Focus mode: filters files to only include those relevant to specific features\n * - 'transactions': Only files with signing, transactions, or blockchain operations\n * - 'all': Include all files (default)\n * @default 'all'\n */\n focusMode?: 'transactions' | 'all';\n}\n\n/**\n * Result of the code gathering operation\n */\nexport interface InspectResult {\n /**\n * The generated markdown content with all collected code\n */\n markdown: string;\n\n /**\n * List of files that were included\n */\n includedFiles: string[];\n\n /**\n * Total size in KB of all included files\n */\n totalSizeKB: number;\n}\n\n/**\n * Checks if file content is relevant to transactions/signing operations\n *\n * Scans file content and filename for keywords related to blockchain transactions,\n * message signing, smart contracts, and web3 operations.\n *\n * @param content - The file content to analyze\n * @param filename - The name of the file being checked\n * @returns True if the file contains transaction-related code\n * @internal\n */\nfunction isTransactionRelevant(content: string, filename: string): boolean {\n const transactionKeywords = [\n // Signing operations\n 'signMessage',\n 'signTypedData',\n 'sign(',\n 'signature',\n 'personalSign',\n 'eth_sign',\n 'sendTransaction',\n\n // Transaction operations\n 'transaction',\n 'sendTx',\n 'executeTx',\n 'Contract.connect',\n 'signer.send',\n 'wallet.send',\n 'provider.send',\n 'eth_sendTransaction',\n 'eth_sendRawTransaction',\n\n // Blockchain interaction\n 'Contract(',\n 'ethers.Contract',\n 'new Contract',\n 'ContractFactory',\n 'deployContract',\n\n // W3PK specific\n 'w3pk.sign',\n 'w3pk.send',\n 'Web3Passkey',\n 'useW3PK',\n\n // Smart contract calls\n 'call(',\n 'estimateGas',\n 'gasLimit',\n 'gasPrice',\n 'maxFeePerGas',\n 'maxPriorityFeePerGas',\n\n // Web3 providers\n 'JsonRpcProvider',\n 'Web3Provider',\n 'BrowserProvider',\n\n // EIP-7702\n 'EIP7702',\n 'authorization',\n 'delegation',\n ];\n\n const lowerContent = content.toLowerCase();\n const lowerFilename = filename.toLowerCase();\n\n // Check if filename suggests transaction relevance\n if (\n lowerFilename.includes('transaction') ||\n lowerFilename.includes('sign') ||\n lowerFilename.includes('wallet') ||\n lowerFilename.includes('contract') ||\n lowerFilename.includes('blockchain') ||\n lowerFilename.includes('web3') ||\n lowerFilename.includes('ethers')\n ) {\n return true;\n }\n\n // Check content for transaction-related keywords\n return transactionKeywords.some(keyword =>\n lowerContent.includes(keyword.toLowerCase())\n );\n}\n\n/**\n * Recursively walks a directory tree and collects matching files\n *\n * Traverses the directory structure, applying filters based on file patterns,\n * size limits, and focus mode settings.\n *\n * @param dir - The directory path to walk\n * @param options - Inspection options with filters and limits\n * @param rootDir - The root directory for calculating relative paths\n * @returns Array of file objects with path, content, and relative path\n * @internal\n */\nasync function walkDirectory(\n dir: string,\n options: Required<InspectOptions>,\n rootDir: string\n): Promise<{ path: string; content: string; relativePath: string }[]> {\n const files: { path: string; content: string; relativePath: string }[] = [];\n\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n const relativePath = path.relative(rootDir, fullPath);\n\n if (entry.isDirectory()) {\n // Skip excluded directories\n if (options.excludeDirs.includes(entry.name)) {\n continue;\n }\n\n // Recursively walk subdirectory\n const subFiles = await walkDirectory(fullPath, options, rootDir);\n files.push(...subFiles);\n } else if (entry.isFile()) {\n // Check if file matches include patterns\n const ext = path.extname(entry.name);\n const shouldInclude = options.includePatterns.some(pattern => {\n // Simple pattern matching for common extensions\n if (pattern.includes('*')) {\n const patternExt = pattern.split('.').pop();\n return ext === `.${patternExt}`;\n }\n return entry.name === pattern;\n });\n\n if (!shouldInclude) {\n continue;\n }\n\n // Check file size\n const stats = await fs.stat(fullPath);\n const fileSizeKB = stats.size / 1024;\n\n if (fileSizeKB > options.maxFileSizeKB) {\n console.warn(`Skipping ${relativePath} (${fileSizeKB.toFixed(2)} KB exceeds limit)`);\n continue;\n }\n\n // Read file content\n const content = await fs.readFile(fullPath, 'utf-8');\n\n // Apply focus mode filter if enabled\n if (options.focusMode === 'transactions') {\n if (!isTransactionRelevant(content, entry.name)) {\n continue;\n }\n }\n\n files.push({ path: fullPath, content, relativePath });\n }\n }\n } catch (error) {\n console.error(`Error reading directory ${dir}:`, error);\n }\n\n return files;\n}\n\n/**\n * Gathers application code and generates a markdown document\n *\n * @param options - Configuration options for the inspection\n * @returns An object containing the markdown content and metadata\n *\n * @example\n * ```typescript\n * const result = await gatherCode({\n * appPath: '../genji-passkey',\n * includePatterns: ['**\\/*.ts', '**\\/*.tsx']\n * });\n *\n * console.log(`Collected ${result.includedFiles.length} files`);\n * console.log(`Total size: ${result.totalSizeKB} KB`);\n * ```\n */\nexport async function gatherCode(options: InspectOptions = {}): Promise<InspectResult> {\n const opts: Required<InspectOptions> = {\n appPath: options.appPath || process.cwd(),\n includePatterns: options.includePatterns || [\n '**/*.ts',\n '**/*.tsx',\n '**/*.js',\n '**/*.jsx',\n '**/*.json'\n ],\n excludeDirs: options.excludeDirs || [\n 'node_modules',\n 'dist',\n '.next',\n '.git',\n 'build',\n 'coverage',\n '.cache',\n 'out',\n '.vercel',\n '.turbo'\n ],\n maxFileSizeKB: options.maxFileSizeKB || 500,\n focusMode: options.focusMode || 'all'\n };\n\n // Resolve app path\n const appPath = path.resolve(opts.appPath);\n\n // Check if app path exists\n try {\n await fs.access(appPath);\n } catch (error) {\n throw new Error(`App path does not exist: ${appPath}`);\n }\n\n // Walk directory and collect files\n const files = await walkDirectory(appPath, opts, appPath);\n\n // Generate markdown\n let markdown = `# Application Code Inspection\\n\\n`;\n markdown += `**Inspected Path:** \\`${appPath}\\`\\n\\n`;\n markdown += `**Files Collected:** ${files.length}\\n\\n`;\n markdown += `**Focus Mode:** ${opts.focusMode}\\n\\n`;\n markdown += `**Timestamp:** ${new Date().toISOString()}\\n\\n`;\n markdown += `---\\n\\n`;\n\n if (opts.focusMode === 'transactions') {\n markdown += `## Analysis Focus\\n\\n`;\n markdown += `This inspection is focused on **transaction and signing operations**. Only files containing:\\n\\n`;\n markdown += `- Message signing (signMessage, signTypedData, etc.)\\n`;\n markdown += `- Transaction sending (sendTransaction, eth_sendTransaction, etc.)\\n`;\n markdown += `- Smart contract interactions (Contract calls, deployments)\\n`;\n markdown += `- Blockchain providers and wallets\\n`;\n markdown += `- W3PK signing/transaction methods\\n`;\n markdown += `- EIP-7702 authorization/delegation\\n\\n`;\n markdown += `---\\n\\n`;\n }\n\n // Add table of contents\n markdown += `## Table of Contents\\n\\n`;\n files.forEach(file => {\n markdown += `- [\\`${file.relativePath}\\`](#${file.relativePath.replace(/[^a-z0-9]/gi, '-').toLowerCase()})\\n`;\n });\n markdown += `\\n---\\n\\n`;\n\n // Add file contents\n markdown += `## Files\\n\\n`;\n\n for (const file of files) {\n const ext = path.extname(file.relativePath).slice(1);\n const language = ext === 'tsx' || ext === 'ts' ? 'typescript' :\n ext === 'jsx' || ext === 'js' ? 'javascript' :\n ext === 'json' ? 'json' : ext;\n\n markdown += `### \\`${file.relativePath}\\`\\n\\n`;\n markdown += '```' + language + '\\n';\n markdown += file.content;\n markdown += '\\n```\\n\\n';\n markdown += `---\\n\\n`;\n }\n\n // Calculate total size\n const totalSizeKB = files.reduce((sum, file) => {\n return sum + Buffer.byteLength(file.content, 'utf-8') / 1024;\n }, 0);\n\n return {\n markdown,\n includedFiles: files.map(f => f.relativePath),\n totalSizeKB: Math.round(totalSizeKB * 100) / 100\n };\n}\n\n/**\n * Inspects an application and returns a security report via Rukh API\n *\n * @param appPath - Path to the application to inspect\n * @param rukhUrl - The Rukh API endpoint URL\n * @param context - The context name to use (should have an instruction file with report format)\n * @param model - The AI model to use ('anthropic', 'mistral', or 'openai')\n * @param focusMode - Focus on 'transactions' or include 'all' files\n * @returns A markdown-formatted security report\n *\n * @example\n * ```typescript\n * const report = await inspect(\n * '../genji-passkey',\n * 'https://rukh.w3hc.org',\n * 'w3pk',\n * 'anthropic',\n * 'transactions'\n * );\n *\n * console.log(report);\n * // Outputs:\n * // # Genji Passkey Report\n * // ## Available Methods\n * // ### Method #1: Sign Message\n * // ...\n * ```\n */\nexport async function inspect(\n appPath: string,\n rukhUrl: string = 'https://rukh.w3hc.org',\n context: string = 'w3pk',\n model: 'anthropic' | 'mistral' | 'openai' = 'anthropic',\n focusMode: 'transactions' | 'all' = 'transactions'\n): Promise<string> {\n // Gather application code with focus mode\n const result = await gatherCode({ appPath, focusMode });\n\n console.log(`Inspected ${result.includedFiles.length} files (${result.totalSizeKB} KB) [Focus: ${focusMode}]`);\n\n // Default message that triggers the structured report\n const message = 'Analyze this application and provide a security report listing all transaction and signing methods.';\n\n // Prepare form data\n const formData = new FormData();\n formData.append('message', message);\n formData.append('model', model);\n formData.append('context', context);\n\n // Create a blob from the markdown content\n const blob = new Blob([result.markdown], { type: 'text/markdown' });\n formData.append('file', blob, 'app-code.md');\n\n // Send request to Rukh API\n const response = await fetch(`${rukhUrl}/ask`, {\n method: 'POST',\n body: formData\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Rukh API error: ${response.status} - ${error}`);\n }\n\n const data = await response.json();\n return data.response || data.message || JSON.stringify(data);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/inspect/node.ts"],"sourcesContent":["/**\n * Node.js Inspection Module\n *\n * Provides filesystem-based code inspection capabilities for analyzing web3 applications\n * from the server-side or during development. Scans application source code and generates\n * security reports via the Rukh API.\n *\n * @module inspect/node\n */\n\nimport { promises as fs } from 'fs';\nimport path from 'path';\n\n/**\n * Configuration options for Node.js-based application inspection\n */\nexport interface InspectOptions {\n /**\n * The root directory of the application to inspect\n * @default process.cwd()\n */\n appPath?: string;\n\n /**\n * File patterns to include (glob patterns)\n * @default ['**\\/*.ts', '**\\/*.tsx', '**\\/*.js', '**\\/*.jsx', '**\\/*.json']\n */\n includePatterns?: string[];\n\n /**\n * Directories to exclude from inspection\n * @default ['node_modules', 'dist', '.next', '.git', 'build', 'coverage']\n */\n excludeDirs?: string[];\n\n /**\n * Maximum file size in KB to include\n * @default 500\n */\n maxFileSizeKB?: number;\n\n /**\n * Maximum total size in KB for all collected code (prevents exceeding API token limits)\n * @default 100\n */\n maxTotalSizeKB?: number;\n\n /**\n * Focus mode: filters files to only include those relevant to specific features\n * - 'transactions': Only files with signing, transactions, or blockchain operations\n * - 'all': Include all files (default)\n * @default 'all'\n */\n focusMode?: 'transactions' | 'all';\n}\n\n/**\n * Result of the code gathering operation\n */\nexport interface InspectResult {\n /**\n * The generated markdown content with all collected code\n */\n markdown: string;\n\n /**\n * List of files that were included\n */\n includedFiles: string[];\n\n /**\n * Total size in KB of all included files\n */\n totalSizeKB: number;\n}\n\n/**\n * Checks if file content is relevant to transactions/signing operations\n *\n * Scans file content and filename for keywords related to blockchain transactions,\n * message signing, smart contracts, and web3 operations.\n *\n * @param content - The file content to analyze\n * @param filename - The name of the file being checked\n * @returns True if the file contains transaction-related code\n * @internal\n */\nfunction isTransactionRelevant(content: string, filename: string): boolean {\n const transactionKeywords = [\n // Signing operations\n 'signMessage',\n 'signTypedData',\n 'sign(',\n 'signature',\n 'personalSign',\n 'eth_sign',\n 'sendTransaction',\n\n // Transaction operations\n 'transaction',\n 'sendTx',\n 'executeTx',\n 'Contract.connect',\n 'signer.send',\n 'wallet.send',\n 'provider.send',\n 'eth_sendTransaction',\n 'eth_sendRawTransaction',\n\n // Blockchain interaction\n 'Contract(',\n 'ethers.Contract',\n 'new Contract',\n 'ContractFactory',\n 'deployContract',\n\n // W3PK specific\n 'w3pk.sign',\n 'w3pk.send',\n 'Web3Passkey',\n 'useW3PK',\n\n // Smart contract calls\n 'call(',\n 'estimateGas',\n 'gasLimit',\n 'gasPrice',\n 'maxFeePerGas',\n 'maxPriorityFeePerGas',\n\n // Web3 providers\n 'JsonRpcProvider',\n 'Web3Provider',\n 'BrowserProvider',\n\n // EIP-7702\n 'EIP7702',\n 'authorization',\n 'delegation',\n ];\n\n const lowerContent = content.toLowerCase();\n const lowerFilename = filename.toLowerCase();\n\n // Check if filename suggests transaction relevance\n if (\n lowerFilename.includes('transaction') ||\n lowerFilename.includes('sign') ||\n lowerFilename.includes('wallet') ||\n lowerFilename.includes('contract') ||\n lowerFilename.includes('blockchain') ||\n lowerFilename.includes('web3') ||\n lowerFilename.includes('ethers')\n ) {\n return true;\n }\n\n // Check content for transaction-related keywords\n return transactionKeywords.some(keyword =>\n lowerContent.includes(keyword.toLowerCase())\n );\n}\n\n/**\n * Recursively walks a directory tree and collects matching files\n *\n * Traverses the directory structure, applying filters based on file patterns,\n * size limits, and focus mode settings.\n *\n * @param dir - The directory path to walk\n * @param options - Inspection options with filters and limits\n * @param rootDir - The root directory for calculating relative paths\n * @param cumulativeSizeKB - Tracks total size collected so far\n * @returns Array of file objects with path, content, and relative path\n * @internal\n */\nasync function walkDirectory(\n dir: string,\n options: Required<InspectOptions>,\n rootDir: string,\n cumulativeSizeKB: { value: number } = { value: 0 }\n): Promise<{ path: string; content: string; relativePath: string }[]> {\n const files: { path: string; content: string; relativePath: string }[] = [];\n\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n // Check if we've reached the total size limit\n if (cumulativeSizeKB.value >= options.maxTotalSizeKB) {\n console.warn(`Reached total size limit of ${options.maxTotalSizeKB} KB - stopping collection`);\n break;\n }\n\n const fullPath = path.join(dir, entry.name);\n const relativePath = path.relative(rootDir, fullPath);\n\n if (entry.isDirectory()) {\n // Skip excluded directories\n if (options.excludeDirs.includes(entry.name)) {\n continue;\n }\n\n // Recursively walk subdirectory\n const subFiles = await walkDirectory(fullPath, options, rootDir, cumulativeSizeKB);\n files.push(...subFiles);\n } else if (entry.isFile()) {\n // Check if file matches include patterns\n const ext = path.extname(entry.name);\n const shouldInclude = options.includePatterns.some(pattern => {\n // Simple pattern matching for common extensions\n if (pattern.includes('*')) {\n const patternExt = pattern.split('.').pop();\n return ext === `.${patternExt}`;\n }\n return entry.name === pattern;\n });\n\n if (!shouldInclude) {\n continue;\n }\n\n // Check file size\n const stats = await fs.stat(fullPath);\n const fileSizeKB = stats.size / 1024;\n\n if (fileSizeKB > options.maxFileSizeKB) {\n console.warn(`Skipping ${relativePath} (${fileSizeKB.toFixed(2)} KB exceeds limit)`);\n continue;\n }\n\n // Read file content\n const content = await fs.readFile(fullPath, 'utf-8');\n\n // Apply focus mode filter if enabled\n if (options.focusMode === 'transactions') {\n if (!isTransactionRelevant(content, entry.name)) {\n continue;\n }\n }\n\n // Calculate content size\n const contentSizeKB = Buffer.byteLength(content, 'utf-8') / 1024;\n\n // Check if adding this file would exceed the total limit\n if (cumulativeSizeKB.value + contentSizeKB > options.maxTotalSizeKB) {\n console.warn(`Skipping ${relativePath} - would exceed total size limit (${(cumulativeSizeKB.value + contentSizeKB).toFixed(2)} KB > ${options.maxTotalSizeKB} KB)`);\n break;\n }\n\n // Add file and update cumulative size\n cumulativeSizeKB.value += contentSizeKB;\n files.push({ path: fullPath, content, relativePath });\n }\n }\n } catch (error) {\n console.error(`Error reading directory ${dir}:`, error);\n }\n\n return files;\n}\n\n/**\n * Gathers application code and generates a markdown document\n *\n * @param options - Configuration options for the inspection\n * @returns An object containing the markdown content and metadata\n *\n * @example\n * ```typescript\n * const result = await gatherCode({\n * appPath: '../genji-passkey',\n * includePatterns: ['**\\/*.ts', '**\\/*.tsx']\n * });\n *\n * console.log(`Collected ${result.includedFiles.length} files`);\n * console.log(`Total size: ${result.totalSizeKB} KB`);\n * ```\n */\nexport async function gatherCode(options: InspectOptions = {}): Promise<InspectResult> {\n const opts: Required<InspectOptions> = {\n appPath: options.appPath || process.cwd(),\n includePatterns: options.includePatterns || [\n '**/*.ts',\n '**/*.tsx',\n '**/*.js',\n '**/*.jsx',\n '**/*.json'\n ],\n excludeDirs: options.excludeDirs || [\n 'node_modules',\n 'dist',\n '.next',\n '.git',\n 'build',\n 'coverage',\n '.cache',\n 'out',\n '.vercel',\n '.turbo'\n ],\n maxFileSizeKB: options.maxFileSizeKB || 500,\n maxTotalSizeKB: options.maxTotalSizeKB || 100,\n focusMode: options.focusMode || 'all'\n };\n\n // Resolve app path\n const appPath = path.resolve(opts.appPath);\n\n // Check if app path exists\n try {\n await fs.access(appPath);\n } catch (error) {\n throw new Error(`App path does not exist: ${appPath}`);\n }\n\n // Walk directory and collect files\n const files = await walkDirectory(appPath, opts, appPath);\n\n // Generate markdown\n let markdown = `# Application Code Inspection\\n\\n`;\n markdown += `**Inspected Path:** \\`${appPath}\\`\\n\\n`;\n markdown += `**Files Collected:** ${files.length}\\n\\n`;\n markdown += `**Focus Mode:** ${opts.focusMode}\\n\\n`;\n markdown += `**Timestamp:** ${new Date().toISOString()}\\n\\n`;\n markdown += `---\\n\\n`;\n\n if (opts.focusMode === 'transactions') {\n markdown += `## Analysis Focus\\n\\n`;\n markdown += `This inspection is focused on **transaction and signing operations**. Only files containing:\\n\\n`;\n markdown += `- Message signing (signMessage, signTypedData, etc.)\\n`;\n markdown += `- Transaction sending (sendTransaction, eth_sendTransaction, etc.)\\n`;\n markdown += `- Smart contract interactions (Contract calls, deployments)\\n`;\n markdown += `- Blockchain providers and wallets\\n`;\n markdown += `- W3PK signing/transaction methods\\n`;\n markdown += `- EIP-7702 authorization/delegation\\n\\n`;\n markdown += `---\\n\\n`;\n }\n\n // Add table of contents\n markdown += `## Table of Contents\\n\\n`;\n files.forEach(file => {\n markdown += `- [\\`${file.relativePath}\\`](#${file.relativePath.replace(/[^a-z0-9]/gi, '-').toLowerCase()})\\n`;\n });\n markdown += `\\n---\\n\\n`;\n\n // Add file contents\n markdown += `## Files\\n\\n`;\n\n for (const file of files) {\n const ext = path.extname(file.relativePath).slice(1);\n const language = ext === 'tsx' || ext === 'ts' ? 'typescript' :\n ext === 'jsx' || ext === 'js' ? 'javascript' :\n ext === 'json' ? 'json' : ext;\n\n markdown += `### \\`${file.relativePath}\\`\\n\\n`;\n markdown += '```' + language + '\\n';\n markdown += file.content;\n markdown += '\\n```\\n\\n';\n markdown += `---\\n\\n`;\n }\n\n // Calculate total size\n const totalSizeKB = files.reduce((sum, file) => {\n return sum + Buffer.byteLength(file.content, 'utf-8') / 1024;\n }, 0);\n\n return {\n markdown,\n includedFiles: files.map(f => f.relativePath),\n totalSizeKB: Math.round(totalSizeKB * 100) / 100\n };\n}\n\n/**\n * Inspects an application and returns a security report via Rukh API\n *\n * @param appPath - Path to the application to inspect\n * @param rukhUrl - The Rukh API endpoint URL\n * @param context - The context name to use (should have an instruction file with report format)\n * @param model - The AI model to use ('anthropic', 'mistral', or 'openai')\n * @param focusMode - Focus on 'transactions' or include 'all' files\n * @returns A markdown-formatted security report\n *\n * @example\n * ```typescript\n * const report = await inspect(\n * '../genji-passkey',\n * 'https://rukh.w3hc.org',\n * 'w3pk',\n * 'anthropic',\n * 'transactions'\n * );\n *\n * console.log(report);\n * // Outputs:\n * // # Genji Passkey Report\n * // ## Available Methods\n * // ### Method #1: Sign Message\n * // ...\n * ```\n */\nexport async function inspect(\n appPath: string,\n rukhUrl: string = 'https://rukh.w3hc.org',\n context: string = 'w3pk',\n model: 'anthropic' | 'mistral' | 'openai' = 'anthropic',\n focusMode: 'transactions' | 'all' = 'transactions'\n): Promise<string> {\n // Gather application code with focus mode\n const result = await gatherCode({ appPath, focusMode });\n\n console.log(`Inspected ${result.includedFiles.length} files (${result.totalSizeKB} KB) [Focus: ${focusMode}]`);\n\n // Default message that triggers the structured report\n const message = 'Analyze this application and provide a security report listing all transaction and signing methods.';\n\n // Prepare form data\n const formData = new FormData();\n formData.append('message', message);\n formData.append('model', model);\n formData.append('context', context);\n\n // Create a blob from the markdown content\n const blob = new Blob([result.markdown], { type: 'text/markdown' });\n formData.append('file', blob, 'app-code.md');\n\n // Send request to Rukh API\n const response = await fetch(`${rukhUrl}/ask`, {\n method: 'POST',\n body: formData\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Rukh API error: ${response.status} - ${error}`);\n }\n\n const data = await response.json();\n return data.response || data.message || JSON.stringify(data);\n}\n"],"mappings":"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","names":[]}
|
package/docs/API_REFERENCE.md
CHANGED
|
@@ -57,6 +57,8 @@ interface PersistentSessionConfig {
|
|
|
57
57
|
}
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
**PRF requirement:** persistent sessions are encrypted under a key derived from the WebAuthn PRF extension, which the authenticator only releases during a user-verified assertion. On authenticators without PRF support, no persistent session is stored — the device falls back to in-memory sessions only (there is no weaker at-rest fallback). The session is re-keyed at every real (prompted) login; the configured `duration` is therefore also the renewal interval. With `requireReauth: true` the decryption key is never written to disk; with `requireReauth: false` it is stored as a non-extractable `CryptoKey` to allow silent restore. See [SECURITY.md](./SECURITY.md#at-rest-encryption-what-it-protects-and-what-it-doesnt) for the full model.
|
|
61
|
+
|
|
60
62
|
**Example:**
|
|
61
63
|
|
|
62
64
|
```typescript
|
|
@@ -2847,13 +2849,12 @@ Enable persistent sessions to maintain user login across page refreshes.
|
|
|
2847
2849
|
4. `requireReauth: true` prompts for biometric on refresh (more secure)
|
|
2848
2850
|
5. `requireReauth: false` silently restores session (more convenient)
|
|
2849
2851
|
|
|
2850
|
-
**Security:**
|
|
2851
|
-
- Sessions only persist for STANDARD and YOLO modes
|
|
2852
|
-
-
|
|
2853
|
-
-
|
|
2854
|
-
-
|
|
2855
|
-
-
|
|
2856
|
-
- Origin-isolated via IndexedDB
|
|
2852
|
+
**Security (read the trade-off):**
|
|
2853
|
+
- Sessions only persist for STANDARD and YOLO modes; STRICT mode is NEVER persisted
|
|
2854
|
+
- Time-limited expiration; origin-isolated via IndexedDB
|
|
2855
|
+
- ⚠️ The "WebAuthn-derived key" is derived from **public** credential metadata stored in the same browser profile, **not** from an authenticator-held secret. A persistent session is therefore **decryptable by anyone who can read this origin's storage** (malicious extension, XSS exfil, disk image), and it keeps the seed in that state for the whole duration it's enabled. This is a UX-for-security trade-off, not hardware-backed protection.
|
|
2856
|
+
- Enabling "Remember Me" does not add a new trust dependency (you already trust your origin's code), it **widens the window** in which a compromise of your origin can reach the seed. Keep `duration` short for higher-value wallets; don't enable on shared/untrusted devices.
|
|
2857
|
+
- Full threat model: **[SECURITY.md → At-Rest Encryption](./SECURITY.md#at-rest-encryption-what-it-protects-and-what-it-doesnt)**.
|
|
2857
2858
|
|
|
2858
2859
|
**Example:**
|
|
2859
2860
|
|
|
@@ -3075,7 +3076,7 @@ Compute IPFS CIDv1 hash for the currently installed w3pk version from unpkg CDN.
|
|
|
3075
3076
|
```typescript
|
|
3076
3077
|
const hash = await getCurrentBuildHash()
|
|
3077
3078
|
console.log('Build hash:', hash)
|
|
3078
|
-
// =>
|
|
3079
|
+
// => bafybeicce26ztznqcdn6n6j6hhnoye37nb7nt7gtubelwk2ku4txbp7acy
|
|
3079
3080
|
```
|
|
3080
3081
|
|
|
3081
3082
|
---
|
|
@@ -3210,89 +3211,7 @@ console.log(encoded) // => SGVsbG8gV29ybGQ=
|
|
|
3210
3211
|
### Cryptographic Utilities
|
|
3211
3212
|
|
|
3212
3213
|
```typescript
|
|
3213
|
-
import {
|
|
3214
|
-
extractRS,
|
|
3215
|
-
deriveEncryptionKeyFromWebAuthn,
|
|
3216
|
-
generateSalt
|
|
3217
|
-
} from 'w3pk'
|
|
3218
|
-
```
|
|
3219
|
-
|
|
3220
|
-
#### `deriveEncryptionKeyFromWebAuthn(prfOutput: ArrayBuffer, salt: Uint8Array): Promise<CryptoKey>`
|
|
3221
|
-
|
|
3222
|
-
**🔐 SECURE - WebAuthn PRF-based encryption**
|
|
3223
|
-
|
|
3224
|
-
Derives an AES-256-GCM encryption key from WebAuthn PRF (Pseudo-Random Function) extension output.
|
|
3225
|
-
|
|
3226
|
-
**Security:**
|
|
3227
|
-
- Uses authenticator-held secrets via PRF extension (never exposed)
|
|
3228
|
-
- Implements random unique salts (no precomputation attacks)
|
|
3229
|
-
- PBKDF2 with 210,000 iterations (OWASP 2023 recommendation)
|
|
3230
|
-
- Prevents offline decryption attacks
|
|
3231
|
-
|
|
3232
|
-
**Parameters:**
|
|
3233
|
-
- `prfOutput: ArrayBuffer` - 32-byte secret from WebAuthn PRF extension
|
|
3234
|
-
- `salt: Uint8Array` - 32-byte random salt (generate with `generateSalt()`)
|
|
3235
|
-
|
|
3236
|
-
**Returns:** `Promise<CryptoKey>` - AES-256-GCM key for encryption/decryption
|
|
3237
|
-
|
|
3238
|
-
**Example:**
|
|
3239
|
-
```typescript
|
|
3240
|
-
// 1. Enable PRF during WebAuthn registration
|
|
3241
|
-
const credential = await navigator.credentials.create({
|
|
3242
|
-
publicKey: {
|
|
3243
|
-
challenge: new Uint8Array(32),
|
|
3244
|
-
rp: { name: "Example" },
|
|
3245
|
-
user: { id: userId, name: "user", displayName: "User" },
|
|
3246
|
-
pubKeyCredParams: [{ alg: -7, type: "public-key" }],
|
|
3247
|
-
extensions: {
|
|
3248
|
-
prf: {} // Enable PRF extension
|
|
3249
|
-
}
|
|
3250
|
-
}
|
|
3251
|
-
});
|
|
3252
|
-
|
|
3253
|
-
// 2. Get PRF output during authentication
|
|
3254
|
-
const assertion = await navigator.credentials.get({
|
|
3255
|
-
publicKey: {
|
|
3256
|
-
challenge: new Uint8Array(32),
|
|
3257
|
-
extensions: {
|
|
3258
|
-
prf: {
|
|
3259
|
-
eval: {
|
|
3260
|
-
first: new Uint8Array(32) // Salt for PRF
|
|
3261
|
-
}
|
|
3262
|
-
}
|
|
3263
|
-
}
|
|
3264
|
-
}
|
|
3265
|
-
});
|
|
3266
|
-
|
|
3267
|
-
const prfOutput = assertion.getClientExtensionResults().prf.results.first;
|
|
3268
|
-
|
|
3269
|
-
// 3. Generate and store salt
|
|
3270
|
-
const salt = generateSalt(); // Store this with your ciphertext
|
|
3271
|
-
|
|
3272
|
-
// 4. Derive encryption key
|
|
3273
|
-
const encryptionKey = await deriveEncryptionKeyFromWebAuthn(
|
|
3274
|
-
prfOutput,
|
|
3275
|
-
salt
|
|
3276
|
-
);
|
|
3277
|
-
|
|
3278
|
-
// 5. Encrypt sensitive data
|
|
3279
|
-
const encrypted = await crypto.subtle.encrypt(
|
|
3280
|
-
{ name: "AES-GCM", iv: crypto.getRandomValues(new Uint8Array(12)) },
|
|
3281
|
-
encryptionKey,
|
|
3282
|
-
new TextEncoder().encode("secret data")
|
|
3283
|
-
);
|
|
3284
|
-
```
|
|
3285
|
-
|
|
3286
|
-
#### `generateSalt(): Uint8Array`
|
|
3287
|
-
|
|
3288
|
-
Generates a cryptographically secure random 32-byte salt for encryption.
|
|
3289
|
-
|
|
3290
|
-
**Returns:** `Uint8Array` - 32 random bytes
|
|
3291
|
-
|
|
3292
|
-
**Example:**
|
|
3293
|
-
```typescript
|
|
3294
|
-
const salt = generateSalt();
|
|
3295
|
-
// Store salt alongside ciphertext for later decryption
|
|
3214
|
+
import { extractRS } from 'w3pk'
|
|
3296
3215
|
```
|
|
3297
3216
|
|
|
3298
3217
|
#### `extractRS(derSignature: Uint8Array): { r: string; s: string }`
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -148,42 +148,33 @@ m/44'/60'/0'/0/2 ← Third address
|
|
|
148
148
|
|
|
149
149
|
**Purpose**: Link WebAuthn authentication to wallet encryption
|
|
150
150
|
|
|
151
|
-
**Implementation**: [src/wallet/crypto.ts](src/wallet/crypto.ts)
|
|
152
|
-
|
|
153
|
-
✅ **SECURITY**: Uses PRF-based encryption with authenticator-held secrets and random salts
|
|
151
|
+
**Implementation**: [src/wallet/crypto.ts:21-66](src/wallet/crypto.ts#L21-L66)
|
|
154
152
|
|
|
155
|
-
**
|
|
153
|
+
**Process**:
|
|
156
154
|
```typescript
|
|
157
155
|
function deriveEncryptionKeyFromWebAuthn(
|
|
158
|
-
|
|
159
|
-
|
|
156
|
+
credentialId: string,
|
|
157
|
+
publicKey: string
|
|
160
158
|
): CryptoKey {
|
|
161
|
-
// Step 1:
|
|
162
|
-
|
|
163
|
-
assert(salt.byteLength === 32);
|
|
159
|
+
// Step 1: Combine inputs
|
|
160
|
+
const keyMaterial = `w3pk-v4:${credentialId}:${publicKey}`;
|
|
164
161
|
|
|
165
|
-
// Step 2:
|
|
166
|
-
const
|
|
162
|
+
// Step 2: Hash with SHA-256
|
|
163
|
+
const hash = SHA256(keyMaterial);
|
|
167
164
|
|
|
168
|
-
// Step 3: PBKDF2 key derivation
|
|
169
|
-
const encryptionKey = PBKDF2(
|
|
165
|
+
// Step 3: PBKDF2 key derivation (210,000 iterations)
|
|
166
|
+
const encryptionKey = PBKDF2(hash, salt, 210000);
|
|
170
167
|
|
|
171
168
|
// Result: AES-256-GCM key (NEVER STORED)
|
|
172
169
|
return encryptionKey;
|
|
173
170
|
}
|
|
174
171
|
```
|
|
175
172
|
|
|
176
|
-
**
|
|
177
|
-
- **
|
|
178
|
-
- **
|
|
179
|
-
- **
|
|
180
|
-
- **
|
|
181
|
-
- **Authentication-gated**: Requires biometric/PIN to trigger PRF
|
|
182
|
-
|
|
183
|
-
**Implementation Note**:
|
|
184
|
-
The SDK uses `deriveEncryptionKeyAuto()` as a helper that:
|
|
185
|
-
1. Uses PRF-based encryption when available (secure)
|
|
186
|
-
2. Falls back to v2 implementation for existing wallets without PRF support
|
|
173
|
+
**Properties**:
|
|
174
|
+
- **Deterministic**: Same inputs → Same output
|
|
175
|
+
- **Secure**: 210,000 PBKDF2 iterations (OWASP 2023)
|
|
176
|
+
- **Stateless**: No key storage required
|
|
177
|
+
- **Authentication-gated**: Requires biometric/PIN to access inputs
|
|
187
178
|
|
|
188
179
|
---
|
|
189
180
|
|
|
@@ -226,12 +217,11 @@ STEP 2: Create WebAuthn Credential (INDEPENDENT)
|
|
|
226
217
|
|
|
227
218
|
STEP 3: Derive Encryption Key
|
|
228
219
|
┌──────────────────────────────────────┐
|
|
229
|
-
│
|
|
220
|
+
│ deriveEncryptionKeyFromWebAuthn() │
|
|
230
221
|
│ │
|
|
231
|
-
│
|
|
232
|
-
│ + random salt (32 bytes) │
|
|
222
|
+
│ Input: credentialId + publicKey │
|
|
233
223
|
│ ↓ │
|
|
234
|
-
│
|
|
224
|
+
│ SHA-256 hash │
|
|
235
225
|
│ ↓ │
|
|
236
226
|
│ PBKDF2 (210,000 iterations) │
|
|
237
227
|
│ ↓ │
|
|
@@ -496,29 +486,43 @@ const signedTx = await wallet.signTransaction(tx);
|
|
|
496
486
|
**Implementation**: [src/wallet/crypto.ts:21-66](src/wallet/crypto.ts#L21-L66)
|
|
497
487
|
|
|
498
488
|
```javascript
|
|
499
|
-
// SECURE: PRF-based key derivation
|
|
500
489
|
async function deriveEncryptionKeyFromWebAuthn(
|
|
501
|
-
|
|
502
|
-
|
|
490
|
+
credentialId: string,
|
|
491
|
+
publicKey: string
|
|
503
492
|
): Promise<CryptoKey> {
|
|
504
|
-
// 1.
|
|
505
|
-
const keyMaterial =
|
|
493
|
+
// 1. Combine inputs into key material
|
|
494
|
+
const keyMaterial = `w3pk-v4:${credentialId}:${publicKey}`;
|
|
495
|
+
|
|
496
|
+
// 2. SHA-256 hash
|
|
497
|
+
const keyMaterialHash = await crypto.subtle.digest(
|
|
498
|
+
"SHA-256",
|
|
499
|
+
new TextEncoder().encode(keyMaterial)
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
// 3. Import as PBKDF2 base key
|
|
503
|
+
const importedKey = await crypto.subtle.importKey(
|
|
506
504
|
"raw",
|
|
507
|
-
|
|
505
|
+
keyMaterialHash,
|
|
508
506
|
{ name: "PBKDF2" },
|
|
509
507
|
false,
|
|
510
508
|
["deriveKey"]
|
|
511
509
|
);
|
|
512
510
|
|
|
513
|
-
//
|
|
511
|
+
// 4. Generate deterministic salt
|
|
512
|
+
const salt = await crypto.subtle.digest(
|
|
513
|
+
"SHA-256",
|
|
514
|
+
new TextEncoder().encode("w3pk-salt-v4")
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
// 5. Derive AES-256-GCM key
|
|
514
518
|
return crypto.subtle.deriveKey(
|
|
515
519
|
{
|
|
516
520
|
name: "PBKDF2",
|
|
517
|
-
salt:
|
|
521
|
+
salt: new Uint8Array(salt),
|
|
518
522
|
iterations: 210000, // OWASP 2023 recommendation
|
|
519
523
|
hash: "SHA-256",
|
|
520
524
|
},
|
|
521
|
-
|
|
525
|
+
importedKey,
|
|
522
526
|
{ name: "AES-GCM", length: 256 },
|
|
523
527
|
false,
|
|
524
528
|
["encrypt", "decrypt"]
|
|
@@ -526,12 +530,11 @@ async function deriveEncryptionKeyFromWebAuthn(
|
|
|
526
530
|
}
|
|
527
531
|
```
|
|
528
532
|
|
|
529
|
-
**
|
|
530
|
-
- **
|
|
531
|
-
- **
|
|
532
|
-
- **Strong KDF**: 210,000 PBKDF2 iterations make brute-force impractical (OWASP 2023)
|
|
533
|
+
**Properties**:
|
|
534
|
+
- **Deterministic**: Same inputs always produce same output
|
|
535
|
+
- **Secure**: 210,000 iterations make brute-force impractical
|
|
533
536
|
- **Stateless**: No need to store the encryption key
|
|
534
|
-
- **Fast**: ~100-200ms on modern devices
|
|
537
|
+
- **Fast enough**: ~100-200ms on modern devices
|
|
535
538
|
|
|
536
539
|
---
|
|
537
540
|
|
|
@@ -72,7 +72,7 @@ import { getCurrentBuildHash } from 'w3pk';
|
|
|
72
72
|
|
|
73
73
|
const hash = await getCurrentBuildHash();
|
|
74
74
|
console.log('Build hash:', hash);
|
|
75
|
-
// =>
|
|
75
|
+
// => bafybeicce26ztznqcdn6n6j6hhnoye37nb7nt7gtubelwk2ku4txbp7acy
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
### `getW3pkBuildHash(distUrl)`
|
|
@@ -108,7 +108,7 @@ Verifies if the current build matches an expected hash.
|
|
|
108
108
|
```typescript
|
|
109
109
|
import { verifyBuildHash } from 'w3pk';
|
|
110
110
|
|
|
111
|
-
const trustedHash = '
|
|
111
|
+
const trustedHash = 'bafybeicce26ztznqcdn6n6j6hhnoye37nb7nt7gtubelwk2ku4txbp7acy';
|
|
112
112
|
const isValid = await verifyBuildHash(trustedHash);
|
|
113
113
|
|
|
114
114
|
if (isValid) {
|
|
@@ -152,7 +152,7 @@ Output:
|
|
|
152
152
|
📊 Total size: 273992 bytes
|
|
153
153
|
|
|
154
154
|
🔐 IPFS Build Hash (CIDv1):
|
|
155
|
-
|
|
155
|
+
bafybeicce26ztznqcdn6n6j6hhnoye37nb7nt7gtubelwk2ku4txbp7acy
|
|
156
156
|
bafyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
157
157
|
|
|
158
158
|
📌 Version: x.x.x
|
|
@@ -1412,13 +1412,10 @@ const strictWallet = await w3pk.deriveWallet('STRICT')
|
|
|
1412
1412
|
```
|
|
1413
1413
|
|
|
1414
1414
|
**Persistent Session Security:**
|
|
1415
|
-
- STANDARD
|
|
1416
|
-
-
|
|
1417
|
-
-
|
|
1418
|
-
-
|
|
1419
|
-
- Requires valid credential to decrypt
|
|
1420
|
-
- Time-limited expiration
|
|
1421
|
-
- Origin-isolated via IndexedDB
|
|
1415
|
+
- STANDARD / YOLO: persistent sessions allowed. STRICT: ❌ NEVER allowed.
|
|
1416
|
+
- Time-limited expiration; origin-isolated via IndexedDB.
|
|
1417
|
+
- ⚠️ The at-rest key is derived from **public** credential metadata in the same browser profile, not an authenticator secret — a persistent session is **decryptable by anyone who can read this origin's storage**, for the whole duration it's enabled. "Remember Me" is a UX-for-security trade-off that widens the exposure window; it does not add a new trust dependency beyond the origin-code integrity you already rely on. Keep the duration short for higher-value wallets and don't enable on shared devices.
|
|
1418
|
+
- Decide this deliberately against your threat model: **[SECURITY.md → At-Rest Encryption](./SECURITY.md#at-rest-encryption-what-it-protects-and-what-it-doesnt)**.
|
|
1422
1419
|
|
|
1423
1420
|
### Build Verification
|
|
1424
1421
|
|
|
@@ -1426,7 +1423,7 @@ const strictWallet = await w3pk.deriveWallet('STRICT')
|
|
|
1426
1423
|
// ✅ Verify package integrity on app initialization
|
|
1427
1424
|
import { verifyBuildHash } from 'w3pk'
|
|
1428
1425
|
|
|
1429
|
-
const TRUSTED_HASH = '
|
|
1426
|
+
const TRUSTED_HASH = 'bafybeicce26ztznqcdn6n6j6hhnoye37nb7nt7gtubelwk2ku4txbp7acy'
|
|
1430
1427
|
|
|
1431
1428
|
async function initializeApp() {
|
|
1432
1429
|
const isValid = await verifyBuildHash(TRUSTED_HASH)
|
|
@@ -1486,7 +1483,7 @@ class WalletManager {
|
|
|
1486
1483
|
|
|
1487
1484
|
async initialize() {
|
|
1488
1485
|
// Verify package integrity
|
|
1489
|
-
const TRUSTED_HASH = '
|
|
1486
|
+
const TRUSTED_HASH = 'bafybeicce26ztznqcdn6n6j6hhnoye37nb7nt7gtubelwk2ku4txbp7acy'
|
|
1490
1487
|
const isValid = await verifyBuildHash(TRUSTED_HASH)
|
|
1491
1488
|
if (!isValid) throw new Error('Package integrity check failed')
|
|
1492
1489
|
}
|