opsscript-gate 0.4.0__tar.gz → 0.5.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.
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/.github/workflows/test.yml +15 -2
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/CHANGELOG.md +37 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/PKG-INFO +117 -12
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/README.md +116 -11
- opsscript_gate-0.5.0/README.zh-CN.md +67 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/SECURITY.md +6 -4
- opsscript_gate-0.5.0/action.yml +130 -0
- opsscript_gate-0.5.0/docs/configuration.md +115 -0
- opsscript_gate-0.5.0/docs/troubleshooting.md +67 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/pyproject.toml +1 -1
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/src/opsscript_gate/__init__.py +1 -1
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/src/opsscript_gate/cli.py +89 -4
- opsscript_gate-0.5.0/src/opsscript_gate/config.py +78 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/src/opsscript_gate/discovery.py +16 -3
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/src/opsscript_gate/reporter.py +151 -32
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/src/opsscript_gate/runner.py +274 -120
- opsscript_gate-0.5.0/tests/test_action_contract.py +22 -0
- opsscript_gate-0.5.0/tests/test_onboarding.py +133 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/tests/test_runner.py +450 -1
- opsscript_gate-0.4.0/action.yml +0 -90
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/.gitattributes +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/.github/ISSUE_TEMPLATE/distro_request.yml +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/.github/assets/social-preview.png +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/.github/pull_request_template.md +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/.github/workflows/demo.yml +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/.github/workflows/release.yml +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/.gitignore +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/CONTRIBUTING.md +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/LICENSE +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/ROADMAP.md +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/docs/promotion/awesome-lists-pr.md +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/docs/promotion/juejin-zhihu-article.md +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/docs/promotion/reddit-devops.md +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/docs/promotion/show-hn.md +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/docs/promotion/v2ex-post.md +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/examples/alpine-incompatibility/README.md +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/examples/alpine-incompatibility/apt_dependency.sh +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/examples/basic/README.md +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/examples/basic/clean_setup.sh +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/examples/github-actions/README.md +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/examples/github-actions/workflow.yml +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/examples/interactive-hang/README.md +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/examples/interactive-hang/prompt_confirm.sh +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/src/opsscript_gate/models.py +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/src/opsscript_gate/remediation.py +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/tests/fixtures/fail_deps.sh +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/tests/fixtures/fail_hang.sh +0 -0
- {opsscript_gate-0.4.0 → opsscript_gate-0.5.0}/tests/fixtures/pass_basic.sh +0 -0
|
@@ -8,7 +8,11 @@ on:
|
|
|
8
8
|
|
|
9
9
|
jobs:
|
|
10
10
|
test:
|
|
11
|
-
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
os: [ubuntu-latest, windows-latest]
|
|
14
|
+
python: ['3.10', '3.12']
|
|
15
|
+
runs-on: ${{ matrix.os }}
|
|
12
16
|
steps:
|
|
13
17
|
- name: Checkout Code
|
|
14
18
|
uses: actions/checkout@v7
|
|
@@ -16,7 +20,7 @@ jobs:
|
|
|
16
20
|
- name: Set up Python
|
|
17
21
|
uses: actions/setup-python@v7
|
|
18
22
|
with:
|
|
19
|
-
python-version:
|
|
23
|
+
python-version: ${{ matrix.python }}
|
|
20
24
|
|
|
21
25
|
- name: Install Dependencies
|
|
22
26
|
run: |
|
|
@@ -50,3 +54,12 @@ jobs:
|
|
|
50
54
|
- name: Run Docker Integration Tests
|
|
51
55
|
run: |
|
|
52
56
|
pytest -v -m integration
|
|
57
|
+
|
|
58
|
+
- name: Exercise the local composite Action
|
|
59
|
+
uses: ./
|
|
60
|
+
with:
|
|
61
|
+
script-path: tests/fixtures/pass_basic.sh
|
|
62
|
+
preset: minimal
|
|
63
|
+
network: none
|
|
64
|
+
output: action-report.json
|
|
65
|
+
format: json
|
|
@@ -1,10 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.0] - 2026-09-24
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `init`: non-overwriting project configuration and GitHub workflow generation.
|
|
7
|
+
- Shared, strictly validated `.opsscript-gate.json` settings; explicit CLI/Action inputs win.
|
|
8
|
+
- Docker-free `--dry-run` plans in text or JSON, including execution counts.
|
|
9
|
+
- `minimal` and `ubuntu` distribution presets, repeatable exclusion globs and configurable discovery limit.
|
|
10
|
+
- `--output` report artifacts, including failure reports; source/config overwrite protection.
|
|
11
|
+
- Action inputs for configuration, presets, exclusions, preview and saved reports.
|
|
12
|
+
- Chinese quickstart, configuration guide and Ubuntu/Alpine troubleshooting guide.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- Discovery now fails on overflow instead of silently testing only the first 20 scripts.
|
|
16
|
+
- Discovery skips symlinks and non-files and rejects shebang prefix collisions.
|
|
17
|
+
- CLI rejects non-positive limits and unsupported network modes before execution.
|
|
18
|
+
- Default Action inputs no longer override a user's project configuration.
|
|
19
|
+
|
|
20
|
+
### Migration
|
|
21
|
+
- Repositories with more than 20 discovered scripts must exclude unwanted scripts or raise `max_scripts`.
|
|
22
|
+
- Auto-discovery no longer follows file symlinks; explicit script paths remain supported.
|
|
23
|
+
- Default settings without a configuration file remain compatible with v0.4.1 and earlier.
|
|
24
|
+
- Generated workflows use the v0.5.0 release tag.
|
|
25
|
+
|
|
3
26
|
All notable changes to this project will be documented in this file.
|
|
4
27
|
|
|
5
28
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
29
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
30
|
|
|
31
|
+
## [0.4.1] - 2026-09-19
|
|
32
|
+
|
|
33
|
+
### Fixed & Hardened
|
|
34
|
+
- **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.
|
|
35
|
+
- **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.
|
|
36
|
+
- **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.
|
|
37
|
+
- **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.
|
|
38
|
+
- **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`).
|
|
39
|
+
- **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.
|
|
40
|
+
- **Shebang evaluation refactoring**: Deduplicated shebang resolution across `shebang` and `auto` modes via `_resolve_shell_command`.
|
|
41
|
+
- **Regression test coverage**: Added extensive adversarial and edge-case tests covering oversized streams, boundary splits, workflow spoofing, Markdown injections, and error branches.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
8
45
|
## [0.4.0] - 2026-09-19
|
|
9
46
|
|
|
10
47
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: opsscript-gate
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Run Linux shell scripts across Debian, Ubuntu and Alpine before release to catch runtime compatibility failures
|
|
5
5
|
Project-URL: Homepage, https://github.com/Mresyzz/opsscript-gate
|
|
6
6
|
Project-URL: Repository, https://github.com/Mresyzz/opsscript-gate
|
|
@@ -26,7 +26,9 @@ Provides-Extra: test
|
|
|
26
26
|
Requires-Dist: pytest>=8.0.0; extra == 'test'
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
28
28
|
|
|
29
|
-
# OpsScript Gate
|
|
29
|
+
# OpsScript Gate — shell script compatibility testing
|
|
30
|
+
|
|
31
|
+
[简体中文](README.zh-CN.md) · [Configuration](docs/configuration.md) · [Troubleshooting](docs/troubleshooting.md)
|
|
30
32
|
|
|
31
33
|
> **ShellCheck tells you if your script looks portable. OpsScript Gate checks if it actually runs there.**
|
|
32
34
|
|
|
@@ -41,8 +43,32 @@ Description-Content-Type: text/markdown
|
|
|
41
43
|
|
|
42
44
|
**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
45
|
|
|
46
|
+
It is designed for shell testing, portable shell validation, Bash/POSIX compatibility checks, and cross-distro CI where syntax-only tooling is not enough.
|
|
47
|
+
|
|
44
48
|
---
|
|
45
49
|
|
|
50
|
+
## New in v0.5.0
|
|
51
|
+
|
|
52
|
+
Preview exactly what will run, keep local and CI settings together, and exclude test
|
|
53
|
+
fixtures before executing scripts. These controls are included in v0.5.0. Projects on
|
|
54
|
+
v0.4.1 and earlier do not include them.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
opsscript-gate init
|
|
58
|
+
opsscript-gate run --dry-run
|
|
59
|
+
opsscript-gate run --format json --output reports/compatibility.json
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`init` creates `.opsscript-gate.json` and a GitHub Actions workflow without replacing
|
|
63
|
+
existing files. Review its exclusions (`tests/*`, `examples/*`) and offline network
|
|
64
|
+
default before running. No Docker is required for `init` or `--dry-run`.
|
|
65
|
+
|
|
66
|
+
Useful for standalone installers, container entrypoints and release scripts that
|
|
67
|
+
must work on both GNU/Linux and Alpine/BusyBox. Each script runs independently;
|
|
68
|
+
repository files, sibling scripts and project dependencies are **not** mounted.
|
|
69
|
+
See [configuration and migration](docs/configuration.md) and
|
|
70
|
+
[why a shell script works on Ubuntu but fails on Alpine](docs/troubleshooting.md).
|
|
71
|
+
|
|
46
72
|
## ⚡ 30-Second Quickstart
|
|
47
73
|
|
|
48
74
|
### In GitHub Actions (Zero Config)
|
|
@@ -58,15 +84,15 @@ jobs:
|
|
|
58
84
|
runs-on: ubuntu-latest
|
|
59
85
|
steps:
|
|
60
86
|
- uses: actions/checkout@v7
|
|
61
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
87
|
+
- uses: Mresyzz/opsscript-gate@v0.5.0
|
|
62
88
|
```
|
|
63
89
|
|
|
64
|
-
> **Zero Config**: If `script-path` is omitted, OpsScript Gate automatically discovers shell scripts in your repository
|
|
90
|
+
> **Zero Config**: If `script-path` is omitted, OpsScript Gate automatically discovers shell scripts in your repository. Scripts run sequentially, with concurrent distribution checks for each script. Preview discovery before running unfamiliar repositories.
|
|
65
91
|
|
|
66
92
|
Or test a specific script with custom execution modes:
|
|
67
93
|
|
|
68
94
|
```yaml
|
|
69
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
95
|
+
- uses: Mresyzz/opsscript-gate@v0.5.0
|
|
70
96
|
with:
|
|
71
97
|
script-path: scripts/install.sh
|
|
72
98
|
shell: auto
|
|
@@ -88,6 +114,35 @@ opsscript-gate run ./scripts/install.sh
|
|
|
88
114
|
opsscript-gate run
|
|
89
115
|
```
|
|
90
116
|
|
|
117
|
+
### Try a Real Failure in 30 Seconds
|
|
118
|
+
|
|
119
|
+
Create `install.sh`:
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
#!/bin/sh
|
|
123
|
+
set -e
|
|
124
|
+
apt-get --version
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Then run:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
opsscript-gate run ./install.sh
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
A typical result across the default matrix looks like this:
|
|
134
|
+
|
|
135
|
+
```text
|
|
136
|
+
Debian 12 PASS
|
|
137
|
+
Ubuntu 22.04 PASS
|
|
138
|
+
Ubuntu 24.04 PASS
|
|
139
|
+
Alpine 3.20 FAIL
|
|
140
|
+
|
|
141
|
+
apt-get: not found
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The script is valid shell, but the runtime environment is incompatible. That is exactly the class of failure OpsScript Gate is designed to catch.
|
|
145
|
+
|
|
91
146
|
---
|
|
92
147
|
|
|
93
148
|
## 🎯 Reviewer-First Experience: What It Produces
|
|
@@ -190,7 +245,10 @@ sh: line 4: apt-get: not found
|
|
|
190
245
|
|
|
191
246
|
## 🔒 Security Boundaries & Hardened Isolation
|
|
192
247
|
|
|
193
|
-
OpsScript Gate is not a security sandbox for untrusted code
|
|
248
|
+
OpsScript Gate is a runtime compatibility testing tool, **not a security sandbox for hostile or fully untrusted code**. Containers still share the host kernel, so target scripts should be treated accordingly.
|
|
249
|
+
|
|
250
|
+
<details>
|
|
251
|
+
<summary><strong>View security boundaries and runtime hardening details</strong></summary>
|
|
194
252
|
|
|
195
253
|
OpsScript Gate applies conservative, restricted container defaults when running scripts:
|
|
196
254
|
|
|
@@ -208,14 +266,37 @@ OpsScript Gate applies conservative, restricted container defaults when running
|
|
|
208
266
|
4. **Anti-Hang Deadlock Defense**:
|
|
209
267
|
- Disables TTY and stdin (`stdin_open=False`, `tty=False`).
|
|
210
268
|
- 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
|
|
269
|
+
- Injects `DEBIAN_FRONTEND=noninteractive` and `CI=true`. Interactive prompts (`read -p`) fail instead of hanging CI runners.
|
|
212
270
|
5. **Hard Timeout & Container Cleanup**:
|
|
213
271
|
- Enforces configurable timeout (default: 60s). Timed-out containers are sent `SIGKILL` and marked `TIMED_OUT`.
|
|
214
272
|
- Container removal is attempted from a `finally` block in normal, failure, and timeout execution paths.
|
|
215
|
-
6. **
|
|
216
|
-
-
|
|
217
|
-
7. **
|
|
218
|
-
-
|
|
273
|
+
6. **Bounded Output & Memory Protection**:
|
|
274
|
+
- Captures container logs using a rolling byte buffer capped at 256 KiB (`MAX_CAPTURED_LOG_BYTES`) and a 500-line tail limit (`MAX_LOG_TAIL_LINES`) to reduce memory-exhaustion risk from runaway output.
|
|
275
|
+
7. **Untrusted Log Neutralization & Terminal Defense**:
|
|
276
|
+
- Neutralizes line-leading workflow commands (`[container] ::`) to prevent forged GitHub Actions annotations in CI runners.
|
|
277
|
+
- Strips ANSI escape sequences and dangerous C0 control characters, and normalizes carriage returns (`\r`) to defeat terminal line-overwrite spoofing.
|
|
278
|
+
- Employs context-sensitive escaping (`escape_inline_code`, `escape_markdown_text`, `escape_markdown_table_cell`, `escape_html_text`, `format_safe_code_fence`) to protect Step Summary output contexts.
|
|
279
|
+
8. **Command Injection Defense**:
|
|
280
|
+
- OpsScript Gate's own annotations (`::error`) apply strict percent-encoding for workflow-command fields and message bodies.
|
|
281
|
+
9. **Bounded Streaming CRLF & Shebang Defense**:
|
|
282
|
+
- Stream-normalizes CRLF in 64 KiB chunks and bounds shebang parsing to 4096 bytes without whole-file memory allocation.
|
|
283
|
+
- Pre-normalizes scripts once before parallel matrix runs, sharing a read-only prepared path across worker threads.
|
|
284
|
+
|
|
285
|
+
For the complete supported-version policy and vulnerability reporting guidance, see [SECURITY.md](SECURITY.md).
|
|
286
|
+
|
|
287
|
+
</details>
|
|
288
|
+
|
|
289
|
+
### When NOT to use OpsScript Gate
|
|
290
|
+
|
|
291
|
+
OpsScript Gate is intentionally focused on Linux shell runtime compatibility. It is not designed for:
|
|
292
|
+
|
|
293
|
+
- executing hostile or fully untrusted third-party scripts
|
|
294
|
+
- kernel-level or privileged behavior testing
|
|
295
|
+
- replacing full integration or end-to-end test suites
|
|
296
|
+
- validating macOS or Windows behavior
|
|
297
|
+
- proving that a script is secure
|
|
298
|
+
|
|
299
|
+
Use it when you want to know whether a shell script actually runs across the supported Linux distributions.
|
|
219
300
|
|
|
220
301
|
---
|
|
221
302
|
|
|
@@ -269,7 +350,23 @@ usage: opsscript-gate run [-h] [--matrix MATRIX] [-j JOBS] [--timeout TIMEOUT]
|
|
|
269
350
|
|
|
270
351
|
---
|
|
271
352
|
|
|
272
|
-
|
|
353
|
+
### v0.5.0 project controls
|
|
354
|
+
|
|
355
|
+
| Option | Purpose |
|
|
356
|
+
| :--- | :--- |
|
|
357
|
+
| `init` | Generate configuration and a GitHub workflow without overwriting files |
|
|
358
|
+
| `--dry-run` | Preview selected scripts, images and total executions without Docker |
|
|
359
|
+
| `--config PATH` | Load a specific JSON configuration file |
|
|
360
|
+
| `--preset minimal` | Debian + Alpine (two container runs per script) |
|
|
361
|
+
| `--preset ubuntu` | Ubuntu 22.04 + 24.04 |
|
|
362
|
+
| `--exclude 'tests/*'` | Exclude a repository-relative glob; repeatable |
|
|
363
|
+
| `--max-scripts 50` | Raise the default discovery limit of 20; overflow is an error |
|
|
364
|
+
| `--output report.json` | Save the selected output format, including failed test reports |
|
|
365
|
+
|
|
366
|
+
Explicit CLI options override project settings. Explicit script paths bypass discovery
|
|
367
|
+
and its exclusions. See [complete configuration semantics](docs/configuration.md).
|
|
368
|
+
|
|
369
|
+
## 🌍 Real-World Usage
|
|
273
370
|
|
|
274
371
|
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.
|
|
275
372
|
|
|
@@ -296,6 +393,14 @@ pytest -v
|
|
|
296
393
|
|
|
297
394
|
---
|
|
298
395
|
|
|
396
|
+
## 💛 If OpsScript Gate Helps
|
|
397
|
+
|
|
398
|
+
If OpsScript Gate catches a compatibility problem in one of your scripts, consider starring the repository so other shell and DevOps maintainers can find it too.
|
|
399
|
+
|
|
400
|
+
Bug reports, real-world compatibility cases, and pull requests are especially welcome.
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
299
404
|
## 📄 License
|
|
300
405
|
|
|
301
406
|
OpsScript Gate is licensed under the [MIT License](LICENSE).
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
# OpsScript Gate
|
|
1
|
+
# OpsScript Gate — shell script compatibility testing
|
|
2
|
+
|
|
3
|
+
[简体中文](README.zh-CN.md) · [Configuration](docs/configuration.md) · [Troubleshooting](docs/troubleshooting.md)
|
|
2
4
|
|
|
3
5
|
> **ShellCheck tells you if your script looks portable. OpsScript Gate checks if it actually runs there.**
|
|
4
6
|
|
|
@@ -13,8 +15,32 @@
|
|
|
13
15
|
|
|
14
16
|
**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.
|
|
15
17
|
|
|
18
|
+
It is designed for shell testing, portable shell validation, Bash/POSIX compatibility checks, and cross-distro CI where syntax-only tooling is not enough.
|
|
19
|
+
|
|
16
20
|
---
|
|
17
21
|
|
|
22
|
+
## New in v0.5.0
|
|
23
|
+
|
|
24
|
+
Preview exactly what will run, keep local and CI settings together, and exclude test
|
|
25
|
+
fixtures before executing scripts. These controls are included in v0.5.0. Projects on
|
|
26
|
+
v0.4.1 and earlier do not include them.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
opsscript-gate init
|
|
30
|
+
opsscript-gate run --dry-run
|
|
31
|
+
opsscript-gate run --format json --output reports/compatibility.json
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`init` creates `.opsscript-gate.json` and a GitHub Actions workflow without replacing
|
|
35
|
+
existing files. Review its exclusions (`tests/*`, `examples/*`) and offline network
|
|
36
|
+
default before running. No Docker is required for `init` or `--dry-run`.
|
|
37
|
+
|
|
38
|
+
Useful for standalone installers, container entrypoints and release scripts that
|
|
39
|
+
must work on both GNU/Linux and Alpine/BusyBox. Each script runs independently;
|
|
40
|
+
repository files, sibling scripts and project dependencies are **not** mounted.
|
|
41
|
+
See [configuration and migration](docs/configuration.md) and
|
|
42
|
+
[why a shell script works on Ubuntu but fails on Alpine](docs/troubleshooting.md).
|
|
43
|
+
|
|
18
44
|
## ⚡ 30-Second Quickstart
|
|
19
45
|
|
|
20
46
|
### In GitHub Actions (Zero Config)
|
|
@@ -30,15 +56,15 @@ jobs:
|
|
|
30
56
|
runs-on: ubuntu-latest
|
|
31
57
|
steps:
|
|
32
58
|
- uses: actions/checkout@v7
|
|
33
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
59
|
+
- uses: Mresyzz/opsscript-gate@v0.5.0
|
|
34
60
|
```
|
|
35
61
|
|
|
36
|
-
> **Zero Config**: If `script-path` is omitted, OpsScript Gate automatically discovers shell scripts in your repository
|
|
62
|
+
> **Zero Config**: If `script-path` is omitted, OpsScript Gate automatically discovers shell scripts in your repository. Scripts run sequentially, with concurrent distribution checks for each script. Preview discovery before running unfamiliar repositories.
|
|
37
63
|
|
|
38
64
|
Or test a specific script with custom execution modes:
|
|
39
65
|
|
|
40
66
|
```yaml
|
|
41
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
67
|
+
- uses: Mresyzz/opsscript-gate@v0.5.0
|
|
42
68
|
with:
|
|
43
69
|
script-path: scripts/install.sh
|
|
44
70
|
shell: auto
|
|
@@ -60,6 +86,35 @@ opsscript-gate run ./scripts/install.sh
|
|
|
60
86
|
opsscript-gate run
|
|
61
87
|
```
|
|
62
88
|
|
|
89
|
+
### Try a Real Failure in 30 Seconds
|
|
90
|
+
|
|
91
|
+
Create `install.sh`:
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
#!/bin/sh
|
|
95
|
+
set -e
|
|
96
|
+
apt-get --version
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Then run:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
opsscript-gate run ./install.sh
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
A typical result across the default matrix looks like this:
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
Debian 12 PASS
|
|
109
|
+
Ubuntu 22.04 PASS
|
|
110
|
+
Ubuntu 24.04 PASS
|
|
111
|
+
Alpine 3.20 FAIL
|
|
112
|
+
|
|
113
|
+
apt-get: not found
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The script is valid shell, but the runtime environment is incompatible. That is exactly the class of failure OpsScript Gate is designed to catch.
|
|
117
|
+
|
|
63
118
|
---
|
|
64
119
|
|
|
65
120
|
## 🎯 Reviewer-First Experience: What It Produces
|
|
@@ -162,7 +217,10 @@ sh: line 4: apt-get: not found
|
|
|
162
217
|
|
|
163
218
|
## 🔒 Security Boundaries & Hardened Isolation
|
|
164
219
|
|
|
165
|
-
OpsScript Gate is not a security sandbox for untrusted code
|
|
220
|
+
OpsScript Gate is a runtime compatibility testing tool, **not a security sandbox for hostile or fully untrusted code**. Containers still share the host kernel, so target scripts should be treated accordingly.
|
|
221
|
+
|
|
222
|
+
<details>
|
|
223
|
+
<summary><strong>View security boundaries and runtime hardening details</strong></summary>
|
|
166
224
|
|
|
167
225
|
OpsScript Gate applies conservative, restricted container defaults when running scripts:
|
|
168
226
|
|
|
@@ -180,14 +238,37 @@ OpsScript Gate applies conservative, restricted container defaults when running
|
|
|
180
238
|
4. **Anti-Hang Deadlock Defense**:
|
|
181
239
|
- Disables TTY and stdin (`stdin_open=False`, `tty=False`).
|
|
182
240
|
- Disconnects standard input: `/bin/sh -c "... /tmp/target_script.sh </dev/null"`.
|
|
183
|
-
- Injects `DEBIAN_FRONTEND=noninteractive` and `CI=true`. Interactive prompts (`read -p`) fail
|
|
241
|
+
- Injects `DEBIAN_FRONTEND=noninteractive` and `CI=true`. Interactive prompts (`read -p`) fail instead of hanging CI runners.
|
|
184
242
|
5. **Hard Timeout & Container Cleanup**:
|
|
185
243
|
- Enforces configurable timeout (default: 60s). Timed-out containers are sent `SIGKILL` and marked `TIMED_OUT`.
|
|
186
244
|
- Container removal is attempted from a `finally` block in normal, failure, and timeout execution paths.
|
|
187
|
-
6. **
|
|
188
|
-
-
|
|
189
|
-
7. **
|
|
190
|
-
-
|
|
245
|
+
6. **Bounded Output & Memory Protection**:
|
|
246
|
+
- Captures container logs using a rolling byte buffer capped at 256 KiB (`MAX_CAPTURED_LOG_BYTES`) and a 500-line tail limit (`MAX_LOG_TAIL_LINES`) to reduce memory-exhaustion risk from runaway output.
|
|
247
|
+
7. **Untrusted Log Neutralization & Terminal Defense**:
|
|
248
|
+
- Neutralizes line-leading workflow commands (`[container] ::`) to prevent forged GitHub Actions annotations in CI runners.
|
|
249
|
+
- Strips ANSI escape sequences and dangerous C0 control characters, and normalizes carriage returns (`\r`) to defeat terminal line-overwrite spoofing.
|
|
250
|
+
- Employs context-sensitive escaping (`escape_inline_code`, `escape_markdown_text`, `escape_markdown_table_cell`, `escape_html_text`, `format_safe_code_fence`) to protect Step Summary output contexts.
|
|
251
|
+
8. **Command Injection Defense**:
|
|
252
|
+
- OpsScript Gate's own annotations (`::error`) apply strict percent-encoding for workflow-command fields and message bodies.
|
|
253
|
+
9. **Bounded Streaming CRLF & Shebang Defense**:
|
|
254
|
+
- Stream-normalizes CRLF in 64 KiB chunks and bounds shebang parsing to 4096 bytes without whole-file memory allocation.
|
|
255
|
+
- Pre-normalizes scripts once before parallel matrix runs, sharing a read-only prepared path across worker threads.
|
|
256
|
+
|
|
257
|
+
For the complete supported-version policy and vulnerability reporting guidance, see [SECURITY.md](SECURITY.md).
|
|
258
|
+
|
|
259
|
+
</details>
|
|
260
|
+
|
|
261
|
+
### When NOT to use OpsScript Gate
|
|
262
|
+
|
|
263
|
+
OpsScript Gate is intentionally focused on Linux shell runtime compatibility. It is not designed for:
|
|
264
|
+
|
|
265
|
+
- executing hostile or fully untrusted third-party scripts
|
|
266
|
+
- kernel-level or privileged behavior testing
|
|
267
|
+
- replacing full integration or end-to-end test suites
|
|
268
|
+
- validating macOS or Windows behavior
|
|
269
|
+
- proving that a script is secure
|
|
270
|
+
|
|
271
|
+
Use it when you want to know whether a shell script actually runs across the supported Linux distributions.
|
|
191
272
|
|
|
192
273
|
---
|
|
193
274
|
|
|
@@ -241,7 +322,23 @@ usage: opsscript-gate run [-h] [--matrix MATRIX] [-j JOBS] [--timeout TIMEOUT]
|
|
|
241
322
|
|
|
242
323
|
---
|
|
243
324
|
|
|
244
|
-
|
|
325
|
+
### v0.5.0 project controls
|
|
326
|
+
|
|
327
|
+
| Option | Purpose |
|
|
328
|
+
| :--- | :--- |
|
|
329
|
+
| `init` | Generate configuration and a GitHub workflow without overwriting files |
|
|
330
|
+
| `--dry-run` | Preview selected scripts, images and total executions without Docker |
|
|
331
|
+
| `--config PATH` | Load a specific JSON configuration file |
|
|
332
|
+
| `--preset minimal` | Debian + Alpine (two container runs per script) |
|
|
333
|
+
| `--preset ubuntu` | Ubuntu 22.04 + 24.04 |
|
|
334
|
+
| `--exclude 'tests/*'` | Exclude a repository-relative glob; repeatable |
|
|
335
|
+
| `--max-scripts 50` | Raise the default discovery limit of 20; overflow is an error |
|
|
336
|
+
| `--output report.json` | Save the selected output format, including failed test reports |
|
|
337
|
+
|
|
338
|
+
Explicit CLI options override project settings. Explicit script paths bypass discovery
|
|
339
|
+
and its exclusions. See [complete configuration semantics](docs/configuration.md).
|
|
340
|
+
|
|
341
|
+
## 🌍 Real-World Usage
|
|
245
342
|
|
|
246
343
|
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.
|
|
247
344
|
|
|
@@ -268,6 +365,14 @@ pytest -v
|
|
|
268
365
|
|
|
269
366
|
---
|
|
270
367
|
|
|
368
|
+
## 💛 If OpsScript Gate Helps
|
|
369
|
+
|
|
370
|
+
If OpsScript Gate catches a compatibility problem in one of your scripts, consider starring the repository so other shell and DevOps maintainers can find it too.
|
|
371
|
+
|
|
372
|
+
Bug reports, real-world compatibility cases, and pull requests are especially welcome.
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
271
376
|
## 📄 License
|
|
272
377
|
|
|
273
378
|
OpsScript Gate is licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# OpsScript Gate:Shell 脚本跨发行版运行检查
|
|
2
|
+
|
|
3
|
+
[English](README.md) · [配置说明](docs/configuration.md) · [常见问题](docs/troubleshooting.md)
|
|
4
|
+
|
|
5
|
+
在 Debian、Ubuntu、Alpine 容器里实际运行 Shell 脚本,提前发现缺失命令、
|
|
6
|
+
Bash 依赖、包管理器假设和交互式阻塞。它与 ShellCheck 互补。
|
|
7
|
+
|
|
8
|
+
适合独立安装脚本、容器入口脚本和发布脚本。每次只挂载目标脚本,
|
|
9
|
+
不挂载整个仓库;依赖其他仓库文件的脚本需要现有项目测试配合。
|
|
10
|
+
|
|
11
|
+
## 试用本次 v0.5.0 更新
|
|
12
|
+
|
|
13
|
+
v0.5.0 已作为正式版本发布;如果你要从源码验证当前分支,也可以执行:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install -e .
|
|
17
|
+
opsscript-gate run examples/basic/clean_setup.sh --dry-run
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
在你希望接入检查的仓库根目录执行:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
opsscript-gate init
|
|
24
|
+
opsscript-gate run --dry-run
|
|
25
|
+
opsscript-gate run --format json --output reports/compatibility.json
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`init` 生成配置和 GitHub 工作流,不覆盖已有文件。工作流引用 `v0.5.0`。
|
|
29
|
+
预览不需要 Docker;真实运行需要 Python 3.10+
|
|
30
|
+
和可访问的 Linux Docker 引擎。
|
|
31
|
+
|
|
32
|
+
生成的配置默认排除 `tests/*`、`examples/*`,使用 Debian + Alpine,尊重
|
|
33
|
+
脚本 shebang,并关闭网络。请根据实际脚本审核配置;需要下载文件时启用
|
|
34
|
+
`--network bridge`。保留现有无配置行为:四个发行版、POSIX shell、bridge 网络。
|
|
35
|
+
|
|
36
|
+
## 本轮改进
|
|
37
|
+
|
|
38
|
+
- 运行前展示脚本、镜像和总容器执行次数。
|
|
39
|
+
- 本地与 GitHub Actions 共用 `.opsscript-gate.json`。
|
|
40
|
+
- 排除规则、发行版预设、扫描数量限制。
|
|
41
|
+
- 超过扫描上限明确报错,避免只测前 20 个却误以为全部通过。
|
|
42
|
+
- 报告保存为 JSON、Markdown 或文本,失败时同样保留结果。
|
|
43
|
+
- 自动发现跳过符号链接,并严格校验配置与执行参数。
|
|
44
|
+
|
|
45
|
+
返回码 `0` 表示全部通过,`1` 表示检查失败或发生错误。`--dry-run` 成功只表示
|
|
46
|
+
执行计划生成成功。容器不是不可信代码的安全沙箱。
|
|
47
|
+
|
|
48
|
+
## 示例:为什么 Alpine 上会失败
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
#!/bin/sh
|
|
52
|
+
set -e
|
|
53
|
+
apt-get --version
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
这个脚本可以符合 POSIX 语法,但 Alpine 使用 `apk`,实际执行会暴露命令缺失。
|
|
57
|
+
查看 [Ubuntu/Alpine 排错说明](docs/troubleshooting.md) 获取常见原因与修复方向。
|
|
58
|
+
|
|
59
|
+
## 测试
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install -e '.[test]'
|
|
63
|
+
pytest -q -m 'not integration'
|
|
64
|
+
pytest -q -m integration
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
第二组需要 Docker。跳过容器测试不能算作完整集成验证。
|
|
@@ -7,8 +7,7 @@ Only the latest release of **OpsScript Gate** receives security updates and bug
|
|
|
7
7
|
| Version | Supported |
|
|
8
8
|
| :--- | :---: |
|
|
9
9
|
| 0.4.x | :white_check_mark: |
|
|
10
|
-
| 0.
|
|
11
|
-
| < 0.3.0 | :x: |
|
|
10
|
+
| < 0.4.0 | :x: |
|
|
12
11
|
|
|
13
12
|
---
|
|
14
13
|
|
|
@@ -32,9 +31,12 @@ Reports will be reviewed as maintainer availability allows.
|
|
|
32
31
|
|
|
33
32
|
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.
|
|
34
33
|
|
|
35
|
-
OpsScript Gate applies conservative container defaults when running scripts:
|
|
34
|
+
OpsScript Gate applies conservative container defaults and hardened I/O boundaries when running scripts:
|
|
36
35
|
- **Restricted Container Defaults**: Containers run with `privileged=False`, `cap_drop=["ALL"]`, and `security_opt=["no-new-privileges:true"]`.
|
|
37
36
|
- **Resource Limits & Isolation**: Enforces memory caps (`--mem-limit`, default 256m), process table caps (`--pids-limit`, default 128), and optional network isolation (`--network none`).
|
|
38
37
|
- **Read-Only Mounting**: Target scripts are mounted read-only (`:ro`). OpsScript Gate does not mount additional host filesystem paths into test containers.
|
|
39
38
|
- **Resource Protection & Hard Timeout**: Containers are subject to hard timeouts (default 60s) with `SIGKILL` termination. Container removal is attempted from a `finally` block in normal, failure, and timeout execution paths.
|
|
40
|
-
- **
|
|
39
|
+
- **Bounded Output & Rolling Buffer**: Container output capture is strictly bounded by tail lines (500) and byte caps (256 KiB) via an immediate rolling byte buffer, bounding retained container log data to reduce memory-exhaustion risk.
|
|
40
|
+
- **Untrusted Log Neutralization**: Container logs neutralize line-leading workflow commands (`[container] ::`), strip terminal ANSI/CSI/OSC sequences and dangerous C0 control characters, and apply context-sensitive Markdown/HTML escaping to protect Step Summaries.
|
|
41
|
+
- **Workflow Command Encoding**: OpsScript Gate's own GitHub Actions annotations (`::error`) apply strict percent-encoding for properties (`%`, `\r`, `\n`, `:`, `,`) and data (`%`, `\r`, `\n`), protecting GitHub Actions workflow-command fields against injection.
|
|
42
|
+
- **Bounded Streaming I/O**: Script preparation and line ending normalization operate in bounded 64 KiB chunks, and shebang line inspection is bounded to 4096 bytes.
|