shellsafe 0.1.0__tar.gz → 0.1.1__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.
- {shellsafe-0.1.0 → shellsafe-0.1.1}/CHANGELOG.md +8 -0
- shellsafe-0.1.1/PKG-INFO +128 -0
- shellsafe-0.1.1/README.md +106 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/pyproject.toml +1 -1
- shellsafe-0.1.1/src/shellsafe/_version.py +1 -0
- shellsafe-0.1.1/tests/unit/test_raw.py +15 -0
- shellsafe-0.1.0/PKG-INFO +0 -126
- shellsafe-0.1.0/README.md +0 -104
- shellsafe-0.1.0/src/shellsafe/_version.py +0 -1
- shellsafe-0.1.0/tests/unit/test_raw.py +0 -28
- {shellsafe-0.1.0 → shellsafe-0.1.1}/.github/workflows/ci.yml +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/.github/workflows/release.yml +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/.gitignore +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/CONTRIBUTING.md +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/LICENSE +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/examples/demo.py +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/src/shellsafe/__init__.py +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/src/shellsafe/__main__.py +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/src/shellsafe/audit/__init__.py +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/src/shellsafe/audit/rules.py +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/src/shellsafe/cli.py +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/src/shellsafe/errors.py +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/src/shellsafe/execute.py +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/src/shellsafe/exitcodes.py +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/src/shellsafe/platforms.py +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/src/shellsafe/py.typed +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/src/shellsafe/raw.py +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/src/shellsafe/render.py +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/src/shellsafe/reporters.py +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/golden/.gitkeep +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/integration/.gitkeep +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/integration/test_exec_posix.py +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/payloads/cases/README.md +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/payloads/cases/case_01_semicolon.txt +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/payloads/cases/case_02_substitution.txt +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/payloads/cases/case_03_backticks.txt +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/payloads/cases/case_04_background_chain.txt +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/payloads/cases/case_05_or_chain.txt +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/payloads/cases/case_06_redirect_out.txt +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/payloads/cases/case_07_redirect_in.txt +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/payloads/cases/case_08_pipe_out.txt +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/payloads/cases/case_09_quote_smuggle.txt +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/payloads/cases/case_10_quote_storm.txt +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/payloads/cases/case_11_globs_expansions.txt +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/payloads/cases/case_12_newline.txt +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/payloads/cases/case_13_fullwidth_semicolon.txt +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/payloads/cases/case_14_whitespace.txt +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/payloads/cases/case_15_unicode_dashes.txt +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/property/.gitkeep +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/property/test_invariants.py +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/unit/test_cli.py +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/unit/test_errors.py +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/unit/test_payload_corpus.py +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/unit/test_public_surface.py +0 -0
- {shellsafe-0.1.0 → shellsafe-0.1.1}/tests/unit/test_render.py +0 -0
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
All notable changes to this project are documented here. Format follows
|
|
4
4
|
Keep a Changelog; versioning follows SemVer.
|
|
5
5
|
|
|
6
|
+
## [0.1.1] - 2026-08-24
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
|
|
10
|
+
- Project description rewritten
|
|
11
|
+
- README restructured for PyPI: package page now leads with the why, usage and
|
|
12
|
+
limits; contributing details stay in the repository only
|
|
13
|
+
|
|
6
14
|
## [0.1.0] - 2026-08-24
|
|
7
15
|
|
|
8
16
|
### Added
|
shellsafe-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: shellsafe
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Run shell commands safely using Python 3.14 template strings. Values can never turn into commands.
|
|
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
|
+
> Run shell commands safely using Python 3.14 template strings. Values can never
|
|
26
|
+
> turn into commands.
|
|
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 this package exists
|
|
38
|
+
|
|
39
|
+
Python 3.14 added template strings (PEP 750). Now Python keeps your fixed text
|
|
40
|
+
and your values separate at the language level.
|
|
41
|
+
|
|
42
|
+
Shell commands are the first place people want to use this. That is because
|
|
43
|
+
f-strings inside shell commands have caused real security bugs for ten years:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
subprocess.run(f"git commit -m {message}", shell=True)
|
|
47
|
+
# if message = "fix; rm -rf ~" -> two commands run. The second one is bad.
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Python planned to solve this officially (PEP 787), but that plan was postponed.
|
|
51
|
+
So today there is no standard way to run shell commands safely with templates.
|
|
52
|
+
This package fills that gap.
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install shellsafe
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Needs Python 3.14 or newer.
|
|
61
|
+
|
|
62
|
+
## How to use
|
|
63
|
+
|
|
64
|
+
Run a command. Your values always stay one argument each:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from shellsafe import run
|
|
68
|
+
|
|
69
|
+
run(t"mkdir {path}")
|
|
70
|
+
run(t"docker build -t {tag} .", check=True, timeout=300)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Get the output as text:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from shellsafe import capture
|
|
77
|
+
|
|
78
|
+
res = capture(t"grep {pattern} {file}")
|
|
79
|
+
print(res.stdout, res.returncode)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Need pipes? Works on Linux and macOS. Your values are quoted safely first:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from shellsafe import shx
|
|
86
|
+
|
|
87
|
+
shx(t"cat {file} | wc -l")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Want to see exactly what will run?
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from shellsafe import plan
|
|
94
|
+
|
|
95
|
+
print(plan(t"git commit -m {message}"))
|
|
96
|
+
# argv: ["git","commit","-m","fix; rm -rf ~"]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Safety rules
|
|
100
|
+
|
|
101
|
+
| Case | What happens |
|
|
102
|
+
|---|---|
|
|
103
|
+
| Any value you pass | becomes one argument, exactly as given |
|
|
104
|
+
| Value used as the program name | error: program names must be written as fixed text |
|
|
105
|
+
| Shell features on Windows | error: we cannot make Windows safe this way, so we say no |
|
|
106
|
+
| RAW() misuse | error: one value only, used as-is, no nesting |
|
|
107
|
+
|
|
108
|
+
`RAW(...)` marks content you have already made safe by hand. It is loud and easy
|
|
109
|
+
to find in code review, so trust is never hidden.
|
|
110
|
+
|
|
111
|
+
## Limits
|
|
112
|
+
|
|
113
|
+
- On Windows, commands with pipes do not work. Plain commands work fully.
|
|
114
|
+
- Byte values are rejected. Decode them first.
|
|
115
|
+
- We keep your command safe to build and run. Testing what your command does is
|
|
116
|
+
still your job.
|
|
117
|
+
|
|
118
|
+
## Needs
|
|
119
|
+
|
|
120
|
+
- Python 3.14 or newer
|
|
121
|
+
- Linux, macOS, Windows (pipes work on Linux and macOS only)
|
|
122
|
+
|
|
123
|
+
## More
|
|
124
|
+
|
|
125
|
+
- Source and issues: [github.com/rahulXs/shellsafe](https://github.com/rahulXs/shellsafe)
|
|
126
|
+
- Want to help? See CONTRIBUTING.md in the repository.
|
|
127
|
+
|
|
128
|
+
License: MIT
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# shellsafe
|
|
2
|
+
|
|
3
|
+
> Run shell commands safely using Python 3.14 template strings. Values can never
|
|
4
|
+
> turn into commands.
|
|
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 this package exists
|
|
16
|
+
|
|
17
|
+
Python 3.14 added template strings (PEP 750). Now Python keeps your fixed text
|
|
18
|
+
and your values separate at the language level.
|
|
19
|
+
|
|
20
|
+
Shell commands are the first place people want to use this. That is because
|
|
21
|
+
f-strings inside shell commands have caused real security bugs for ten years:
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
subprocess.run(f"git commit -m {message}", shell=True)
|
|
25
|
+
# if message = "fix; rm -rf ~" -> two commands run. The second one is bad.
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Python planned to solve this officially (PEP 787), but that plan was postponed.
|
|
29
|
+
So today there is no standard way to run shell commands safely with templates.
|
|
30
|
+
This package fills that gap.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install shellsafe
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Needs Python 3.14 or newer.
|
|
39
|
+
|
|
40
|
+
## How to use
|
|
41
|
+
|
|
42
|
+
Run a command. Your values always stay one argument each:
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from shellsafe import run
|
|
46
|
+
|
|
47
|
+
run(t"mkdir {path}")
|
|
48
|
+
run(t"docker build -t {tag} .", check=True, timeout=300)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Get the output as text:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from shellsafe import capture
|
|
55
|
+
|
|
56
|
+
res = capture(t"grep {pattern} {file}")
|
|
57
|
+
print(res.stdout, res.returncode)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Need pipes? Works on Linux and macOS. Your values are quoted safely first:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from shellsafe import shx
|
|
64
|
+
|
|
65
|
+
shx(t"cat {file} | wc -l")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Want to see exactly what will run?
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from shellsafe import plan
|
|
72
|
+
|
|
73
|
+
print(plan(t"git commit -m {message}"))
|
|
74
|
+
# argv: ["git","commit","-m","fix; rm -rf ~"]
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Safety rules
|
|
78
|
+
|
|
79
|
+
| Case | What happens |
|
|
80
|
+
|---|---|
|
|
81
|
+
| Any value you pass | becomes one argument, exactly as given |
|
|
82
|
+
| Value used as the program name | error: program names must be written as fixed text |
|
|
83
|
+
| Shell features on Windows | error: we cannot make Windows safe this way, so we say no |
|
|
84
|
+
| RAW() misuse | error: one value only, used as-is, no nesting |
|
|
85
|
+
|
|
86
|
+
`RAW(...)` marks content you have already made safe by hand. It is loud and easy
|
|
87
|
+
to find in code review, so trust is never hidden.
|
|
88
|
+
|
|
89
|
+
## Limits
|
|
90
|
+
|
|
91
|
+
- On Windows, commands with pipes do not work. Plain commands work fully.
|
|
92
|
+
- Byte values are rejected. Decode them first.
|
|
93
|
+
- We keep your command safe to build and run. Testing what your command does is
|
|
94
|
+
still your job.
|
|
95
|
+
|
|
96
|
+
## Needs
|
|
97
|
+
|
|
98
|
+
- Python 3.14 or newer
|
|
99
|
+
- Linux, macOS, Windows (pipes work on Linux and macOS only)
|
|
100
|
+
|
|
101
|
+
## More
|
|
102
|
+
|
|
103
|
+
- Source and issues: [github.com/rahulXs/shellsafe](https://github.com/rahulXs/shellsafe)
|
|
104
|
+
- Want to help? See CONTRIBUTING.md in the repository.
|
|
105
|
+
|
|
106
|
+
License: MIT
|
|
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "shellsafe"
|
|
7
7
|
dynamic = ["version"]
|
|
8
|
-
description = "
|
|
8
|
+
description = "Run shell commands safely using Python 3.14 template strings. Values can never turn into commands."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
11
11
|
requires-python = ">=3.14"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.1"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""RAW marker invariants: nesting refused, verbatim display, single argument."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from shellsafe import RAW
|
|
6
|
+
from shellsafe.errors import RawUsageError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_raw_repr_shows_trust_boundary():
|
|
10
|
+
assert "<RAW" in repr(RAW("pre-quoted"))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_nested_raw_refused():
|
|
14
|
+
with pytest.raises(RawUsageError):
|
|
15
|
+
RAW(RAW(["x"]))
|
shellsafe-0.1.0/PKG-INFO
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
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
|
shellsafe-0.1.0/README.md
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.0"
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
"""RAW marker invariants: nesting refused, verbatim display, single argument."""
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
|
|
5
|
-
from shellsafe import RAW
|
|
6
|
-
from shellsafe.errors import RawUsageError
|
|
7
|
-
from shellsafe.raw import Raw
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def test_raw_wraps_list_verbatim():
|
|
11
|
-
r = RAW(["git", "log", "--oneline"])
|
|
12
|
-
assert isinstance(r, Raw)
|
|
13
|
-
assert r.value == ["git", "log", "--oneline"]
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def test_raw_repr_shows_trust_boundary():
|
|
17
|
-
assert "<RAW" in repr(RAW("pre-quoted"))
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def test_nested_raw_refused():
|
|
21
|
-
with pytest.raises(RawUsageError):
|
|
22
|
-
RAW(RAW(["x"]))
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def test_raw_is_greppable():
|
|
26
|
-
# house rule: trust boundaries are greppable; this documents the pattern
|
|
27
|
-
source = 'RAW("trusted")'
|
|
28
|
-
assert "RAW(" in source
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|