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.
- relay_shell-0.1.0/.claude/settings.json +10 -0
- relay_shell-0.1.0/.env.example +63 -0
- relay_shell-0.1.0/.github/ISSUE_TEMPLATE/bug.md +57 -0
- relay_shell-0.1.0/.github/ISSUE_TEMPLATE/feature.md +58 -0
- relay_shell-0.1.0/.github/ISSUE_TEMPLATE/security.md +73 -0
- relay_shell-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +71 -0
- relay_shell-0.1.0/.github/dependabot.yml +16 -0
- relay_shell-0.1.0/.github/workflows/ci.yml +59 -0
- relay_shell-0.1.0/.github/workflows/codeql.yml +38 -0
- relay_shell-0.1.0/.github/workflows/dependency-review.yml +24 -0
- relay_shell-0.1.0/.github/workflows/nightly-fuzz.yml +41 -0
- relay_shell-0.1.0/.github/workflows/release.yml +191 -0
- relay_shell-0.1.0/.github/workflows/sbom.yml +123 -0
- relay_shell-0.1.0/.gitignore +33 -0
- relay_shell-0.1.0/.hypothesis/.gitignore +9 -0
- relay_shell-0.1.0/.hypothesis/constants/060c4657af60692d +4 -0
- relay_shell-0.1.0/.hypothesis/constants/1a9a2338dd557cf6 +4 -0
- relay_shell-0.1.0/.hypothesis/constants/2f71ce4aa5de0b48 +4 -0
- relay_shell-0.1.0/.hypothesis/constants/531d010a93279f64 +4 -0
- relay_shell-0.1.0/.hypothesis/constants/654e10b2a716d6c4 +4 -0
- relay_shell-0.1.0/.hypothesis/constants/7d76440294249bc2 +4 -0
- relay_shell-0.1.0/.hypothesis/constants/828c112bd3d46feb +4 -0
- relay_shell-0.1.0/.hypothesis/constants/8b6000d1862c95be +4 -0
- relay_shell-0.1.0/.hypothesis/constants/8e009fed5620e2e8 +4 -0
- relay_shell-0.1.0/.hypothesis/constants/aeca4f1f08b784e6 +4 -0
- relay_shell-0.1.0/.hypothesis/constants/bbb3cb72c6a57ed5 +4 -0
- relay_shell-0.1.0/.hypothesis/constants/bef274fdff8cb14f +4 -0
- relay_shell-0.1.0/.hypothesis/constants/ced86f8de2d92b27 +4 -0
- relay_shell-0.1.0/.hypothesis/constants/d3969dbe54c370b6 +4 -0
- relay_shell-0.1.0/.hypothesis/constants/d78536b0c96f72a7 +4 -0
- relay_shell-0.1.0/.hypothesis/constants/e948b6f3cf68583c +4 -0
- relay_shell-0.1.0/.hypothesis/constants/edc2ee49885c8565 +4 -0
- relay_shell-0.1.0/.hypothesis/constants/f2bec7ffccba40ef +4 -0
- relay_shell-0.1.0/.pre-commit-config.yaml +48 -0
- relay_shell-0.1.0/AGENTS.md +110 -0
- relay_shell-0.1.0/CHANGELOG.md +350 -0
- relay_shell-0.1.0/CLAUDE.md +85 -0
- relay_shell-0.1.0/CODE_OF_CONDUCT.md +36 -0
- relay_shell-0.1.0/CONTRIBUTING.md +175 -0
- relay_shell-0.1.0/LICENSE +201 -0
- relay_shell-0.1.0/NOTICE +15 -0
- relay_shell-0.1.0/PKG-INFO +253 -0
- relay_shell-0.1.0/README.md +211 -0
- relay_shell-0.1.0/SECURITY.md +104 -0
- relay_shell-0.1.0/deploy/Caddyfile +78 -0
- relay_shell-0.1.0/deploy/install-edge.sh +272 -0
- relay_shell-0.1.0/deploy/install.sh +59 -0
- relay_shell-0.1.0/deploy/logrotate/relay-shell +22 -0
- relay_shell-0.1.0/deploy/systemd/relay-shell.service +28 -0
- relay_shell-0.1.0/deploy/systemd/relay-shell.service.d/hardening.conf +37 -0
- relay_shell-0.1.0/docs/adr/0001-runtime-and-sdk.md +41 -0
- relay_shell-0.1.0/docs/adr/0002-no-sandbox-full-access.md +47 -0
- relay_shell-0.1.0/docs/adr/0003-tiered-authority.md +58 -0
- relay_shell-0.1.0/docs/adr/0004-edge-tls-automation.md +112 -0
- relay_shell-0.1.0/docs/adr/0005-codebase-validation.md +127 -0
- relay_shell-0.1.0/docs/adr/0006-seccomp-notify-audit-channel.md +180 -0
- relay_shell-0.1.0/docs/adr/README.md +68 -0
- relay_shell-0.1.0/docs/architecture.md +130 -0
- relay_shell-0.1.0/docs/audit-shipper.md +531 -0
- relay_shell-0.1.0/docs/deployment.md +335 -0
- relay_shell-0.1.0/docs/runbook.md +972 -0
- relay_shell-0.1.0/docs/tools.md +234 -0
- relay_shell-0.1.0/pyproject.toml +144 -0
- relay_shell-0.1.0/requirements.txt +21 -0
- relay_shell-0.1.0/scripts/healthcheck.sh +20 -0
- relay_shell-0.1.0/src/relay_shell/__init__.py +7 -0
- relay_shell-0.1.0/src/relay_shell/__main__.py +230 -0
- relay_shell-0.1.0/src/relay_shell/audit.py +199 -0
- relay_shell-0.1.0/src/relay_shell/auth/__init__.py +7 -0
- relay_shell-0.1.0/src/relay_shell/auth/oauth.py +295 -0
- relay_shell-0.1.0/src/relay_shell/config.py +109 -0
- relay_shell-0.1.0/src/relay_shell/errors.py +27 -0
- relay_shell-0.1.0/src/relay_shell/inventory.py +173 -0
- relay_shell-0.1.0/src/relay_shell/metrics.py +134 -0
- relay_shell-0.1.0/src/relay_shell/patterns.py +190 -0
- relay_shell-0.1.0/src/relay_shell/policy.py +138 -0
- relay_shell-0.1.0/src/relay_shell/py.typed +0 -0
- relay_shell-0.1.0/src/relay_shell/redaction.py +66 -0
- relay_shell-0.1.0/src/relay_shell/server.py +1126 -0
- relay_shell-0.1.0/src/relay_shell/sessions.py +378 -0
- relay_shell-0.1.0/src/relay_shell/shelltools.py +157 -0
- relay_shell-0.1.0/src/relay_shell/sshpool.py +337 -0
- relay_shell-0.1.0/src/relay_shell/util.py +58 -0
- relay_shell-0.1.0/src/relay_shell/verifier.py +198 -0
- relay_shell-0.1.0/tests/conftest.py +33 -0
- relay_shell-0.1.0/tests/test_audit.py +213 -0
- relay_shell-0.1.0/tests/test_audit_tail_tool.py +104 -0
- relay_shell-0.1.0/tests/test_config.py +42 -0
- relay_shell-0.1.0/tests/test_errors.py +23 -0
- relay_shell-0.1.0/tests/test_fuzz.py +272 -0
- relay_shell-0.1.0/tests/test_inventory.py +61 -0
- relay_shell-0.1.0/tests/test_main.py +103 -0
- relay_shell-0.1.0/tests/test_metrics.py +166 -0
- relay_shell-0.1.0/tests/test_oauth.py +210 -0
- relay_shell-0.1.0/tests/test_patterns.py +277 -0
- relay_shell-0.1.0/tests/test_policy.py +57 -0
- relay_shell-0.1.0/tests/test_redaction.py +131 -0
- relay_shell-0.1.0/tests/test_resources.py +236 -0
- relay_shell-0.1.0/tests/test_server.py +42 -0
- relay_shell-0.1.0/tests/test_sessions.py +68 -0
- relay_shell-0.1.0/tests/test_shell.py +83 -0
- relay_shell-0.1.0/tests/test_ssh_fanout_tool.py +385 -0
- relay_shell-0.1.0/tests/test_ssh_integration.py +449 -0
- relay_shell-0.1.0/tests/test_ssh_keyscan_tool.py +193 -0
- relay_shell-0.1.0/tests/test_stdio_e2e.py +60 -0
- relay_shell-0.1.0/tests/test_tool_wrappers.py +269 -0
- relay_shell-0.1.0/tests/test_util.py +44 -0
- relay_shell-0.1.0/tests/test_verifier.py +196 -0
|
@@ -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
|