safereach 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. safereach-0.1.0/.github/workflows/ci.yml +43 -0
  2. safereach-0.1.0/.github/workflows/publish.yml +125 -0
  3. safereach-0.1.0/.gitignore +14 -0
  4. safereach-0.1.0/LICENSE +21 -0
  5. safereach-0.1.0/PKG-INFO +546 -0
  6. safereach-0.1.0/README.md +519 -0
  7. safereach-0.1.0/config/commands.yaml +586 -0
  8. safereach-0.1.0/config/hosts.example.yaml +75 -0
  9. safereach-0.1.0/pyproject.toml +63 -0
  10. safereach-0.1.0/shim/build.py +145 -0
  11. safereach-0.1.0/shim/install.sh +163 -0
  12. safereach-0.1.0/shim/shim_main.py +571 -0
  13. safereach-0.1.0/src/safereach/__init__.py +3 -0
  14. safereach-0.1.0/src/safereach/__main__.py +7 -0
  15. safereach-0.1.0/src/safereach/audit.py +99 -0
  16. safereach-0.1.0/src/safereach/cli.py +1884 -0
  17. safereach-0.1.0/src/safereach/config.py +292 -0
  18. safereach-0.1.0/src/safereach/discovery.py +212 -0
  19. safereach-0.1.0/src/safereach/install/__init__.py +0 -0
  20. safereach-0.1.0/src/safereach/install/adapters.py +353 -0
  21. safereach-0.1.0/src/safereach/naming.py +249 -0
  22. safereach-0.1.0/src/safereach/redact.py +297 -0
  23. safereach-0.1.0/src/safereach/server.py +635 -0
  24. safereach-0.1.0/src/safereach/ssh.py +245 -0
  25. safereach-0.1.0/src/safereach/validator.py +905 -0
  26. safereach-0.1.0/src/safereach/versioning.py +54 -0
  27. safereach-0.1.0/tests/conftest.py +273 -0
  28. safereach-0.1.0/tests/test_canary.py +151 -0
  29. safereach-0.1.0/tests/test_config.py +120 -0
  30. safereach-0.1.0/tests/test_discovery.py +175 -0
  31. safereach-0.1.0/tests/test_enroll.py +275 -0
  32. safereach-0.1.0/tests/test_install.py +211 -0
  33. safereach-0.1.0/tests/test_kubernetes.py +102 -0
  34. safereach-0.1.0/tests/test_naming.py +240 -0
  35. safereach-0.1.0/tests/test_redact.py +125 -0
  36. safereach-0.1.0/tests/test_rename.py +256 -0
  37. safereach-0.1.0/tests/test_secrets.py +131 -0
  38. safereach-0.1.0/tests/test_shim.py +277 -0
  39. safereach-0.1.0/tests/test_spec_lint.py +198 -0
  40. safereach-0.1.0/tests/test_stdio_clean.py +289 -0
  41. safereach-0.1.0/tests/test_validator_accept.py +107 -0
  42. safereach-0.1.0/tests/test_validator_attacks.py +106 -0
  43. safereach-0.1.0/uv.lock +910 -0
@@ -0,0 +1,43 @@
1
+ name: CI
2
+
3
+ on:
4
+ push: { branches: [main] }
5
+ pull_request:
6
+
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-latest
10
+ strategy:
11
+ matrix:
12
+ python: ["3.11", "3.12", "3.13"]
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: astral-sh/setup-uv@v5
16
+ - run: uv python install ${{ matrix.python }}
17
+ - run: uv venv --python ${{ matrix.python }}
18
+ - run: uv pip install -e ".[dev]"
19
+ # The spec linter runs first: it fails the build if the allowlist ever permits a
20
+ # mutating verb, which is cheaper to learn before the rest of the suite runs.
21
+ - run: uv run pytest tests/test_spec_lint.py -q
22
+ - run: uv run pytest -q
23
+ - run: uv run ruff check .
24
+ - run: uv run ruff format --check .
25
+ - run: uv run python shim/build.py
26
+
27
+ # A single stable check name to require in branch protection.
28
+ #
29
+ # The matrix above produces `test (3.11)`, `test (3.12)`, `test (3.13)` — three names
30
+ # that change whenever the matrix does, and a required check that no longer exists
31
+ # silently stops gating anything. This job is one name that never changes.
32
+ #
33
+ # `if: always()` matters: without it, a skipped or failed matrix leg would leave this
34
+ # job skipped, and GitHub treats a skipped required check as passing.
35
+ ci-ok:
36
+ needs: test
37
+ if: always()
38
+ runs-on: ubuntu-latest
39
+ steps:
40
+ - name: All matrix legs must have succeeded
41
+ run: |
42
+ echo "test result: ${{ needs.test.result }}"
43
+ [ "${{ needs.test.result }}" = "success" ] || exit 1
@@ -0,0 +1,125 @@
1
+ name: Publish
2
+
3
+ # Trusted Publishing (OIDC) — no API token is stored anywhere. GitHub mints a
4
+ # short-lived identity token for this workflow, and PyPI accepts it only for the exact
5
+ # repository, workflow filename and environment configured on the PyPI side.
6
+ #
7
+ # That is why the `environment:` key below matters: PyPI checks it. If it does not match
8
+ # the "Environment name" on the trusted publisher, the upload is rejected — which is the
9
+ # point. It means a workflow added to some other branch cannot publish.
10
+
11
+ # Publishing is a deliberate act, run from `main`.
12
+ #
13
+ # The environment's deployment-branch rule is evaluated against the ref that triggered
14
+ # the run. A GitHub Release runs on the TAG ref, not on `main` — so an environment
15
+ # restricted to `main` would block every release with a fairly opaque message. Triggering
16
+ # from workflow_dispatch on `main` keeps the trigger and the rule in agreement.
17
+ #
18
+ # Cut the GitHub Release afterwards for the changelog; it no longer drives publishing.
19
+ on:
20
+ workflow_dispatch:
21
+ inputs:
22
+ target:
23
+ description: "Where to publish"
24
+ required: true
25
+ default: pypi
26
+ type: choice
27
+ options: [pypi, testpypi]
28
+
29
+ permissions:
30
+ contents: read
31
+
32
+ jobs:
33
+ build:
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - uses: actions/checkout@v4
37
+ - uses: astral-sh/setup-uv@v5
38
+
39
+ # The tag and pyproject version must agree. Tagging v0.2.0 while pyproject still
40
+ # says 0.1.0 otherwise builds and uploads 0.1.0 under a 0.2.0 release — or fails
41
+ # much later with "file already exists", which reads like an infrastructure problem
42
+ # rather than the version bump you forgot.
43
+ - name: Must be publishing from main
44
+ run: |
45
+ [ "$GITHUB_REF_NAME" = "main" ] || {
46
+ echo "::error::publish only from main (got $GITHUB_REF_NAME)"
47
+ exit 1
48
+ }
49
+
50
+ # PyPI versions are immutable: a published 0.1.0 can never be replaced, only
51
+ # yanked. Failing here is far cheaper than burning a version number.
52
+ - name: Version must not already be on PyPI
53
+ if: inputs.target == 'pypi'
54
+ run: |
55
+ PKG=$(grep -m1 '^version = ' pyproject.toml | cut -d'"' -f2)
56
+ CODE=$(curl -s -o /dev/null -w '%{http_code}' "https://pypi.org/pypi/safereach/$PKG/json")
57
+ [ "$CODE" = "404" ] || {
58
+ echo "::error::safereach $PKG is already published — bump the version"
59
+ exit 1
60
+ }
61
+
62
+ - run: uv python install 3.11
63
+ - run: uv venv --python 3.11
64
+ - run: uv pip install -e ".[dev]"
65
+
66
+ # Never ship a release that fails its own tests. The spec linter runs first: it
67
+ # fails the build if the allowlist ever permits a mutating verb.
68
+ - name: Spec linter
69
+ run: uv run pytest tests/test_spec_lint.py -q
70
+ - name: Full suite
71
+ run: uv run pytest -q
72
+ - name: Lint
73
+ run: uv run ruff check .
74
+
75
+ - name: Build sdist and wheel
76
+ run: uv build
77
+
78
+ # PyPI rejects a malformed long_description at upload time; catching it here is
79
+ # the one gap that skipping TestPyPI would otherwise leave.
80
+ - name: README must render on PyPI
81
+ run: uv run --with twine twine check dist/*
82
+
83
+ # A wheel that imports but cannot find its packaged commands.yaml is useless, and
84
+ # that failure only shows up outside the source tree.
85
+ - name: Smoke-test the built wheel in a clean environment
86
+ run: |
87
+ uv venv /tmp/verify
88
+ uv pip install --python /tmp/verify/bin/python dist/*.whl
89
+ /tmp/verify/bin/safereach --help
90
+ /tmp/verify/bin/safereach validate "journalctl -u nginx -n 5"
91
+
92
+ - uses: actions/upload-artifact@v4
93
+ with:
94
+ name: dist
95
+ path: dist/
96
+
97
+ publish-testpypi:
98
+ needs: build
99
+ if: inputs.target == 'testpypi'
100
+ runs-on: ubuntu-latest
101
+ environment:
102
+ name: testpypi
103
+ url: https://test.pypi.org/p/safereach
104
+ permissions:
105
+ id-token: write
106
+ steps:
107
+ - uses: actions/download-artifact@v4
108
+ with: { name: dist, path: dist }
109
+ - uses: pypa/gh-action-pypi-publish@release/v1
110
+ with:
111
+ repository-url: https://test.pypi.org/legacy/
112
+
113
+ publish-pypi:
114
+ needs: build
115
+ if: inputs.target == 'pypi'
116
+ runs-on: ubuntu-latest
117
+ environment:
118
+ name: pypi
119
+ url: https://pypi.org/p/safereach
120
+ permissions:
121
+ id-token: write
122
+ steps:
123
+ - uses: actions/download-artifact@v4
124
+ with: { name: dist, path: dist }
125
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,14 @@
1
+ *.bak-*
2
+ build/
3
+ .coverage
4
+ dist/
5
+ docs/
6
+ *.egg-info/
7
+ hosts.yaml
8
+ htmlcov/
9
+ __pycache__/
10
+ *.py[cod]
11
+ .pytest_cache/
12
+ .ruff_cache/
13
+ shim/dist/
14
+ .venv/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 imtiaz
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.