* Check if a sed expression contains dangerous operations (denylist) * @param expression Single sed expression (without quotes) * @returns true if dangerous, false if safe
(expression: string)
| 471 | * @returns true if dangerous, false if safe |
| 472 | */ |
| 473 | function containsDangerousOperations(expression: string): boolean { |
| 474 | const cmd = expression.trim() |
| 475 | if (!cmd) return false |
| 476 | |
| 477 | // CONSERVATIVE REJECTIONS: Broadly reject patterns that could be dangerous |
| 478 | // When in doubt, treat as unsafe |
| 479 | |
| 480 | // Reject non-ASCII characters (Unicode homoglyphs, combining chars, etc.) |
| 481 | // Examples: w (fullwidth), ᴡ (small capital), w̃ (combining tilde) |
| 482 | // Check for characters outside ASCII range (0x01-0x7F, excluding null byte) |
| 483 | // eslint-disable-next-line no-control-regex |
| 484 | if (/[^\x01-\x7F]/.test(cmd)) { |
| 485 | return true |
| 486 | } |
| 487 | |
| 488 | // Reject curly braces (blocks) - too complex to parse |
| 489 | if (cmd.includes('{') || cmd.includes('}')) { |
| 490 | return true |
| 491 | } |
| 492 | |
| 493 | // Reject newlines - multi-line commands are too complex |
| 494 | if (cmd.includes('\n')) { |
| 495 | return true |
| 496 | } |
| 497 | |
| 498 | // Reject comments (# not immediately after s command) |
| 499 | // Comments look like: #comment or start with # |
| 500 | // Delimiter looks like: s#pattern#replacement# |
| 501 | const hashIndex = cmd.indexOf('#') |
| 502 | if (hashIndex !== -1 && !(hashIndex > 0 && cmd[hashIndex - 1] === 's')) { |
| 503 | return true |
| 504 | } |
| 505 | |
| 506 | // Reject negation operator |
| 507 | // Negation can appear: at start (!/pattern/), after address (/pattern/!, 1,10!, $!) |
| 508 | // Delimiter looks like: s!pattern!replacement! (has 's' before it) |
| 509 | if (/^!/.test(cmd) || /[/\d$]!/.test(cmd)) { |
| 510 | return true |
| 511 | } |
| 512 | |
| 513 | // Reject tilde in GNU step address format (digit~digit, ,~digit, or $~digit) |
| 514 | // Allow whitespace around tilde |
| 515 | if (/\d\s*~\s*\d|,\s*~\s*\d|\$\s*~\s*\d/.test(cmd)) { |
| 516 | return true |
| 517 | } |
| 518 | |
| 519 | // Reject comma at start (bare comma is shorthand for 1,$ address range) |
| 520 | if (/^,/.test(cmd)) { |
| 521 | return true |
| 522 | } |
| 523 | |
| 524 | // Reject comma followed by +/- (GNU offset addresses) |
| 525 | if (/,\s*[+-]/.test(cmd)) { |
| 526 | return true |
| 527 | } |
| 528 | |
| 529 | // Reject backslash tricks: |
| 530 | // 1. s\ (substitution with backslash delimiter) |
no outgoing calls
no test coverage detected