relay-shell 0.1.0__tar.gz → 0.3.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 (178) hide show
  1. relay_shell-0.3.0/.claude/settings.json +45 -0
  2. relay_shell-0.3.0/.env.example +107 -0
  3. relay_shell-0.3.0/.github/CODEOWNERS +48 -0
  4. {relay_shell-0.1.0 → relay_shell-0.3.0}/.github/PULL_REQUEST_TEMPLATE.md +5 -4
  5. {relay_shell-0.1.0 → relay_shell-0.3.0}/.github/workflows/ci.yml +5 -2
  6. {relay_shell-0.1.0 → relay_shell-0.3.0}/.github/workflows/codeql.yml +4 -4
  7. relay_shell-0.3.0/.github/workflows/dependency-review.yml +26 -0
  8. relay_shell-0.3.0/.github/workflows/docs.yml +64 -0
  9. relay_shell-0.3.0/.github/workflows/gitleaks.yml +73 -0
  10. {relay_shell-0.1.0 → relay_shell-0.3.0}/.github/workflows/nightly-fuzz.yml +3 -3
  11. relay_shell-0.3.0/.github/workflows/pip-audit.yml +53 -0
  12. {relay_shell-0.1.0 → relay_shell-0.3.0}/.github/workflows/release.yml +32 -8
  13. {relay_shell-0.1.0 → relay_shell-0.3.0}/.github/workflows/sbom.yml +51 -17
  14. {relay_shell-0.1.0 → relay_shell-0.3.0}/.gitignore +3 -0
  15. relay_shell-0.3.0/.gitleaks.toml +27 -0
  16. {relay_shell-0.1.0 → relay_shell-0.3.0}/.hypothesis/constants/060c4657af60692d +1 -1
  17. relay_shell-0.3.0/.hypothesis/constants/06d9884c28b2a63f +4 -0
  18. relay_shell-0.3.0/.hypothesis/constants/09f931d5fb70626a +4 -0
  19. relay_shell-0.1.0/.hypothesis/constants/8e009fed5620e2e8 → relay_shell-0.3.0/.hypothesis/constants/0f0449cb9acd7923 +2 -2
  20. relay_shell-0.1.0/.hypothesis/constants/e948b6f3cf68583c → relay_shell-0.3.0/.hypothesis/constants/0fbd7313047d9e52 +2 -2
  21. relay_shell-0.1.0/.hypothesis/constants/8b6000d1862c95be → relay_shell-0.3.0/.hypothesis/constants/29d0bc1435e72910 +1 -1
  22. relay_shell-0.1.0/.hypothesis/constants/bbb3cb72c6a57ed5 → relay_shell-0.3.0/.hypothesis/constants/29ed694dbb01fab5 +2 -2
  23. relay_shell-0.3.0/.hypothesis/constants/31cd5feb5b48ec66 +4 -0
  24. relay_shell-0.1.0/.hypothesis/constants/ced86f8de2d92b27 → relay_shell-0.3.0/.hypothesis/constants/4017e2a6f3ee7cfd +2 -2
  25. relay_shell-0.3.0/.hypothesis/constants/877af75780422922 +4 -0
  26. relay_shell-0.3.0/.hypothesis/constants/8967c8193f2aab55 +4 -0
  27. relay_shell-0.3.0/.hypothesis/constants/95970940d76c226a +4 -0
  28. relay_shell-0.1.0/.hypothesis/constants/2f71ce4aa5de0b48 → relay_shell-0.3.0/.hypothesis/constants/a5fe03a1c421948b +1 -1
  29. relay_shell-0.3.0/.hypothesis/constants/c7900d7ab9e5fea7 +4 -0
  30. {relay_shell-0.1.0 → relay_shell-0.3.0}/.hypothesis/constants/d78536b0c96f72a7 +1 -1
  31. relay_shell-0.3.0/.hypothesis/constants/d8887ef76582104d +4 -0
  32. relay_shell-0.3.0/.hypothesis/constants/e562d9f2652151f0 +4 -0
  33. relay_shell-0.3.0/.hypothesis/constants/f4684eb785e4fb5b +4 -0
  34. relay_shell-0.3.0/.hypothesis/constants/f7f625a7f9fbf8e6 +4 -0
  35. relay_shell-0.1.0/.hypothesis/constants/aeca4f1f08b784e6 → relay_shell-0.3.0/.hypothesis/constants/f947d359e3d0041d +1 -1
  36. relay_shell-0.3.0/.hypothesis/unicode_data/16.0.0/charmap.json.gz +0 -0
  37. {relay_shell-0.1.0 → relay_shell-0.3.0}/.pre-commit-config.yaml +2 -2
  38. {relay_shell-0.1.0 → relay_shell-0.3.0}/AGENTS.md +3 -1
  39. relay_shell-0.3.0/BACKLOG.md +237 -0
  40. relay_shell-0.3.0/CHANGELOG.md +1231 -0
  41. {relay_shell-0.1.0 → relay_shell-0.3.0}/CLAUDE.md +3 -1
  42. {relay_shell-0.1.0 → relay_shell-0.3.0}/CONTRIBUTING.md +8 -5
  43. relay_shell-0.1.0/README.md → relay_shell-0.3.0/PKG-INFO +80 -8
  44. relay_shell-0.1.0/PKG-INFO → relay_shell-0.3.0/README.md +36 -50
  45. {relay_shell-0.1.0 → relay_shell-0.3.0}/SECURITY.md +62 -4
  46. relay_shell-0.3.0/audit/00-inventory.md +142 -0
  47. relay_shell-0.3.0/audit/01-baseline.md +94 -0
  48. relay_shell-0.3.0/audit/02-security-findings.md +266 -0
  49. relay_shell-0.3.0/audit/03-final-report.md +121 -0
  50. relay_shell-0.3.0/audit/2026-05-27-engagement.md +296 -0
  51. relay_shell-0.3.0/audit/2026-06-01-engagement.md +215 -0
  52. relay_shell-0.3.0/audit/2026-06-21-adversarial-engagement.md +140 -0
  53. relay_shell-0.3.0/audit/2026-06-21-engagement.md +215 -0
  54. relay_shell-0.3.0/audit/2026-07-15-adversarial-engagement.md +72 -0
  55. relay_shell-0.3.0/audit/2026-07-15-engagement.md +190 -0
  56. relay_shell-0.3.0/audit/2026-07-15-release-v0.3.0.md +133 -0
  57. {relay_shell-0.1.0 → relay_shell-0.3.0}/deploy/Caddyfile +17 -2
  58. {relay_shell-0.1.0 → relay_shell-0.3.0}/deploy/install-edge.sh +32 -3
  59. {relay_shell-0.1.0 → relay_shell-0.3.0}/deploy/install.sh +5 -1
  60. relay_shell-0.3.0/docs/_config.yml +29 -0
  61. {relay_shell-0.1.0 → relay_shell-0.3.0}/docs/adr/0001-runtime-and-sdk.md +12 -4
  62. relay_shell-0.3.0/docs/adr/0005-codebase-validation.md +319 -0
  63. {relay_shell-0.1.0 → relay_shell-0.3.0}/docs/adr/0006-seccomp-notify-audit-channel.md +124 -28
  64. relay_shell-0.3.0/docs/adr/0007-audit-hash-chain.md +261 -0
  65. relay_shell-0.3.0/docs/adr/0008-operating-guidance-prompt.md +81 -0
  66. relay_shell-0.3.0/docs/adr/0009-tier3-confirmation-broker.md +220 -0
  67. relay_shell-0.3.0/docs/adr/0010-rollback-verify-broker.md +173 -0
  68. relay_shell-0.3.0/docs/adr/0011-windows-openssh-powershell.md +199 -0
  69. relay_shell-0.3.0/docs/adr/README.md +73 -0
  70. {relay_shell-0.1.0 → relay_shell-0.3.0}/docs/architecture.md +37 -9
  71. {relay_shell-0.1.0 → relay_shell-0.3.0}/docs/audit-shipper.md +1 -1
  72. relay_shell-0.3.0/docs/auth.md +177 -0
  73. relay_shell-0.3.0/docs/deployment.md +603 -0
  74. relay_shell-0.3.0/docs/index.md +37 -0
  75. {relay_shell-0.1.0 → relay_shell-0.3.0}/docs/runbook.md +445 -73
  76. {relay_shell-0.1.0 → relay_shell-0.3.0}/docs/tools.md +103 -11
  77. relay_shell-0.3.0/mkdocs.yml +106 -0
  78. {relay_shell-0.1.0 → relay_shell-0.3.0}/pyproject.toml +11 -8
  79. relay_shell-0.3.0/renovate.json5 +35 -0
  80. relay_shell-0.3.0/requirements-docs.txt +5 -0
  81. {relay_shell-0.1.0 → relay_shell-0.3.0}/requirements.txt +10 -10
  82. {relay_shell-0.1.0 → relay_shell-0.3.0}/src/relay_shell/__init__.py +1 -1
  83. {relay_shell-0.1.0 → relay_shell-0.3.0}/src/relay_shell/__main__.py +161 -11
  84. relay_shell-0.3.0/src/relay_shell/audit.py +563 -0
  85. relay_shell-0.3.0/src/relay_shell/auth/oauth.py +416 -0
  86. relay_shell-0.3.0/src/relay_shell/broker.py +189 -0
  87. relay_shell-0.3.0/src/relay_shell/config.py +159 -0
  88. {relay_shell-0.1.0 → relay_shell-0.3.0}/src/relay_shell/errors.py +5 -1
  89. {relay_shell-0.1.0 → relay_shell-0.3.0}/src/relay_shell/inventory.py +13 -3
  90. {relay_shell-0.1.0 → relay_shell-0.3.0}/src/relay_shell/metrics.py +32 -2
  91. relay_shell-0.3.0/src/relay_shell/patterns.py +483 -0
  92. {relay_shell-0.1.0 → relay_shell-0.3.0}/src/relay_shell/policy.py +8 -0
  93. relay_shell-0.3.0/src/relay_shell/redaction.py +131 -0
  94. relay_shell-0.3.0/src/relay_shell/seccomp.py +806 -0
  95. relay_shell-0.3.0/src/relay_shell/server.py +1906 -0
  96. {relay_shell-0.1.0 → relay_shell-0.3.0}/src/relay_shell/sessions.py +84 -26
  97. {relay_shell-0.1.0 → relay_shell-0.3.0}/src/relay_shell/shelltools.py +42 -22
  98. relay_shell-0.3.0/src/relay_shell/sshpool.py +638 -0
  99. {relay_shell-0.1.0 → relay_shell-0.3.0}/src/relay_shell/verifier.py +17 -2
  100. relay_shell-0.3.0/tests/test_audit.py +526 -0
  101. {relay_shell-0.1.0 → relay_shell-0.3.0}/tests/test_audit_tail_tool.py +68 -0
  102. relay_shell-0.3.0/tests/test_broker.py +302 -0
  103. relay_shell-0.3.0/tests/test_config.py +105 -0
  104. relay_shell-0.3.0/tests/test_main.py +205 -0
  105. {relay_shell-0.1.0 → relay_shell-0.3.0}/tests/test_metrics.py +46 -11
  106. relay_shell-0.3.0/tests/test_oauth.py +551 -0
  107. relay_shell-0.3.0/tests/test_patterns.py +668 -0
  108. {relay_shell-0.1.0 → relay_shell-0.3.0}/tests/test_policy.py +37 -0
  109. relay_shell-0.3.0/tests/test_prompts.py +81 -0
  110. relay_shell-0.3.0/tests/test_redaction.py +369 -0
  111. relay_shell-0.3.0/tests/test_seccomp.py +784 -0
  112. relay_shell-0.3.0/tests/test_server.py +184 -0
  113. relay_shell-0.3.0/tests/test_sessions.py +241 -0
  114. {relay_shell-0.1.0 → relay_shell-0.3.0}/tests/test_ssh_integration.py +221 -0
  115. {relay_shell-0.1.0 → relay_shell-0.3.0}/tests/test_ssh_keyscan_tool.py +134 -0
  116. relay_shell-0.3.0/tests/test_sshpool_unit.py +561 -0
  117. {relay_shell-0.1.0 → relay_shell-0.3.0}/tests/test_stdio_e2e.py +1 -1
  118. relay_shell-0.3.0/tests/test_tool_wrappers.py +605 -0
  119. relay_shell-0.3.0/tests/test_util.py +118 -0
  120. {relay_shell-0.1.0 → relay_shell-0.3.0}/tests/test_verifier.py +68 -0
  121. relay_shell-0.1.0/.claude/settings.json +0 -10
  122. relay_shell-0.1.0/.env.example +0 -63
  123. relay_shell-0.1.0/.github/dependabot.yml +0 -16
  124. relay_shell-0.1.0/.github/workflows/dependency-review.yml +0 -24
  125. relay_shell-0.1.0/.hypothesis/constants/1a9a2338dd557cf6 +0 -4
  126. relay_shell-0.1.0/.hypothesis/constants/531d010a93279f64 +0 -4
  127. relay_shell-0.1.0/.hypothesis/constants/654e10b2a716d6c4 +0 -4
  128. relay_shell-0.1.0/.hypothesis/constants/7d76440294249bc2 +0 -4
  129. relay_shell-0.1.0/.hypothesis/constants/828c112bd3d46feb +0 -4
  130. relay_shell-0.1.0/.hypothesis/constants/bef274fdff8cb14f +0 -4
  131. relay_shell-0.1.0/.hypothesis/constants/d3969dbe54c370b6 +0 -4
  132. relay_shell-0.1.0/.hypothesis/constants/edc2ee49885c8565 +0 -4
  133. relay_shell-0.1.0/.hypothesis/constants/f2bec7ffccba40ef +0 -4
  134. relay_shell-0.1.0/CHANGELOG.md +0 -350
  135. relay_shell-0.1.0/docs/adr/0005-codebase-validation.md +0 -127
  136. relay_shell-0.1.0/docs/adr/README.md +0 -68
  137. relay_shell-0.1.0/docs/deployment.md +0 -335
  138. relay_shell-0.1.0/src/relay_shell/audit.py +0 -199
  139. relay_shell-0.1.0/src/relay_shell/auth/oauth.py +0 -295
  140. relay_shell-0.1.0/src/relay_shell/config.py +0 -109
  141. relay_shell-0.1.0/src/relay_shell/patterns.py +0 -190
  142. relay_shell-0.1.0/src/relay_shell/redaction.py +0 -66
  143. relay_shell-0.1.0/src/relay_shell/server.py +0 -1126
  144. relay_shell-0.1.0/src/relay_shell/sshpool.py +0 -337
  145. relay_shell-0.1.0/tests/test_audit.py +0 -213
  146. relay_shell-0.1.0/tests/test_config.py +0 -42
  147. relay_shell-0.1.0/tests/test_main.py +0 -103
  148. relay_shell-0.1.0/tests/test_oauth.py +0 -210
  149. relay_shell-0.1.0/tests/test_patterns.py +0 -277
  150. relay_shell-0.1.0/tests/test_redaction.py +0 -131
  151. relay_shell-0.1.0/tests/test_server.py +0 -42
  152. relay_shell-0.1.0/tests/test_sessions.py +0 -68
  153. relay_shell-0.1.0/tests/test_tool_wrappers.py +0 -269
  154. relay_shell-0.1.0/tests/test_util.py +0 -44
  155. {relay_shell-0.1.0 → relay_shell-0.3.0}/.github/ISSUE_TEMPLATE/bug.md +0 -0
  156. {relay_shell-0.1.0 → relay_shell-0.3.0}/.github/ISSUE_TEMPLATE/feature.md +0 -0
  157. {relay_shell-0.1.0 → relay_shell-0.3.0}/.github/ISSUE_TEMPLATE/security.md +0 -0
  158. {relay_shell-0.1.0 → relay_shell-0.3.0}/.hypothesis/.gitignore +0 -0
  159. {relay_shell-0.1.0 → relay_shell-0.3.0}/CODE_OF_CONDUCT.md +0 -0
  160. {relay_shell-0.1.0 → relay_shell-0.3.0}/LICENSE +0 -0
  161. {relay_shell-0.1.0 → relay_shell-0.3.0}/NOTICE +0 -0
  162. {relay_shell-0.1.0 → relay_shell-0.3.0}/deploy/logrotate/relay-shell +0 -0
  163. {relay_shell-0.1.0 → relay_shell-0.3.0}/deploy/systemd/relay-shell.service +0 -0
  164. {relay_shell-0.1.0 → relay_shell-0.3.0}/deploy/systemd/relay-shell.service.d/hardening.conf +0 -0
  165. {relay_shell-0.1.0 → relay_shell-0.3.0}/docs/adr/0002-no-sandbox-full-access.md +0 -0
  166. {relay_shell-0.1.0 → relay_shell-0.3.0}/docs/adr/0003-tiered-authority.md +0 -0
  167. {relay_shell-0.1.0 → relay_shell-0.3.0}/docs/adr/0004-edge-tls-automation.md +0 -0
  168. {relay_shell-0.1.0 → relay_shell-0.3.0}/scripts/healthcheck.sh +0 -0
  169. {relay_shell-0.1.0 → relay_shell-0.3.0}/src/relay_shell/auth/__init__.py +0 -0
  170. {relay_shell-0.1.0 → relay_shell-0.3.0}/src/relay_shell/py.typed +0 -0
  171. {relay_shell-0.1.0 → relay_shell-0.3.0}/src/relay_shell/util.py +0 -0
  172. {relay_shell-0.1.0 → relay_shell-0.3.0}/tests/conftest.py +0 -0
  173. {relay_shell-0.1.0 → relay_shell-0.3.0}/tests/test_errors.py +0 -0
  174. {relay_shell-0.1.0 → relay_shell-0.3.0}/tests/test_fuzz.py +0 -0
  175. {relay_shell-0.1.0 → relay_shell-0.3.0}/tests/test_inventory.py +0 -0
  176. {relay_shell-0.1.0 → relay_shell-0.3.0}/tests/test_resources.py +0 -0
  177. {relay_shell-0.1.0 → relay_shell-0.3.0}/tests/test_shell.py +0 -0
  178. {relay_shell-0.1.0 → relay_shell-0.3.0}/tests/test_ssh_fanout_tool.py +0 -0
@@ -0,0 +1,45 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/claude-code-settings.json",
3
+ "permissions": {
4
+ "allow": [
5
+ "Bash(uv:*)",
6
+ "Bash(uvx:*)",
7
+ "Bash(ruff:*)",
8
+ "Bash(python:*)",
9
+ "Bash(python3:*)",
10
+ "Bash(pytest:*)",
11
+ "Bash(pre-commit run:*)",
12
+ "Bash(pip list)",
13
+ "Bash(pip list:*)",
14
+ "Bash(pip show:*)",
15
+ "Bash(pip freeze)",
16
+ "Bash(pipx list)",
17
+ "Bash(pipx run:*)",
18
+ "Bash(mypy:*)",
19
+ "Bash(just:*)",
20
+ "Bash(bats:*)",
21
+ "Bash(bash -n:*)",
22
+ "Bash(bash --version)"
23
+ ],
24
+ "deny": [
25
+ "Read(./.env)",
26
+ "Read(./.env.*)",
27
+ "Read(**/.env)",
28
+ "Read(**/.env.*)",
29
+ "Read(**/secrets/**)",
30
+ "Read(~/.ssh/**)",
31
+ "Read(~/.aws/**)",
32
+ "Read(~/.config/gh/**)",
33
+ "Read(**/*.pem)",
34
+ "Read(**/id_rsa*)",
35
+ "Bash(curl:*)",
36
+ "Bash(wget:*)",
37
+ "Bash(git push --force)",
38
+ "Bash(git push --force:*)",
39
+ "Bash(git push --force-with-lease)",
40
+ "Bash(git push --force-with-lease:*)",
41
+ "Bash(git push -f)",
42
+ "Bash(git push -f:*)"
43
+ ]
44
+ }
45
+ }
@@ -0,0 +1,107 @@
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
+ # --- Tier-3 confirmation broker (ADR 0009) ---
29
+ # Opt-in, default off. When true, a Tier-3 (IRREVERSIBLE) command that already
30
+ # passed the deny list and mode check is not executed on first request: the
31
+ # runner returns a single-use, TTL-bounded token (audited action=confirm_plan,
32
+ # no side effect) and the caller must arm it via the operation_confirm tool,
33
+ # then re-issue the exact same call (audited action=confirm_execute). This is an
34
+ # added safeguard, never a bypass — the deny list and mode policy still run
35
+ # first. Default off keeps the audit record byte-identical.
36
+ RELAY_SHELL_CONFIRM_TIER3=false
37
+ # How long a minted confirmation token stays valid before it must be re-planned.
38
+ # Seconds, 5..3600.
39
+ RELAY_SHELL_CONFIRM_TTL=120
40
+
41
+ # --- Audit ---
42
+ RELAY_SHELL_AUDIT_PATH=/var/log/relay-shell/audit.jsonl
43
+ RELAY_SHELL_AUDIT_STDERR=false
44
+ # jsonl | cef | leef
45
+ RELAY_SHELL_AUDIT_FORMAT=jsonl
46
+ # Tamper-evident audit (ADR 0007). When true, each record carries seq/prev/chain
47
+ # so an edit, insertion, reorder, or interior deletion of the on-disk log is
48
+ # detectable with `relay-shell --verify-audit` (head-truncation via
49
+ # --require-genesis; tail-truncation and cross-file continuity are the off-host
50
+ # shipper's job). Default off keeps the record byte-identical. Requires
51
+ # RELAY_SHELL_AUDIT_FORMAT=jsonl. Enable on a freshly rotated log for a clean chain.
52
+ RELAY_SHELL_AUDIT_CHAIN=false
53
+
54
+ # --- Syscall-level audit channel (ADR 0006) ---
55
+ # Opt-in, audit-only seccomp-notify channel. When true, locally-spawned
56
+ # children - one-shot (shell_exec / shell_script / ssh_keyscan) and
57
+ # shell_spawn PTY sessions (the filter rides the session child for the
58
+ # session's life) - get a seccomp-bpf USER_NOTIF filter and one
59
+ # `syscall_notify` audit line is appended per observed syscall (execve,
60
+ # set-uid/namespace/mount changes, write-opens, privilege-relevant prctls).
61
+ # It NEVER blocks a syscall. It activates only when the process holds
62
+ # CAP_SYS_ADMIN (so set-uid/sudo behaviour is preserved verbatim — no
63
+ # no_new_privs is ever latched) on Linux/x86_64 with kernel >= 5.5;
64
+ # otherwise it cleanly no-ops (server_info.seccomp reports why). Default off
65
+ # keeps the spawn path byte-identical.
66
+ RELAY_SHELL_SECCOMP_NOTIFY=false
67
+ # Event cap per spawned child (per call for one-shot tools, per session
68
+ # for shell_spawn). Beyond it, one `syscall_notify_overflow` line is
69
+ # written and emission stops for that child (it still runs to
70
+ # completion). 1..65536.
71
+ RELAY_SHELL_SECCOMP_NOTIFY_CAP=256
72
+
73
+ # --- SSH ---
74
+ RELAY_SHELL_SSH_CONFIG=~/.ssh/config
75
+ RELAY_SHELL_INVENTORY=
76
+ # strict | accept-new | ignore
77
+ RELAY_SHELL_SSH_KNOWN_HOSTS=accept-new
78
+ RELAY_SHELL_SSH_CONNECT_TIMEOUT=10
79
+ RELAY_SHELL_SSH_KEEPALIVE=30
80
+ # Idle eviction for cached SSH connections (seconds). The pool consults
81
+ # this on every connect() and drops any entry that has not been used in
82
+ # the last N seconds. 0 disables the reaper (a closed connection is
83
+ # still evicted opportunistically).
84
+ RELAY_SHELL_SSH_IDLE_TIMEOUT=1800
85
+
86
+ # --- Edge TLS (consumed by deploy/install-edge.sh and the Caddyfile) ---
87
+ # Required to issue a public Let's Encrypt certificate:
88
+ RELAY_SHELL_EDGE_DOMAIN=
89
+ RELAY_SHELL_EDGE_ACME_EMAIL=
90
+ # Space-separated source allowlist for tool traffic and /token. Defaults to
91
+ # loopback only, which blocks remote clients until you set this. The value
92
+ # is quoted so it is safe both as a systemd EnvironmentFile entry and as a
93
+ # bash-sourced assignment.
94
+ RELAY_SHELL_EDGE_CLIENT_CIDRS="127.0.0.1/8 ::1"
95
+ # Override only if relay-shell binds a non-default loopback host:port.
96
+ RELAY_SHELL_EDGE_UPSTREAM=127.0.0.1:8080
97
+ # Set to https://acme-staging-v02.api.letsencrypt.org/directory for dry runs.
98
+ RELAY_SHELL_EDGE_ACME_CA=https://acme-v02.api.letsencrypt.org/directory
99
+
100
+ # --- OAuth 2.1 (HTTP transport only; requires the [http] extra) ---
101
+ RELAY_SHELL_AUTH_ENABLED=false
102
+ RELAY_SHELL_AUTH_ISSUER=https://localhost:8080
103
+ RELAY_SHELL_AUTH_STATE_DIR=/var/lib/relay-shell/oauth
104
+ RELAY_SHELL_AUTH_SINGLE_CLIENT=true
105
+ RELAY_SHELL_AUTH_ACCESS_TTL=3600
106
+ RELAY_SHELL_AUTH_REFRESH_TTL=2592000
107
+ RELAY_SHELL_AUTH_CODE_TTL=300
@@ -0,0 +1,48 @@
1
+ # CODEOWNERS for relay-shell.
2
+ #
3
+ # These rules auto-request review from the listed owner when matching
4
+ # paths change. Without branch protection requiring CODEOWNERS review
5
+ # (Settings -> Branches on github.com), these requests are advisory.
6
+ #
7
+ # The trust-boundary list mirrors docs/runbook.md §3.3; keep the two in
8
+ # sync when adding or removing a security-sensitive surface.
9
+
10
+ # Default owner: everything routes here unless a more specific rule below wins.
11
+ * @rmednitzer
12
+
13
+ # Trust boundary (runbook §3.3): policy admission, audit pipeline,
14
+ # secret redaction, the central Relay.run() body, and the auth provider.
15
+ /src/relay_shell/audit.py @rmednitzer
16
+ /src/relay_shell/redaction.py @rmednitzer
17
+ /src/relay_shell/policy.py @rmednitzer
18
+ /src/relay_shell/patterns.py @rmednitzer
19
+ /src/relay_shell/server.py @rmednitzer
20
+ /src/relay_shell/metrics.py @rmednitzer
21
+ /src/relay_shell/verifier.py @rmednitzer
22
+ /src/relay_shell/auth/ @rmednitzer
23
+
24
+ # Deploy + edge: systemd unit, hardening drop-in, Caddyfile (CIDR + headers),
25
+ # installers that write EnvironmentFiles. Any change here lands a
26
+ # privileged-execution artifact on a host.
27
+ /deploy/install.sh @rmednitzer
28
+ /deploy/install-edge.sh @rmednitzer
29
+ /deploy/Caddyfile @rmednitzer
30
+ /deploy/systemd/ @rmednitzer
31
+ /deploy/logrotate/ @rmednitzer
32
+
33
+ # CI / supply chain: workflow definitions, Renovate config, release
34
+ # automation, SBOM generation.
35
+ /.github/ @rmednitzer
36
+ /.github/workflows/ @rmednitzer
37
+ /renovate.json5 @rmednitzer
38
+ /.github/CODEOWNERS @rmednitzer
39
+
40
+ # Project + governance manifests.
41
+ /pyproject.toml @rmednitzer
42
+ /requirements.txt @rmednitzer
43
+ /SECURITY.md @rmednitzer
44
+ /CODE_OF_CONDUCT.md @rmednitzer
45
+ /CONTRIBUTING.md @rmednitzer
46
+ /AGENTS.md @rmednitzer
47
+ /CLAUDE.md @rmednitzer
48
+ /docs/adr/ @rmednitzer
@@ -30,7 +30,7 @@ backlog item if applicable: "Closes B-007 from docs/runbook.md". -->
30
30
  ## Runbook §3.1 checklist
31
31
 
32
32
  - [ ] CI is green on the PR head commit (lint, type-check, tests,
33
- CodeQL, dependency-review).
33
+ CodeQL, dependency-review, pip-audit, gitleaks).
34
34
  - [ ] No new file is undocumented in `docs/architecture.md` module
35
35
  table.
36
36
  - [ ] No new tool is missing from `docs/tools.md`,
@@ -43,9 +43,10 @@ backlog item if applicable: "Closes B-007 from docs/runbook.md". -->
43
43
 
44
44
  ## Security-sensitive diff (runbook §3.3)
45
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`.
46
+ Tick if this PR touches any of: `patterns.py`, `audit.py`, `redaction.py`,
47
+ `policy.py`, the `Relay.run()` body or a resource handler in `server.py`,
48
+ `auth/oauth.py`, `metrics.py`, `seccomp.py`, `deploy/install*.sh`, or
49
+ `deploy/Caddyfile`. (Full list with rationale: runbook §3.3.)
49
50
 
50
51
  - [ ] This PR is security-sensitive.
51
52
 
@@ -5,6 +5,9 @@ on:
5
5
  branches: [main]
6
6
  pull_request:
7
7
 
8
+ permissions:
9
+ contents: read
10
+
8
11
  jobs:
9
12
  check:
10
13
  permissions:
@@ -21,9 +24,9 @@ jobs:
21
24
  python-version: ["3.12", "3.13", "3.14"]
22
25
  name: check (py${{ matrix.python-version }})
23
26
  steps:
24
- - uses: actions/checkout@v6
27
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
25
28
 
26
- - uses: actions/setup-python@v6
29
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
27
30
  with:
28
31
  python-version: ${{ matrix.python-version }}
29
32
  cache: "pip"
@@ -24,15 +24,15 @@ jobs:
24
24
 
25
25
  steps:
26
26
  - name: Checkout
27
- uses: actions/checkout@v6
27
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
28
28
 
29
29
  - name: Initialize CodeQL
30
- uses: github/codeql-action/init@v4
30
+ uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
31
31
  with:
32
32
  languages: ${{ matrix.language }}
33
33
 
34
34
  - name: Autobuild
35
- uses: github/codeql-action/autobuild@v4
35
+ uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
36
36
 
37
37
  - name: Perform CodeQL Analysis
38
- uses: github/codeql-action/analyze@v4
38
+ uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
@@ -0,0 +1,26 @@
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
+ # No checkout step, deliberately (audit finding SEC-2). At the pinned
15
+ # SHA the action is API-only: for `pull_request` events it takes the
16
+ # base/head SHAs from the event payload (src/git-refs.ts) and calls the
17
+ # Dependency Graph compare API (src/main.ts); it spawns no git process
18
+ # and never reads the working tree unless a local `config-file` input
19
+ # is used (we pass none). Verified against the action source at this
20
+ # SHA, 2026-06-12. Dropping the checkout removes the persisted-token
21
+ # surface a checkout would add and the repo bytes on disk. If a future
22
+ # bump starts needing the workspace, this job fails loudly on the PR
23
+ # that bumps it; restore a checkout WITHOUT `persist-credentials: true`
24
+ # and re-verify, rather than reverting to the credential helper.
25
+ - name: Dependency review
26
+ uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
@@ -0,0 +1,64 @@
1
+ name: docs
2
+
3
+ # Build the MkDocs Material documentation site from docs/ and publish it to
4
+ # GitHub Pages. Requires a one-time repo setting:
5
+ # Settings -> Pages -> Build and deployment -> Source: "GitHub Actions"
6
+ # (this replaces the former Jekyll "Deploy from a branch" source).
7
+
8
+ on:
9
+ push:
10
+ branches: [main]
11
+ paths:
12
+ - "docs/**"
13
+ - "mkdocs.yml"
14
+ - "requirements-docs.txt"
15
+ - ".github/workflows/docs.yml"
16
+ workflow_dispatch:
17
+
18
+ # Least-privilege default; the deploy job adds only the Pages-specific scopes.
19
+ permissions:
20
+ contents: read
21
+
22
+ # One Pages deployment at a time; let an in-flight publish finish.
23
+ concurrency:
24
+ group: pages
25
+ cancel-in-progress: false
26
+
27
+ jobs:
28
+ build:
29
+ name: Build (MkDocs Material)
30
+ runs-on: ubuntu-latest
31
+ timeout-minutes: 10
32
+ steps:
33
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
34
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
35
+ with:
36
+ python-version: "3.14"
37
+ cache: pip
38
+ cache-dependency-path: requirements-docs.txt
39
+ - name: Install MkDocs Material
40
+ run: |
41
+ python -m pip install --upgrade pip
42
+ pip install -r requirements-docs.txt
43
+ - name: Build site (strict)
44
+ run: mkdocs build --strict
45
+ - name: Upload Pages artifact
46
+ uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5
47
+ with:
48
+ path: site
49
+
50
+ deploy:
51
+ name: Deploy to GitHub Pages
52
+ needs: build
53
+ runs-on: ubuntu-latest
54
+ timeout-minutes: 10
55
+ permissions:
56
+ pages: write # publish the Pages deployment
57
+ id-token: write # OIDC token required by deploy-pages
58
+ environment:
59
+ name: github-pages
60
+ url: ${{ steps.deployment.outputs.page_url }}
61
+ steps:
62
+ - name: Deploy
63
+ id: deployment
64
+ uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5
@@ -0,0 +1,73 @@
1
+ name: gitleaks
2
+
3
+ # Secret scan of the working tree and full git history on every PR + push to
4
+ # main, plus a daily rerun so a credential committed between releases surfaces
5
+ # within 24h. This repository is a redaction tool and deliberately ships
6
+ # *synthetic* secret-shaped fixtures (tests, the runbook redaction sample, the
7
+ # audit evidence); `.gitleaks.toml` allowlists exactly those documented paths,
8
+ # so a finding here means a real secret reached a non-fixture location (e.g.
9
+ # under `src/`). See `.gitleaks.toml` and audit finding S-004/TOOL-1.
10
+
11
+ on:
12
+ push:
13
+ branches: [main]
14
+ pull_request:
15
+ schedule:
16
+ # 04:47 UTC daily. The odd minute mirrors nightly-fuzz / pip-audit so the
17
+ # security signals cluster together in the activity timeline.
18
+ - cron: "47 4 * * *"
19
+ workflow_dispatch:
20
+
21
+ permissions:
22
+ contents: read
23
+
24
+ jobs:
25
+ scan:
26
+ name: gitleaks (secret scan)
27
+ runs-on: ubuntu-latest
28
+ timeout-minutes: 10
29
+ steps:
30
+ - name: Checkout (full history so the scan covers every commit)
31
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
32
+ with:
33
+ fetch-depth: 0
34
+
35
+ - name: Install gitleaks (pinned, checksum-verified)
36
+ env:
37
+ # Bump together with any local-tooling expectation; gitleaks is
38
+ # feature-complete, so updates are security patches only.
39
+ GITLEAKS_VERSION: "8.30.1"
40
+ run: |
41
+ set -euo pipefail
42
+ base="https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}"
43
+ # Download the release's own checksums file, then discover the exact
44
+ # linux x64/amd64 tarball name from it (the arch token has changed
45
+ # across gitleaks releases). This avoids hardcoding a checksum here
46
+ # and fails loudly on any name/integrity mismatch.
47
+ curl -fsSL -o checksums.txt "${base}/gitleaks_${GITLEAKS_VERSION}_checksums.txt"
48
+ tarball="$(grep -oE "gitleaks_${GITLEAKS_VERSION}_linux_(x64|amd64)\.tar\.gz" checksums.txt | head -1)"
49
+ if [ -z "${tarball}" ]; then
50
+ echo "could not find a linux x64/amd64 asset in the checksums file" >&2
51
+ cat checksums.txt >&2
52
+ exit 1
53
+ fi
54
+ curl -fsSL -o "${tarball}" "${base}/${tarball}"
55
+ # Verify the downloaded tarball against the exact line in the
56
+ # checksums file (field 2 == filename) before extracting anything.
57
+ awk -v f="${tarball}" '$2 == f' checksums.txt | sha256sum --check -
58
+ tar -xzf "${tarball}" gitleaks
59
+ install -m 0755 gitleaks /usr/local/bin/gitleaks
60
+ rm -f "${tarball}" checksums.txt gitleaks
61
+ gitleaks version
62
+
63
+ - name: Run gitleaks
64
+ run: |
65
+ # Default exit code 1 on any finding fails the job. `.gitleaks.toml`
66
+ # (config auto-discovered at the repo root, passed explicitly here
67
+ # for clarity) allowlists only the synthetic-fixture paths.
68
+ gitleaks detect \
69
+ --source . \
70
+ --config .gitleaks.toml \
71
+ --no-banner \
72
+ --redact \
73
+ --verbose
@@ -22,11 +22,11 @@ jobs:
22
22
  runs-on: ubuntu-latest
23
23
  timeout-minutes: 30
24
24
  steps:
25
- - uses: actions/checkout@v6
25
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
26
26
 
27
- - uses: actions/setup-python@v6
27
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
28
28
  with:
29
- python-version: "3.12"
29
+ python-version: "3.14"
30
30
  cache: "pip"
31
31
 
32
32
  - name: Install
@@ -0,0 +1,53 @@
1
+ name: pip-audit
2
+
3
+ # CVE scan of the resolved Python dependency tree on every PR + push to
4
+ # main, plus a daily rerun so a CVE landing against a pinned dep
5
+ # surfaces within 24h instead of waiting for the next release-time
6
+ # audit. dependency-review-action (on PR) only reviews *added*
7
+ # dependencies; pip-audit covers the whole graph.
8
+
9
+ on:
10
+ push:
11
+ branches: [main]
12
+ pull_request:
13
+ schedule:
14
+ # 04:35 UTC daily. The odd minute mirrors nightly-fuzz so the audit
15
+ # signals appear close together in the activity timeline.
16
+ - cron: "35 4 * * *"
17
+ workflow_dispatch:
18
+
19
+ permissions:
20
+ contents: read
21
+
22
+ jobs:
23
+ audit:
24
+ name: pip-audit (CVE scan)
25
+ runs-on: ubuntu-latest
26
+ timeout-minutes: 10
27
+ steps:
28
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
29
+
30
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
31
+ with:
32
+ python-version: "3.14"
33
+ cache: "pip"
34
+
35
+ - name: Install package + dev extras
36
+ run: |
37
+ python -m pip install --upgrade pip pip-audit
38
+ # Audit against the resolved dev set so transitive deps
39
+ # introduced via the optional [dev] / [http] extras are also
40
+ # covered.
41
+ pip install -e ".[dev,http]"
42
+
43
+ - name: Run pip-audit
44
+ # `--skip-editable` excludes the local editable `relay-shell` install:
45
+ # pip-audit resolves each distribution on PyPI for known CVEs, but the
46
+ # project itself is not a third-party dependency to audit, and an
47
+ # unreleased in-repo version (e.g. a release-prep bump before the tag is
48
+ # published) is not on PyPI. `--strict` is intentionally NOT combined
49
+ # with it: --strict treats the skipped editable project as an incomplete
50
+ # audit and fails ("distribution marked as editable"). Every actual
51
+ # third-party dependency (incl. the [dev]/[http] extras) is on PyPI and
52
+ # still audited, and pip-audit still exits non-zero on any CVE found.
53
+ run: pip-audit --skip-editable
@@ -66,11 +66,15 @@ jobs:
66
66
  # without this `ref` would compare the tag against whatever version
67
67
  # currently sits on the default branch and falsely reject a valid
68
68
  # signed tag.
69
- - uses: actions/checkout@v6
69
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
70
70
  with:
71
71
  ref: ${{ steps.tag.outputs.tag }}
72
72
  fetch-depth: 0
73
- persist-credentials: true
73
+ # No step after checkout uses git auth (the tag signature is
74
+ # verified via the REST API with an explicit GH_TOKEN, and the
75
+ # version check reads the working tree), so do not persist the
76
+ # token in .git/config (CI-2 finding; matches the SEC-2 posture).
77
+ persist-credentials: false
74
78
 
75
79
  - name: Assert tag matches pyproject version (at the tagged commit)
76
80
  id: version
@@ -124,14 +128,23 @@ jobs:
124
128
  needs: verify
125
129
  runs-on: ubuntu-latest
126
130
  timeout-minutes: 20
131
+ permissions:
132
+ # Sigstore OIDC + attestation publishing for the SLSA v1.2 Build
133
+ # Track L3 provenance attestation produced below. Job-level
134
+ # `permissions:` REPLACES the workflow-level `contents: read`
135
+ # default, so keep `contents: read` explicit (we don't need any
136
+ # broader write).
137
+ contents: read
138
+ id-token: write
139
+ attestations: write
127
140
  steps:
128
- - uses: actions/checkout@v6
141
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
129
142
  with:
130
143
  ref: ${{ needs.verify.outputs.release_commit }}
131
144
 
132
- - uses: actions/setup-python@v6
145
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
133
146
  with:
134
- python-version: "3.12"
147
+ python-version: "3.14"
135
148
  cache: "pip"
136
149
 
137
150
  - name: Install package + build backend
@@ -151,8 +164,19 @@ jobs:
151
164
  pip install twine
152
165
  twine check dist/*
153
166
 
167
+ - name: Generate SLSA Build Provenance attestation
168
+ # Produces a Sigstore-signed in-toto attestation linking the
169
+ # workflow run + commit SHA to every artifact under dist/.
170
+ # Closes the SLSA v1.2 Build Track L3 gap (Phase-3 cross-check):
171
+ # PyPI OIDC trusted publishing already provides L2; this step
172
+ # adds a verifier-checkable provenance record in Sigstore's
173
+ # public transparency log.
174
+ uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4
175
+ with:
176
+ subject-path: 'dist/*'
177
+
154
178
  - name: Upload artifact
155
- uses: actions/upload-artifact@v4
179
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
156
180
  with:
157
181
  name: dist-${{ needs.verify.outputs.version }}
158
182
  path: dist/
@@ -176,13 +200,13 @@ jobs:
176
200
  actions: read
177
201
  contents: read
178
202
  steps:
179
- - uses: actions/download-artifact@v4
203
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
180
204
  with:
181
205
  name: dist-${{ needs.verify.outputs.version }}
182
206
  path: dist/
183
207
 
184
208
  - name: Publish via PyPI trusted publishing
185
- uses: pypa/gh-action-pypi-publish@release/v1
209
+ uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
186
210
  with:
187
211
  # Make a workflow re-run after a successful upload idempotent
188
212
  # instead of failing on "file already exists" - the runbook