({ file_path, pages }, toolUseContext: ToolUseContext)
| 416 | }, |
| 417 | renderToolUseErrorMessage, |
| 418 | async validateInput({ file_path, pages }, toolUseContext: ToolUseContext) { |
| 419 | // Validate pages parameter (pure string parsing, no I/O) |
| 420 | if (pages !== undefined) { |
| 421 | const parsed = parsePDFPageRange(pages) |
| 422 | if (!parsed) { |
| 423 | return { |
| 424 | result: false, |
| 425 | message: `Invalid pages parameter: "${pages}". Use formats like "1-5", "3", or "10-20". Pages are 1-indexed.`, |
| 426 | errorCode: 7, |
| 427 | } |
| 428 | } |
| 429 | const rangeSize = |
| 430 | parsed.lastPage === Infinity |
| 431 | ? PDF_MAX_PAGES_PER_READ + 1 |
| 432 | : parsed.lastPage - parsed.firstPage + 1 |
| 433 | if (rangeSize > PDF_MAX_PAGES_PER_READ) { |
| 434 | return { |
| 435 | result: false, |
| 436 | message: `Page range "${pages}" exceeds maximum of ${PDF_MAX_PAGES_PER_READ} pages per request. Please use a smaller range.`, |
| 437 | errorCode: 8, |
| 438 | } |
| 439 | } |
| 440 | } |
| 441 | |
| 442 | // Path expansion + deny rule check (no I/O) |
| 443 | const fullFilePath = expandPath(file_path) |
| 444 | |
| 445 | const appState = toolUseContext.getAppState() |
| 446 | const denyRule = matchingRuleForInput( |
| 447 | fullFilePath, |
| 448 | appState.toolPermissionContext, |
| 449 | 'read', |
| 450 | 'deny', |
| 451 | ) |
| 452 | if (denyRule !== null) { |
| 453 | return { |
| 454 | result: false, |
| 455 | message: |
| 456 | 'File is in a directory that is denied by your permission settings.', |
| 457 | errorCode: 1, |
| 458 | } |
| 459 | } |
| 460 | |
| 461 | // SECURITY: UNC path check (no I/O) — defer filesystem operations |
| 462 | // until after user grants permission to prevent NTLM credential leaks |
| 463 | const isUncPath = |
| 464 | fullFilePath.startsWith('\\\\') || fullFilePath.startsWith('//') |
| 465 | if (isUncPath) { |
| 466 | return { result: true } |
| 467 | } |
| 468 | |
| 469 | // Binary extension check (string check on extension only, no I/O). |
| 470 | // PDF, images, and SVG are excluded - this tool renders them natively. |
| 471 | const ext = path.extname(fullFilePath).toLowerCase() |
| 472 | if ( |
| 473 | hasBinaryExtension(fullFilePath) && |
| 474 | !isPDFExtension(ext) && |
| 475 | !IMAGE_EXTENSIONS.has(ext.slice(1)) |
nothing calls this directly
no test coverage detected