relay-shell 0.5.0__tar.gz → 0.5.2__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 (217) hide show
  1. {relay_shell-0.5.0 → relay_shell-0.5.2}/.github/workflows/ci.yml +1 -1
  2. {relay_shell-0.5.0 → relay_shell-0.5.2}/.github/workflows/codeql.yml +5 -5
  3. {relay_shell-0.5.0 → relay_shell-0.5.2}/.github/workflows/copilot-setup-steps.yml +1 -1
  4. {relay_shell-0.5.0 → relay_shell-0.5.2}/.github/workflows/docs.yml +1 -1
  5. {relay_shell-0.5.0 → relay_shell-0.5.2}/.github/workflows/nightly-fuzz.yml +1 -1
  6. {relay_shell-0.5.0 → relay_shell-0.5.2}/.github/workflows/pip-audit.yml +3 -2
  7. {relay_shell-0.5.0 → relay_shell-0.5.2}/.github/workflows/release-rehearsal.yml +4 -4
  8. {relay_shell-0.5.0 → relay_shell-0.5.2}/.github/workflows/release.yml +4 -1
  9. {relay_shell-0.5.0 → relay_shell-0.5.2}/.github/workflows/sbom.yml +1 -1
  10. {relay_shell-0.5.0 → relay_shell-0.5.2}/.pre-commit-config.yaml +1 -1
  11. {relay_shell-0.5.0 → relay_shell-0.5.2}/CHANGELOG.md +39 -1
  12. {relay_shell-0.5.0 → relay_shell-0.5.2}/PKG-INFO +3 -3
  13. {relay_shell-0.5.0 → relay_shell-0.5.2}/README.md +1 -1
  14. {relay_shell-0.5.0 → relay_shell-0.5.2}/deploy/install.sh +6 -0
  15. {relay_shell-0.5.0 → relay_shell-0.5.2}/docs/adr/0003-tiered-authority.md +23 -0
  16. {relay_shell-0.5.0 → relay_shell-0.5.2}/docs/adr/0007-audit-hash-chain.md +36 -0
  17. {relay_shell-0.5.0 → relay_shell-0.5.2}/docs/architecture.md +25 -0
  18. {relay_shell-0.5.0 → relay_shell-0.5.2}/docs/index.md +1 -1
  19. {relay_shell-0.5.0 → relay_shell-0.5.2}/docs/runbook.md +33 -0
  20. {relay_shell-0.5.0 → relay_shell-0.5.2}/pyproject.toml +2 -2
  21. {relay_shell-0.5.0 → relay_shell-0.5.2}/renovate.json5 +3 -2
  22. {relay_shell-0.5.0 → relay_shell-0.5.2}/requirements/README.md +12 -0
  23. {relay_shell-0.5.0 → relay_shell-0.5.2}/requirements/development-py3.12.txt +22 -22
  24. {relay_shell-0.5.0 → relay_shell-0.5.2}/requirements/development-py3.14.txt +22 -22
  25. relay_shell-0.5.2/requirements-bootstrap.in +1 -0
  26. relay_shell-0.5.2/requirements-bootstrap.txt +6 -0
  27. {relay_shell-0.5.0 → relay_shell-0.5.2}/requirements.txt +1 -1
  28. {relay_shell-0.5.0 → relay_shell-0.5.2}/scripts/rehearse_install.py +27 -0
  29. {relay_shell-0.5.0 → relay_shell-0.5.2}/scripts/rehearse_upgrade.py +123 -12
  30. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/__init__.py +1 -1
  31. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/audit.py +63 -28
  32. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/patterns.py +72 -26
  33. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/policy.py +10 -9
  34. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/fixtures/upgrade-v0.3/README.md +21 -3
  35. relay_shell-0.5.2/tests/fixtures/upgrade-v0.3/advisory-description-hashes.json +14 -0
  36. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/fixtures/upgrade-v0.3/baseline.json +2 -1
  37. relay_shell-0.5.2/tests/test_audit_sink.py +273 -0
  38. relay_shell-0.5.2/tests/test_classifier_scan_gates.py +101 -0
  39. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_deploy_packaging.py +36 -0
  40. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_install_rehearsal.py +21 -0
  41. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_patterns.py +2 -2
  42. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_upgrade_rehearsal.py +70 -15
  43. {relay_shell-0.5.0 → relay_shell-0.5.2}/.claude/settings.json +0 -0
  44. {relay_shell-0.5.0 → relay_shell-0.5.2}/.editorconfig +0 -0
  45. {relay_shell-0.5.0 → relay_shell-0.5.2}/.env.example +0 -0
  46. {relay_shell-0.5.0 → relay_shell-0.5.2}/.github/CODEOWNERS +0 -0
  47. {relay_shell-0.5.0 → relay_shell-0.5.2}/.github/ISSUE_TEMPLATE/bug.md +0 -0
  48. {relay_shell-0.5.0 → relay_shell-0.5.2}/.github/ISSUE_TEMPLATE/feature.md +0 -0
  49. {relay_shell-0.5.0 → relay_shell-0.5.2}/.github/ISSUE_TEMPLATE/security.md +0 -0
  50. {relay_shell-0.5.0 → relay_shell-0.5.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  51. {relay_shell-0.5.0 → relay_shell-0.5.2}/.github/copilot-instructions.md +0 -0
  52. {relay_shell-0.5.0 → relay_shell-0.5.2}/.github/workflows/ci-gate.yml +0 -0
  53. {relay_shell-0.5.0 → relay_shell-0.5.2}/.github/workflows/dependency-review.yml +0 -0
  54. {relay_shell-0.5.0 → relay_shell-0.5.2}/.github/workflows/gitleaks.yml +0 -0
  55. {relay_shell-0.5.0 → relay_shell-0.5.2}/.gitignore +0 -0
  56. {relay_shell-0.5.0 → relay_shell-0.5.2}/.gitleaks.toml +0 -0
  57. {relay_shell-0.5.0 → relay_shell-0.5.2}/AGENTS.md +0 -0
  58. {relay_shell-0.5.0 → relay_shell-0.5.2}/BACKLOG.md +0 -0
  59. {relay_shell-0.5.0 → relay_shell-0.5.2}/CLAUDE.md +0 -0
  60. {relay_shell-0.5.0 → relay_shell-0.5.2}/CODE_OF_CONDUCT.md +0 -0
  61. {relay_shell-0.5.0 → relay_shell-0.5.2}/CONTRIBUTING.md +0 -0
  62. {relay_shell-0.5.0 → relay_shell-0.5.2}/LICENSE +0 -0
  63. {relay_shell-0.5.0 → relay_shell-0.5.2}/NOTICE +0 -0
  64. {relay_shell-0.5.0 → relay_shell-0.5.2}/SECURITY.md +0 -0
  65. {relay_shell-0.5.0 → relay_shell-0.5.2}/audit/00-inventory.md +0 -0
  66. {relay_shell-0.5.0 → relay_shell-0.5.2}/audit/01-baseline.md +0 -0
  67. {relay_shell-0.5.0 → relay_shell-0.5.2}/audit/02-security-findings.md +0 -0
  68. {relay_shell-0.5.0 → relay_shell-0.5.2}/audit/03-final-report.md +0 -0
  69. {relay_shell-0.5.0 → relay_shell-0.5.2}/audit/2026-05-27-engagement.md +0 -0
  70. {relay_shell-0.5.0 → relay_shell-0.5.2}/audit/2026-06-01-engagement.md +0 -0
  71. {relay_shell-0.5.0 → relay_shell-0.5.2}/audit/2026-06-21-adversarial-engagement.md +0 -0
  72. {relay_shell-0.5.0 → relay_shell-0.5.2}/audit/2026-06-21-engagement.md +0 -0
  73. {relay_shell-0.5.0 → relay_shell-0.5.2}/audit/2026-07-15-adversarial-engagement.md +0 -0
  74. {relay_shell-0.5.0 → relay_shell-0.5.2}/audit/2026-07-15-engagement.md +0 -0
  75. {relay_shell-0.5.0 → relay_shell-0.5.2}/audit/2026-07-15-release-v0.3.0.md +0 -0
  76. {relay_shell-0.5.0 → relay_shell-0.5.2}/audit/2026-08-04-backlog-followup.md +0 -0
  77. {relay_shell-0.5.0 → relay_shell-0.5.2}/audit/2026-08-04-engagement.md +0 -0
  78. {relay_shell-0.5.0 → relay_shell-0.5.2}/audit/2026-10-14-engagement.md +0 -0
  79. {relay_shell-0.5.0 → relay_shell-0.5.2}/deploy/Caddyfile +0 -0
  80. {relay_shell-0.5.0 → relay_shell-0.5.2}/deploy/evidence/README.md +0 -0
  81. {relay_shell-0.5.0 → relay_shell-0.5.2}/deploy/evidence/approved-resets.json.example +0 -0
  82. {relay_shell-0.5.0 → relay_shell-0.5.2}/deploy/evidence/audit-evidence.env.example +0 -0
  83. {relay_shell-0.5.0 → relay_shell-0.5.2}/deploy/evidence/relay-audit-evidence-sync +0 -0
  84. {relay_shell-0.5.0 → relay_shell-0.5.2}/deploy/evidence/relay-audit-evidence.py +0 -0
  85. {relay_shell-0.5.0 → relay_shell-0.5.2}/deploy/evidence/relay-audit-evidence.service +0 -0
  86. {relay_shell-0.5.0 → relay_shell-0.5.2}/deploy/evidence/relay-audit-evidence.timer +0 -0
  87. {relay_shell-0.5.0 → relay_shell-0.5.2}/deploy/install-edge.sh +0 -0
  88. {relay_shell-0.5.0 → relay_shell-0.5.2}/deploy/logrotate/relay-shell +0 -0
  89. {relay_shell-0.5.0 → relay_shell-0.5.2}/deploy/monitoring/PACKAGE.lock +0 -0
  90. {relay_shell-0.5.0 → relay_shell-0.5.2}/deploy/monitoring/README.md +0 -0
  91. {relay_shell-0.5.0 → relay_shell-0.5.2}/deploy/monitoring/nftables.conf.snippet +0 -0
  92. {relay_shell-0.5.0 → relay_shell-0.5.2}/deploy/monitoring/prometheus-node-exporter.service.d/override.conf +0 -0
  93. {relay_shell-0.5.0 → relay_shell-0.5.2}/deploy/monitoring/relay-health-metrics.py +0 -0
  94. {relay_shell-0.5.0 → relay_shell-0.5.2}/deploy/monitoring/relay-health-metrics.service +0 -0
  95. {relay_shell-0.5.0 → relay_shell-0.5.2}/deploy/monitoring/relay-health-metrics.timer +0 -0
  96. {relay_shell-0.5.0 → relay_shell-0.5.2}/deploy/monitoring/relay-monitoring.env.example +0 -0
  97. {relay_shell-0.5.0 → relay_shell-0.5.2}/deploy/release-signing.pub +0 -0
  98. {relay_shell-0.5.0 → relay_shell-0.5.2}/deploy/systemd/relay-shell.service +0 -0
  99. {relay_shell-0.5.0 → relay_shell-0.5.2}/deploy/systemd/relay-shell.service.d/hardening.conf +0 -0
  100. {relay_shell-0.5.0 → relay_shell-0.5.2}/docs/adr/0001-runtime-and-sdk.md +0 -0
  101. {relay_shell-0.5.0 → relay_shell-0.5.2}/docs/adr/0002-no-sandbox-full-access.md +0 -0
  102. {relay_shell-0.5.0 → relay_shell-0.5.2}/docs/adr/0004-edge-tls-automation.md +0 -0
  103. {relay_shell-0.5.0 → relay_shell-0.5.2}/docs/adr/0005-codebase-validation.md +0 -0
  104. {relay_shell-0.5.0 → relay_shell-0.5.2}/docs/adr/0006-seccomp-notify-audit-channel.md +0 -0
  105. {relay_shell-0.5.0 → relay_shell-0.5.2}/docs/adr/0008-operating-guidance-prompt.md +0 -0
  106. {relay_shell-0.5.0 → relay_shell-0.5.2}/docs/adr/0009-tier3-confirmation-broker.md +0 -0
  107. {relay_shell-0.5.0 → relay_shell-0.5.2}/docs/adr/0010-rollback-verify-broker.md +0 -0
  108. {relay_shell-0.5.0 → relay_shell-0.5.2}/docs/adr/0011-windows-openssh-powershell.md +0 -0
  109. {relay_shell-0.5.0 → relay_shell-0.5.2}/docs/adr/0012-cancel-safe-session-teardown.md +0 -0
  110. {relay_shell-0.5.0 → relay_shell-0.5.2}/docs/adr/0013-open-by-default-with-unsafe-notices.md +0 -0
  111. {relay_shell-0.5.0 → relay_shell-0.5.2}/docs/adr/0014-seccomp-launch-and-install-recovery.md +0 -0
  112. {relay_shell-0.5.0 → relay_shell-0.5.2}/docs/adr/README.md +0 -0
  113. {relay_shell-0.5.0 → relay_shell-0.5.2}/docs/audit-shipper.md +0 -0
  114. {relay_shell-0.5.0 → relay_shell-0.5.2}/docs/auth.md +0 -0
  115. {relay_shell-0.5.0 → relay_shell-0.5.2}/docs/deployment.md +0 -0
  116. {relay_shell-0.5.0 → relay_shell-0.5.2}/docs/release-trust.md +0 -0
  117. {relay_shell-0.5.0 → relay_shell-0.5.2}/docs/tools.md +0 -0
  118. {relay_shell-0.5.0 → relay_shell-0.5.2}/mkdocs.yml +0 -0
  119. {relay_shell-0.5.0 → relay_shell-0.5.2}/requirements/runtime-py3.12.txt +0 -0
  120. {relay_shell-0.5.0 → relay_shell-0.5.2}/requirements/runtime-py3.14.txt +0 -0
  121. {relay_shell-0.5.0 → relay_shell-0.5.2}/requirements-audit.in +0 -0
  122. {relay_shell-0.5.0 → relay_shell-0.5.2}/requirements-audit.txt +0 -0
  123. {relay_shell-0.5.0 → relay_shell-0.5.2}/requirements-build.in +0 -0
  124. {relay_shell-0.5.0 → relay_shell-0.5.2}/requirements-build.txt +0 -0
  125. {relay_shell-0.5.0 → relay_shell-0.5.2}/requirements-docs.txt +0 -0
  126. {relay_shell-0.5.0 → relay_shell-0.5.2}/requirements-sbom.in +0 -0
  127. {relay_shell-0.5.0 → relay_shell-0.5.2}/requirements-sbom.txt +0 -0
  128. {relay_shell-0.5.0 → relay_shell-0.5.2}/scripts/check_ci_checks.py +0 -0
  129. {relay_shell-0.5.0 → relay_shell-0.5.2}/scripts/healthcheck.sh +0 -0
  130. {relay_shell-0.5.0 → relay_shell-0.5.2}/scripts/installer_transaction.py +0 -0
  131. {relay_shell-0.5.0 → relay_shell-0.5.2}/scripts/reconcile_runtime.py +0 -0
  132. {relay_shell-0.5.0 → relay_shell-0.5.2}/scripts/rehearse_release.py +0 -0
  133. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/__main__.py +0 -0
  134. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/auth/__init__.py +0 -0
  135. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/auth/admin.py +0 -0
  136. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/auth/oauth.py +0 -0
  137. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/auth/resource.py +0 -0
  138. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/broker.py +0 -0
  139. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/config.py +0 -0
  140. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/errors.py +0 -0
  141. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/inventory.py +0 -0
  142. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/metrics.py +0 -0
  143. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/notices.py +0 -0
  144. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/py.typed +0 -0
  145. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/redaction.py +0 -0
  146. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/seccomp.py +0 -0
  147. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/server.py +0 -0
  148. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/sessions.py +0 -0
  149. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/shelltools.py +0 -0
  150. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/sshpool.py +0 -0
  151. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/util.py +0 -0
  152. {relay_shell-0.5.0 → relay_shell-0.5.2}/src/relay_shell/verifier.py +0 -0
  153. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/conftest.py +0 -0
  154. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/fixtures/upgrade-v0.3/advisory-description.txt +0 -0
  155. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/fixtures/upgrade-v0.3/runtime.in +0 -0
  156. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/fixtures/upgrade-v0.3/runtime.lock +0 -0
  157. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/helpers.py +0 -0
  158. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_a14_rs.py +0 -0
  159. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_a14_t1_gaps.py +0 -0
  160. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_admission_guards.py +0 -0
  161. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_audit.py +0 -0
  162. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_audit_lows.py +0 -0
  163. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_audit_tail_tool.py +0 -0
  164. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_authenticated_audit.py +0 -0
  165. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_broker.py +0 -0
  166. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_broker_client_binding.py +0 -0
  167. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_cancellation_audit.py +0 -0
  168. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_ci_checks.py +0 -0
  169. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_ci_gate.py +0 -0
  170. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_classifier_v12.py +0 -0
  171. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_classifier_v16.py +0 -0
  172. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_config.py +0 -0
  173. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_config_hygiene.py +0 -0
  174. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_config_template.py +0 -0
  175. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_dependency_floors.py +0 -0
  176. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_edge_routing.py +0 -0
  177. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_errors.py +0 -0
  178. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_evidence_deploy.py +0 -0
  179. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_fuzz.py +0 -0
  180. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_healthcheck.py +0 -0
  181. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_install_edge_validation.py +0 -0
  182. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_installer_transaction.py +0 -0
  183. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_inventory.py +0 -0
  184. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_logrotate_config.py +0 -0
  185. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_main.py +0 -0
  186. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_metrics.py +0 -0
  187. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_notices.py +0 -0
  188. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_oauth.py +0 -0
  189. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_oauth_approval.py +0 -0
  190. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_oauth_resource.py +0 -0
  191. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_oauth_reuse.py +0 -0
  192. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_oauth_store.py +0 -0
  193. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_policy.py +0 -0
  194. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_prompts.py +0 -0
  195. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_redaction.py +0 -0
  196. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_redaction_operators.py +0 -0
  197. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_redaction_v16.py +0 -0
  198. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_release_admission.py +0 -0
  199. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_release_workflows.py +0 -0
  200. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_resources.py +0 -0
  201. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_root_execution_contract.py +0 -0
  202. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_runtime_inventory.py +0 -0
  203. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_seccomp.py +0 -0
  204. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_server.py +0 -0
  205. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_session_secret_audit.py +0 -0
  206. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_sessions.py +0 -0
  207. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_sessions_cancel.py +0 -0
  208. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_shell.py +0 -0
  209. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_ssh_fanout_tool.py +0 -0
  210. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_ssh_integration.py +0 -0
  211. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_ssh_keyscan_tool.py +0 -0
  212. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_sshpool_unit.py +0 -0
  213. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_stdio_e2e.py +0 -0
  214. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_tool_wrappers.py +0 -0
  215. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_util.py +0 -0
  216. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_verifier.py +0 -0
  217. {relay_shell-0.5.0 → relay_shell-0.5.2}/tests/test_workflow_hygiene.py +0 -0
@@ -41,7 +41,7 @@ jobs:
41
41
  with:
42
42
  persist-credentials: false
43
43
 
44
- - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
44
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7
45
45
  with:
46
46
  python-version: ${{ matrix.python-version }}
47
47
  cache: "pip"
@@ -38,15 +38,15 @@ jobs:
38
38
  persist-credentials: false
39
39
 
40
40
  - name: Initialize CodeQL
41
- uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4
41
+ uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4
42
42
  with:
43
43
  languages: ${{ matrix.language }}
44
44
 
45
45
  - name: Autobuild
46
- uses: github/codeql-action/autobuild@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4
46
+ uses: github/codeql-action/autobuild@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4
47
47
 
48
48
  - name: Perform CodeQL Analysis
49
- uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4
49
+ uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4
50
50
 
51
51
  # A14-D12: the workflows themselves are code that holds tokens. A separate job (not a
52
52
  # matrix entry) so the required check `Analyze (python) (python)` keeps its name. It is in
@@ -63,12 +63,12 @@ jobs:
63
63
  persist-credentials: false
64
64
 
65
65
  - name: Initialize CodeQL
66
- uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4
66
+ uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4
67
67
  with:
68
68
  languages: actions
69
69
  build-mode: none
70
70
 
71
71
  - name: Perform CodeQL Analysis
72
- uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4
72
+ uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4
73
73
  with:
74
74
  category: "/language:actions"
@@ -27,7 +27,7 @@ jobs:
27
27
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
28
28
  with:
29
29
  persist-credentials: false
30
- - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
30
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7
31
31
  with:
32
32
  python-version: "3.14"
33
33
  - name: Prepare the constrained development environment
@@ -34,7 +34,7 @@ jobs:
34
34
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
35
35
  with:
36
36
  persist-credentials: false
37
- - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
37
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7
38
38
  with:
39
39
  python-version: "3.12"
40
40
  cache: pip
@@ -26,7 +26,7 @@ jobs:
26
26
  with:
27
27
  persist-credentials: false
28
28
 
29
- - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
29
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7
30
30
  with:
31
31
  python-version: "3.14"
32
32
  cache: "pip"
@@ -38,7 +38,7 @@ jobs:
38
38
  with:
39
39
  persist-credentials: false
40
40
 
41
- - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
41
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7
42
42
  with:
43
43
  python-version: "3.14"
44
44
  cache: "pip"
@@ -70,7 +70,7 @@ jobs:
70
70
  # still audited, and pip-audit still exits non-zero on any CVE found.
71
71
  run: .audit-tool/bin/pip-audit --path "$AUDIT_SITE_PACKAGES" --skip-editable
72
72
 
73
- - name: Audit the hash-locked release and SBOM tooling
73
+ - name: Audit the hash-locked bootstrap, release and SBOM tooling
74
74
  # requirements-build.txt / requirements-sbom.txt feed the jobs that build,
75
75
  # sign and publish (A14-D7); they are not part of the package's own tree, so
76
76
  # audit them explicitly. They are fully pinned, so no resolution is needed.
@@ -78,4 +78,5 @@ jobs:
78
78
  .audit-tool/bin/pip-audit --disable-pip --no-deps -r requirements-build.txt
79
79
  .audit-tool/bin/pip-audit --disable-pip --no-deps -r requirements-sbom.txt
80
80
  .audit-tool/bin/pip-audit --disable-pip --no-deps -r requirements-audit.txt
81
+ .audit-tool/bin/pip-audit --disable-pip --no-deps -r requirements-bootstrap.txt
81
82
 
@@ -31,7 +31,7 @@ jobs:
31
31
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
32
32
  with:
33
33
  persist-credentials: false
34
- - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
34
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7
35
35
  with:
36
36
  python-version: "3.14"
37
37
  - name: Prepare the locked rehearsal driver
@@ -65,14 +65,14 @@ jobs:
65
65
  with:
66
66
  persist-credentials: false
67
67
  fetch-depth: 0
68
- - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
68
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7
69
69
  id: build-python
70
70
  with:
71
71
  python-version: "3.14"
72
72
  - name: Prepare hash-locked build tools
73
73
  run: |
74
74
  python -m pip install --only-binary=:all: --require-hashes -r requirements/development-py3.12.txt
75
- - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
75
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7
76
76
  with:
77
77
  python-version: "3.14"
78
78
  - name: Prepare independent audit tools with their locked interpreter
@@ -87,7 +87,7 @@ jobs:
87
87
  set -e
88
88
  test "$audit_result" -eq 0 || test "$audit_result" -eq 1
89
89
  test -s "$RUNNER_TEMP/legacy-audit.json"
90
- - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
90
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7
91
91
  id: legacy-python
92
92
  with:
93
93
  python-version: "3.12"
@@ -161,7 +161,7 @@ jobs:
161
161
  ref: ${{ needs.verify.outputs.release_commit }}
162
162
  persist-credentials: false
163
163
 
164
- - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
164
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7
165
165
  with:
166
166
  python-version: "3.14"
167
167
  # No pip cache on the release path: nothing restored from a shared
@@ -201,6 +201,9 @@ jobs:
201
201
  python -m pip check
202
202
 
203
203
  - name: Run full test suite
204
+ env:
205
+ # Keep generated test data outside the source distribution input tree.
206
+ HYPOTHESIS_STORAGE_DIRECTORY: ${{ runner.temp }}/release-hypothesis
204
207
  run: pytest -q
205
208
 
206
209
  - name: Build wheel + sdist
@@ -53,7 +53,7 @@ jobs:
53
53
  persist-credentials: false
54
54
 
55
55
  - name: Set up Python
56
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
56
+ uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7
57
57
  with:
58
58
  python-version: "3.14"
59
59
 
@@ -15,7 +15,7 @@ repos:
15
15
  # Pin to a known-good ruff release. Update this `rev` and the
16
16
  # `ruff==` pin in `[project.optional-dependencies] dev` together
17
17
  # so local pre-commit and `pip install -e ".[dev]"` agree.
18
- rev: v0.17.1
18
+ rev: v0.17.2
19
19
  hooks:
20
20
  - id: ruff
21
21
  args: [--fix]
@@ -6,6 +6,42 @@ All notable changes to this project are documented here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.5.2] - 2026-11-02
10
+
11
+ ### Fixed
12
+
13
+ - Validate the actual audit descriptor before initial permission hardening and on
14
+ rotation reopens. Unsupported devices/FIFOs now degrade to stderr without
15
+ metadata changes or blocking; regular append sinks retain their behavior.
16
+ - Validate regular descriptors for audit-tail and verification reads;
17
+ handle invalid paths and unavailable home directories through audit fallback.
18
+ - Report rotation-open failures through audit health and preserve the first
19
+ degraded reason. Skip chain-tail reads after sink initialization fails.
20
+ - Preserve operator-created rotation-file modes; newly created files remain
21
+ private. See [ADR 0007](docs/adr/0007-audit-hash-chain.md#audit-sink-descriptor-validation-2026-11-02).
22
+
23
+ ## [0.5.1] - 2026-11-02
24
+
25
+ ### Performance
26
+
27
+ - Avoid unnecessary delete/format scans and privilege-context parsing; use an
28
+ ASCII fast path while preserving Unicode input and published-table overrides.
29
+ Keep classification semantics and timing thresholds unchanged. See
30
+ [ADR 0003](docs/adr/0003-tiered-authority.md#2026-11-02-equivalent-scan-gates-patterns-version-17).
31
+
32
+ ### Security
33
+
34
+ - Hash-pin and audit installer pip independently; upgrade both the disposable
35
+ builder and transaction-protected runtime before installing dependency closures.
36
+
37
+ ### Changed
38
+
39
+ - Refresh Ruff, virtualenv development tooling and pinned GitHub Actions.
40
+
41
+ ### Fixed
42
+
43
+ - Keep release Hypothesis caches outside package source inputs.
44
+
9
45
  ## [0.5.0] - 2026-10-22
10
46
 
11
47
  - Change edge-installer precedence: explicitly exported `RELAY_SHELL_EDGE_*` values override the operator environment file; explicitly empty optional values now fail validation instead of selecting defaults. Review exported values before reinstalling the edge.
@@ -1793,7 +1829,9 @@ All notable changes to this project are documented here. The format follows
1793
1829
  bundled logrotate config drops and restores the append-only attribute
1794
1830
  across rotation. See `docs/deployment.md` §6.
1795
1831
 
1796
- [Unreleased]: https://github.com/rmednitzer/relay-shell/compare/v0.5.0...HEAD
1832
+ [Unreleased]: https://github.com/rmednitzer/relay-shell/compare/v0.5.2...HEAD
1833
+ [0.5.2]: https://github.com/rmednitzer/relay-shell/compare/v0.5.1...v0.5.2
1834
+ [0.5.1]: https://github.com/rmednitzer/relay-shell/compare/v0.5.0...v0.5.1
1797
1835
  [0.5.0]: https://github.com/rmednitzer/relay-shell/compare/v0.4.0...v0.5.0
1798
1836
  [0.4.0]: https://github.com/rmednitzer/relay-shell/compare/v0.3.0...v0.4.0
1799
1837
  [0.3.0]: https://github.com/rmednitzer/relay-shell/compare/v0.2.0...v0.3.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: relay-shell
3
- Version: 0.5.0
3
+ Version: 0.5.2
4
4
  Summary: A highly reliable, maximally capable MCP server for complete shell and SSH mastery: a governed relay in the shell/SSH path.
5
5
  Project-URL: Homepage, https://github.com/rmednitzer/relay-shell
6
6
  Project-URL: Documentation, https://rmednitzer.github.io/relay-shell/
@@ -36,13 +36,13 @@ Requires-Dist: pre-commit<5,>=4.0; extra == 'dev'
36
36
  Requires-Dist: pyjwt<3,>=2.15.0; extra == 'dev'
37
37
  Requires-Dist: pytest-asyncio<2,>=0.24; extra == 'dev'
38
38
  Requires-Dist: pytest<10,>=8.3; extra == 'dev'
39
- Requires-Dist: ruff==0.17.1; extra == 'dev'
39
+ Requires-Dist: ruff==0.17.2; extra == 'dev'
40
40
  Provides-Extra: http
41
41
  Description-Content-Type: text/markdown
42
42
 
43
43
  # Relay Shell
44
44
 
45
- v0.5.0 · Python 3.12 / 3.14 · transports `stdio` and `streamable-http` · MCP SDK
45
+ v0.5.2 · Python 3.12 / 3.14 · transports `stdio` and `streamable-http` · MCP SDK
46
46
  `mcp>=2,<3` · Apache-2.0
47
47
 
48
48
  `relay-shell` is a [Model Context Protocol](https://modelcontextprotocol.io) server that gives
@@ -1,6 +1,6 @@
1
1
  # Relay Shell
2
2
 
3
- v0.5.0 · Python 3.12 / 3.14 · transports `stdio` and `streamable-http` · MCP SDK
3
+ v0.5.2 · Python 3.12 / 3.14 · transports `stdio` and `streamable-http` · MCP SDK
4
4
  `mcp>=2,<3` · Apache-2.0
5
5
 
6
6
  `relay-shell` is a [Model Context Protocol](https://modelcontextprotocol.io) server that gives
@@ -129,6 +129,8 @@ esac
129
129
  RUNTIME_LOCK="$SRC_DIR/requirements/runtime-py$PYTHON_PROFILE.txt"
130
130
  [ -r "$RUNTIME_LOCK" ] || die "runtime dependency lock missing"
131
131
  [ -r "$SRC_DIR/requirements-build.txt" ] || die "build tooling lock missing"
132
+ BOOTSTRAP_LOCK="$SRC_DIR/requirements-bootstrap.txt"
133
+ [ -r "$BOOTSTRAP_LOCK" ] || die "installer bootstrap lock missing"
132
134
 
133
135
  log "Ensuring service account 'relay-shell'"
134
136
  if ! id relay-shell >/dev/null 2>&1; then
@@ -139,6 +141,8 @@ fi
139
141
  # Build with locked tooling in an owned disposable environment, not in the runtime.
140
142
  BUILD_DIR=$(sudo -u relay-shell mktemp -d "$PREFIX/.build-XXXXXXXX")
141
143
  sudo -u relay-shell "$INSTALL_PYTHON" -m venv "$BUILD_DIR/venv"
144
+ sudo -u relay-shell "$BUILD_DIR/venv/bin/python" -m pip install --quiet \
145
+ --only-binary=:all: --no-deps --require-hashes -r "$BOOTSTRAP_LOCK"
142
146
  sudo -u relay-shell "$BUILD_DIR/venv/bin/python" -m pip install --quiet \
143
147
  --only-binary=:all: --require-hashes -r "$SRC_DIR/requirements-build.txt"
144
148
  sudo -u relay-shell "$BUILD_DIR/venv/bin/python" -m build --wheel --no-isolation \
@@ -165,6 +169,8 @@ log "Creating venv at $PREFIX/venv"
165
169
  if [ ! -x "$PREFIX/venv/bin/python" ]; then
166
170
  sudo -u relay-shell python3 -m venv "$PREFIX/venv"
167
171
  fi
172
+ sudo -u relay-shell "$PREFIX/venv/bin/python" -m pip install --quiet \
173
+ --only-binary=:all: --no-deps --require-hashes -r "$BOOTSTRAP_LOCK"
168
174
  sudo -u relay-shell "$PREFIX/venv/bin/python" -m pip install --quiet \
169
175
  --only-binary=:all: --require-hashes -r "$RUNTIME_LOCK"
170
176
  sudo -u relay-shell "$PREFIX/venv/bin/python" -m pip install --quiet \
@@ -148,3 +148,26 @@ review.
148
148
  nothing) the tier and basis now also reach the caller as a trailing `[NOTICE ...]` line for
149
149
  Tier 2 and Tier 3 calls. Admission rules are unchanged. See
150
150
  [ADR 0013](0013-open-by-default-with-unsafe-notices.md).
151
+
152
+
153
+ ## 2026-11-02: equivalent scan gates (patterns version 17)
154
+
155
+ This performance repair addresses SC-4/UCA-4 without changing SC-1/SC-2
156
+ classification intent. The risk is a false-negative prefilter (H-1/UCA-1), so
157
+ gates use necessary syntax only: every separated delete rule consumes whitespace
158
+ immediately before a dash or slash; the drive-format rule consumes a colon. The
159
+ complete matcher and optimized matcher share rule sources. Unicode whitespace,
160
+ positive/near-miss fixtures, generated command sequences and arbitrary Unicode
161
+ inputs compare the optimized path to the full matcher. The privilege fast path
162
+ skips context processing only when no original privilege keyword exists; replacing
163
+ whole reader segments with a space cannot create one. Regex heuristics remain
164
+ heuristics, not containment. No new dependency or runtime posture change.
165
+
166
+ Rollback restores the previous complete runtime. Acceptance requires unchanged
167
+ classification on the regression corpus, existing timing limits, full tests and
168
+ fuzz gates; measured improvements are host- and workload-dependent.
169
+
170
+ ASCII-only inputs also use equivalent ASCII regex variants for common Tier-3
171
+ and Tier-2 scans. Inputs containing any non-ASCII character retain the original
172
+ Unicode regexes; replacing a published table disables its optimized variant.
173
+ Differential tests cover both paths.
@@ -245,3 +245,39 @@ unchained, head-truncated (with and without `--segment`), and genesis logs.
245
245
  No change to policy admission, tier semantics, the no-sandbox posture, or
246
246
  any tool's response shape. This pass hardened a compensating control
247
247
  (ADR 0002's first one); it did not move the trust boundary.
248
+
249
+
250
+ ## Audit sink descriptor validation (2026-11-02)
251
+
252
+ A privileged validation run exposed path-based permission hardening of a device
253
+ before file-type validation. FIFO opens could also block, including chain resume
254
+ after initialization failure, while a rotation-open error could escape the
255
+ handler's degraded-state update.
256
+
257
+ Validate the descriptor used for writes with fstat, both initially and after
258
+ rotation. Open nonblocking so unsupported FIFOs cannot stall initialization or
259
+ reopen. Only the initial regular descriptor is subject to best-effort fchmod;
260
+ preserve existing rotation-file permissions and create missing files at 0600.
261
+ Initialization failure uses the existing stderr fallback and skips chain-tail
262
+ reads. Reopen failures mark degraded through the existing first-reason-wins API.
263
+
264
+ This changes unsupported sink handling, with no record-schema, hash-chain,
265
+ execution-authority or policy-mode change. Control-authority STPA changes are
266
+ not applicable; the operational constraints are nonblocking FIFO failure, no device
267
+ metadata mutation, visible degradation and unchanged valid-file append/rotation.
268
+ Device/symlink, FIFO with/without readers, rotation, path replacement, descriptor
269
+ cleanup and chmod-refusal regressions test these constraints. Qualified Python
270
+ profiles and privileged file-mode evidence must be retained at release.
271
+
272
+ Operator-controlled paths and other filesystem actors remain trusted. This does
273
+ not establish durable storage, fsync guarantees, a hostile-directory boundary or
274
+ independent off-host evidence. Roll back the complete runtime if necessary while
275
+ preserving accepted audit records; no data migration is required.
276
+
277
+ Audit-tail reads (plain and gzip) and chain-verification opens use the same
278
+ regular-descriptor validation. This also prevents a regular-to-FIFO replacement
279
+ from blocking between a path check and the actual read. Invalid paths and an
280
+ unavailable home directory degrade initialization through the existing fallback.
281
+
282
+ O_NONBLOCK is not a general storage or network-filesystem timeout; regular-file
283
+ I/O latency remains governed by the operating system and storage.
@@ -28,6 +28,13 @@ MCP client (Claude / Inspector / SDK)
28
28
  \---- SessionRegistry -------/ (unified local + SSH PTYs)
29
29
  ```
30
30
 
31
+ Tier classification uses the shared pattern tables. Necessary-syntax gates skip
32
+ delete/format rule families only when their required syntax is absent. The full
33
+ Tier-3 regex remains the reference and replacement contract; replacing it disables
34
+ the built-in optimization. Privilege context is parsed only when a privilege
35
+ keyword is present. These optimizations do not truncate input or alter policy
36
+ precedence.
37
+
31
38
  ## Request lifecycle
32
39
 
33
40
  Every tool body is identical in shape (`Relay.run`):
@@ -69,6 +76,14 @@ Every tool body is identical in shape (`Relay.run`):
69
76
  for tamper-evidence ([ADR 0007](adr/0007-audit-hash-chain.md)); default off
70
77
  keeps the record byte-identical.
71
78
 
79
+ The audit file handler validates its opened descriptor at initialization and every
80
+ rotation reopen. It uses nonblocking open to avoid FIFO stalls, rejects nonregular
81
+ sinks and hardens only the initial regular descriptor. Invalid initialization
82
+ falls back to stderr and skips chain-tail reads. Reopen failures mark audit health
83
+ degraded while tool calls continue; existing rotation-file modes are preserved.
84
+ Regular-file type checks alone do not prove storage durability or constrain an
85
+ operator-controlled path.
86
+
72
87
  A tool may fail, time out, or be denied, but it always returns a single bounded, audited
73
88
  string; the one exception is a cancelled call, which is audited and then re-raised.
74
89
 
@@ -180,3 +195,13 @@ bounds), see [`runbook.md`](runbook.md) §2. The reproducible validation
180
195
  pass against the upstream `mcp` / `asyncssh` / OAuth surfaces lives in
181
196
  [ADR 0005](adr/0005-codebase-validation.md), which records the
182
197
  methodology and the most recent pass outcome.
198
+
199
+ ASCII-only inputs also use equivalent ASCII regex variants for common Tier-3
200
+ and Tier-2 scans. Inputs containing any non-ASCII character retain the original
201
+ Unicode regexes; replacing a published table disables its optimized variant.
202
+ Differential tests cover both paths.
203
+
204
+ Audit-tail reads (plain and gzip) and chain-verification opens use the same
205
+ regular-descriptor validation. This also prevents a regular-to-FIFO replacement
206
+ from blocking between a path check and the actual read. Invalid paths and an
207
+ unavailable home directory degrade initialization through the existing fallback.
@@ -6,7 +6,7 @@ title: Overview
6
6
 
7
7
  A [Model Context Protocol](https://modelcontextprotocol.io) server for **complete shell and
8
8
  SSH operation**: local commands and scripts, interactive PTY sessions, fleet operation over
9
- SSH, SFTP transfers and port forwards. Current release: **v0.4.0** (2026-10-15).
9
+ SSH, SFTP transfers and port forwards. Current release: **[v0.5.2](https://github.com/rmednitzer/relay-shell/releases/tag/v0.5.2)** (2026-11-02).
10
10
 
11
11
  It is operator infrastructure for hosts you own and administer. It runs with the privileges
12
12
  of its service account, with no sandbox, and it is **open by default**: with the default
@@ -271,6 +271,14 @@ alertable; any HTTP response (including 401/403/404) means the listener is up.
271
271
 
272
272
  ### 2.9 Audit-log hygiene
273
273
 
274
+ Configure a regular audit file. Device/FIFO sinks are unsupported: initialization
275
+ degrades to stderr without changing their permissions or waiting for a reader.
276
+ Each rotation reopen also validates the actual descriptor; failure marks runtime
277
+ audit health degraded. Initial regular-file hardening is best effort (append-only
278
+ files may refuse chmod), and existing operator-created rotation-file modes remain
279
+ unchanged. New files use mode 0600. Do not interpret a regular-file check as a
280
+ proof of durable persistence.
281
+
274
282
  On a real deployment:
275
283
 
276
284
  ```bash
@@ -1106,6 +1114,8 @@ caches, signing or publishing rights. `scripts/rehearse_release.py` archives the
1106
1114
  committed source into a disposable directory and executes the named release
1107
1115
  install/build/distribution-check and SBOM preparation/generation shell steps
1108
1116
  verbatim. It uses Python 3.14 and the same workflow-revision tooling locks.
1117
+ The release workflow keeps Hypothesis storage under the runner temporary directory
1118
+ so its pre-build test run cannot add generated caches to the source distribution.
1109
1119
  The normal two-profile Ubuntu/Python CI matrix runs the full test suite; the rehearsal does
1110
1120
  not run it again. Package and actual installer/recovery rehearsals run on both
1111
1121
  Ubuntu 24.04 and 26.04, using each host's native Python for the installed runtime. Its receipt binds source, interpreter, distribution/SBOM hashes
@@ -1184,3 +1194,26 @@ it does not make PR-edited workflows immutable or establish independent human
1184
1194
  review. Zero required approvals and CODEOWNERS enforcement remain separate policy
1185
1195
  choices. Restore settings only from a reviewed prior ruleset, preserving required
1186
1196
  check identity; removing an aggregate is not an equivalent rollback.
1197
+
1198
+
1199
+ ### Classifier scan gates (patterns version 17)
1200
+
1201
+ Tier-3 matching partitions common rules from delete rules requiring whitespace
1202
+ followed by a dash/slash, and format rules requiring a colon. Necessary-syntax
1203
+ gates skip only families that cannot match; the complete regex is assembled from
1204
+ the same sources and retained as a differential-test reference. Privilege-context
1205
+ parsing runs only when the original text contains a privilege keyword. Reader
1206
+ replacement removes whole command segments and cannot introduce such a keyword.
1207
+ No command text is truncated, no flags are normalized and no policy mode, denial,
1208
+ confirmation or audit behavior changes. Keep the existing absolute timing limits
1209
+ and run the complete test suite plus fuzz tests when modifying these gates.
1210
+
1211
+ ASCII-only inputs also use equivalent ASCII regex variants for common Tier-3
1212
+ and Tier-2 scans. Inputs containing any non-ASCII character retain the original
1213
+ Unicode regexes; replacing a published table disables its optimized variant.
1214
+ Differential tests cover both paths.
1215
+
1216
+ Audit-tail reads (plain and gzip) and chain-verification opens use the same
1217
+ regular-descriptor validation. This also prevents a regular-to-FIFO replacement
1218
+ from blocking between a path check and the actual read. Invalid paths and an
1219
+ unavailable home directory degrade initialization through the existing fallback.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "relay-shell"
7
- version = "0.5.0"
7
+ version = "0.5.2"
8
8
  description = "A highly reliable, maximally capable MCP server for complete shell and SSH mastery: a governed relay in the shell/SSH path."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -45,7 +45,7 @@ dev = [
45
45
  # -e ".[dev]"` without the development lock no longer provides it. Declared explicitly
46
46
  # because a package the suite imports should never be a transitive.
47
47
  "httpx>=0.28,<1",
48
- "ruff==0.17.1",
48
+ "ruff==0.17.2",
49
49
  "mypy>=1.13,<3",
50
50
  "coverage[toml]>=7.6,<8",
51
51
  "pre-commit>=4.0,<5",
@@ -137,7 +137,8 @@
137
137
  "requirements-build.txt",
138
138
  "requirements-sbom.txt",
139
139
  "requirements-audit.txt",
140
- "requirements/*.txt"
140
+ "requirements/*.txt",
141
+ "requirements-bootstrap.txt"
141
142
  ],
142
143
  "enabled": false
143
144
  },
@@ -196,7 +197,7 @@
196
197
  },
197
198
  "pip-compile": {
198
199
  "managerFilePatterns": [
199
- "/^requirements-(?:build|sbom|audit)\\.txt$/",
200
+ "/^requirements-(?:build|sbom|audit|bootstrap)\\.txt$/",
200
201
  "/^requirements/(?:runtime|development)-py3\\.(?:12|14)\\.txt$/"
201
202
  ]
202
203
  }
@@ -34,3 +34,15 @@ A lock fixes selected package bytes; it does not establish provenance by itself,
34
34
  absence of vulnerabilities, cross-platform qualification or reproducible native
35
35
  compilation. CI exercises each selected profile. The package is installed after
36
36
  dependencies with --no-deps; source builds require the separately locked backend.
37
+
38
+ The requirements-bootstrap.txt lock, generated from requirements-bootstrap.in,
39
+ pins installer pip independently of application profiles and build dependencies.
40
+ The installer upgrades this hash-locked, binary-only bootstrap in its disposable
41
+ builder and in the stopped, transaction-protected runtime before installing their
42
+ dependency closures. Pip remains excluded from application-inventory reconciliation;
43
+ that exception does not mean its bootstrap version is unmanaged.
44
+ Regenerate the lock with its recorded command, review hashes/advisories, and run
45
+ the actual installer/recovery rehearsals on both qualified OS/Python profiles.
46
+ The pip-audit workflow audits this lock explicitly; Renovate's pip-compile manager
47
+ owns its generated output. Bootstrap failure retains the installer's existing
48
+ rollback and stopped-service behavior.
@@ -1522,25 +1522,25 @@ rpds-py==2026.9.1 \
1522
1522
  # via
1523
1523
  # jsonschema
1524
1524
  # referencing
1525
- ruff==0.17.1 \
1526
- --hash=sha256:056c5d5fa0ac790051ae165d7035c7bf956d02de2db557f2e9b31d877a505fca \
1527
- --hash=sha256:1796c41eafec66f4635125dd1e074a7aa27b8d20c187d484360acf11d247fc4a \
1528
- --hash=sha256:1de0030a8aa78c65c5fecb84b2e5c390ac684c0452875a02afc7c083d49a496a \
1529
- --hash=sha256:29b454f18ca3eeaf37fbf140841dcf27d36109e29dee9aae8acecb68c26c6f74 \
1530
- --hash=sha256:42400b9a6ff8515ff27796f07f92e0195be011c86e81e381606f58ea5502fef4 \
1531
- --hash=sha256:4c0d6cb5f8fbac77eec9af49f4fc0eaf856b05545e372b0694d24b4e76b1c640 \
1532
- --hash=sha256:5bb796c5112e9fb9527f2ef2faa0a130d6312d19de92bcfaddd70dbb2d41020f \
1533
- --hash=sha256:5df76f16580fab58d0f6c98a3ac254a4abceb7b2489b8b678335869cff77b3d7 \
1534
- --hash=sha256:684154861a819bf1f433669d2a0ce9c3e54677c65b8ebb866cc2a7ec86345785 \
1535
- --hash=sha256:78beced4a3c94dfedbee4b999b95b6a27b35820fdc0afbacc6d37a8da26c1cd1 \
1536
- --hash=sha256:7feefc63921202894f0e158b0e32f45a5950d70a7fb693ccb9264816c25b1c05 \
1537
- --hash=sha256:80e53b0e5faa3c5667f035daaa032893ae42b7cfe708c886d39ed131e54688ab \
1538
- --hash=sha256:872e03dd231744d6c5ec5c8af6be52b71323fc6ee096e4c8b34d9f844ee3b978 \
1539
- --hash=sha256:8eadb09a788010be29c895418bdfa513e4e1c65e64b94e5b19e4cd381f6cc7fb \
1540
- --hash=sha256:8f9aa5b5bd5fa1937803ea609c8f153b8de39bf3e0e149897ae34b0c5b52f467 \
1541
- --hash=sha256:a6ef98e51de8ef38082f90653ceeb84944bd7673783eea13e1647c5b9b064189 \
1542
- --hash=sha256:bd8f5a55b10f371642eacd0e8665a42a8b97da15c9a7187c79e697b77b5e0ce4 \
1543
- --hash=sha256:ce05e6f3973611d9550a600e04bb531061e368baba31610eb57c1509a8f21a0a
1525
+ ruff==0.17.2 \
1526
+ --hash=sha256:122258f6ecde0e0dd1bb60b24263c3edde4338caf723fca711cc729f9bfd414a \
1527
+ --hash=sha256:195ad918027f817c119310ca89a42d347887dfc28934aaa869325e9c5dc9d962 \
1528
+ --hash=sha256:1b59d447f197bef9ef48a22ba1a6016137753c6099e9bac9c36eb566e422424c \
1529
+ --hash=sha256:2d086be452eed686aa51271ecf2cbe5037956517cc6ee39a17b4489ac12f8bbe \
1530
+ --hash=sha256:39c5b55c960515cdb32be16e69efb5734c589c06a0eb38a22694524a336b889d \
1531
+ --hash=sha256:583bd67bb9e7061dd09bc46a0a884bae2bbd214dbcd48e509813612619683869 \
1532
+ --hash=sha256:5875a16b0de70b43cb81eabea5ae5e8f5d1b2fd1b5e6ed07f80cfe5019d0bd5a \
1533
+ --hash=sha256:6bce7d1f5165701ae18f5df85c65afe78cddfa6c5fb4b9bdb0014d60388f3edd \
1534
+ --hash=sha256:7cfd783590edf2b77317882631900f6a13ea2e8eacefe6ebf2370d2715299160 \
1535
+ --hash=sha256:7f8fba429d6ac4128ab29cfb8fa6240d2fa671d04adf7f84d3b22fc7b6b6bea4 \
1536
+ --hash=sha256:84d632402d14c23b488f941db23712d6736a013225162062d34558c3f3c780eb \
1537
+ --hash=sha256:8c21b4ab24485358f17291a4f8dc5399b834e963b8efedd920fe4fdb02081bb4 \
1538
+ --hash=sha256:92a80d2f44584dabb0189b3a6c393e37e525e04942838482010575cb81945be7 \
1539
+ --hash=sha256:96a65e755d8dba094707fb23ab602111d9a31ec2835b3a6992a1914a8fcb211e \
1540
+ --hash=sha256:b7d0906677d89bbf3cf73cde64b5bbe2ab35df3ae43e49ee2627301e91265bac \
1541
+ --hash=sha256:ba405cbd5ec7bda1f555d5e52be1fd68d02608d4d95d24591bdac92cfacb579f \
1542
+ --hash=sha256:c555a877f8f41447e318101cfefea9bf12dfa9f82eedf90c376e57e069f917d0 \
1543
+ --hash=sha256:c60f93fec7142dba5b8f487f94bf2f6231dae24dc11e4c48648347c8b0720004
1544
1544
  # via
1545
1545
  # -c requirements.txt
1546
1546
  # relay-shell (pyproject.toml)
@@ -1630,7 +1630,7 @@ uvicorn==0.54.0 \
1630
1630
  # -c requirements.txt
1631
1631
  # relay-shell (pyproject.toml)
1632
1632
  # mcp
1633
- virtualenv==21.14.12 \
1634
- --hash=sha256:7820aeaef23f1f59a96458edfc7f9d3deac937155718de15f1c9a64471e15551 \
1635
- --hash=sha256:bbcfb78e36c22ad70c6917c4aecb5e63477d3c138e9f88c946f320e31859c76a
1633
+ virtualenv==21.14.13 \
1634
+ --hash=sha256:5fbb04138a4861580b78ba9110d529a26bc48fcc93c8ec36bc0202eeed96a788 \
1635
+ --hash=sha256:de5307ba4b9a0001d9c8ea02832d38f63ca432880f416c1bface61c4323fd905
1636
1636
  # via pre-commit
@@ -1522,25 +1522,25 @@ rpds-py==2026.9.1 \
1522
1522
  # via
1523
1523
  # jsonschema
1524
1524
  # referencing
1525
- ruff==0.17.1 \
1526
- --hash=sha256:056c5d5fa0ac790051ae165d7035c7bf956d02de2db557f2e9b31d877a505fca \
1527
- --hash=sha256:1796c41eafec66f4635125dd1e074a7aa27b8d20c187d484360acf11d247fc4a \
1528
- --hash=sha256:1de0030a8aa78c65c5fecb84b2e5c390ac684c0452875a02afc7c083d49a496a \
1529
- --hash=sha256:29b454f18ca3eeaf37fbf140841dcf27d36109e29dee9aae8acecb68c26c6f74 \
1530
- --hash=sha256:42400b9a6ff8515ff27796f07f92e0195be011c86e81e381606f58ea5502fef4 \
1531
- --hash=sha256:4c0d6cb5f8fbac77eec9af49f4fc0eaf856b05545e372b0694d24b4e76b1c640 \
1532
- --hash=sha256:5bb796c5112e9fb9527f2ef2faa0a130d6312d19de92bcfaddd70dbb2d41020f \
1533
- --hash=sha256:5df76f16580fab58d0f6c98a3ac254a4abceb7b2489b8b678335869cff77b3d7 \
1534
- --hash=sha256:684154861a819bf1f433669d2a0ce9c3e54677c65b8ebb866cc2a7ec86345785 \
1535
- --hash=sha256:78beced4a3c94dfedbee4b999b95b6a27b35820fdc0afbacc6d37a8da26c1cd1 \
1536
- --hash=sha256:7feefc63921202894f0e158b0e32f45a5950d70a7fb693ccb9264816c25b1c05 \
1537
- --hash=sha256:80e53b0e5faa3c5667f035daaa032893ae42b7cfe708c886d39ed131e54688ab \
1538
- --hash=sha256:872e03dd231744d6c5ec5c8af6be52b71323fc6ee096e4c8b34d9f844ee3b978 \
1539
- --hash=sha256:8eadb09a788010be29c895418bdfa513e4e1c65e64b94e5b19e4cd381f6cc7fb \
1540
- --hash=sha256:8f9aa5b5bd5fa1937803ea609c8f153b8de39bf3e0e149897ae34b0c5b52f467 \
1541
- --hash=sha256:a6ef98e51de8ef38082f90653ceeb84944bd7673783eea13e1647c5b9b064189 \
1542
- --hash=sha256:bd8f5a55b10f371642eacd0e8665a42a8b97da15c9a7187c79e697b77b5e0ce4 \
1543
- --hash=sha256:ce05e6f3973611d9550a600e04bb531061e368baba31610eb57c1509a8f21a0a
1525
+ ruff==0.17.2 \
1526
+ --hash=sha256:122258f6ecde0e0dd1bb60b24263c3edde4338caf723fca711cc729f9bfd414a \
1527
+ --hash=sha256:195ad918027f817c119310ca89a42d347887dfc28934aaa869325e9c5dc9d962 \
1528
+ --hash=sha256:1b59d447f197bef9ef48a22ba1a6016137753c6099e9bac9c36eb566e422424c \
1529
+ --hash=sha256:2d086be452eed686aa51271ecf2cbe5037956517cc6ee39a17b4489ac12f8bbe \
1530
+ --hash=sha256:39c5b55c960515cdb32be16e69efb5734c589c06a0eb38a22694524a336b889d \
1531
+ --hash=sha256:583bd67bb9e7061dd09bc46a0a884bae2bbd214dbcd48e509813612619683869 \
1532
+ --hash=sha256:5875a16b0de70b43cb81eabea5ae5e8f5d1b2fd1b5e6ed07f80cfe5019d0bd5a \
1533
+ --hash=sha256:6bce7d1f5165701ae18f5df85c65afe78cddfa6c5fb4b9bdb0014d60388f3edd \
1534
+ --hash=sha256:7cfd783590edf2b77317882631900f6a13ea2e8eacefe6ebf2370d2715299160 \
1535
+ --hash=sha256:7f8fba429d6ac4128ab29cfb8fa6240d2fa671d04adf7f84d3b22fc7b6b6bea4 \
1536
+ --hash=sha256:84d632402d14c23b488f941db23712d6736a013225162062d34558c3f3c780eb \
1537
+ --hash=sha256:8c21b4ab24485358f17291a4f8dc5399b834e963b8efedd920fe4fdb02081bb4 \
1538
+ --hash=sha256:92a80d2f44584dabb0189b3a6c393e37e525e04942838482010575cb81945be7 \
1539
+ --hash=sha256:96a65e755d8dba094707fb23ab602111d9a31ec2835b3a6992a1914a8fcb211e \
1540
+ --hash=sha256:b7d0906677d89bbf3cf73cde64b5bbe2ab35df3ae43e49ee2627301e91265bac \
1541
+ --hash=sha256:ba405cbd5ec7bda1f555d5e52be1fd68d02608d4d95d24591bdac92cfacb579f \
1542
+ --hash=sha256:c555a877f8f41447e318101cfefea9bf12dfa9f82eedf90c376e57e069f917d0 \
1543
+ --hash=sha256:c60f93fec7142dba5b8f487f94bf2f6231dae24dc11e4c48648347c8b0720004
1544
1544
  # via
1545
1545
  # -c requirements.txt
1546
1546
  # relay-shell (pyproject.toml)
@@ -1626,7 +1626,7 @@ uvicorn==0.54.0 \
1626
1626
  # -c requirements.txt
1627
1627
  # relay-shell (pyproject.toml)
1628
1628
  # mcp
1629
- virtualenv==21.14.12 \
1630
- --hash=sha256:7820aeaef23f1f59a96458edfc7f9d3deac937155718de15f1c9a64471e15551 \
1631
- --hash=sha256:bbcfb78e36c22ad70c6917c4aecb5e63477d3c138e9f88c946f320e31859c76a
1629
+ virtualenv==21.14.13 \
1630
+ --hash=sha256:5fbb04138a4861580b78ba9110d529a26bc48fcc93c8ec36bc0202eeed96a788 \
1631
+ --hash=sha256:de5307ba4b9a0001d9c8ea02832d38f63ca432880f416c1bface61c4323fd905
1632
1632
  # via pre-commit
@@ -0,0 +1 @@
1
+ pip>=26.2.2,<27