Validate chmod commands - only allow making files executable with +x. Returns: Tuple of (is_allowed, reason_if_blocked)
(command_string: str)
| 369 | |
| 370 | |
| 371 | def validate_chmod_command(command_string: str) -> tuple[bool, str]: |
| 372 | """ |
| 373 | Validate chmod commands - only allow making files executable with +x. |
| 374 | |
| 375 | Returns: |
| 376 | Tuple of (is_allowed, reason_if_blocked) |
| 377 | """ |
| 378 | try: |
| 379 | tokens = shlex.split(command_string) |
| 380 | except ValueError: |
| 381 | return False, "Could not parse chmod command" |
| 382 | |
| 383 | if not tokens or tokens[0] != "chmod": |
| 384 | return False, "Not a chmod command" |
| 385 | |
| 386 | # Look for the mode argument |
| 387 | # Valid modes: +x, u+x, a+x, etc. (anything ending with +x for execute permission) |
| 388 | mode = None |
| 389 | files = [] |
| 390 | |
| 391 | for token in tokens[1:]: |
| 392 | if token.startswith("-"): |
| 393 | # Skip flags like -R (we don't allow recursive chmod anyway) |
| 394 | return False, "chmod flags are not allowed" |
| 395 | elif mode is None: |
| 396 | mode = token |
| 397 | else: |
| 398 | files.append(token) |
| 399 | |
| 400 | if mode is None: |
| 401 | return False, "chmod requires a mode" |
| 402 | |
| 403 | if not files: |
| 404 | return False, "chmod requires at least one file" |
| 405 | |
| 406 | # Only allow +x variants (making files executable) |
| 407 | # This matches: +x, u+x, g+x, o+x, a+x, ug+x, etc. |
| 408 | import re |
| 409 | |
| 410 | if not re.match(r"^[ugoa]*\+x$", mode): |
| 411 | return False, f"chmod only allowed with +x mode, got: {mode}" |
| 412 | |
| 413 | return True, "" |
| 414 | |
| 415 | |
| 416 | def validate_init_script(command_string: str) -> tuple[bool, str]: |
no outgoing calls