relay-shell 0.2.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 (150) hide show
  1. {relay_shell-0.2.0 → relay_shell-0.3.0}/.env.example +13 -0
  2. {relay_shell-0.2.0 → relay_shell-0.3.0}/.github/workflows/ci.yml +2 -2
  3. {relay_shell-0.2.0 → relay_shell-0.3.0}/.github/workflows/codeql.yml +1 -1
  4. relay_shell-0.3.0/.github/workflows/docs.yml +64 -0
  5. {relay_shell-0.2.0 → relay_shell-0.3.0}/.github/workflows/gitleaks.yml +1 -1
  6. {relay_shell-0.2.0 → relay_shell-0.3.0}/.github/workflows/nightly-fuzz.yml +2 -2
  7. {relay_shell-0.2.0 → relay_shell-0.3.0}/.github/workflows/pip-audit.yml +2 -2
  8. {relay_shell-0.2.0 → relay_shell-0.3.0}/.github/workflows/release.yml +4 -4
  9. {relay_shell-0.2.0 → relay_shell-0.3.0}/.github/workflows/sbom.yml +3 -3
  10. {relay_shell-0.2.0 → relay_shell-0.3.0}/.gitignore +3 -0
  11. {relay_shell-0.2.0 → relay_shell-0.3.0}/.hypothesis/constants/060c4657af60692d +1 -1
  12. {relay_shell-0.2.0 → relay_shell-0.3.0}/.hypothesis/constants/06d9884c28b2a63f +1 -1
  13. {relay_shell-0.2.0 → relay_shell-0.3.0}/.hypothesis/constants/09f931d5fb70626a +1 -1
  14. relay_shell-0.2.0/.hypothesis/constants/eb76034b3e66dae0 → relay_shell-0.3.0/.hypothesis/constants/0f0449cb9acd7923 +2 -2
  15. relay_shell-0.2.0/.hypothesis/constants/0a19132992ca9818 → relay_shell-0.3.0/.hypothesis/constants/0fbd7313047d9e52 +1 -1
  16. relay_shell-0.2.0/.hypothesis/constants/45397debbdfd60bc → relay_shell-0.3.0/.hypothesis/constants/29d0bc1435e72910 +1 -1
  17. {relay_shell-0.2.0 → relay_shell-0.3.0}/.hypothesis/constants/29ed694dbb01fab5 +1 -1
  18. relay_shell-0.3.0/.hypothesis/constants/31cd5feb5b48ec66 +4 -0
  19. relay_shell-0.2.0/.hypothesis/constants/6cf4f480c0f10b72 → relay_shell-0.3.0/.hypothesis/constants/4017e2a6f3ee7cfd +1 -1
  20. relay_shell-0.3.0/.hypothesis/constants/877af75780422922 +4 -0
  21. relay_shell-0.3.0/.hypothesis/constants/8967c8193f2aab55 +4 -0
  22. {relay_shell-0.2.0 → relay_shell-0.3.0}/.hypothesis/constants/95970940d76c226a +1 -1
  23. {relay_shell-0.2.0 → relay_shell-0.3.0}/.hypothesis/constants/a5fe03a1c421948b +1 -1
  24. relay_shell-0.3.0/.hypothesis/constants/c7900d7ab9e5fea7 +4 -0
  25. {relay_shell-0.2.0 → relay_shell-0.3.0}/.hypothesis/constants/d78536b0c96f72a7 +1 -1
  26. relay_shell-0.2.0/.hypothesis/constants/a3d3e5c3935b6ab5 → relay_shell-0.3.0/.hypothesis/constants/d8887ef76582104d +1 -1
  27. relay_shell-0.3.0/.hypothesis/constants/e562d9f2652151f0 +4 -0
  28. relay_shell-0.3.0/.hypothesis/constants/f4684eb785e4fb5b +4 -0
  29. relay_shell-0.2.0/.hypothesis/constants/0eb061c63a8794f0 → relay_shell-0.3.0/.hypothesis/constants/f7f625a7f9fbf8e6 +1 -1
  30. {relay_shell-0.2.0 → relay_shell-0.3.0}/.hypothesis/constants/f947d359e3d0041d +1 -1
  31. {relay_shell-0.2.0 → relay_shell-0.3.0}/.hypothesis/unicode_data/16.0.0/charmap.json.gz +0 -0
  32. {relay_shell-0.2.0 → relay_shell-0.3.0}/.pre-commit-config.yaml +1 -1
  33. {relay_shell-0.2.0 → relay_shell-0.3.0}/BACKLOG.md +57 -0
  34. {relay_shell-0.2.0 → relay_shell-0.3.0}/CHANGELOG.md +227 -1
  35. {relay_shell-0.2.0 → relay_shell-0.3.0}/PKG-INFO +10 -7
  36. {relay_shell-0.2.0 → relay_shell-0.3.0}/README.md +6 -5
  37. relay_shell-0.3.0/audit/2026-07-15-adversarial-engagement.md +72 -0
  38. relay_shell-0.3.0/audit/2026-07-15-engagement.md +190 -0
  39. relay_shell-0.3.0/audit/2026-07-15-release-v0.3.0.md +133 -0
  40. relay_shell-0.3.0/docs/_config.yml +29 -0
  41. {relay_shell-0.2.0 → relay_shell-0.3.0}/docs/adr/0005-codebase-validation.md +2 -2
  42. relay_shell-0.3.0/docs/adr/0009-tier3-confirmation-broker.md +220 -0
  43. relay_shell-0.3.0/docs/adr/0010-rollback-verify-broker.md +173 -0
  44. relay_shell-0.3.0/docs/adr/0011-windows-openssh-powershell.md +199 -0
  45. {relay_shell-0.2.0 → relay_shell-0.3.0}/docs/adr/README.md +4 -1
  46. {relay_shell-0.2.0 → relay_shell-0.3.0}/docs/architecture.md +10 -1
  47. {relay_shell-0.2.0 → relay_shell-0.3.0}/docs/audit-shipper.md +1 -1
  48. {relay_shell-0.2.0 → relay_shell-0.3.0}/docs/auth.md +5 -5
  49. {relay_shell-0.2.0 → relay_shell-0.3.0}/docs/deployment.md +148 -6
  50. {relay_shell-0.2.0 → relay_shell-0.3.0}/docs/index.md +1 -1
  51. {relay_shell-0.2.0 → relay_shell-0.3.0}/docs/runbook.md +104 -34
  52. {relay_shell-0.2.0 → relay_shell-0.3.0}/docs/tools.md +32 -8
  53. relay_shell-0.3.0/mkdocs.yml +106 -0
  54. {relay_shell-0.2.0 → relay_shell-0.3.0}/pyproject.toml +4 -2
  55. relay_shell-0.3.0/requirements-docs.txt +5 -0
  56. {relay_shell-0.2.0 → relay_shell-0.3.0}/requirements.txt +6 -6
  57. {relay_shell-0.2.0 → relay_shell-0.3.0}/src/relay_shell/__init__.py +1 -1
  58. {relay_shell-0.2.0 → relay_shell-0.3.0}/src/relay_shell/audit.py +12 -1
  59. {relay_shell-0.2.0 → relay_shell-0.3.0}/src/relay_shell/auth/oauth.py +24 -0
  60. relay_shell-0.3.0/src/relay_shell/broker.py +189 -0
  61. {relay_shell-0.2.0 → relay_shell-0.3.0}/src/relay_shell/config.py +10 -0
  62. {relay_shell-0.2.0 → relay_shell-0.3.0}/src/relay_shell/metrics.py +7 -1
  63. {relay_shell-0.2.0 → relay_shell-0.3.0}/src/relay_shell/patterns.py +168 -10
  64. {relay_shell-0.2.0 → relay_shell-0.3.0}/src/relay_shell/policy.py +8 -0
  65. {relay_shell-0.2.0 → relay_shell-0.3.0}/src/relay_shell/redaction.py +43 -9
  66. {relay_shell-0.2.0 → relay_shell-0.3.0}/src/relay_shell/server.py +238 -25
  67. {relay_shell-0.2.0 → relay_shell-0.3.0}/src/relay_shell/sessions.py +42 -21
  68. {relay_shell-0.2.0 → relay_shell-0.3.0}/src/relay_shell/sshpool.py +227 -123
  69. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_audit_tail_tool.py +68 -0
  70. relay_shell-0.3.0/tests/test_broker.py +302 -0
  71. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_oauth.py +79 -0
  72. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_patterns.py +208 -0
  73. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_policy.py +37 -0
  74. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_redaction.py +121 -1
  75. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_server.py +2 -1
  76. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_sessions.py +37 -0
  77. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_ssh_integration.py +59 -0
  78. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_sshpool_unit.py +233 -0
  79. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_stdio_e2e.py +1 -1
  80. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_tool_wrappers.py +63 -3
  81. relay_shell-0.2.0/.hypothesis/constants/bef274fdff8cb14f +0 -4
  82. relay_shell-0.2.0/.hypothesis/constants/c0cd33c96d9b3122 +0 -4
  83. relay_shell-0.2.0/.hypothesis/constants/c17f8b18040c167e +0 -4
  84. relay_shell-0.2.0/.hypothesis/constants/c83a8d1a7bc9e18d +0 -4
  85. relay_shell-0.2.0/.hypothesis/constants/d44e4bb8fa74f2f1 +0 -4
  86. relay_shell-0.2.0/docs/_config.yml +0 -18
  87. {relay_shell-0.2.0 → relay_shell-0.3.0}/.claude/settings.json +0 -0
  88. {relay_shell-0.2.0 → relay_shell-0.3.0}/.github/CODEOWNERS +0 -0
  89. {relay_shell-0.2.0 → relay_shell-0.3.0}/.github/ISSUE_TEMPLATE/bug.md +0 -0
  90. {relay_shell-0.2.0 → relay_shell-0.3.0}/.github/ISSUE_TEMPLATE/feature.md +0 -0
  91. {relay_shell-0.2.0 → relay_shell-0.3.0}/.github/ISSUE_TEMPLATE/security.md +0 -0
  92. {relay_shell-0.2.0 → relay_shell-0.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  93. {relay_shell-0.2.0 → relay_shell-0.3.0}/.github/workflows/dependency-review.yml +0 -0
  94. {relay_shell-0.2.0 → relay_shell-0.3.0}/.gitleaks.toml +0 -0
  95. {relay_shell-0.2.0 → relay_shell-0.3.0}/.hypothesis/.gitignore +0 -0
  96. {relay_shell-0.2.0 → relay_shell-0.3.0}/AGENTS.md +0 -0
  97. {relay_shell-0.2.0 → relay_shell-0.3.0}/CLAUDE.md +0 -0
  98. {relay_shell-0.2.0 → relay_shell-0.3.0}/CODE_OF_CONDUCT.md +0 -0
  99. {relay_shell-0.2.0 → relay_shell-0.3.0}/CONTRIBUTING.md +0 -0
  100. {relay_shell-0.2.0 → relay_shell-0.3.0}/LICENSE +0 -0
  101. {relay_shell-0.2.0 → relay_shell-0.3.0}/NOTICE +0 -0
  102. {relay_shell-0.2.0 → relay_shell-0.3.0}/SECURITY.md +0 -0
  103. {relay_shell-0.2.0 → relay_shell-0.3.0}/audit/00-inventory.md +0 -0
  104. {relay_shell-0.2.0 → relay_shell-0.3.0}/audit/01-baseline.md +0 -0
  105. {relay_shell-0.2.0 → relay_shell-0.3.0}/audit/02-security-findings.md +0 -0
  106. {relay_shell-0.2.0 → relay_shell-0.3.0}/audit/03-final-report.md +0 -0
  107. {relay_shell-0.2.0 → relay_shell-0.3.0}/audit/2026-05-27-engagement.md +0 -0
  108. {relay_shell-0.2.0 → relay_shell-0.3.0}/audit/2026-06-01-engagement.md +0 -0
  109. {relay_shell-0.2.0 → relay_shell-0.3.0}/audit/2026-06-21-adversarial-engagement.md +0 -0
  110. {relay_shell-0.2.0 → relay_shell-0.3.0}/audit/2026-06-21-engagement.md +0 -0
  111. {relay_shell-0.2.0 → relay_shell-0.3.0}/deploy/Caddyfile +0 -0
  112. {relay_shell-0.2.0 → relay_shell-0.3.0}/deploy/install-edge.sh +0 -0
  113. {relay_shell-0.2.0 → relay_shell-0.3.0}/deploy/install.sh +0 -0
  114. {relay_shell-0.2.0 → relay_shell-0.3.0}/deploy/logrotate/relay-shell +0 -0
  115. {relay_shell-0.2.0 → relay_shell-0.3.0}/deploy/systemd/relay-shell.service +0 -0
  116. {relay_shell-0.2.0 → relay_shell-0.3.0}/deploy/systemd/relay-shell.service.d/hardening.conf +0 -0
  117. {relay_shell-0.2.0 → relay_shell-0.3.0}/docs/adr/0001-runtime-and-sdk.md +0 -0
  118. {relay_shell-0.2.0 → relay_shell-0.3.0}/docs/adr/0002-no-sandbox-full-access.md +0 -0
  119. {relay_shell-0.2.0 → relay_shell-0.3.0}/docs/adr/0003-tiered-authority.md +0 -0
  120. {relay_shell-0.2.0 → relay_shell-0.3.0}/docs/adr/0004-edge-tls-automation.md +0 -0
  121. {relay_shell-0.2.0 → relay_shell-0.3.0}/docs/adr/0006-seccomp-notify-audit-channel.md +0 -0
  122. {relay_shell-0.2.0 → relay_shell-0.3.0}/docs/adr/0007-audit-hash-chain.md +0 -0
  123. {relay_shell-0.2.0 → relay_shell-0.3.0}/docs/adr/0008-operating-guidance-prompt.md +0 -0
  124. {relay_shell-0.2.0 → relay_shell-0.3.0}/renovate.json5 +0 -0
  125. {relay_shell-0.2.0 → relay_shell-0.3.0}/scripts/healthcheck.sh +0 -0
  126. {relay_shell-0.2.0 → relay_shell-0.3.0}/src/relay_shell/__main__.py +0 -0
  127. {relay_shell-0.2.0 → relay_shell-0.3.0}/src/relay_shell/auth/__init__.py +0 -0
  128. {relay_shell-0.2.0 → relay_shell-0.3.0}/src/relay_shell/errors.py +0 -0
  129. {relay_shell-0.2.0 → relay_shell-0.3.0}/src/relay_shell/inventory.py +0 -0
  130. {relay_shell-0.2.0 → relay_shell-0.3.0}/src/relay_shell/py.typed +0 -0
  131. {relay_shell-0.2.0 → relay_shell-0.3.0}/src/relay_shell/seccomp.py +0 -0
  132. {relay_shell-0.2.0 → relay_shell-0.3.0}/src/relay_shell/shelltools.py +0 -0
  133. {relay_shell-0.2.0 → relay_shell-0.3.0}/src/relay_shell/util.py +0 -0
  134. {relay_shell-0.2.0 → relay_shell-0.3.0}/src/relay_shell/verifier.py +0 -0
  135. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/conftest.py +0 -0
  136. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_audit.py +0 -0
  137. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_config.py +0 -0
  138. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_errors.py +0 -0
  139. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_fuzz.py +0 -0
  140. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_inventory.py +0 -0
  141. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_main.py +0 -0
  142. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_metrics.py +0 -0
  143. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_prompts.py +0 -0
  144. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_resources.py +0 -0
  145. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_seccomp.py +0 -0
  146. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_shell.py +0 -0
  147. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_ssh_fanout_tool.py +0 -0
  148. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_ssh_keyscan_tool.py +0 -0
  149. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_util.py +0 -0
  150. {relay_shell-0.2.0 → relay_shell-0.3.0}/tests/test_verifier.py +0 -0
@@ -25,6 +25,19 @@ RELAY_SHELL_POLICY_MODE=open
25
25
  RELAY_SHELL_POLICY_DENY=
26
26
  RELAY_SHELL_POLICY_ALLOW=
27
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
+
28
41
  # --- Audit ---
29
42
  RELAY_SHELL_AUDIT_PATH=/var/log/relay-shell/audit.jsonl
30
43
  RELAY_SHELL_AUDIT_STDERR=false
@@ -24,9 +24,9 @@ jobs:
24
24
  python-version: ["3.12", "3.13", "3.14"]
25
25
  name: check (py${{ matrix.python-version }})
26
26
  steps:
27
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
27
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
28
28
 
29
- - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
29
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
30
30
  with:
31
31
  python-version: ${{ matrix.python-version }}
32
32
  cache: "pip"
@@ -24,7 +24,7 @@ jobs:
24
24
 
25
25
  steps:
26
26
  - name: Checkout
27
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
27
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
28
28
 
29
29
  - name: Initialize CodeQL
30
30
  uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
@@ -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
@@ -28,7 +28,7 @@ jobs:
28
28
  timeout-minutes: 10
29
29
  steps:
30
30
  - name: Checkout (full history so the scan covers every commit)
31
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
31
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
32
32
  with:
33
33
  fetch-depth: 0
34
34
 
@@ -22,9 +22,9 @@ jobs:
22
22
  runs-on: ubuntu-latest
23
23
  timeout-minutes: 30
24
24
  steps:
25
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
25
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
26
26
 
27
- - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
27
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
28
28
  with:
29
29
  python-version: "3.14"
30
30
  cache: "pip"
@@ -25,9 +25,9 @@ jobs:
25
25
  runs-on: ubuntu-latest
26
26
  timeout-minutes: 10
27
27
  steps:
28
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
28
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
29
29
 
30
- - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
30
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
31
31
  with:
32
32
  python-version: "3.14"
33
33
  cache: "pip"
@@ -66,7 +66,7 @@ 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
69
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
70
70
  with:
71
71
  ref: ${{ steps.tag.outputs.tag }}
72
72
  fetch-depth: 0
@@ -138,11 +138,11 @@ jobs:
138
138
  id-token: write
139
139
  attestations: write
140
140
  steps:
141
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
141
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
142
142
  with:
143
143
  ref: ${{ needs.verify.outputs.release_commit }}
144
144
 
145
- - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
145
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
146
146
  with:
147
147
  python-version: "3.14"
148
148
  cache: "pip"
@@ -171,7 +171,7 @@ jobs:
171
171
  # PyPI OIDC trusted publishing already provides L2; this step
172
172
  # adds a verifier-checkable provenance record in Sigstore's
173
173
  # public transparency log.
174
- uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4
174
+ uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4
175
175
  with:
176
176
  subject-path: 'dist/*'
177
177
 
@@ -33,10 +33,10 @@ jobs:
33
33
  attestations: write # CI-3: write the provenance attestation
34
34
  steps:
35
35
  - name: Checkout
36
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
36
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
37
37
 
38
38
  - name: Set up Python
39
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
39
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
40
40
  with:
41
41
  python-version: "3.14"
42
42
  cache: "pip"
@@ -117,7 +117,7 @@ jobs:
117
117
  # commit produced this SBOM — so a consumer can verify the SBOM is the
118
118
  # genuine output of this repo's CI (`gh attestation verify <file>
119
119
  # --repo ${{ github.repository }}`) rather than a swapped artifact.
120
- uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4
120
+ uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4
121
121
  with:
122
122
  subject-path: |
123
123
  relay-shell-${{ steps.tag.outputs.name }}.cdx.json
@@ -14,6 +14,9 @@ venv/
14
14
  .coverage.*
15
15
  htmlcov/
16
16
 
17
+ # MkDocs build output
18
+ site/
19
+
17
20
  # Runtime state / secrets (NEVER commit)
18
21
  *.env
19
22
  .env
@@ -1,4 +1,4 @@
1
1
  # file: /home/runner/work/relay-shell/relay-shell/src/relay_shell/util.py
2
- # hypothesis_version: 6.155.7
2
+ # hypothesis_version: 6.156.6
3
3
 
4
4
  ['clamp', 'gen_id', 'now_iso', 'replace', 'sha256_hex', 'truncate', 'utf-8', 'utf8_len']
@@ -1,4 +1,4 @@
1
1
  # file: /opt/hostedtoolcache/Python/3.14.6/x64/bin/pytest
2
- # hypothesis_version: 6.155.7
2
+ # hypothesis_version: 6.156.6
3
3
 
4
4
  ['.exe', '__main__']
@@ -1,4 +1,4 @@
1
1
  # file: /home/runner/work/relay-shell/relay-shell/src/relay_shell/errors.py
2
- # hypothesis_version: 6.155.7
2
+ # hypothesis_version: 6.156.6
3
3
 
4
4
  ['ForwardError', 'PolicyDenied', 'RelayError', 'SessionError', 'fmt_exc']
@@ -1,4 +1,4 @@
1
1
  # file: /home/runner/work/relay-shell/relay-shell/src/relay_shell/__init__.py
2
- # hypothesis_version: 6.155.7
2
+ # hypothesis_version: 6.156.6
3
3
 
4
- ['0.2.0', '__version__']
4
+ ['0.3.0', '__version__']
@@ -1,4 +1,4 @@
1
1
  # file: /home/runner/work/relay-shell/relay-shell/src/relay_shell/sshpool.py
2
- # hypothesis_version: 6.155.7
2
+ # hypothesis_version: 6.156.6
3
3
 
4
4
  [65536, ', ', ':', 'D', 'ForwardHandle', 'HUP', 'INT', 'KILL', 'L', 'QUIT', 'R', 'SshPool', 'SshProcessTransport', 'TERM', 'a', 'accept-new', 'client_keys', 'config', 'connect_timeout', 'dynamic', 'fwd', 'id', 'ignore', 'keepalive_interval', 'kind', 'known_hosts', 'listen_port', 'local', 'openssh', 'port', 'remote', 'replace', 'socks', 'spec', 'ssh_idle_timeout', 'strict', 'target', 'tunnel', 'username', 'utf-8', 'xterm-256color', '~/.ssh/known_hosts']
@@ -1,4 +1,4 @@
1
1
  # file: /home/runner/work/relay-shell/relay-shell/src/relay_shell/sessions.py
2
- # hypothesis_version: 6.155.7
2
+ # hypothesis_version: 6.156.6
3
3
 
4
4
  [0.0, 65536, 'HHHH', 'LocalPtyTransport', 'Session', 'SessionRegistry', 'Transport', 'age_s', 'buffered', 'closed', 'dropped', 'exit_code', 'id', 'idle_s', 'kind', 'produced', 'replace', 'sh', 'size', 'ssh', 'title', 'utf-8']
@@ -1,4 +1,4 @@
1
1
  # file: /home/runner/work/relay-shell/relay-shell/src/relay_shell/verifier.py
2
- # hypothesis_version: 6.155.7
2
+ # hypothesis_version: 6.156.6
3
3
 
4
4
  ['/', '/etc/caddy/Caddyfile', 'ABSENT_TEMPLATE', 'Caddyfile', 'DRIFT', 'MISSING', 'OK', '_deploy', 'caddyfile', 'deploy', 'logrotate', 'relay_shell', 'systemd-hardening', 'systemd-unit', 'utf-8']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/relay-shell/relay-shell/src/relay_shell/redaction.py
2
+ # hypothesis_version: 6.156.6
3
+
4
+ [500, 16384, 'redact', 'redact_args', 'replace', 'utf-8']
@@ -1,4 +1,4 @@
1
1
  # file: /home/runner/work/relay-shell/relay-shell/src/relay_shell/metrics.py
2
- # hypothesis_version: 6.155.7
2
+ # hypothesis_version: 6.156.6
3
3
 
4
4
  ['"', ',', '\\', '\\"', '\\\\', '\\n', 'counter', 'gauge', 'mode', 'outcome', 'syscall', 'tier', 'tool', '{', '}']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/relay-shell/relay-shell/src/relay_shell/policy.py
2
+ # hypothesis_version: 6.156.6
3
+
4
+ ['Policy', 'PolicyDecision', 'Tier', 'audit_tail', 'classify', 'guarded', 'operation_confirm', 'permitted', 'readonly', 'server_info', 'session_kill', 'session_list', 'session_recv', 'session_resize', 'session_send', 'shell_exec', 'shell_script', 'shell_spawn', 'ssh_check', 'ssh_download', 'ssh_exec', 'ssh_fanout', 'ssh_forward', 'ssh_forward_list', 'ssh_hosts', 'ssh_spawn', 'ssh_upload']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/relay-shell/relay-shell/src/relay_shell/server.py
2
+ # hypothesis_version: 6.156.6
3
+
4
+ [0.0, 1.0, 2.0, 60.0, 100, 120, 128, 200, 256, 512, 1000, 1024, 2048, 4096, 8192, 32768, 65535, 65536, ',', '--', '-T', '-p', '-t', '.', '/bin/bash', '/bin/sudo', '/metrics', '/usr/bin/sudo', '/usr/local/bin/sudo', '0x', ':', 'GET', 'Host inventory', 'L', 'R', 'Relay', 'SIG', 'SSH config metadata', 'Single host spec', 'TERM', '[]', 'aliases', 'application/json', 'audit', 'audit_tail', 'auth', 'auth_server_provider', 'bash', 'build_server', 'cap', 'chain', 'client_id', 'close', 'cols', 'command', 'concurrency', 'confirm', 'confirm_execute', 'confirm_plan', 'confirm_required', 'connect_timeout', 'cwd', 'data', 'default_timeout', 'degraded', 'denied', 'denied_filter', 'dsa', 'ecdsa', 'echo ok', 'ed25519', 'enter', 'env_json', 'error', 'euid', 'exit_code', 'filter_version', 'format', 'forward_id', 'host', 'host_count', 'hosts', 'http', 'idle_timeout', 'instructions', 'interpreter', 'inventory', 'inventory_host', 'inventory_hosts', 'ipv4_mapped', 'is_root', 'json_response', 'jump', 'keepalive', 'key_path', 'key_types', 'known_hosts', 'known_hosts_default', 'limits', 'lines', 'local', 'max_forwards', 'max_output', 'max_output_hard', 'max_sessions', 'max_timeout', 'name', 'notify', 'ok', 'operating_guide', 'operation_confirm', 'output', 'path', 'pending', 'policy_mode', 'port', 'reason', 'relay-shell', 'relay_shell.server', 'remote', 'request_id', 'results', 'rows', 'rsa', 'rsa,ecdsa,ed25519', 'runtime', 'script', 'seccomp', 'server_info', 'session_id', 'session_kill', 'session_list', 'session_recv', 'session_resize', 'session_send', 'shell', 'shell_exec', 'shell_script', 'shell_spawn', 'signal', 'size', 'spec', 'ssh', 'ssh-config', 'ssh-keyscan', 'ssh_check', 'ssh_config', 'ssh_download', 'ssh_exec', 'ssh_fanout', 'ssh_forward', 'ssh_forward_close', 'ssh_forward_list', 'ssh_hosts', 'ssh_keyscan', 'ssh_spawn', 'ssh_upload', 'stateless_http', 'stdin', 'strict', 'sudo_binary', 'supported', 'tier', 'tier3', 'tier_filter', 'timeout', 'token_sha256', 'tool', 'tool_filter', 'transport', 'ttl', 'uid', 'use_shell', 'user', 'utf-8', 'version']
@@ -1,4 +1,4 @@
1
1
  # file: /home/runner/work/relay-shell/relay-shell/src/relay_shell/seccomp.py
2
- # hypothesis_version: 6.155.7
2
+ # hypothesis_version: 6.156.6
3
3
 
4
4
  [b'\x00', b'\x01', b'x', 5.0, 101, 105, 106, 113, 114, 117, 119, 155, 157, 161, 165, 166, 255, 257, 272, 308, 317, 322, 1000, 2143289344, 2147418112, 3221225534, 4294967295, '!', '-', '.', '/proc/self/status', '6Q', '@fall', 'CapEff:', 'HBBI', 'NOTIFIED_SYSCALLS', 'PlatformSupport', 'QI', 'QqiI', 'SeccompMonitor', 'SyscallEvent', '_listener_fd', '_stop_r', '_stop_w', 'allow', 'build_filter', 'chroot', 'clear_active', 'data', 'execve', 'execveat', 'filter', 'get_active', 'i', 'ja', 'jump', 'len', 'libc unavailable', 'libc.so.6', 'linux', 'mount', 'notif', 'notify', 'ok', 'open', 'openat', 'pass_fds', 'pivot_root', 'platform_support', 'prctl', 'preexec_fn', 'ptrace', 'relay-seccomp-notify', 'relay_shell.seccomp', 'relay_shell_seccomp', 'resp', 'set_active', 'setgid', 'setns', 'setregid', 'setresgid', 'setresuid', 'setreuid', 'setuid', 'stmt', 'syscall_name', 'umount2', 'unshare', 'utf-8', 'x86_64']
@@ -1,4 +1,4 @@
1
1
  # file: /home/runner/work/relay-shell/relay-shell/src/relay_shell/inventory.py
2
- # hypothesis_version: 6.155.7
2
+ # hypothesis_version: 6.156.6
3
3
 
4
4
  ['#', '*?!', '=', '@', 'HostSpec', 'Inventory', 'explicit', 'host', 'hostname', 'identity_file', 'identityfile', 'inventory', 'jump', 'known_hosts', 'name', 'port', 'proxyjump', 'replace', 'source', 'ssh_config', 'user', 'utf-8']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/relay-shell/relay-shell/src/relay_shell/patterns.py
2
+ # hypothesis_version: 6.156.6
3
+
4
+ ['11', 'PATTERNS_VERSION', 'PRIV_ESC_PATTERN', 'REDACTION_PATTERNS', 'TIER2_PATTERN', 'TIER3_PATTERN', 'URL_CREDS_PATTERN', '[REDACTED]', '\\bAKIA[0-9A-Z]{16}\\b', '\\g<prefix>[REDACTED]']
@@ -1,4 +1,4 @@
1
1
  # file: /home/runner/work/relay-shell/relay-shell/src/relay_shell/auth/__init__.py
2
- # hypothesis_version: 6.155.7
2
+ # hypothesis_version: 6.156.6
3
3
 
4
4
  ['FileOAuthProvider', 'build_auth_settings', 'make_oauth_provider']
@@ -1,4 +1,4 @@
1
1
  # file: /home/runner/work/relay-shell/relay-shell/src/relay_shell/auth/oauth.py
2
- # hypothesis_version: 6.155.7
2
+ # hypothesis_version: 6.156.6
3
3
 
4
4
  [384, 448, 511, '.tmp', 'Bearer', 'FileOAuthProvider', 'build_auth_settings', 'client_id', 'clients.json', 'code', 'code_challenge', 'codes.json', 'expires_at', 'invalid_grant', 'make_oauth_provider', 'mcp:tools', 'redirect_uri', 'refresh:', 'resource', 'scopes', 'state', 'token', 'tokens.json', 'utf-8', 'w']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/relay-shell/relay-shell/src/relay_shell/broker.py
2
+ # hypothesis_version: 6.156.6
3
+
4
+ [256, 'Challenge', 'ConfirmationBroker']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/relay-shell/relay-shell/src/relay_shell/audit.py
2
+ # hypothesis_version: 6.156.6
3
+
4
+ [b'\n', 384, 8192, '%(message)s', ',', '0', '1.0', '5', ':', '=', 'AUDIT %(message)s', 'AuditLogger', 'ChainResult', '\\', '\\=', '\\\\', '\\n', '\\r', '\\t', '\\|', 'action', 'args', 'audit', 'cap', 'cef', 'chain', 'client_id', 'denied', 'exit_code', 'jsonl', 'leef', 'nr', 'output_len', 'output_sha256', 'pid', 'prev', 'r', 'rb', 'relay-shell', 'relay_shell.audit', 'replace', 'request_id', 'seq', 'syscall', 'syscall_args', 'syscall_notify', 'tier', 'tool', 'tool-call', 'ts', 'utf-8', 'verify_chain', '|']
@@ -1,4 +1,4 @@
1
1
  # file: /home/runner/work/relay-shell/relay-shell/src/relay_shell/config.py
2
- # hypothesis_version: 6.155.7
2
+ # hypothesis_version: 6.156.6
3
3
 
4
4
  [120, 256, 300, 600, 900, 1024, 1800, 3600, 4096, 8080, 65535, 65536, 86400, 262144, 1048576, 2592000, 16777216, 134217728, '.env', '127.0.0.1', 'RELAY_SHELL_', 'Settings', 'accept-new', 'after', 'audit_format', 'cef', 'get_settings', 'guarded', 'http', 'ignore', 'jsonl', 'leef', 'open', 'policy_mode', 'readonly', 'ssh_known_hosts', 'stdio', 'strict', 'transport', 'utf-8', '~/.ssh/config']
@@ -1,4 +1,4 @@
1
1
  # file: /home/runner/work/relay-shell/relay-shell/src/relay_shell/shelltools.py
2
- # hypothesis_version: 6.155.7
2
+ # hypothesis_version: 6.156.6
3
3
 
4
4
  ['-', '-s', '/bin/bash', '/bin/sh', '0', 'C.UTF-8', 'DEBIAN_FRONTEND', 'GIT_TERMINAL_PROMPT', 'LANG', 'PATH', 'bash', 'build_env', 'cwd', 'env', 'noninteractive', 'py', 'python', 'python3', 'replace', 'run_command', 'run_script', 'set -euo pipefail\n', 'sh', 'spawn_argv', 'start_new_session', 'stderr', 'stdin', 'stdout', 'utf-8']
@@ -15,7 +15,7 @@ repos:
15
15
  # Pin to a known-good ruff release. Update this `rev` and the
16
16
  # `ruff>=` floor in `[project.optional-dependencies] dev` together
17
17
  # so local pre-commit and `pip install -e ".[dev]"` agree.
18
- rev: v0.15.17
18
+ rev: v0.15.20
19
19
  hooks:
20
20
  - id: ruff
21
21
  args: [--fix]
@@ -13,6 +13,38 @@ its kind is tracked (runbook §7 for capability/quality/ops/security-hardening,
13
13
 
14
14
  Severity order within each section: low before info; smaller effort first.
15
15
 
16
+ ## 2026-07-15 adversarial + performance pass
17
+
18
+ Findings from the red-team + profiling pass
19
+ ([`audit/2026-07-15-adversarial-engagement.md`](audit/2026-07-15-adversarial-engagement.md)),
20
+ prompted by "is everything lean, secure, performant?".
21
+
22
+ Closed (engagement PR):
23
+
24
+ | ID | Sev | Title | Resolution |
25
+ |----|-----|-------|------------|
26
+ | RED-6 | HIGH | JSON-quoted-key secret leak in redaction | Quote-tolerant separator + linear char-class value run on the generic + AWS `secret_access_key` rules (`PATTERNS_VERSION` 8). Paired JSON-shape over/under-scrub tests. |
27
+ | RED-6a | HIGH | ReDoS introduced by the first RED-6 draft (lookahead O(n²)) | Replaced with a linear char-class matcher; 1 MB scan 165 s → 196 ms. Linearity regression test. |
28
+ | P1 | MED | Redaction scanned the full untruncated arg on the event loop | `_scrub_str` bounds the scan to `max_len + 16 KiB`; 1–4 MB arg now constant ~12 ms. Bounded-scan test. Lossless for every truncation-safe pattern — see RED-8 for the one quoted-value edge the original "lossless" claim missed. |
29
+ | POL-2 | MED | `TIER3_PATTERN` missed non-obfuscated `rm` long options | Bounded (`{0,16}`) long-option `rm` alternative; positive/negative + ReDoS-ceiling tests. |
30
+ | SSH-4 | MED | Deny-list blind to host for `ssh_exec`/`ssh_spawn`/`ssh_fanout`/`ssh_check` | Fold the destination host (canonical-IP widened) into their deny probes; wiring test. |
31
+ | BRK-3 | MED | Broker binding omitted SSH identity (`user`/`port`/`key_path`) | Added to `ssh_exec`/`ssh_spawn` audit_args → op-key binding + audit visibility; ADR 0009 follow-up; binding test. |
32
+
33
+ Closed (post-engagement follow-up review):
34
+
35
+ | ID | Sev | Title | Resolution |
36
+ |----|-----|-------|------------|
37
+ | RED-8 | MED | P1 scan-window leaked the tail of a quoted secret past the window | The CLI-flag rule's quoted-value branches required a closing quote and were length-unbounded, so a `--password="…"` value longer than `max_len + 16 KiB` lost its close quote to truncation → the quoted branch failed, the greedy bare fallback stopped at the first internal space, and the post-space tail leaked into the audit record (full-string redaction still caught it, so a P1 regression). Added unterminated-quote fallback branches consuming to end-of-line (`PATTERNS_VERSION` 9); well-formed values byte-identical. Authorization rule verified unaffected (its `$` branch already tolerates truncation). PoC + `test_red8_*` regressions + linearity check. |
38
+ | AUTH-3 | LOW | OAuth `exchange_*` re-checked `client_id` but not `expires_at` | `exchange_authorization_code` / `exchange_refresh_token` enforced expiry only via their `load_*` gate, not atomically at exchange — a record valid at load could lapse in the load→exchange window (or a caller could reach exchange without load) and still mint a token (PoC: an expired code and an expired refresh both minted access tokens). Added an `expires_at` re-check after the existing `client_id` re-check in both paths (raise `invalid_grant`, drop the stale record), mirroring the file's defense-in-depth pattern. Not exploitable in the normal SDK flow (load runs first); closes the TOCTOU window. Paired `test_exchange_*_refuses_expired_*` regressions. |
39
+
40
+ Open deferrals (low-priority perf; measure before acting — none on a hot path):
41
+
42
+ | ID | Item | Sev | Effort | Rationale | Owner role |
43
+ |----|------|-----|--------|-----------|------------|
44
+ | PERF-1 | `AuditLogger._emit` does a synchronous file write+flush on the event loop (the read path is already offloaded via `asyncio.to_thread`) | low | S | Local append is fast; offload only if a slow/networked audit sink shows up. Consistency, not a measured stall. | maintainer |
45
+ | PERF-2 | `SshPool._sweep_conns` is O(n) over cached connections under one lock per `connect()` | low | S | n is bounded by the connection cache (small); revisit only for very large fleets. | maintainer |
46
+ | PERF-3 | `Session.buffer` uses `bytearray` front-deletion (memmove) on overflow/drain | low | S | Buffer is bounded (`session_buffer_bytes`); a deque-of-chunks would avoid the memmove if it ever matters. | maintainer |
47
+
16
48
  ## Closed (follow-up work after the audit pass)
17
49
 
18
50
  Resolved in the backlog-work PRs that followed the 2026-06-12 audit pass.
@@ -168,6 +200,31 @@ accepted-as-designed with Renovate evidence; see the Closed table above.)
168
200
  |---|---|---|---|---|---|
169
201
  | F-G2 | Branch protection on `main` — **fully resolved** (rules enumerated and hardened) | high (governance), now closed | S (done) | Prior packs (`audit/2026-06-01-engagement.md` §7.1) carried this as an open HIGH item ("`main` accepts direct pushes"). Resolved in two steps. 2026-06-12 (audit pass): the branches API reports `protected: true` [V]. 2026-06-12 (follow-up, via the Vertex-held `gh` credential): protection comes from ruleset `main-protection` (id 17307996; classic protection is unset), which enforced pull_request (0 approvals), non_fast_forward, deletion, and required_linear_history [V]. With operator confirmation (a T3 change per the operator's `github-vertex.md` contract), the ruleset gained `required_status_checks` — `check (py3.12)` / `check (py3.13)` / `check (py3.14)` / `gitleaks (secret scan)`, all bound to GitHub Actions, strict=false — and `required_signatures` (closing the prior pack's deferred **P2-3**). Verified effective post-change via `GET /repos/rmednitzer/relay-shell/rules/branches/main` [V]. pip-audit / dependency-review / CodeQL stay advisory by operator choice. | repo owner (executed with operator confirmation) |
170
202
 
203
+ ## 2026-07-15 full audit + Vertex/Axiom comparison pass
204
+
205
+ Findings and lessons from the 2026-07-15 pass
206
+ ([`audit/2026-07-15-engagement.md`](audit/2026-07-15-engagement.md)). The
207
+ scanner battery (pip-audit, trivy vuln+secret, bandit, semgrep, actionlint,
208
+ shellcheck) was clean and no pinned dependency carries a known CVE; there were
209
+ no P0/P1 findings. The pass compared `relay-shell` against two sibling MCP
210
+ control planes and turned the highest-value lesson (L1) into shipped code.
211
+
212
+ Closed (engagement PR):
213
+
214
+ | ID | Title | Resolution |
215
+ |---|---|---|
216
+ | L1 (broker) | No per-call confirmation for irreversible ops | **Closed** (this PR). Implemented the opt-in Tier-3 confirmation broker: [ADR 0009](docs/adr/0009-tier3-confirmation-broker.md), `src/relay_shell/broker.py` (100% covered), `operation_confirm` tool (contract 21 → 22), `action=confirm_plan`/`confirm_execute` audit markers, `server_info.confirm` block. Default-off byte-identical; layered after the deny/mode check. |
217
+ | DOC-1 | Living docs named the superseded pin (`mcp==1.27.2` / `asyncssh` 2.23.1) after Renovate moved it to `mcp==1.28.1` / `asyncssh==2.24.0` | **Closed** (this PR). Reconciled the README status line + compatibility matrix and the `docs/architecture.md` diagram; "last validated" → 2026-07-15. Frozen ADRs / prior engagement records left intact per convention. |
218
+ | ENV-1 | `.env.example` missing `RELAY_SHELL_CONFIRM_TIER3` / `RELAY_SHELL_CONFIRM_TTL` | **Closed** (PR #129). Added the two commented vars mirroring `docs/deployment.md` §8a (this session couldn't edit `.env*` paths; done in a follow-up). |
219
+ | DOC-6 (L3) | No deploy-host HIDS / config-drift guidance | **Closed** (DOC-6 PR). `docs/deployment.md` §3a "Host integrity + config-drift monitoring" documents etckeeper / AIDE / fail2ban / lynis as detection compensating for the intentionally-partial systemd confinement. Docs only. |
220
+ | AUD-1 (L2) | No in-band audit verify / correlate tool | **Closed as a split disposition** (AUD-1 PR), after evaluating the lesson against ADR 0007 + the actual audit schema. **Shipped:** read-only triage filters on `audit_tail` (`tool`/`tier`/`denied`, Tier 0, tool contract unchanged) — the operator-diagnostic use case that already justifies `audit_tail`. **Declined:** in-band chain-*verify* stays the CLI `--verify-audit` (ADR 0007 — the audited model must not drive verification of its own trail). **N/A:** correlate-by-input-`sha256` — relay-shell hashes the *output*, not the input, so there is no input hash to correlate on. |
221
+
222
+ Open deferrals (severity order; smaller effort first):
223
+
224
+ | ID | Item | Sev | Effort | Rationale / approach | Owner role |
225
+ |---|---|---|---|---|---|
226
+ | OPS-2 (L4) | `pip-audit` has no KEV/EPSS exploit prioritization | info | S | Lesson from the comparison: layer Known-Exploited / EPSS signal on top of the existing `pip-audit` gate for prioritization. Advisory only — `pip-audit` already fails closed. Low value for the small pinned set; revisit if the dependency set grows. | maintainer |
227
+
171
228
  ## Notes on items NOT added here
172
229
 
173
230
  - No critical / high / medium **code** security findings were produced by