relay-shell 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 (108) hide show
  1. relay_shell-0.1.0/.claude/settings.json +10 -0
  2. relay_shell-0.1.0/.env.example +63 -0
  3. relay_shell-0.1.0/.github/ISSUE_TEMPLATE/bug.md +57 -0
  4. relay_shell-0.1.0/.github/ISSUE_TEMPLATE/feature.md +58 -0
  5. relay_shell-0.1.0/.github/ISSUE_TEMPLATE/security.md +73 -0
  6. relay_shell-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +71 -0
  7. relay_shell-0.1.0/.github/dependabot.yml +16 -0
  8. relay_shell-0.1.0/.github/workflows/ci.yml +59 -0
  9. relay_shell-0.1.0/.github/workflows/codeql.yml +38 -0
  10. relay_shell-0.1.0/.github/workflows/dependency-review.yml +24 -0
  11. relay_shell-0.1.0/.github/workflows/nightly-fuzz.yml +41 -0
  12. relay_shell-0.1.0/.github/workflows/release.yml +191 -0
  13. relay_shell-0.1.0/.github/workflows/sbom.yml +123 -0
  14. relay_shell-0.1.0/.gitignore +33 -0
  15. relay_shell-0.1.0/.hypothesis/.gitignore +9 -0
  16. relay_shell-0.1.0/.hypothesis/constants/060c4657af60692d +4 -0
  17. relay_shell-0.1.0/.hypothesis/constants/1a9a2338dd557cf6 +4 -0
  18. relay_shell-0.1.0/.hypothesis/constants/2f71ce4aa5de0b48 +4 -0
  19. relay_shell-0.1.0/.hypothesis/constants/531d010a93279f64 +4 -0
  20. relay_shell-0.1.0/.hypothesis/constants/654e10b2a716d6c4 +4 -0
  21. relay_shell-0.1.0/.hypothesis/constants/7d76440294249bc2 +4 -0
  22. relay_shell-0.1.0/.hypothesis/constants/828c112bd3d46feb +4 -0
  23. relay_shell-0.1.0/.hypothesis/constants/8b6000d1862c95be +4 -0
  24. relay_shell-0.1.0/.hypothesis/constants/8e009fed5620e2e8 +4 -0
  25. relay_shell-0.1.0/.hypothesis/constants/aeca4f1f08b784e6 +4 -0
  26. relay_shell-0.1.0/.hypothesis/constants/bbb3cb72c6a57ed5 +4 -0
  27. relay_shell-0.1.0/.hypothesis/constants/bef274fdff8cb14f +4 -0
  28. relay_shell-0.1.0/.hypothesis/constants/ced86f8de2d92b27 +4 -0
  29. relay_shell-0.1.0/.hypothesis/constants/d3969dbe54c370b6 +4 -0
  30. relay_shell-0.1.0/.hypothesis/constants/d78536b0c96f72a7 +4 -0
  31. relay_shell-0.1.0/.hypothesis/constants/e948b6f3cf68583c +4 -0
  32. relay_shell-0.1.0/.hypothesis/constants/edc2ee49885c8565 +4 -0
  33. relay_shell-0.1.0/.hypothesis/constants/f2bec7ffccba40ef +4 -0
  34. relay_shell-0.1.0/.pre-commit-config.yaml +48 -0
  35. relay_shell-0.1.0/AGENTS.md +110 -0
  36. relay_shell-0.1.0/CHANGELOG.md +350 -0
  37. relay_shell-0.1.0/CLAUDE.md +85 -0
  38. relay_shell-0.1.0/CODE_OF_CONDUCT.md +36 -0
  39. relay_shell-0.1.0/CONTRIBUTING.md +175 -0
  40. relay_shell-0.1.0/LICENSE +201 -0
  41. relay_shell-0.1.0/NOTICE +15 -0
  42. relay_shell-0.1.0/PKG-INFO +253 -0
  43. relay_shell-0.1.0/README.md +211 -0
  44. relay_shell-0.1.0/SECURITY.md +104 -0
  45. relay_shell-0.1.0/deploy/Caddyfile +78 -0
  46. relay_shell-0.1.0/deploy/install-edge.sh +272 -0
  47. relay_shell-0.1.0/deploy/install.sh +59 -0
  48. relay_shell-0.1.0/deploy/logrotate/relay-shell +22 -0
  49. relay_shell-0.1.0/deploy/systemd/relay-shell.service +28 -0
  50. relay_shell-0.1.0/deploy/systemd/relay-shell.service.d/hardening.conf +37 -0
  51. relay_shell-0.1.0/docs/adr/0001-runtime-and-sdk.md +41 -0
  52. relay_shell-0.1.0/docs/adr/0002-no-sandbox-full-access.md +47 -0
  53. relay_shell-0.1.0/docs/adr/0003-tiered-authority.md +58 -0
  54. relay_shell-0.1.0/docs/adr/0004-edge-tls-automation.md +112 -0
  55. relay_shell-0.1.0/docs/adr/0005-codebase-validation.md +127 -0
  56. relay_shell-0.1.0/docs/adr/0006-seccomp-notify-audit-channel.md +180 -0
  57. relay_shell-0.1.0/docs/adr/README.md +68 -0
  58. relay_shell-0.1.0/docs/architecture.md +130 -0
  59. relay_shell-0.1.0/docs/audit-shipper.md +531 -0
  60. relay_shell-0.1.0/docs/deployment.md +335 -0
  61. relay_shell-0.1.0/docs/runbook.md +972 -0
  62. relay_shell-0.1.0/docs/tools.md +234 -0
  63. relay_shell-0.1.0/pyproject.toml +144 -0
  64. relay_shell-0.1.0/requirements.txt +21 -0
  65. relay_shell-0.1.0/scripts/healthcheck.sh +20 -0
  66. relay_shell-0.1.0/src/relay_shell/__init__.py +7 -0
  67. relay_shell-0.1.0/src/relay_shell/__main__.py +230 -0
  68. relay_shell-0.1.0/src/relay_shell/audit.py +199 -0
  69. relay_shell-0.1.0/src/relay_shell/auth/__init__.py +7 -0
  70. relay_shell-0.1.0/src/relay_shell/auth/oauth.py +295 -0
  71. relay_shell-0.1.0/src/relay_shell/config.py +109 -0
  72. relay_shell-0.1.0/src/relay_shell/errors.py +27 -0
  73. relay_shell-0.1.0/src/relay_shell/inventory.py +173 -0
  74. relay_shell-0.1.0/src/relay_shell/metrics.py +134 -0
  75. relay_shell-0.1.0/src/relay_shell/patterns.py +190 -0
  76. relay_shell-0.1.0/src/relay_shell/policy.py +138 -0
  77. relay_shell-0.1.0/src/relay_shell/py.typed +0 -0
  78. relay_shell-0.1.0/src/relay_shell/redaction.py +66 -0
  79. relay_shell-0.1.0/src/relay_shell/server.py +1126 -0
  80. relay_shell-0.1.0/src/relay_shell/sessions.py +378 -0
  81. relay_shell-0.1.0/src/relay_shell/shelltools.py +157 -0
  82. relay_shell-0.1.0/src/relay_shell/sshpool.py +337 -0
  83. relay_shell-0.1.0/src/relay_shell/util.py +58 -0
  84. relay_shell-0.1.0/src/relay_shell/verifier.py +198 -0
  85. relay_shell-0.1.0/tests/conftest.py +33 -0
  86. relay_shell-0.1.0/tests/test_audit.py +213 -0
  87. relay_shell-0.1.0/tests/test_audit_tail_tool.py +104 -0
  88. relay_shell-0.1.0/tests/test_config.py +42 -0
  89. relay_shell-0.1.0/tests/test_errors.py +23 -0
  90. relay_shell-0.1.0/tests/test_fuzz.py +272 -0
  91. relay_shell-0.1.0/tests/test_inventory.py +61 -0
  92. relay_shell-0.1.0/tests/test_main.py +103 -0
  93. relay_shell-0.1.0/tests/test_metrics.py +166 -0
  94. relay_shell-0.1.0/tests/test_oauth.py +210 -0
  95. relay_shell-0.1.0/tests/test_patterns.py +277 -0
  96. relay_shell-0.1.0/tests/test_policy.py +57 -0
  97. relay_shell-0.1.0/tests/test_redaction.py +131 -0
  98. relay_shell-0.1.0/tests/test_resources.py +236 -0
  99. relay_shell-0.1.0/tests/test_server.py +42 -0
  100. relay_shell-0.1.0/tests/test_sessions.py +68 -0
  101. relay_shell-0.1.0/tests/test_shell.py +83 -0
  102. relay_shell-0.1.0/tests/test_ssh_fanout_tool.py +385 -0
  103. relay_shell-0.1.0/tests/test_ssh_integration.py +449 -0
  104. relay_shell-0.1.0/tests/test_ssh_keyscan_tool.py +193 -0
  105. relay_shell-0.1.0/tests/test_stdio_e2e.py +60 -0
  106. relay_shell-0.1.0/tests/test_tool_wrappers.py +269 -0
  107. relay_shell-0.1.0/tests/test_util.py +44 -0
  108. relay_shell-0.1.0/tests/test_verifier.py +196 -0
@@ -0,0 +1,10 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(bats:*)",
5
+ "Bash(bash -n:*)",
6
+ "Bash(bash --version)",
7
+ "Bash(just:*)"
8
+ ]
9
+ }
10
+ }
@@ -0,0 +1,63 @@
1
+ # relay-shell configuration. Copy to .env (never commit a real .env).
2
+ # All variables are optional; defaults are shown.
3
+
4
+ # --- Transport ---
5
+ # stdio | http
6
+ RELAY_SHELL_TRANSPORT=stdio
7
+ RELAY_SHELL_HTTP_HOST=127.0.0.1
8
+ RELAY_SHELL_HTTP_PORT=8080
9
+
10
+ # --- Limits (bytes / seconds) ---
11
+ RELAY_SHELL_MAX_OUTPUT=65536
12
+ RELAY_SHELL_MAX_OUTPUT_HARD=1048576
13
+ RELAY_SHELL_DEFAULT_TIMEOUT=60
14
+ RELAY_SHELL_MAX_TIMEOUT=900
15
+ RELAY_SHELL_MAX_SESSIONS=64
16
+ RELAY_SHELL_SESSION_IDLE_TIMEOUT=1800
17
+ RELAY_SHELL_SESSION_BUFFER_BYTES=262144
18
+
19
+ # --- Policy ---
20
+ # open | guarded | readonly
21
+ # open : full access (default; matches the documented single-owner posture)
22
+ # guarded : Tier 2+ commands are refused unless RELAY_SHELL_POLICY_ALLOW matches
23
+ # readonly : only Tier 0 (read-only) commands permitted
24
+ RELAY_SHELL_POLICY_MODE=open
25
+ RELAY_SHELL_POLICY_DENY=
26
+ RELAY_SHELL_POLICY_ALLOW=
27
+
28
+ # --- Audit ---
29
+ RELAY_SHELL_AUDIT_PATH=/var/log/relay-shell/audit.jsonl
30
+ RELAY_SHELL_AUDIT_STDERR=false
31
+ # jsonl | cef | leef
32
+ RELAY_SHELL_AUDIT_FORMAT=jsonl
33
+
34
+ # --- SSH ---
35
+ RELAY_SHELL_SSH_CONFIG=~/.ssh/config
36
+ RELAY_SHELL_INVENTORY=
37
+ # strict | accept-new | ignore
38
+ RELAY_SHELL_SSH_KNOWN_HOSTS=accept-new
39
+ RELAY_SHELL_SSH_CONNECT_TIMEOUT=10
40
+ RELAY_SHELL_SSH_KEEPALIVE=30
41
+
42
+ # --- Edge TLS (consumed by deploy/install-edge.sh and the Caddyfile) ---
43
+ # Required to issue a public Let's Encrypt certificate:
44
+ RELAY_SHELL_EDGE_DOMAIN=
45
+ RELAY_SHELL_EDGE_ACME_EMAIL=
46
+ # Space-separated source allowlist for tool traffic and /token. Defaults to
47
+ # loopback only, which blocks remote clients until you set this. The value
48
+ # is quoted so it is safe both as a systemd EnvironmentFile entry and as a
49
+ # bash-sourced assignment.
50
+ RELAY_SHELL_EDGE_CLIENT_CIDRS="127.0.0.1/8 ::1"
51
+ # Override only if relay-shell binds a non-default loopback host:port.
52
+ RELAY_SHELL_EDGE_UPSTREAM=127.0.0.1:8080
53
+ # Set to https://acme-staging-v02.api.letsencrypt.org/directory for dry runs.
54
+ RELAY_SHELL_EDGE_ACME_CA=https://acme-v02.api.letsencrypt.org/directory
55
+
56
+ # --- OAuth 2.1 (HTTP transport only; requires the [http] extra) ---
57
+ RELAY_SHELL_AUTH_ENABLED=false
58
+ RELAY_SHELL_AUTH_ISSUER=https://localhost:8080
59
+ RELAY_SHELL_AUTH_STATE_DIR=/var/lib/relay-shell/oauth
60
+ RELAY_SHELL_AUTH_SINGLE_CLIENT=true
61
+ RELAY_SHELL_AUTH_ACCESS_TTL=3600
62
+ RELAY_SHELL_AUTH_REFRESH_TTL=2592000
63
+ RELAY_SHELL_AUTH_CODE_TTL=300
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: Bug report
3
+ about: A tool behaves incorrectly, a guarantee is violated, or output is wrong.
4
+ title: "[bug] "
5
+ labels: ["bug"]
6
+ ---
7
+
8
+ <!--
9
+ For security-sensitive bugs (audit-trail evasion, policy bypass, secret
10
+ leak, transport / auth issues) please use the "Security report" template
11
+ or open a private advisory instead. See SECURITY.md for the disclosure
12
+ process.
13
+ -->
14
+
15
+ ## What happened
16
+
17
+ <!-- One or two sentences. What did the tool do, and what was wrong
18
+ about it? Paste the relevant `[ERROR: ...]` / `[DENIED: ...]` string if
19
+ there was one. -->
20
+
21
+ ## Expected behavior
22
+
23
+ <!-- What should have happened, and where is that contract documented
24
+ (`docs/tools.md`, an ADR, a runbook section)? -->
25
+
26
+ ## Reproduction
27
+
28
+ ```text
29
+ # Minimal command or MCP tool call that reproduces the issue.
30
+ # If reproducing through an MCP client, include the tool name and
31
+ # arguments. Redact any real secrets before pasting.
32
+ ```
33
+
34
+ ## Environment
35
+
36
+ - `relay-shell` version (`relay-shell --help` or `pyproject.toml`):
37
+ - Python version (`python --version`):
38
+ - OS / distro:
39
+ - Transport (`stdio` / `http`):
40
+ - Policy mode (`RELAY_SHELL_POLICY_MODE`):
41
+ - MCP client (Claude Desktop / Inspector / SDK / other):
42
+
43
+ ## Audit record (if relevant)
44
+
45
+ <!-- One JSON line from `audit.jsonl` if applicable. Strip the args
46
+ field if it might contain anything sensitive; the rest of the record
47
+ (timestamp, tool, tier, output_sha256, output_len, exit_code) is
48
+ generally safe to share. -->
49
+
50
+ ```json
51
+ ```
52
+
53
+ ## Logs / additional context
54
+
55
+ <!-- Anything else that helps. Stack traces are appreciated but bear in
56
+ mind that user-facing output is intentionally bounded; checking the
57
+ server's stderr is usually more informative. -->
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: Feature request
3
+ about: Propose a new tool, transport, auth provider, or capability.
4
+ title: "[feature] "
5
+ labels: ["enhancement"]
6
+ ---
7
+
8
+ <!--
9
+ Before filing, check `docs/runbook.md` §7 to see if this is already in
10
+ the backlog. If it is, reference the backlog ID (e.g. B-007) in this
11
+ issue's body so the maintainer can link the discussion to the existing
12
+ item instead of treating it as a duplicate.
13
+
14
+ The runbook §6 has recipes for adding tools / transports / auth
15
+ providers / policy heuristics / redaction rules. If you plan to send
16
+ the PR yourself, skim the relevant recipe first.
17
+ -->
18
+
19
+ ## Problem
20
+
21
+ <!-- The operational need this addresses. Not "add X" but "I cannot do
22
+ Y today because Z." -->
23
+
24
+ ## Proposal
25
+
26
+ <!-- The change in one paragraph. Tool name (if applicable), default
27
+ tier, the parameters it takes, the shape of the response, the failure
28
+ modes. -->
29
+
30
+ ## Capability impact
31
+
32
+ - [ ] Adds a new tool. Default tier: <!-- 0 / 1 / 2 / 3 -->
33
+ - [ ] Adds a new transport (`stdio` / `streamable-http` / other).
34
+ - [ ] Adds a new auth provider.
35
+ - [ ] Adds a new policy heuristic.
36
+ - [ ] Adds a new redaction rule.
37
+ - [ ] Adds a new env var (`RELAY_SHELL_*`).
38
+ - [ ] Adds a new runtime dependency.
39
+
40
+ ## Alternatives considered
41
+
42
+ <!-- Existing tools that almost solve this; why they fall short. If the
43
+ operator can already accomplish this via composition (e.g. `shell_exec`
44
+ plus a script) and the only ask is ergonomics, say so explicitly. -->
45
+
46
+ ## Documentation and tests this will need (best guess)
47
+
48
+ <!-- Reference the runbook §6 recipe. For a new tool that is:
49
+ - a row in `docs/tools.md`
50
+ - an entry in `tests/test_server.py::_EXPECTED` plus the `len()` assertion
51
+ - an entry in the README capability table
52
+ - a unit test of the underlying helper
53
+ - a wiring test that exercises the tool through FastMCP
54
+ - the `_INSTRUCTIONS` string in `server.py` -->
55
+
56
+ ## Out of scope
57
+
58
+ <!-- Anything related you do *not* want addressed in the same PR. -->
@@ -0,0 +1,73 @@
1
+ ---
2
+ name: Security report
3
+ about: Report a vulnerability or a violation of a security guarantee.
4
+ title: "[security] "
5
+ labels: ["security"]
6
+ ---
7
+
8
+ <!--
9
+ STOP. Please do not paste working exploit payloads into a public issue.
10
+
11
+ Preferred reporting channels (in order):
12
+
13
+ 1. Open a private security advisory on the repository:
14
+ https://github.com/rmednitzer/relay-shell/security/advisories/new
15
+ 2. Or open an issue without exploit detail and request a private channel
16
+ to follow up.
17
+
18
+ This template exists so that something *can* be filed publicly when the
19
+ finding is generic (e.g. "the redaction pattern misses a known token
20
+ shape") and exploitability is low. Use the private path for anything
21
+ sensitive. See SECURITY.md.
22
+
23
+ Indicative response target: 7 days to triage.
24
+ -->
25
+
26
+ ## In scope?
27
+
28
+ The following are explicitly in scope (per `SECURITY.md`):
29
+
30
+ - [ ] Audit-trail evasion (output body leaking into the audit log, hash
31
+ / length missing, append-only bypass).
32
+ - [ ] Policy / tier bypass (denylist bypass; `readonly` or `guarded`
33
+ admitting commands they should refuse).
34
+ - [ ] Secret leakage into logs (a real secret shape that redaction
35
+ misses).
36
+ - [ ] Auth or transport handling (OAuth provider, TLS edge, CIDR
37
+ allowlist).
38
+ - [ ] Sandbox-escape-equivalent privilege gain *beyond* the documented
39
+ service-account posture.
40
+
41
+ Out of scope: the documented unsandboxed full-access posture itself,
42
+ and the ability of a correctly authenticated, policy-permitted caller
43
+ to run commands.
44
+
45
+ ## Summary
46
+
47
+ <!-- One or two sentences. What is the issue and which guarantee does
48
+ it violate? Reference the section of `SECURITY.md` or the ADR it
49
+ contradicts. -->
50
+
51
+ ## Impact
52
+
53
+ <!-- Which posture is affected (scoped / privileged), which mode (open
54
+ / guarded / readonly), and what the attacker gains. Be specific about
55
+ prerequisites (already-authenticated MCP client? Compromised SSH host?
56
+ Local user on the relay host?). -->
57
+
58
+ ## Reproduction sketch
59
+
60
+ <!-- Enough to confirm the issue. Do NOT include working exploit code
61
+ in a public issue. A sentence and a pointer to the affected file is
62
+ fine; use the private advisory channel for full detail. -->
63
+
64
+ ## Suggested mitigation (optional)
65
+
66
+ <!-- Where in the source the fix likely belongs. Helpful but not
67
+ required. -->
68
+
69
+ ## Disclosure preference
70
+
71
+ - [ ] Standard disclosure (public issue + fix in a regular release).
72
+ - [ ] Coordinated disclosure (private advisory until fix ships; credit
73
+ requested as `<your handle>`).
@@ -0,0 +1,71 @@
1
+ <!--
2
+ Thanks for contributing to relay-shell.
3
+
4
+ Before opening this PR, walk the runbook §3.1 checklist below. The
5
+ checklist enforces the project's "documentation moves with code" rule:
6
+ new tools, env vars, and modules must be reflected in every cross-
7
+ referenced source in the same PR.
8
+
9
+ Run the local loop before pushing:
10
+
11
+ ruff check . && ruff format --check . && mypy && pytest -q
12
+
13
+ Reference: docs/runbook.md
14
+ -->
15
+
16
+ ## Summary
17
+
18
+ <!-- One or two sentences explaining what changes and why. Link the
19
+ backlog item if applicable: "Closes B-007 from docs/runbook.md". -->
20
+
21
+ ## Type of change
22
+
23
+ - [ ] Bug fix
24
+ - [ ] New capability (tool / transport / auth provider)
25
+ - [ ] Security hardening
26
+ - [ ] Documentation
27
+ - [ ] Build, CI, or release automation
28
+ - [ ] Refactor / cleanup (no behavior change)
29
+
30
+ ## Runbook §3.1 checklist
31
+
32
+ - [ ] CI is green on the PR head commit (lint, type-check, tests,
33
+ CodeQL, dependency-review).
34
+ - [ ] No new file is undocumented in `docs/architecture.md` module
35
+ table.
36
+ - [ ] No new tool is missing from `docs/tools.md`,
37
+ `tests/test_server.py::_EXPECTED`, and the README capability
38
+ tables.
39
+ - [ ] No new env var is missing from `.env.example`, `Settings`, and
40
+ `docs/deployment.md`.
41
+ - [ ] No new dependency is added without a justification in the PR body
42
+ and a pinned version in `requirements.txt`.
43
+
44
+ ## Security-sensitive diff (runbook §3.3)
45
+
46
+ Tick if this PR touches any of: `audit.py`, `redaction.py`,
47
+ `policy.py`, the `Relay.run()` body in `server.py`, `auth/oauth.py`,
48
+ `deploy/install*.sh`, or `deploy/Caddyfile`.
49
+
50
+ - [ ] This PR is security-sensitive.
51
+
52
+ If ticked, also confirm:
53
+
54
+ - [ ] `/security-review` was run on the diff (or the manual checklist
55
+ in runbook §3.3 was walked).
56
+ - [ ] Audit-record fields unchanged (`ts, tool, tier, denied, args,
57
+ output_sha256, output_len, exit_code`); output body still hashed
58
+ only.
59
+ - [ ] `policy_text` passed to `Relay.run()` covers every byte the
60
+ executor will see (command + stdin + env_json + script body).
61
+ - [ ] Redaction patterns have paired over-scrub and under-scrub tests.
62
+
63
+ ## Test plan
64
+
65
+ <!-- How was this verified locally? Which tests were added or changed?
66
+ For UI-less server changes the full local loop is usually enough;
67
+ for behavior changes, name the new test(s). -->
68
+
69
+ - [ ] `ruff check . && ruff format --check . && mypy && pytest -q`
70
+ passes locally.
71
+ - [ ] New behavior has a paired test (under `tests/`).
@@ -0,0 +1,16 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "pip"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ open-pull-requests-limit: 10
8
+ labels:
9
+ - "dependencies"
10
+ - package-ecosystem: "github-actions"
11
+ directory: "/"
12
+ schedule:
13
+ interval: "weekly"
14
+ open-pull-requests-limit: 10
15
+ labels:
16
+ - "dependencies"
@@ -0,0 +1,59 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ check:
10
+ permissions:
11
+ contents: read
12
+ timeout-minutes: 20
13
+ runs-on: ubuntu-latest
14
+ strategy:
15
+ # Surface every Python version's result so a single 3.14 wheel gap
16
+ # does not mask a 3.13 regression. The package floor is `>=3.12` in
17
+ # pyproject.toml; the matrix covers the floor plus the two newer
18
+ # interpreters in active CPython support.
19
+ fail-fast: false
20
+ matrix:
21
+ python-version: ["3.12", "3.13", "3.14"]
22
+ name: check (py${{ matrix.python-version }})
23
+ steps:
24
+ - uses: actions/checkout@v6
25
+
26
+ - uses: actions/setup-python@v6
27
+ with:
28
+ python-version: ${{ matrix.python-version }}
29
+ cache: "pip"
30
+
31
+ - name: Install
32
+ run: |
33
+ python -m pip install --upgrade pip
34
+ pip install -e ".[dev]"
35
+ # Wire subprocess coverage: the stdio e2e launches `python -m
36
+ # relay_shell` in a child, so without this the server.py wrappers
37
+ # register as uncovered. The .pth runs at every interpreter start;
38
+ # COVERAGE_PROCESS_START gates the actual recording to runs that opt
39
+ # in. See `docs/runbook.md` §4.3.
40
+ python -c "import site, pathlib; \
41
+ pathlib.Path(site.getsitepackages()[0], 'coverage_subprocess.pth') \
42
+ .write_text('import coverage; coverage.process_startup()\n')"
43
+
44
+ - name: Lint (ruff)
45
+ run: |
46
+ ruff check .
47
+ ruff format --check .
48
+
49
+ - name: Type-check (mypy --strict)
50
+ run: mypy
51
+
52
+ - name: Test (pytest with coverage)
53
+ env:
54
+ COVERAGE_PROCESS_START: ${{ github.workspace }}/pyproject.toml
55
+ run: |
56
+ coverage erase
57
+ coverage run -m pytest -q
58
+ coverage combine
59
+ coverage report
@@ -0,0 +1,38 @@
1
+ name: codeql
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ schedule:
8
+ - cron: "26 4 * * 1"
9
+
10
+ permissions:
11
+ actions: read
12
+ contents: read
13
+ security-events: write
14
+
15
+ jobs:
16
+ analyze:
17
+ name: Analyze (python)
18
+ runs-on: ubuntu-latest
19
+ timeout-minutes: 30
20
+ strategy:
21
+ fail-fast: false
22
+ matrix:
23
+ language: [python]
24
+
25
+ steps:
26
+ - name: Checkout
27
+ uses: actions/checkout@v6
28
+
29
+ - name: Initialize CodeQL
30
+ uses: github/codeql-action/init@v4
31
+ with:
32
+ languages: ${{ matrix.language }}
33
+
34
+ - name: Autobuild
35
+ uses: github/codeql-action/autobuild@v4
36
+
37
+ - name: Perform CodeQL Analysis
38
+ uses: github/codeql-action/analyze@v4
@@ -0,0 +1,24 @@
1
+ name: dependency-review
2
+
3
+ on:
4
+ pull_request:
5
+
6
+ permissions:
7
+ contents: read
8
+
9
+ jobs:
10
+ dependency-review:
11
+ runs-on: ubuntu-latest
12
+ timeout-minutes: 10
13
+ steps:
14
+ - name: Checkout
15
+ uses: actions/checkout@v6
16
+ # actions/checkout@v6 ships with persist-credentials default-off;
17
+ # the dependency-review-action then shells out to `git fetch` for
18
+ # base/head refs and is prompted for a username (terminal prompts
19
+ # disabled -> exit 128). Restoring the credential helper here
20
+ # keeps the workflow byte-cheap and pinned to v6 elsewhere.
21
+ with:
22
+ persist-credentials: true
23
+ - name: Dependency review
24
+ uses: actions/dependency-review-action@v5
@@ -0,0 +1,41 @@
1
+ name: nightly-fuzz
2
+
3
+ # Hypothesis-driven property-based fuzz tests for `redact` and `classify`.
4
+ # These live behind the `fuzz` pytest marker and are deselected from the
5
+ # default `pytest` run via `pyproject.toml`'s `addopts`. This workflow
6
+ # opts back in and runs them with an amplified example budget
7
+ # (HYPOTHESIS_PROFILE=ci) once a day so the security-sensitive
8
+ # primitives keep finding latent counterexamples.
9
+
10
+ on:
11
+ schedule:
12
+ # 04:17 UTC daily. The odd minute avoids the on-the-hour spike that
13
+ # the GitHub Actions scheduler is known to delay under load.
14
+ - cron: "17 4 * * *"
15
+ workflow_dispatch:
16
+
17
+ permissions:
18
+ contents: read
19
+
20
+ jobs:
21
+ fuzz:
22
+ runs-on: ubuntu-latest
23
+ timeout-minutes: 30
24
+ steps:
25
+ - uses: actions/checkout@v6
26
+
27
+ - uses: actions/setup-python@v6
28
+ with:
29
+ python-version: "3.12"
30
+ cache: "pip"
31
+
32
+ - name: Install
33
+ run: |
34
+ python -m pip install --upgrade pip
35
+ pip install -e ".[dev]"
36
+
37
+ - name: Run fuzz suite (HYPOTHESIS_PROFILE=ci)
38
+ env:
39
+ HYPOTHESIS_PROFILE: ci
40
+ # Opt back in to the `fuzz` marker (default addopts deselects it).
41
+ run: pytest -m fuzz -v