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