shellsafe 0.1.0__tar.gz

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.
Files changed (51) hide show
  1. shellsafe-0.1.0/.github/workflows/ci.yml +28 -0
  2. shellsafe-0.1.0/.github/workflows/release.yml +31 -0
  3. shellsafe-0.1.0/.gitignore +12 -0
  4. shellsafe-0.1.0/CHANGELOG.md +14 -0
  5. shellsafe-0.1.0/CONTRIBUTING.md +12 -0
  6. shellsafe-0.1.0/LICENSE +21 -0
  7. shellsafe-0.1.0/PKG-INFO +126 -0
  8. shellsafe-0.1.0/README.md +104 -0
  9. shellsafe-0.1.0/examples/demo.py +33 -0
  10. shellsafe-0.1.0/pyproject.toml +71 -0
  11. shellsafe-0.1.0/src/shellsafe/__init__.py +18 -0
  12. shellsafe-0.1.0/src/shellsafe/__main__.py +3 -0
  13. shellsafe-0.1.0/src/shellsafe/_version.py +1 -0
  14. shellsafe-0.1.0/src/shellsafe/audit/__init__.py +10 -0
  15. shellsafe-0.1.0/src/shellsafe/audit/rules.py +0 -0
  16. shellsafe-0.1.0/src/shellsafe/cli.py +44 -0
  17. shellsafe-0.1.0/src/shellsafe/errors.py +36 -0
  18. shellsafe-0.1.0/src/shellsafe/execute.py +117 -0
  19. shellsafe-0.1.0/src/shellsafe/exitcodes.py +6 -0
  20. shellsafe-0.1.0/src/shellsafe/platforms.py +35 -0
  21. shellsafe-0.1.0/src/shellsafe/py.typed +0 -0
  22. shellsafe-0.1.0/src/shellsafe/raw.py +30 -0
  23. shellsafe-0.1.0/src/shellsafe/render.py +192 -0
  24. shellsafe-0.1.0/src/shellsafe/reporters.py +1 -0
  25. shellsafe-0.1.0/tests/golden/.gitkeep +0 -0
  26. shellsafe-0.1.0/tests/integration/.gitkeep +0 -0
  27. shellsafe-0.1.0/tests/integration/test_exec_posix.py +44 -0
  28. shellsafe-0.1.0/tests/payloads/cases/README.md +3 -0
  29. shellsafe-0.1.0/tests/payloads/cases/case_01_semicolon.txt +1 -0
  30. shellsafe-0.1.0/tests/payloads/cases/case_02_substitution.txt +1 -0
  31. shellsafe-0.1.0/tests/payloads/cases/case_03_backticks.txt +1 -0
  32. shellsafe-0.1.0/tests/payloads/cases/case_04_background_chain.txt +1 -0
  33. shellsafe-0.1.0/tests/payloads/cases/case_05_or_chain.txt +1 -0
  34. shellsafe-0.1.0/tests/payloads/cases/case_06_redirect_out.txt +1 -0
  35. shellsafe-0.1.0/tests/payloads/cases/case_07_redirect_in.txt +1 -0
  36. shellsafe-0.1.0/tests/payloads/cases/case_08_pipe_out.txt +1 -0
  37. shellsafe-0.1.0/tests/payloads/cases/case_09_quote_smuggle.txt +1 -0
  38. shellsafe-0.1.0/tests/payloads/cases/case_10_quote_storm.txt +1 -0
  39. shellsafe-0.1.0/tests/payloads/cases/case_11_globs_expansions.txt +1 -0
  40. shellsafe-0.1.0/tests/payloads/cases/case_12_newline.txt +2 -0
  41. shellsafe-0.1.0/tests/payloads/cases/case_13_fullwidth_semicolon.txt +1 -0
  42. shellsafe-0.1.0/tests/payloads/cases/case_14_whitespace.txt +1 -0
  43. shellsafe-0.1.0/tests/payloads/cases/case_15_unicode_dashes.txt +1 -0
  44. shellsafe-0.1.0/tests/property/.gitkeep +0 -0
  45. shellsafe-0.1.0/tests/property/test_invariants.py +35 -0
  46. shellsafe-0.1.0/tests/unit/test_cli.py +11 -0
  47. shellsafe-0.1.0/tests/unit/test_errors.py +13 -0
  48. shellsafe-0.1.0/tests/unit/test_payload_corpus.py +43 -0
  49. shellsafe-0.1.0/tests/unit/test_public_surface.py +19 -0
  50. shellsafe-0.1.0/tests/unit/test_raw.py +28 -0
  51. shellsafe-0.1.0/tests/unit/test_render.py +155 -0
@@ -0,0 +1,28 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ${{ matrix.os }}
11
+ continue-on-error: ${{ matrix.experimental == true }}
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ include:
16
+ - { os: ubuntu-latest, python: "3.14" }
17
+ - { os: ubuntu-latest, python: "3.14t", experimental: true }
18
+ - { os: macos-latest, python: "3.14" }
19
+ - { os: windows-latest, python: "3.14" }
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - uses: astral-sh/setup-uv@v5
23
+ with:
24
+ python-version: ${{ matrix.python }}
25
+ - run: uv sync
26
+ - run: uv run pytest -q
27
+ - run: uv run ruff check .
28
+ - run: uv run mypy src/
@@ -0,0 +1,31 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ build:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+ - uses: astral-sh/setup-uv@v5
13
+ - run: uv build
14
+ - run: uv run twine check dist/*
15
+ - uses: actions/upload-artifact@v4
16
+ with:
17
+ name: dist
18
+ path: dist/
19
+
20
+ publish-pypi:
21
+ needs: build
22
+ runs-on: ubuntu-latest
23
+ environment: pypi
24
+ permissions:
25
+ id-token: write
26
+ steps:
27
+ - uses: actions/download-artifact@v4
28
+ with:
29
+ name: dist
30
+ path: dist
31
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,12 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .venv/
7
+ .mypy_cache/
8
+ .ruff_cache/
9
+ .pytest_cache/
10
+ .coverage
11
+ coverage.xml
12
+ .lazyfix/
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. Format follows
4
+ Keep a Changelog; versioning follows SemVer.
5
+
6
+ ## [0.1.0] - 2026-08-24
7
+
8
+ ### Added
9
+
10
+ - Argv-mode rendering and execution via template strings
11
+ - RAW trust marker with argv splicing
12
+ - capture() helper with utf-8 stdout/stderr
13
+ - plan() helper for inspecting commands before execution
14
+ - Injection payload corpus as a release gate; all cases render inert
@@ -0,0 +1,12 @@
1
+ # Contributing
2
+
3
+ Issues and PRs welcome.
4
+
5
+ 1. Run the verification commands (pytest, ruff, mypy) after every change; PRs
6
+ must arrive green.
7
+ 2. Security-relevant changes require corpus or property coverage in the same PR.
8
+ 3. Commit subjects follow Conventional Commits.
9
+ 4. Prose follows plain technical writing: no marketing adjectives, state limits.
10
+
11
+ Report security vulnerabilities privately to the maintainer address in
12
+ pyproject.toml; do not open public issues.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rahul Sharma (rahulXs)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,126 @@
1
+ Metadata-Version: 2.5
2
+ Name: shellsafe
3
+ Version: 0.1.0
4
+ Summary: Safe shell commands via Python 3.14 template strings. Injection-proof by construction.
5
+ Project-URL: Repository, https://github.com/rahulXs/shellsafe
6
+ Project-URL: Issues, https://github.com/rahulXs/shellsafe/issues
7
+ Project-URL: Changelog, https://github.com/rahulXs/shellsafe/blob/main/CHANGELOG.md
8
+ Author-email: Rahul Sharma <rahulxsh@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: automation,command-injection,injection,pep750,security,shell,subprocess,t-strings,template-strings
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Topic :: Security
18
+ Classifier: Topic :: Software Development :: Libraries
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.14
21
+ Description-Content-Type: text/markdown
22
+
23
+ # shellsafe
24
+
25
+ > Safe shell commands via Python 3.14 template strings. Injection-proof by
26
+ > construction.
27
+
28
+ ```python
29
+ from shellsafe import run
30
+
31
+ message = get_user_input() # "fix; rm -rf ~"
32
+ run(t"git commit -m {message}")
33
+ # argv: ["git", "commit", "-m", "fix; rm -rf ~"]
34
+ # one command; the scary text is just an argument
35
+ ```
36
+
37
+ ## Why
38
+
39
+ The dominant pattern in scripts and automation is still this:
40
+
41
+ ```python
42
+ subprocess.run(f"git commit -m {message}", shell=True) # injection waiting to happen
43
+ ```
44
+
45
+ Python 3.14 template strings (`t"..."`) separate static text from interpolated
46
+ values. shellsafe turns that structure into argv lists where interpolated values
47
+ are always data, never commands. When you genuinely need pipes, shell mode quotes
48
+ every value with POSIX rules first.
49
+
50
+ ## Install
51
+
52
+ ```bash
53
+ pip install shellsafe
54
+ ```
55
+
56
+ Requires Python 3.14+ (template strings).
57
+
58
+ ## Usage
59
+
60
+ Run a command. Interpolated values are always single arguments:
61
+
62
+ ```python
63
+ from shellsafe import run
64
+
65
+ run(t"mkdir {path}")
66
+ run(t"docker build -t {tag} .", check=True, timeout=300)
67
+ ```
68
+
69
+ Capture output as text:
70
+
71
+ ```python
72
+ from shellsafe import capture
73
+
74
+ res = capture(t"grep {pattern} {file}")
75
+ print(res.stdout, res.returncode)
76
+ ```
77
+
78
+ Pipes and redirections on POSIX (values are quoted with `shlex.quote` first):
79
+
80
+ ```python
81
+ from shellsafe import shx
82
+
83
+ shx(t"cat {file} | wc -l")
84
+ ```
85
+
86
+ Inspect exactly what will execute:
87
+
88
+ ```python
89
+ from shellsafe import plan # lower-level: render without running
90
+
91
+ print(plan(t"git commit -m {message}"))
92
+ # argv: ["git","commit","-m","fix; rm -rf ~"]
93
+ ```
94
+
95
+ ## What it refuses
96
+
97
+ | Case | Behavior |
98
+ |---|---|
99
+ | Interpolation as the executable | error: the command comes from static text only |
100
+ | Shell route on Windows | error: cmd.exe quoting cannot be made injection-safe; use argv mode |
101
+ | RAW misuse | error: one argument, verbatim, nesting refused |
102
+
103
+ `RAW("...")` / `RAW(["a", "b"])` is the single explicit trust boundary for
104
+ pre-quoted content. Every use site is greppable.
105
+
106
+ ## Limits
107
+
108
+ - Windows: interpolated shell routes are refused rather than approximated;
109
+ argv-mode commands work fully.
110
+ - Bytes interpolations are rejected: decode explicitly first.
111
+ - Runtime behavior after import is your test suite's job, same trust model as
112
+ calling subprocess yourself.
113
+
114
+ ## Contributing
115
+
116
+ Issues and PRs welcome. Security reports go privately to the maintainer, never
117
+ through public issues.
118
+
119
+ ## Requirements
120
+
121
+ - CPython >= 3.14 (uses template strings from PEP 750)
122
+ - Linux, macOS, Windows (Windows supports argv mode; POSIX-only shell mode)
123
+
124
+ ## License
125
+
126
+ MIT
@@ -0,0 +1,104 @@
1
+ # shellsafe
2
+
3
+ > Safe shell commands via Python 3.14 template strings. Injection-proof by
4
+ > construction.
5
+
6
+ ```python
7
+ from shellsafe import run
8
+
9
+ message = get_user_input() # "fix; rm -rf ~"
10
+ run(t"git commit -m {message}")
11
+ # argv: ["git", "commit", "-m", "fix; rm -rf ~"]
12
+ # one command; the scary text is just an argument
13
+ ```
14
+
15
+ ## Why
16
+
17
+ The dominant pattern in scripts and automation is still this:
18
+
19
+ ```python
20
+ subprocess.run(f"git commit -m {message}", shell=True) # injection waiting to happen
21
+ ```
22
+
23
+ Python 3.14 template strings (`t"..."`) separate static text from interpolated
24
+ values. shellsafe turns that structure into argv lists where interpolated values
25
+ are always data, never commands. When you genuinely need pipes, shell mode quotes
26
+ every value with POSIX rules first.
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pip install shellsafe
32
+ ```
33
+
34
+ Requires Python 3.14+ (template strings).
35
+
36
+ ## Usage
37
+
38
+ Run a command. Interpolated values are always single arguments:
39
+
40
+ ```python
41
+ from shellsafe import run
42
+
43
+ run(t"mkdir {path}")
44
+ run(t"docker build -t {tag} .", check=True, timeout=300)
45
+ ```
46
+
47
+ Capture output as text:
48
+
49
+ ```python
50
+ from shellsafe import capture
51
+
52
+ res = capture(t"grep {pattern} {file}")
53
+ print(res.stdout, res.returncode)
54
+ ```
55
+
56
+ Pipes and redirections on POSIX (values are quoted with `shlex.quote` first):
57
+
58
+ ```python
59
+ from shellsafe import shx
60
+
61
+ shx(t"cat {file} | wc -l")
62
+ ```
63
+
64
+ Inspect exactly what will execute:
65
+
66
+ ```python
67
+ from shellsafe import plan # lower-level: render without running
68
+
69
+ print(plan(t"git commit -m {message}"))
70
+ # argv: ["git","commit","-m","fix; rm -rf ~"]
71
+ ```
72
+
73
+ ## What it refuses
74
+
75
+ | Case | Behavior |
76
+ |---|---|
77
+ | Interpolation as the executable | error: the command comes from static text only |
78
+ | Shell route on Windows | error: cmd.exe quoting cannot be made injection-safe; use argv mode |
79
+ | RAW misuse | error: one argument, verbatim, nesting refused |
80
+
81
+ `RAW("...")` / `RAW(["a", "b"])` is the single explicit trust boundary for
82
+ pre-quoted content. Every use site is greppable.
83
+
84
+ ## Limits
85
+
86
+ - Windows: interpolated shell routes are refused rather than approximated;
87
+ argv-mode commands work fully.
88
+ - Bytes interpolations are rejected: decode explicitly first.
89
+ - Runtime behavior after import is your test suite's job, same trust model as
90
+ calling subprocess yourself.
91
+
92
+ ## Contributing
93
+
94
+ Issues and PRs welcome. Security reports go privately to the maintainer, never
95
+ through public issues.
96
+
97
+ ## Requirements
98
+
99
+ - CPython >= 3.14 (uses template strings from PEP 750)
100
+ - Linux, macOS, Windows (Windows supports argv mode; POSIX-only shell mode)
101
+
102
+ ## License
103
+
104
+ MIT
@@ -0,0 +1,33 @@
1
+ """Side-by-side demo: the classic injection, rendered inert.
2
+
3
+ Run from an environment where shellsafe is importable:
4
+
5
+ python examples/demo.py
6
+ """
7
+
8
+ import sys
9
+ from pathlib import Path
10
+ from string.templatelib import Interpolation, Template
11
+
12
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))
13
+
14
+ from shellsafe.render import plan
15
+
16
+
17
+ def main() -> int:
18
+ message = "fix; rm -rf ~"
19
+
20
+ print("UNSAFE (f-string into a shell):")
21
+ print(f" $ git commit -m {message}")
22
+ print(" -> two commands; the second one deletes your home directory")
23
+ print()
24
+
25
+ print("SAFE (shellsafe):")
26
+ p = plan(Template("git commit -m ", Interpolation(message, "message", None, "")))
27
+ print(f" $ {p!r}")
28
+ print(" -> one command; the scary text is just an argument")
29
+ return 0
30
+
31
+
32
+ if __name__ == "__main__":
33
+ raise SystemExit(main())
@@ -0,0 +1,71 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "shellsafe"
7
+ dynamic = ["version"]
8
+ description = "Safe shell commands via Python 3.14 template strings. Injection-proof by construction."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.14"
12
+ authors = [{ name = "Rahul Sharma", email = "rahulxsh@gmail.com" }]
13
+ keywords = [
14
+ "shell", "subprocess", "injection", "security", "template-strings",
15
+ "t-strings", "pep750", "command-injection", "automation",
16
+ ]
17
+ classifiers = [
18
+ "Development Status :: 3 - Alpha",
19
+ "Intended Audience :: Developers",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.14",
23
+ "Topic :: Security",
24
+ "Topic :: Software Development :: Libraries",
25
+ "Typing :: Typed",
26
+ ]
27
+ dependencies = []
28
+
29
+ [project.scripts]
30
+ shellsafe = "shellsafe.cli:main"
31
+
32
+ [project.urls]
33
+ Repository = "https://github.com/rahulXs/shellsafe"
34
+ Issues = "https://github.com/rahulXs/shellsafe/issues"
35
+ Changelog = "https://github.com/rahulXs/shellsafe/blob/main/CHANGELOG.md"
36
+
37
+ [tool.hatch.version]
38
+ path = "src/shellsafe/_version.py"
39
+
40
+ [tool.hatch.build.targets.wheel]
41
+ packages = ["src/shellsafe"]
42
+
43
+ [tool.ruff]
44
+ target-version = "py314"
45
+ line-length = 100
46
+
47
+ [tool.ruff.lint]
48
+ select = ["E", "F", "W", "I", "N", "UP", "B", "SIM", "RUF"]
49
+
50
+ [tool.mypy]
51
+ strict = true
52
+ python_version = "3.14"
53
+
54
+ [tool.pytest.ini_options]
55
+ testpaths = ["tests"]
56
+ addopts = "-q --tb=short"
57
+
58
+ [tool.ruff.lint.per-file-ignores]
59
+ # RAW() is a deliberate loud trust marker; the uppercase name is the API.
60
+ "src/shellsafe/raw.py" = ["N802"]
61
+ "src/shellsafe/__init__.py" = ["N999"]
62
+
63
+ [dependency-groups]
64
+ dev = [
65
+ "pytest>=8",
66
+ "ruff>=0.5",
67
+ "mypy>=1.10",
68
+ "hypothesis>=6",
69
+ "build>=1.2",
70
+ "twine>=6",
71
+ ]
@@ -0,0 +1,18 @@
1
+ """shellsafe: safe shell commands via Python 3.14 template strings.
2
+
3
+ Public surface (stable names, additive only through 1.0):
4
+ run, capture, shx, RAW, CaptureResult.
5
+ """
6
+
7
+ from ._version import __version__
8
+ from .execute import CaptureResult, capture, run, shx
9
+ from .raw import RAW
10
+
11
+ __all__ = [
12
+ "RAW",
13
+ "CaptureResult",
14
+ "__version__",
15
+ "capture",
16
+ "run",
17
+ "shx",
18
+ ]
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,10 @@
1
+ """Offline AST audit for dangerous command construction. Ships with v0.3.0."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ..errors import ShellSafeError
6
+
7
+
8
+ def scan(paths: list[str]) -> list[dict[str, object]]:
9
+ """Scan paths for AU001-AU004 findings. Arrives in shellsafe 0.3."""
10
+ raise ShellSafeError("audit arrives in shellsafe 0.3")
File without changes
@@ -0,0 +1,44 @@
1
+ """CLI wiring: audit, demo, version. Business logic lives in the stages."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import platform
7
+ import sys
8
+
9
+ from . import exitcodes
10
+ from ._version import __version__
11
+
12
+
13
+ def _build_parser() -> argparse.ArgumentParser:
14
+ parser = argparse.ArgumentParser(prog="shellsafe")
15
+ parser.add_argument("-V", "--version", action="store_true")
16
+ sub = parser.add_subparsers(dest="command")
17
+
18
+ sub.add_parser("audit", help="scan code for dangerous command construction (v0.3)")
19
+ sub.add_parser("version", help="detailed version and capability matrix")
20
+ return parser
21
+
22
+
23
+ def _print_version_matrix() -> None:
24
+ py = platform.python_version()
25
+ print(f"shellsafe {__version__} · python {py} · {sys.platform}")
26
+ print("argv-mode: available")
27
+ print("shell-mode: arriving in 0.2")
28
+ print("audit: arriving in 0.3")
29
+
30
+
31
+ def main(argv: list[str] | None = None) -> int:
32
+ args = _build_parser().parse_args(argv)
33
+
34
+ if args.version:
35
+ print(f"shellsafe {__version__}")
36
+ return exitcodes.OK
37
+ if args.command == "version":
38
+ _print_version_matrix()
39
+ return exitcodes.OK
40
+ if args.command == "audit":
41
+ print("audit arrives in shellsafe 0.3", file=sys.stderr)
42
+ return exitcodes.USAGE
43
+ _build_parser().print_help()
44
+ return exitcodes.OK
@@ -0,0 +1,36 @@
1
+ """Exception hierarchy for shellsafe.
2
+
3
+ Every error raised publicly inherits from ShellSafeError so callers can catch
4
+ broadly. Messages are lowercase, state got and expected, and include the fix.
5
+ """
6
+
7
+
8
+ class ShellSafeError(Exception):
9
+ """Base class for every error shellsafe raises."""
10
+
11
+
12
+ class ShellSafeTypeError(ShellSafeError):
13
+ """A template or interpolated value has an unsupported type."""
14
+
15
+
16
+ class UnsupportedPlatformError(ShellSafeError):
17
+ """The requested shell route cannot be made safe on this platform.
18
+
19
+ Interpolated shell routes are refused on Windows by policy; restructure the
20
+ command as argv mode (no pipes or redirections) instead.
21
+ """
22
+
23
+
24
+ class ArgvOnlyError(ShellSafeError):
25
+ """shx() was called with a template that needs no shell at all.
26
+
27
+ Use run() for plain commands; shx() exists for pipes and redirections.
28
+ """
29
+
30
+
31
+ class RawUsageError(ShellSafeError):
32
+ """RAW() was used incorrectly.
33
+
34
+ RAW takes exactly one argument: a list of strings in argv mode or a string
35
+ in shell mode. RAW values are never nested and never modified.
36
+ """