opsscript-gate 0.3.0__tar.gz → 0.4.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.
Files changed (50) hide show
  1. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/.github/workflows/demo.yml +2 -0
  2. opsscript_gate-0.4.1/CHANGELOG.md +114 -0
  3. opsscript_gate-0.4.1/PKG-INFO +308 -0
  4. opsscript_gate-0.4.1/README.md +280 -0
  5. opsscript_gate-0.4.1/ROADMAP.md +44 -0
  6. opsscript_gate-0.4.1/SECURITY.md +42 -0
  7. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/action.yml +34 -6
  8. opsscript_gate-0.4.1/docs/promotion/awesome-lists-pr.md +59 -0
  9. opsscript_gate-0.4.1/docs/promotion/juejin-zhihu-article.md +144 -0
  10. opsscript_gate-0.4.1/docs/promotion/reddit-devops.md +56 -0
  11. opsscript_gate-0.4.1/docs/promotion/show-hn.md +67 -0
  12. opsscript_gate-0.4.1/docs/promotion/v2ex-post.md +68 -0
  13. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/pyproject.toml +1 -1
  14. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/src/opsscript_gate/__init__.py +1 -1
  15. opsscript_gate-0.4.1/src/opsscript_gate/cli.py +233 -0
  16. opsscript_gate-0.4.1/src/opsscript_gate/discovery.py +106 -0
  17. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/src/opsscript_gate/models.py +30 -0
  18. opsscript_gate-0.4.1/src/opsscript_gate/remediation.py +104 -0
  19. opsscript_gate-0.4.1/src/opsscript_gate/reporter.py +487 -0
  20. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/src/opsscript_gate/runner.py +334 -122
  21. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/tests/test_runner.py +817 -2
  22. opsscript_gate-0.3.0/CHANGELOG.md +0 -76
  23. opsscript_gate-0.3.0/PKG-INFO +0 -286
  24. opsscript_gate-0.3.0/README.md +0 -258
  25. opsscript_gate-0.3.0/ROADMAP.md +0 -33
  26. opsscript_gate-0.3.0/SECURITY.md +0 -38
  27. opsscript_gate-0.3.0/src/opsscript_gate/cli.py +0 -136
  28. opsscript_gate-0.3.0/src/opsscript_gate/reporter.py +0 -170
  29. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/.gitattributes +0 -0
  30. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  31. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/.github/ISSUE_TEMPLATE/distro_request.yml +0 -0
  32. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  33. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/.github/assets/social-preview.png +0 -0
  34. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/.github/pull_request_template.md +0 -0
  35. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/.github/workflows/release.yml +0 -0
  36. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/.github/workflows/test.yml +0 -0
  37. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/.gitignore +0 -0
  38. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/CONTRIBUTING.md +0 -0
  39. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/LICENSE +0 -0
  40. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/examples/alpine-incompatibility/README.md +0 -0
  41. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/examples/alpine-incompatibility/apt_dependency.sh +0 -0
  42. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/examples/basic/README.md +0 -0
  43. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/examples/basic/clean_setup.sh +0 -0
  44. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/examples/github-actions/README.md +0 -0
  45. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/examples/github-actions/workflow.yml +0 -0
  46. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/examples/interactive-hang/README.md +0 -0
  47. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/examples/interactive-hang/prompt_confirm.sh +0 -0
  48. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/tests/fixtures/fail_deps.sh +0 -0
  49. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/tests/fixtures/fail_hang.sh +0 -0
  50. {opsscript_gate-0.3.0 → opsscript_gate-0.4.1}/tests/fixtures/pass_basic.sh +0 -0
@@ -3,6 +3,8 @@ name: Demo Shell Script Compatibility Gate
3
3
  on:
4
4
  push:
5
5
  branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
6
8
  workflow_dispatch:
7
9
 
8
10
  jobs:
@@ -0,0 +1,114 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.4.1] - 2026-09-19
9
+
10
+ ### Fixed & Hardened
11
+ - **Bounded container log capture**: Stream-captures container logs using an immediate rolling byte buffer capped at 256 KiB (`MAX_CAPTURED_LOG_BYTES`) and tail limited to 500 lines (`MAX_LOG_TAIL_LINES`), bounding retained container log data to reduce memory-exhaustion risk from runaway output.
12
+ - **Workflow command neutralization**: Neutralizes untrusted container logs starting with `::` into `[container] ::` to prevent malicious scripts from forging GitHub Actions workflow commands in Runner consoles, while keeping OpsScript Gate's own annotations intact.
13
+ - **Terminal ANSI and control character sanitization**: Strips ANSI escape sequences (CSI, OSC, 2-byte escapes), removes dangerous C0 control characters and DEL, and normalizes CRLF and standalone carriage returns (`\r`) to defeat terminal line overwrite and status spoofing, while preserving printable Unicode.
14
+ - **Context-sensitive Markdown and HTML injection defense**: Implemented dedicated escaping (`escape_inline_code`, `escape_markdown_text`, `escape_markdown_table_cell`, `escape_html_text`, `format_safe_code_fence`) to prevent table cell tearing, HTML container breakout (`</details>`), premature code fence closure, and structural Markdown injection from runtime-derived values.
15
+ - **Bounded streaming script preparation**: Stream-normalizes CRLF in bounded 64 KiB chunks (`SCRIPT_READ_CHUNK_SIZE`), properly handling boundary-split CRLF while preserving lone CR bytes without whole-file memory allocation. Bounded shebang parsing to 4096 bytes (`MAX_SHEBANG_BYTES`).
16
+ - **Parallel matrix script preparation deduplication**: Pre-normalizes scripts once before parallel matrix execution, sharing the prepared file read-only across worker threads and centralizing cleanup to eliminate file locking and race conditions on Windows.
17
+ - **Shebang evaluation refactoring**: Deduplicated shebang resolution across `shebang` and `auto` modes via `_resolve_shell_command`.
18
+ - **Regression test coverage**: Added extensive adversarial and edge-case tests covering oversized streams, boundary splits, workflow spoofing, Markdown injections, and error branches.
19
+
20
+ ---
21
+
22
+ ## [0.4.0] - 2026-09-19
23
+
24
+ ### Added
25
+ - **High-confidence line-level GitHub Actions annotations**: Emits inline `::error` annotations with exact script line numbers directly on pull request file diffs.
26
+ - **Workflow command injection defense**: Strictly escapes parameters (`%`, `\r`, `\n`, `:`, `,`) and message bodies (`%`, `\r`, `\n`) to eliminate command injection from unclassified container logs.
27
+ - **Conservative distro-aware remediation hints**: Suggests testable, high-confidence remediation recommendations (`RemediationRule`) with conservative phrasing (`normally`, `may`, `consider`), covering:
28
+ - Alpine package manager mismatches (`apt`/`apt-get` -> `apk`)
29
+ - Debian/Ubuntu package manager mismatches (`apk` -> `apt-get`)
30
+ - Alpine minimal missing Bash (`bash` -> consider POSIX sh or installing bash)
31
+ - Missing network prerequisites (`curl`, `wget`)
32
+ - Missing shebang interpreter paths.
33
+ - **Re-engineered Compatibility Card**: Overhauled GitHub Actions Step Summary with an instant 5-second triage table and an expandable copy-pasteable Markdown snippet ready for PR descriptions and Issue comments.
34
+ - **Parallel matrix execution (`--jobs` / `jobs`)**: Concurrently runs container checks using `ThreadPoolExecutor` (defaults to `min(2, matrix_size)`), strictly preserving matrix output order and ensuring reliable container cleanup.
35
+ - **Hardened container resource limits**: Added `--mem-limit` (default: 256m) and `--pids-limit` (default: 128) flags and Action inputs to prevent runaway resource exhaustion.
36
+ - **Configurable network isolation (`--network`)**: Supports `--network bridge` (default) and `--network none` for offline script execution.
37
+ - **Zero-config repository script auto-discovery**: Running `opsscript-gate run` without arguments automatically discovers and validates shell scripts in the repository, ignoring directories like `.git`, `node_modules`, and `.venv`, capped at 20 scripts and 1 MB per file.
38
+ - **Multi-script test consolidation (`MultiScriptReport`)**: Supports aggregate reporting and Step Summary cards when multiple scripts are evaluated in a single run.
39
+
40
+ ### Changed
41
+ - **Action initialization performance**: Removed redundant `pip install --upgrade pip` step in composite `action.yml` to minimize Action startup latency.
42
+ - **Flexible Action inputs**: Made `script-path` optional in `action.yml` (triggers auto-discovery if omitted); added inputs `jobs`, `mem-limit`, `pids-limit`, and `network`.
43
+
44
+ ---
45
+
46
+ ## [0.3.0] - 2026-09-19
47
+
48
+ ### Added
49
+ - Structured runtime failure diagnostics: detect common "command not found" failures across Linux distributions (Alpine/BusyBox ash, Debian/Ubuntu dash, Bash, and missing interpreter paths).
50
+ - High-confidence dual-signal classification: requires exit code 127 and matching shell output to produce a `missing_command` diagnostic.
51
+ - Additive `diagnostic` field in JSON report format (`SingleResult.to_dict()`) providing structured diagnostic information (backward-compatible for consumers tolerating additional fields).
52
+ - Enhanced terminal table formatting and GitHub Actions Step Summary showing clear failure causes in the summary overview.
53
+
54
+ ---
55
+
56
+ ## [0.2.1] - 2026-09-19
57
+
58
+ ### Fixed
59
+ - Strip complete ANSI CSI and OSC escape sequences from diagnostic text instead of leaving escape-sequence residue.
60
+ - Ensure diagnostic `max_length` includes the trailing ellipsis when truncation occurs.
61
+ - Add regression coverage for ANSI sanitization and truncation edge cases.
62
+
63
+ ---
64
+
65
+ ## [0.2.0] - 2026-09-18
66
+
67
+ ### Added
68
+ - Explicit shell execution modes (`--shell posix|shebang|auto`) in CLI and GitHub Action.
69
+ - `posix` (default): Strictly executes with `/bin/sh`, ignoring any script shebang to verify portability against minimal POSIX environments.
70
+ - `shebang`: Strictly honors recognized shebang interpreters (`sh`, `bash`, `/usr/bin/env`). Fails immediately with clear diagnostics if missing, malformed, or unsupported.
71
+ - `auto`: Uses recognized shebang when present; safely falls back to `/bin/sh` if no shebang is present; rejects unsupported/malformed shebangs without silent fallback.
72
+ - Exact interpreter-path preservation using fixed trusted container command constants (`SUPPORTED_SHEBANG_COMMANDS`), preventing `$PATH` resolution from masking missing paths like `/usr/bin/bash`.
73
+ - Hardened GitHub Action input passing via step-level environment variables to eliminate shell injection risks.
74
+ - Dedicated Docker integration test job in GitHub Actions workflow verifying real container behaviors (including bash missing on Alpine 3.20).
75
+ - Sanitized shebang error diagnostics against control character injection and multiline pollution.
76
+
77
+ ---
78
+
79
+ ## [0.1.2] - 2026-09-18
80
+
81
+ ### Added
82
+ - Configured PyPI Trusted Publishing via GitHub Actions OIDC (`pypa/gh-action-pypi-publish`).
83
+ - Added full PyPI package metadata, classifiers, and project URLs to `pyproject.toml`.
84
+ - Ensured absolute asset URLs in `README.md` for seamless PyPI rendering.
85
+
86
+ ---
87
+
88
+ ## [0.1.1] - 2026-09-18
89
+
90
+ ### Added
91
+ - Created `examples/` directory showcasing basic setup, Alpine package manager incompatibility, and interactive hang prevention.
92
+ - Added GitHub Issue templates for bug reports, feature requests, and distribution support requests.
93
+ - Added `CONTRIBUTING.md`, `SECURITY.md`, and `ROADMAP.md`.
94
+ - Enforced `security_opt=["no-new-privileges:true"]` on all container executions.
95
+
96
+ ### Fixed
97
+ - Upgraded GitHub Actions dependencies to `actions/checkout@v7` and `actions/setup-python@v7` (Node 24 runtime).
98
+ - Fixed secret referencing syntax in release workflow to ensure reliable binary asset uploads.
99
+
100
+ ---
101
+
102
+ ## [0.1.0] - 2026-09-18
103
+
104
+ ### Added
105
+ - Initial MVP release of `opsscript-gate`.
106
+ - CLI entrypoint (`opsscript-gate run <script_path>`).
107
+ - Unprivileged container isolation engine supporting `debian:12-slim`, `ubuntu:22.04`, `ubuntu:24.04`, and `alpine:3.20`.
108
+ - Safe read-only script mounting (`:ro`).
109
+ - Hard per-container timeout with forced `SIGKILL` and cleanup.
110
+ - Stdin disconnection (`</dev/null`) and non-interactive environment enforcement.
111
+ - Windows CRLF line-ending normalization defense.
112
+ - Unified ASCII table, GitHub Actions Step Summary Markdown, and JSON reporting formats.
113
+ - Composite GitHub Action published to Marketplace.
114
+ - Unit and integration test suite.
@@ -0,0 +1,308 @@
1
+ Metadata-Version: 2.5
2
+ Name: opsscript-gate
3
+ Version: 0.4.1
4
+ Summary: Run Linux shell scripts across Debian, Ubuntu and Alpine before release to catch runtime compatibility failures
5
+ Project-URL: Homepage, https://github.com/Mresyzz/opsscript-gate
6
+ Project-URL: Repository, https://github.com/Mresyzz/opsscript-gate
7
+ Project-URL: Issues, https://github.com/Mresyzz/opsscript-gate/issues
8
+ Project-URL: Changelog, https://github.com/Mresyzz/opsscript-gate/blob/main/CHANGELOG.md
9
+ Author: Mresyzz
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: ci,compatibility,cross-distro,devops,docker,github-actions,linux,posix,shell,testing
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: System Administrators
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Software Development :: Testing
22
+ Classifier: Topic :: System :: Systems Administration
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: docker>=7.0.0
25
+ Provides-Extra: test
26
+ Requires-Dist: pytest>=8.0.0; extra == 'test'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # OpsScript Gate
30
+
31
+ > **ShellCheck tells you if your script looks portable. OpsScript Gate checks if it actually runs there.**
32
+
33
+ [![CI](https://github.com/Mresyzz/opsscript-gate/actions/workflows/test.yml/badge.svg)](https://github.com/Mresyzz/opsscript-gate/actions/workflows/test.yml)
34
+ [![Demo](https://github.com/Mresyzz/opsscript-gate/actions/workflows/demo.yml/badge.svg)](https://github.com/Mresyzz/opsscript-gate/actions/workflows/demo.yml)
35
+ [![PyPI](https://img.shields.io/pypi/v/opsscript-gate)](https://pypi.org/project/opsscript-gate/)
36
+ [![Python Versions](https://img.shields.io/pypi/pyversions/opsscript-gate)](https://pypi.org/project/opsscript-gate/)
37
+ [![GitHub Marketplace](https://img.shields.io/badge/Marketplace-OpsScript%20Gate-blue?logo=github&color=2088FF)](https://github.com/marketplace/actions/opsscript-gate)
38
+ [![Release](https://img.shields.io/github/v/release/Mresyzz/opsscript-gate?color=green)](https://github.com/Mresyzz/opsscript-gate/releases)
39
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
40
+ [![Supported Distros](https://img.shields.io/badge/matrix-Debian%20%7C%20Ubuntu%20%7C%20Alpine-orange.svg)](#default-test-matrix)
41
+
42
+ **OpsScript Gate** is a drop-in runtime compatibility gate for Linux shell scripts. It executes your scripts inside unprivileged Debian, Ubuntu, and Alpine containers before merge, catching environment-specific runtime failures, missing interpreters, and package-manager assumptions that static analysis cannot detect.
43
+
44
+ ---
45
+
46
+ ## ⚡ 30-Second Quickstart
47
+
48
+ ### In GitHub Actions (Zero Config)
49
+
50
+ Drop this minimal workflow into `.github/workflows/shell-compat.yml`:
51
+
52
+ ```yaml
53
+ name: Shell Compatibility Gate
54
+ on: [pull_request, push]
55
+
56
+ jobs:
57
+ compat:
58
+ runs-on: ubuntu-latest
59
+ steps:
60
+ - uses: actions/checkout@v7
61
+ - uses: Mresyzz/opsscript-gate@v0.4.1
62
+ ```
63
+
64
+ > **Zero Config**: If `script-path` is omitted, OpsScript Gate automatically discovers shell scripts in your repository and tests them concurrently!
65
+
66
+ Or test a specific script with custom execution modes:
67
+
68
+ ```yaml
69
+ - uses: Mresyzz/opsscript-gate@v0.4.1
70
+ with:
71
+ script-path: scripts/install.sh
72
+ shell: auto
73
+ jobs: 4
74
+ ```
75
+
76
+ ### In Local Terminal (CLI)
77
+
78
+ Requires Python 3.10+ and a running local Docker engine:
79
+
80
+ ```bash
81
+ # Install from PyPI
82
+ pip install opsscript-gate
83
+
84
+ # Run against a specific script
85
+ opsscript-gate run ./scripts/install.sh
86
+
87
+ # Or auto-discover scripts across your repository
88
+ opsscript-gate run
89
+ ```
90
+
91
+ ---
92
+
93
+ ## 🎯 Reviewer-First Experience: What It Produces
94
+
95
+ Every run of OpsScript Gate generates clean, actionable feedback right where developers and reviewers need it:
96
+
97
+ ### 1. Line-Level GitHub Annotations
98
+ When a failure occurs, OpsScript Gate flags the exact script line on your Pull Request's **Files Changed** view with high-confidence diagnostics and conservative remediation hints:
99
+
100
+ ```text
101
+ ::error file=scripts/setup.sh,line=4,title=OpsScript Gate: [alpine:3.20] command not found: apt-get::command not found: apt-get — Alpine normally uses apk instead of apt-get.
102
+ ```
103
+
104
+ ### 2. GitHub Step Summary Compatibility Card
105
+ A beautifully formatted markdown summary is automatically posted to `$GITHUB_STEP_SUMMARY`:
106
+
107
+ ```markdown
108
+ ## 🛡️ OpsScript Gate Compatibility Report
109
+
110
+ **Target Script**: `scripts/setup.sh`
111
+ **Status**: ❌ **CHECKS FAILED (3/4 Passed)**
112
+ **Total Duration**: `1.24s`
113
+
114
+ ### 📊 Compatibility Matrix
115
+
116
+ | Distribution | Status | Exit Code | Time | Diagnostic & Recommendation |
117
+ | :--- | :---: | :---: | :---: | :--- |
118
+ | `debian:12-slim` | ✅ PASS | `0` | `0.42s` | OK |
119
+ | `ubuntu:22.04` | ✅ PASS | `0` | `0.38s` | OK |
120
+ | `ubuntu:24.04` | ✅ PASS | `0` | `0.35s` | OK |
121
+ | `alpine:3.20` | ❌ FAIL | `127` | `0.19s` | ⚠️ Missing command: `apt-get` (line 4)<br>💡 *Alpine normally uses apk instead of apt-get.* |
122
+
123
+ <details>
124
+ <summary>📋 <b>Copyable Markdown (Click to expand & copy to PR / Issue)</b></summary>
125
+ ...
126
+ </details>
127
+ ```
128
+
129
+ ---
130
+
131
+ ## 🔍 Example: What Static Analysis Misses
132
+
133
+ Consider this clean deployment script:
134
+
135
+ ```bash
136
+ #!/bin/sh
137
+ set -e
138
+ echo "Fetching package information..."
139
+ apt-get --version
140
+ ```
141
+
142
+ Running `shellcheck` reports **0 errors, 0 warnings** because the syntax is syntactically valid POSIX shell.
143
+
144
+ However, when verified with **OpsScript Gate**:
145
+
146
+ ```text
147
+ +----------------+----------+-----------+----------+------------------------------------+
148
+ | Distro | Status | Exit Code | Duration | Details |
149
+ +----------------+----------+-----------+----------+------------------------------------+
150
+ | debian:12-slim | PASS | 0 | 0.42s | OK |
151
+ | ubuntu:22.04 | PASS | 0 | 0.38s | OK |
152
+ | ubuntu:24.04 | PASS | 0 | 0.35s | OK |
153
+ | alpine:3.20 | FAIL | 127 | 0.19s | command not found: apt-get (line 4)|
154
+ +----------------+----------+-----------+----------+------------------------------------+
155
+ Total duration: 0.58s | Result: FAILED
156
+
157
+ Remediation Recommendations:
158
+ * [alpine:3.20] Alpine normally uses apk instead of apt-get.
159
+
160
+ ============================================================
161
+ Failed Distributions - Output Snippets (last 15 lines):
162
+ ============================================================
163
+
164
+ --- [alpine:3.20] (FAIL) ---
165
+ sh: line 4: apt-get: not found
166
+ ```
167
+
168
+ **Why it failed:** Alpine Linux is musl/BusyBox-based and uses `apk`, not `apt-get`. OpsScript Gate catches the missing command (`exit code 127`) and gives you the exact line number and conservative remediation hint before deployment.
169
+
170
+ ---
171
+
172
+ ## 🛡️ Why OpsScript Gate?
173
+
174
+ ### OpsScript Gate vs ShellCheck vs Custom CI Matrix
175
+
176
+ | Capability | OpsScript Gate | ShellCheck | Handwritten CI Matrix |
177
+ | :--- | :---: | :---: | :---: |
178
+ | **Runtime execution** | **Yes** | No (Static AST only) | Yes |
179
+ | **Real distro environments** | **Yes (Debian, Ubuntu, Alpine)** | No | Yes |
180
+ | **Preconfigured defaults** | **Yes** | Yes | Requires custom workflow configuration |
181
+ | **Hardened container defaults** | **Built-in (`ro`, `cap_drop`, resource limits)** | N/A | User-defined |
182
+ | **Anti-hang stdin protection** | **Built-in (`</dev/null`, noninteractive)** | No | User-defined |
183
+ | **Line-Level Annotations & Hints** | **Built-in (Zero config)** | Static warnings | User-defined |
184
+ | **Parallel Matrix Execution** | **Built-in (`--jobs`)** | N/A | Manual matrix config |
185
+
186
+ - **ShellCheck** is indispensable for static analysis (syntax, quoting, SC warnings). OpsScript Gate complements it by testing actual execution behavior in real distributions.
187
+ - **Handwritten CI Matrix** requires maintaining complex Docker configurations, volume mounts, timeout guards, and log parsers across every project. OpsScript Gate packages this into a single check.
188
+
189
+ ---
190
+
191
+ ## 🔒 Security Boundaries & Hardened Isolation
192
+
193
+ OpsScript Gate is not a security sandbox for untrusted code. Containers may run as the image's default user, and Docker containers still share the host kernel.
194
+
195
+ OpsScript Gate applies conservative, restricted container defaults when running scripts:
196
+
197
+ 1. **Restricted Container Defaults**:
198
+ - Containers run with `privileged=False`.
199
+ - All Linux capabilities are dropped: `cap_drop=["ALL"]`.
200
+ - Privilege escalation is disabled: `security_opt=["no-new-privileges:true"]`.
201
+ 2. **Resource Constraints**:
202
+ - Memory limits enforced per container (`--mem-limit`, default: `256m`).
203
+ - Process caps enforced to prevent fork bombs (`--pids-limit`, default: `128`).
204
+ - Network isolation configurable (`--network bridge` or `--network none`).
205
+ 3. **Read-Only Target Mount**:
206
+ - The tested script is mounted read-only (`:ro`) at `/tmp/target_script.sh`.
207
+ - No host directories or sensitive sockets are mounted into test containers.
208
+ 4. **Anti-Hang Deadlock Defense**:
209
+ - Disables TTY and stdin (`stdin_open=False`, `tty=False`).
210
+ - Disconnects standard input: `/bin/sh -c "... /tmp/target_script.sh </dev/null"`.
211
+ - Injects `DEBIAN_FRONTEND=noninteractive` and `CI=true`. Interactive prompts (`read -p`) fail immediately instead of hanging CI runners.
212
+ 5. **Hard Timeout & Container Cleanup**:
213
+ - Enforces configurable timeout (default: 60s). Timed-out containers are sent `SIGKILL` and marked `TIMED_OUT`.
214
+ - Container removal is attempted from a `finally` block in normal, failure, and timeout execution paths.
215
+ 6. **Bounded Output & Memory Protection**:
216
+ - Captures container logs using an immediate rolling byte buffer capped at 256 KiB (`MAX_CAPTURED_LOG_BYTES`) and tail limited to 500 lines (`MAX_LOG_TAIL_LINES`), which bounds retained container log data to reduce memory-exhaustion risk.
217
+ 7. **Untrusted Log Neutralization & Terminal Defense**:
218
+ - Neutralizes line-leading workflow commands (`[container] ::`) to prevent forged GitHub Actions annotations in CI runners.
219
+ - Strips ANSI escape sequences and dangerous C0 control characters, and normalizes carriage returns (`\r`) to defeat terminal line overwrite spoofing.
220
+ - Employs context-sensitive escaping (`escape_inline_code`, `escape_markdown_text`, `escape_markdown_table_cell`, `escape_html_text`, `format_safe_code_fence`) to prevent Step Summary layout disruptions.
221
+ 8. **Command Injection Defense**:
222
+ - OpsScript Gate's own annotations (`::error`) apply strict percent-encoding for all properties and message bodies, preventing workflow command injection.
223
+ 9. **Bounded Streaming CRLF & Shebang Defense**:
224
+ - Stream-normalizes carriage returns in 64 KiB chunks, preserving lone CR bytes and bounds shebang parsing to 4096 bytes without whole-file memory allocation.
225
+ - Pre-normalizes scripts once before parallel matrix runs, sharing a read-only prepared path across worker threads.
226
+
227
+ ---
228
+
229
+ ## 📦 Default Test Matrix
230
+
231
+ | Image | Distribution | Focus |
232
+ | :--- | :--- | :--- |
233
+ | `debian:12-slim` | Debian 12 (Bookworm) | Minimal glibc + APT base |
234
+ | `ubuntu:22.04` | Ubuntu 22.04 LTS (Jammy) | Enterprise long-term support baseline |
235
+ | `ubuntu:24.04` | Ubuntu 24.04 LTS (Noble) | Modern glibc, updated coreutils & defaults |
236
+ | `alpine:3.20` | Alpine Linux 3.20 | Minimal musl libc + BusyBox /bin/sh environment |
237
+
238
+ Customize the matrix at any time via `--matrix` or Action input `matrix`.
239
+
240
+ ---
241
+
242
+ ## 🛠️ CLI Reference
243
+
244
+ ```text
245
+ usage: opsscript-gate run [-h] [--matrix MATRIX] [-j JOBS] [--timeout TIMEOUT]
246
+ [--format {table,markdown,json}]
247
+ [--shell {posix,shebang,auto}]
248
+ [--mem-limit MEM_LIMIT] [--pids-limit PIDS_LIMIT]
249
+ [--network NETWORK]
250
+ [script_path]
251
+ ```
252
+
253
+ | Parameter | Type | Default | Description |
254
+ | :--- | :--- | :--- | :--- |
255
+ | `script_path` | Positional | *Optional* | Path to target shell script (auto-discovers if omitted) |
256
+ | `-j, --jobs` | Integer | `min(2, size)` | Number of concurrent container jobs |
257
+ | `--matrix` | String | `debian:12-slim,ubuntu:22.04,ubuntu:24.04,alpine:3.20` | Comma-separated list of Docker images |
258
+ | `--timeout` | Integer | `60` | Hard timeout per container in seconds |
259
+ | `--format` | Choice | `table` | Output format: `table`, `markdown`, or `json` |
260
+ | `--shell` | Choice | `posix` | Execution mode: `posix` (default), `shebang`, or `auto` |
261
+ | `--mem-limit` | String | `256m` | Memory limit per container (e.g. `256m`, `512m`) |
262
+ | `--pids-limit`| Integer | `128` | Maximum number of processes per container |
263
+ | `--network` | Choice | `bridge` | Container network mode: `bridge` or `none` |
264
+ | `--version` | Flag | - | Show version number |
265
+ | `-h, --help` | Flag | - | Show argument help |
266
+
267
+ ### Shell Execution Modes (`--shell`)
268
+
269
+ - **`posix`** (default): Strictly executes with `/bin/sh`, ignoring any script shebang to verify portability against minimal POSIX environments (including Alpine BusyBox).
270
+ - **`shebang`**: Strictly honors the interpreter specified in the script's shebang (`#!/bin/sh`, `#!/bin/bash`, `#!/usr/bin/sh`, `#!/usr/bin/bash`, `#!/usr/bin/env sh`, `#!/usr/bin/env bash`). Fails immediately if shebang is missing, malformed, or unsupported.
271
+ - **`auto`**: Honors recognized shebangs if present; safely falls back to `/bin/sh` if no shebang is present.
272
+
273
+ ### Exit Code Convention
274
+ - **`0`**: All tested scripts and distributions passed (`PASS`).
275
+ - **`1`**: At least one distribution failed (`FAIL`), timed out (`TIMED_OUT`), or errored (`ERROR`).
276
+
277
+ ---
278
+
279
+ ## 🐶 Dogfooding
280
+
281
+ OpsScript Gate is actively used in [`Mresyzz/linux-dev-bootstrap`](https://github.com/Mresyzz/linux-dev-bootstrap) to validate `install.sh` across the default Debian, Ubuntu, and Alpine matrix in GitHub Actions.
282
+
283
+ See the downstream workflow: [`.github/workflows/test.yml`](https://github.com/Mresyzz/linux-dev-bootstrap/blob/main/.github/workflows/test.yml).
284
+
285
+ ---
286
+
287
+ ## 🧪 Development & Testing
288
+
289
+ ```bash
290
+ # Clone repository
291
+ git clone https://github.com/Mresyzz/opsscript-gate.git
292
+ cd opsscript-gate
293
+
294
+ # Install in editable mode with test dependencies
295
+ pip install -e .[test]
296
+
297
+ # Run unit tests (Mocked, no Docker daemon required)
298
+ pytest -v -m "not integration"
299
+
300
+ # Run integration tests (Requires local Docker daemon)
301
+ pytest -v
302
+ ```
303
+
304
+ ---
305
+
306
+ ## 📄 License
307
+
308
+ OpsScript Gate is licensed under the [MIT License](LICENSE).