shellsafe 0.1.0__py3-none-any.whl

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/__init__.py ADDED
@@ -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
+ ]
shellsafe/__main__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
shellsafe/_version.py ADDED
@@ -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
shellsafe/cli.py ADDED
@@ -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
shellsafe/errors.py ADDED
@@ -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
+ """
shellsafe/execute.py ADDED
@@ -0,0 +1,117 @@
1
+ """Subprocess wrappers over rendered ExecutionPlans."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ from dataclasses import dataclass
7
+ from typing import Any, cast
8
+
9
+ from .errors import ArgvOnlyError, ShellSafeError
10
+ from .raw import Raw # noqa: F401 (re-exported through the package root)
11
+ from .render import ExecutionPlan
12
+
13
+ _SHELL_MODE_PENDS = (
14
+ "shell-mode execution arrives in shellsafe 0.2; "
15
+ "this release covers argv-mode commands"
16
+ )
17
+
18
+ _ALLOWED_KWARGS = frozenset(
19
+ {
20
+ "check",
21
+ "timeout",
22
+ "env",
23
+ "cwd",
24
+ "capture_output",
25
+ "input",
26
+ "stdin",
27
+ "stdout",
28
+ "stderr",
29
+ "start_new_session",
30
+ "text",
31
+ "encoding",
32
+ }
33
+ )
34
+
35
+
36
+ def _validate_kwargs(kwargs: dict[str, object]) -> None:
37
+ if "shell" in kwargs:
38
+ raise ShellSafeError(
39
+ "shellsafe never passes shell=True; use shx() on posix for pipes and "
40
+ "redirections"
41
+ )
42
+ unknown = set(kwargs) - _ALLOWED_KWARGS
43
+ if unknown:
44
+ valid = ", ".join(sorted(_ALLOWED_KWARGS))
45
+ raise ShellSafeError(
46
+ f"unsupported keyword(s) {sorted(unknown)}; valid keywords: {valid}"
47
+ )
48
+
49
+
50
+ def plan(template: object) -> ExecutionPlan:
51
+ """Render a t-string into an inspectable ExecutionPlan without executing."""
52
+ from .render import plan as render_plan
53
+
54
+ return render_plan(template)
55
+
56
+
57
+ def run(template: object, /, **kwargs: object) -> subprocess.CompletedProcess[str]:
58
+ """Render the template and execute it.
59
+
60
+ Interpolated values always arrive as single argv elements. Keyword arguments
61
+ pass through to subprocess.run with one exception: shell is rejected by
62
+ design.
63
+ """
64
+ _validate_kwargs(kwargs)
65
+ rendered = plan(template)
66
+ if rendered.mode == "shell":
67
+ raise ShellSafeError(_SHELL_MODE_PENDS)
68
+ assert rendered.argv is not None
69
+ # passthrough boundary: values are caller-owned subprocess kwargs
70
+ typed_kwargs = cast(
71
+ "dict[str, Any]",
72
+ {k: v for k, v in kwargs.items() if k in _ALLOWED_KWARGS},
73
+ )
74
+ result = subprocess.run(rendered.argv, **typed_kwargs)
75
+ return cast("subprocess.CompletedProcess[str]", result)
76
+
77
+
78
+ @dataclass(frozen=True, slots=True)
79
+ class CaptureResult:
80
+ """Text-captured execution result plus the plan that produced it."""
81
+
82
+ stdout: str
83
+ stderr: str
84
+ returncode: int
85
+ plan: ExecutionPlan
86
+
87
+
88
+ def capture(template: object, /, **kwargs: object) -> CaptureResult:
89
+ """Run with captured utf-8 stdout/stderr and return a CaptureResult."""
90
+ kwargs["capture_output"] = True
91
+ kwargs["text"] = True
92
+ kwargs["encoding"] = "utf-8"
93
+ completed = run(template, **kwargs)
94
+ assert isinstance(completed.stdout, str)
95
+ assert isinstance(completed.stderr, str)
96
+ return CaptureResult(
97
+ stdout=completed.stdout,
98
+ stderr=completed.stderr,
99
+ returncode=completed.returncode,
100
+ plan=plan(template),
101
+ )
102
+
103
+
104
+ def shx(template: object, /, **kwargs: object) -> object:
105
+ """Shell-route alias for templates that need pipes or redirections.
106
+
107
+ Raises ArgvOnlyError when the template needs no shell at all, so a missing
108
+ pipe is never silently ignored. Full shell execution ships in shellsafe 0.2;
109
+ rendering and inspection work today via shellsafe.plan().
110
+ """
111
+ _validate_kwargs(kwargs)
112
+ rendered = plan(template)
113
+ if rendered.mode != "shell":
114
+ raise ArgvOnlyError(
115
+ "template contains no shell metacharacters; use run() instead"
116
+ )
117
+ raise ShellSafeError(_SHELL_MODE_PENDS)
shellsafe/exitcodes.py ADDED
@@ -0,0 +1,6 @@
1
+ """Process exit codes for the shellsafe CLI. Part of the public contract."""
2
+
3
+ OK = 0
4
+ FINDINGS = 1
5
+ USAGE = 2
6
+ INTERNAL = 3
shellsafe/platforms.py ADDED
@@ -0,0 +1,35 @@
1
+ """Platform policy tables and helpers. This module IS the security policy."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+
7
+ # Static template text containing any of these routes the command to shell mode.
8
+ # Whitespace is not here: it splits words in argv mode.
9
+ METACHARACTERS: frozenset[str] = frozenset("|&;()<>$`\"'*?!#\\\n\r\t")
10
+
11
+ IS_WINDOWS = sys.platform.startswith("win")
12
+
13
+
14
+ def route_for(static_text: str) -> str:
15
+ """Return "argv" or "shell" for the given static template text.
16
+
17
+ Raises UnsupportedPlatformError when shell features are requested on Windows;
18
+ cmd.exe quoting cannot be made injection-safe, so the refusal is the feature.
19
+ """
20
+ has_meta = any(ch in METACHARACTERS for ch in static_text)
21
+ if not has_meta:
22
+ return "argv"
23
+ if IS_WINDOWS:
24
+ raise _windows_error()
25
+ return "shell"
26
+
27
+
28
+ def _windows_error() -> Exception:
29
+ from .errors import UnsupportedPlatformError
30
+
31
+ return UnsupportedPlatformError(
32
+ "shell metacharacters in a shellsafe template require posix sh; "
33
+ "windows cmd.exe quoting cannot be made injection-safe. "
34
+ "restructure without pipes/redirections, or run under wsl."
35
+ )
shellsafe/py.typed ADDED
File without changes
shellsafe/raw.py ADDED
@@ -0,0 +1,30 @@
1
+ """RAW marker: the single explicit trust boundary."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .errors import RawUsageError
6
+
7
+
8
+ class Raw:
9
+ """Wraps pre-trusted content that rendering must not modify.
10
+
11
+ In argv mode the wrapped value must be a list of strings and is spliced
12
+ into the argv positionally. In POSIX shell mode a str value is inserted
13
+ verbatim into the shell line. Nothing else is accepted, nesting is refused,
14
+ and every use site is greppable.
15
+ """
16
+
17
+ __slots__ = ("value",)
18
+
19
+ def __init__(self, value: list[str] | str) -> None:
20
+ if isinstance(value, Raw):
21
+ raise RawUsageError("raw cannot wrap another raw")
22
+ self.value = value
23
+
24
+ def __repr__(self) -> str:
25
+ return f"<RAW {self.value!r}>"
26
+
27
+
28
+ def RAW(value: list[str] | str) -> Raw:
29
+ """Mark content as pre-trusted. See Raw for mode-specific rules."""
30
+ return Raw(value)
shellsafe/render.py ADDED
@@ -0,0 +1,192 @@
1
+ """Template rendering: t-string in, ExecutionPlan out. Pure and deterministic.
2
+
3
+ Security invariants (property-tested, see docs/07_rendering_engine_spec.md):
4
+ - INV-1: every interpolation contributes exactly one argv element equal to its
5
+ resolved string.
6
+ - INV-2: the executable comes from static template text only.
7
+ - INV-3: element count equals static words plus interpolations plus RAW splices.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import shlex
13
+ from dataclasses import dataclass
14
+ from string.templatelib import Interpolation, Template
15
+
16
+ from .errors import RawUsageError, ShellSafeTypeError
17
+ from .platforms import route_for
18
+ from .raw import Raw
19
+
20
+ _NUL = "\x00"
21
+
22
+ # Static-text characters that require shell semantics (pipes, redirection,
23
+ # substitution, globs, comments). Whitespace is absent here: it splits words
24
+ # in argv mode rather than forcing a shell.
25
+ METACHARACTERS: frozenset[str] = frozenset("|&;()<>$`\"'*?!#\n\r\t\\")
26
+
27
+
28
+ @dataclass(frozen=True, slots=True)
29
+ class ExecutionPlan:
30
+ """The exact thing that will execute. Inspect via repr before running."""
31
+
32
+ mode: str # "argv" or "shell"
33
+ argv: tuple[str, ...] | None = None
34
+ shell_line: str | None = None
35
+
36
+ def __repr__(self) -> str:
37
+ if self.mode == "argv":
38
+ assert self.argv is not None
39
+ body = ",".join(repr(a) for a in self.argv)
40
+ return f"argv: [{body}]"
41
+ assert self.shell_line is not None
42
+ return f"shell: {self.shell_line}"
43
+
44
+
45
+ Segment = str | Interpolation[str] | Raw
46
+
47
+
48
+ def _walk(
49
+ template: Template, seen: frozenset[int]
50
+ ) -> list[Segment]:
51
+ """Flatten a template into an ordered list of static text and interpolations.
52
+
53
+ Nested templates (a t-string interpolated inside a t-string) splice their
54
+ segments at the interpolation position. Cycle-guarded via object ids.
55
+ """
56
+ parts: list[Segment] = []
57
+ for element in template:
58
+ if isinstance(element, str):
59
+ parts.append(element)
60
+ elif isinstance(element, Interpolation):
61
+ if isinstance(element.value, Raw):
62
+ parts.append(Raw(element.value.value))
63
+ elif isinstance(element.value, Template):
64
+ if id(element.value) in seen:
65
+ raise ShellSafeTypeError("template references itself")
66
+ parts.extend(_walk(element.value, seen | {id(element.value)}))
67
+ else:
68
+ parts.append(element)
69
+ else:
70
+ raise ShellSafeTypeError(
71
+ f"unexpected template element {type(element).__name__}"
72
+ )
73
+ return parts
74
+
75
+
76
+ def _resolve(interpolation: Interpolation[str]) -> str:
77
+ """Resolve one interpolation exactly as an f-string would."""
78
+ value = interpolation.value
79
+ if isinstance(value, (bytes, bytearray)):
80
+ raise ShellSafeTypeError(
81
+ "bytes interpolation is ambiguous; decode it explicitly first"
82
+ )
83
+ conversion = interpolation.conversion
84
+ format_spec = interpolation.format_spec
85
+
86
+ converted: object
87
+ if conversion is None:
88
+ converted = value
89
+ elif conversion == "r":
90
+ converted = repr(value)
91
+ elif conversion == "s":
92
+ converted = str(value)
93
+ elif conversion == "a":
94
+ converted = ascii(value)
95
+ else:
96
+ raise ShellSafeTypeError(
97
+ f"unsupported conversion {conversion!r}; expected 'r', 's', 'a', or None"
98
+ )
99
+
100
+ if format_spec:
101
+ return format(converted, format_spec)
102
+ if conversion is None:
103
+ return str(converted)
104
+ assert isinstance(converted, str)
105
+ return converted
106
+
107
+
108
+ def _reject_nul(resolved: str) -> None:
109
+ if _NUL in resolved:
110
+ raise ShellSafeTypeError(
111
+ "interpolated value contains a NUL byte after formatting; "
112
+ "executables cannot receive NUL bytes"
113
+ )
114
+
115
+
116
+ def plan(template: object) -> ExecutionPlan:
117
+ """Render a t-string into an inspectable ExecutionPlan."""
118
+ if not isinstance(template, Template):
119
+ raise ShellSafeTypeError(
120
+ f"expected a t-string template, got {type(template).__name__}"
121
+ )
122
+
123
+ parts = _walk(template, frozenset({id(template)}))
124
+ route = route_for("".join(p for p in parts if isinstance(p, str)))
125
+
126
+ if route == "argv":
127
+ return _render_argv(parts)
128
+ return _render_shell(parts)
129
+
130
+
131
+ def _render_argv(parts: list[Segment]) -> ExecutionPlan:
132
+ argv: list[str] = []
133
+ origins: list[str] = []
134
+
135
+ for part in parts:
136
+ if isinstance(part, str):
137
+ for word in part.split():
138
+ argv.append(word)
139
+ origins.append("static")
140
+ elif isinstance(part, Raw):
141
+ raw_value = part.value
142
+ if not isinstance(raw_value, list):
143
+ raise RawUsageError(
144
+ "RAW str values are only valid in shell mode; "
145
+ "in argv mode pass RAW(['one', 'argv', 'element'])"
146
+ )
147
+ for element in raw_value:
148
+ if not isinstance(element, str):
149
+ raise RawUsageError(
150
+ "RAW lists in argv mode contain strings only; got "
151
+ f"{type(element).__name__}"
152
+ )
153
+ _reject_nul(element)
154
+ argv.append(element)
155
+ origins.append("raw")
156
+ else:
157
+ resolved = _resolve(part)
158
+ _reject_nul(resolved)
159
+ argv.append(resolved)
160
+ origins.append("interpolation")
161
+
162
+ if not argv:
163
+ raise ShellSafeTypeError("empty command")
164
+
165
+ if origins[0] != "static":
166
+ raise ShellSafeTypeError(
167
+ "the executable must come from static template text; "
168
+ "interpolate arguments instead of commands"
169
+ )
170
+
171
+ return ExecutionPlan(mode="argv", argv=tuple(argv))
172
+
173
+
174
+ def _render_shell(parts: list[Segment]) -> ExecutionPlan:
175
+ line_parts: list[str] = []
176
+ for part in parts:
177
+ if isinstance(part, str):
178
+ line_parts.append(part)
179
+ elif isinstance(part, Raw):
180
+ raw_value = part.value
181
+ if not isinstance(raw_value, str):
182
+ raise RawUsageError(
183
+ "RAW list values are only valid in argv mode; "
184
+ "in shell mode pass a single string"
185
+ )
186
+ _reject_nul(raw_value)
187
+ line_parts.append(raw_value)
188
+ else:
189
+ resolved = _resolve(part)
190
+ _reject_nul(resolved)
191
+ line_parts.append(shlex.quote(resolved))
192
+ return ExecutionPlan(mode="shell", shell_line="".join(line_parts))
shellsafe/reporters.py ADDED
@@ -0,0 +1 @@
1
+ """Finding reporters: terminal and json. Arrives in shellsafe 0.3."""
@@ -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,19 @@
1
+ shellsafe/__init__.py,sha256=nTpsWvya8AVgO1OoY2CKfGjllbeUJvAJYkKKPFqJxdk,383
2
+ shellsafe/__main__.py,sha256=k1ocEWawweo1qCJWNFAAvyxz3tcY13dzvCenHszij30,48
3
+ shellsafe/_version.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
4
+ shellsafe/cli.py,sha256=TVjCrcPuS01JLd5DBAnDrovnlVDJYwjFyGBU0r5I_dY,1337
5
+ shellsafe/errors.py,sha256=chDBp9q221IneE2El1jOwKNYi4c55fovvhJkDTjDYps,1092
6
+ shellsafe/execute.py,sha256=RFzzsbz-7Jp94gJbEq2gPUyAZdxRByX9EVkinYH2oe8,3561
7
+ shellsafe/exitcodes.py,sha256=XtYF6a0AWUu13UI0SMij8fh3MnrDTTkWboiN3hPyIF0,121
8
+ shellsafe/platforms.py,sha256=s-tM-R3HARXSdhwgaUgE6Mn-Hc0FxzvKcoVc6e6Ny9U,1176
9
+ shellsafe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ shellsafe/raw.py,sha256=qgx-AW7N1owB65u8lR3thMncAXZqnLvmMQyinEcb3nY,898
11
+ shellsafe/render.py,sha256=yy29C6_NPtKF6nbywbbxWimcD5h2yYPKGnnG6QUiZdE,6528
12
+ shellsafe/reporters.py,sha256=iHjEoqrwG665eOon9Yy6QK19mIwou42BkmZYrl2xc2M,70
13
+ shellsafe/audit/__init__.py,sha256=NLJA1Fg7zRqno4HSoyNOWHHJmxVOZgxvnF31PYuiOUc,341
14
+ shellsafe/audit/rules.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ shellsafe-0.1.0.dist-info/METADATA,sha256=tiaMAuC2Iyp9CINJ8CBUa4-uIvlmbsQDVJE9qqju84A,3543
16
+ shellsafe-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
17
+ shellsafe-0.1.0.dist-info/entry_points.txt,sha256=DmSHwYR25TXX7V-tOAz-WN3o8XRA5wb6tguDBaQq9Z0,49
18
+ shellsafe-0.1.0.dist-info/licenses/LICENSE,sha256=IP9ZsQ9Wh-8yPf3q3yVB83H9MqyWinWhSgU5bTQb_ko,1079
19
+ shellsafe-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ shellsafe = shellsafe.cli:main
@@ -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.