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