@sebastienrousseau/dotfiles 0.2.501 → 0.2.502
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.
- package/CHANGELOG.md +77 -0
- package/README.md +38 -8
- package/docs/COPYRIGHT +1 -1
- package/docs/index.md +2 -2
- package/docs/manual/00-introduction.md +1 -1
- package/docs/manual/03-reference/01-dot-cli.md +63 -45
- package/docs/manual/03-reference/02-config-files.md +2 -2
- package/docs/manual/03-reference/05-feature-flags.md +62 -3
- package/docs/manual/_toc.yml +1 -1
- package/docs/manual/command-index.md +12 -8
- package/docs/manual/index.md +66 -0
- package/docs/operations/COVERAGE.md +36 -2
- package/docs/operations/HARD_AUDIT_2026.md +631 -0
- package/docs/operations/REGISTRY.md +89 -0
- package/docs/operations/ROADMAP_2026.md +665 -0
- package/docs/operations/TRACEABILITY.md +3 -0
- package/docs/operations/VERSION_SYNC.md +4 -4
- package/docs/reference/POWERSHELL_PARITY.md +80 -0
- package/docs/registry.json +6 -0
- package/docs/security/CI_PINNING.md +113 -0
- package/docs/security/COMMIT_SIGNING.md +138 -0
- package/docs/security/DISCLOSURE.md +130 -0
- package/docs/security/KEY_ROTATION.md +81 -1
- package/docs/security/SCORECARD.md +74 -14
- package/docs/security/security-pubkey.asc +15 -0
- package/dot_config/fish/conf.d/direnv.fish +4 -0
- package/dot_config/fish/conf.d/mise-activate.fish +5 -0
- package/dot_config/git/hooks/executable_commit-msg +1 -1
- package/dot_config/shell/00-core-paths.sh.tmpl +8 -1
- package/dot_config/shell/README.md +1 -1
- package/dot_config/zsh/dot_zshrc.tmpl +57 -4
- package/dot_config/zsh/rc.d/30-options.zsh.tmpl +1 -1
- package/dot_local/bin/executable_dot +49 -9
- package/dot_local/bin/executable_dot-bootstrap +0 -1
- package/dot_local/bin/executable_dot-theme-sync +8 -8
- package/dot_local/bin/executable_tour +2 -2
- package/dot_local/share/man/man1/dot.1 +1 -1
- package/dot_local/share/zsh/completions/_dot +4 -0
- package/install.sh +44 -37
- package/package.json +1 -1
- package/scripts/ci/dot-cli-startup-bench.sh +126 -0
- package/scripts/ci/install-chezmoi-verified.sh +4 -1
- package/scripts/ci/lint-reusable-pins.sh +78 -0
- package/scripts/ci/run-coverage.sh +89 -0
- package/scripts/ci/windows-smoke-test.ps1 +136 -0
- package/scripts/diagnostics/doctor.sh +39 -9
- package/scripts/dot/commands/agent.sh +19 -22
- package/scripts/dot/commands/agents.sh +325 -0
- package/scripts/dot/commands/aliases.sh +10 -8
- package/scripts/dot/commands/core.sh +10 -4
- package/scripts/dot/commands/fleet.sh +278 -3
- package/scripts/dot/commands/init.sh +184 -0
- package/scripts/dot/commands/meta.sh +7 -4
- package/scripts/dot/commands/registry.sh +263 -0
- package/scripts/dot/commands/tools.sh +49 -1
- package/scripts/dot/lib/bento.sh +1 -1
- package/scripts/dot/lib/platform.sh +21 -8
- package/scripts/dot/lib/ui.sh +134 -2
- package/scripts/dot/lib/utils.sh +1 -1
- package/scripts/git-hooks/pre-commit-audit.sh +1 -1
- package/scripts/lib/secrets_provider.sh +32 -6
- package/scripts/ops/rollback.sh +14 -0
- package/scripts/security/check-disclosure-key-expiry.sh +110 -0
- package/scripts/security/lock-configs.sh +11 -2
- package/scripts/version-sync.sh +3 -0
|
@@ -33,3 +33,6 @@ It is enforced by `bash ./scripts/qa/traceability-coverage.sh`.
|
|
|
33
33
|
| BT-21 | WSL parity contract | `scripts/qa/wsl-contract.sh` | `tests/unit/misc/test_qa_reliability.sh` | `docs/operations/RELIABILITY.md`, `docs/reference/SUPPORT_MATRIX.md` |
|
|
34
34
|
| BT-22 | Internal behavior traceability contract | `scripts/qa/traceability-coverage.sh` | `tests/unit/misc/test_qa_traceability_coverage.sh` | `docs/operations/TRACEABILITY.md`, `docs/operations/RELIABILITY.md` |
|
|
35
35
|
| BT-23 | Multi-format Manual pipeline | `scripts/docs/build-manual.sh`, `scripts/docs/check-manual.sh`, `scripts/dot/commands/manual.sh` | `tests/unit/docs/test_build_manual.sh`, `tests/unit/docs/test_check_manual.sh`, `tests/unit/dot-cli/test_dot_commands_manual.sh` | `docs/manual/`, `docs/README.md`, `README.md` |
|
|
36
|
+
| BT-24 | Multi-harness AI agent context (AGENTS.md generator) | `scripts/dot/commands/agents.sh` | `tests/unit/auto/test_auto_dot_driver.sh` | `AGENTS.md`, `CLAUDE.md`, `docs/operations/HARD_AUDIT_2026.md` |
|
|
37
|
+
| BT-25 | Bootstrap foreign dotfiles repo through chezmoi harness | `scripts/dot/commands/init.sh` | `tests/unit/auto/test_auto_dot_driver.sh` | `docs/operations/ROADMAP_2026.md`, `docs/manual/03-reference/01-dot-cli.md` |
|
|
38
|
+
| BT-26 | Module registry discovery and install scaffold | `scripts/dot/commands/registry.sh` | `tests/unit/auto/test_auto_dot_driver.sh` | `docs/operations/REGISTRY.md`, `docs/registry.json` |
|
|
@@ -93,9 +93,9 @@ The system recognizes and updates these patterns:
|
|
|
93
93
|
#### Documentation Files
|
|
94
94
|
|
|
95
95
|
```markdown
|
|
96
|
-
**Version**: v0.2.
|
|
97
|
-
**Dotfiles Version**: v0.2.
|
|
98
|
-
Version: v0.2.
|
|
96
|
+
**Version**: v0.2.502
|
|
97
|
+
**Dotfiles Version**: v0.2.502
|
|
98
|
+
Version: v0.2.502
|
|
99
99
|
version 0.2.485
|
|
100
100
|
```
|
|
101
101
|
|
|
@@ -388,6 +388,6 @@ gh workflow run sync-versions.yml --ref feature-branch
|
|
|
388
388
|
---
|
|
389
389
|
|
|
390
390
|
**Last Updated**: 2026-02-15
|
|
391
|
-
**Version**: v0.2.
|
|
391
|
+
**Version**: v0.2.502
|
|
392
392
|
**Maintainer**: Principal Automation Engineer
|
|
393
393
|
**Status**: Production Ready ✅
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
render_with_liquid: false
|
|
3
|
+
title: "PowerShell parity matrix"
|
|
4
|
+
description: "What works on Windows-native PowerShell 7.4 LTS / 7.5+ vs the bash surface."
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# PowerShell parity matrix
|
|
8
|
+
|
|
9
|
+
This document tracks what is supported on Windows-native PowerShell 7.4 LTS / 7.5+ compared to the bash/zsh surface that ships on macOS and Linux. The repo's positioning has shifted (per [ROADMAP_2026 §6.9](../operations/ROADMAP_2026.md)) to call PowerShell a first-class target rather than "best effort." This page is the contract.
|
|
10
|
+
|
|
11
|
+
## Support tiers
|
|
12
|
+
|
|
13
|
+
| Tier | Meaning | Examples |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| **Full** | Native PowerShell path; tested in CI on `windows-latest`. | `dot version`, `dot help`, `dot agents check` (via WSL-bash fallback). |
|
|
16
|
+
| **WSL-bridged** | Runs under WSL2 bash with the same UX as Linux. | All `dot` subcommands, the test suite, the cold-start bench. |
|
|
17
|
+
| **Stub** | The command exists as a PowerShell entry-point but delegates to bash. | `dot init`, `dot fleet apply` (require chezmoi/ssh; both Windows-installable). |
|
|
18
|
+
| **N/A** | The command targets a Unix-only surface and is not exposed on PowerShell. | Wallpaper-driven theming via macOS `defaults`; Linux-only `lock-configs.sh`. |
|
|
19
|
+
|
|
20
|
+
## Command-by-command matrix
|
|
21
|
+
|
|
22
|
+
| Command | Windows-native | WSL2 | macOS | Linux |
|
|
23
|
+
|---|---|---|---|---|
|
|
24
|
+
| `dot version` | Full | Full | Full | Full |
|
|
25
|
+
| `dot help` | Full | Full | Full | Full |
|
|
26
|
+
| `dot doctor` | Full (subset of checks; some Unix-only checks return N/A) | Full | Full | Full |
|
|
27
|
+
| `dot init <user>` | Full (requires `chezmoi` on PATH — install via `scoop install chezmoi`) | Full | Full | Full |
|
|
28
|
+
| `dot agents render` / `check` / `list` | Stub (bash-bridged — requires bash on PATH; the smoke test calls `bash dot agents check` from pwsh) | Full | Full | Full |
|
|
29
|
+
| `dot fleet status` / `drift` / `events` | Full | Full | Full | Full |
|
|
30
|
+
| `dot fleet apply` | Stub (bash-bridged + requires Windows OpenSSH `ssh.exe` on PATH; no native pwsh-side test in CI yet — tracked in ROADMAP §C5 follow-up) | Full | Full | Full |
|
|
31
|
+
| `dot fleet namespace set` | Full | Full | Full | Full |
|
|
32
|
+
| `dot registry list` / `search` / `info` | Full | Full | Full | Full |
|
|
33
|
+
| `dot registry install <name>` | Scaffold (no behavior yet on any platform) | Scaffold | Scaffold | Scaffold |
|
|
34
|
+
| `dot agent` / `dot mode` | Full | Full | Full | Full |
|
|
35
|
+
| `dot ai` / `dot ai-setup` / `dot ai-query` | Full | Full | Full | Full |
|
|
36
|
+
| AI bridges (`cl`, `codex`, `copilot`, `gemini`, `goose`, etc.) | Full when the underlying AI CLI is installed (scoop / winget / native) | Full | Full | Full |
|
|
37
|
+
| `dot lint` | WSL-bridged (shellcheck/shfmt are Unix-native) | Full | Full | Full |
|
|
38
|
+
| `dot perf` | Full (uses pwsh `Measure-Command` on the PS path) | Full | Full | Full |
|
|
39
|
+
| `dot health` | Full | Full | Full | Full |
|
|
40
|
+
| `dot tools` | Full (uses winget/scoop on Windows; brew/apt elsewhere) | Full | Full | Full |
|
|
41
|
+
| `dot theme` (wallpaper-driven) | Stub (Windows wallpaper API needs additional work) | Stub (X11 only — Wayland support pending) | Full | Full (X11 only) |
|
|
42
|
+
| `dot wallpaper` | Stub | Stub | Full | Full |
|
|
43
|
+
| `dot security firewall` | Full (Windows Defender Firewall via `Set-NetFirewallProfile`) | N/A | Full (pf) | Full (ufw/firewalld) |
|
|
44
|
+
| `dot security lock-configs` | N/A (Windows ACL model differs; tracked) | N/A | Full (`chflags uchg`) | Full (`chattr +i`) |
|
|
45
|
+
| Shell startup integration | Full (`Microsoft.PowerShell_profile.ps1` ships) | Full (zsh, bash, fish) | Full (zsh, bash, fish, nu) | Full (zsh, bash, fish, nu) |
|
|
46
|
+
|
|
47
|
+
## What CI verifies
|
|
48
|
+
|
|
49
|
+
| Check | Platforms |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `dot version` exits 0 | windows-latest, ubuntu-latest, macos-latest, macos-14 |
|
|
52
|
+
| `dot help` exits 0 | windows-latest, ubuntu-latest, macos-latest, macos-14 |
|
|
53
|
+
| `dot agents check` exits 0 | windows-latest (via bash on PATH), ubuntu-latest, macos-latest, macos-14 |
|
|
54
|
+
| PowerShell ≥ 7.4 | windows-latest (smoke test at `scripts/ci/windows-smoke-test.ps1`) |
|
|
55
|
+
| PSScriptAnalyzer Error-level findings | windows-latest |
|
|
56
|
+
| `chezmoi --version` exits 0 | windows-latest (via scoop), all Unix matrices |
|
|
57
|
+
| `bash scripts/ci/dot-cli-startup-bench.sh` median < 200ms | macos-latest |
|
|
58
|
+
| `bash scripts/ci/dot-cli-startup-bench.sh` median < 150ms | ubuntu-latest |
|
|
59
|
+
|
|
60
|
+
## Known parity gaps (deferred to follow-up PRs)
|
|
61
|
+
|
|
62
|
+
These are scoped in [`ROADMAP_2026 §C5`](../operations/ROADMAP_2026.md) but not in this PR's scope:
|
|
63
|
+
|
|
64
|
+
1. **Native PowerShell `dot.ps1` dispatcher.** Today the Windows path either uses `bash dot` (when bash is on PATH via Git for Windows / WSL) or shells out via `pwsh -Command`. A truly idiomatic PowerShell dispatcher with native cmdlet semantics (verb-noun naming, `[CmdletBinding()]`, pipeline support) is a separate effort.
|
|
65
|
+
2. **Wallpaper-driven theming on Windows.** Requires SystemParametersInfo W32 API or `Set-ItemProperty HKCU:\Control Panel\Desktop`. macOS-native today.
|
|
66
|
+
3. **`dot security lock-configs` for Windows.** Needs an ACL/EFS-based equivalent of `chattr +i` / `chflags uchg`. Not in this PR's scope; documented here so it doesn't get re-discovered.
|
|
67
|
+
4. **Native Windows installer signing.** The repo signs the bootstrap tarball via Cosign keyless; a SignTool-signed `install.ps1` for code-signing-enforced enterprises is a future enhancement.
|
|
68
|
+
|
|
69
|
+
## Source-of-truth files
|
|
70
|
+
|
|
71
|
+
- `.github/workflows/ci.yml` — `test-windows` job (pwsh + scoop + chezmoi + smoke)
|
|
72
|
+
- `scripts/ci/windows-smoke-test.ps1` — the actual gate
|
|
73
|
+
- `dot_config/powershell/Microsoft.PowerShell_profile.ps1` — the deployed PowerShell profile (if present)
|
|
74
|
+
- `scripts/dot/lib/platform.sh` — the dot_path_to_unix/native bridge (H9 audit fix)
|
|
75
|
+
|
|
76
|
+
## Why this matters
|
|
77
|
+
|
|
78
|
+
The 2026 trend brief flagged Windows-as-first-class as the highest-leverage move post-Codex-Windows-GA (launched 2026-03-04 with 500k waitlist → 2M WAU in 4 weeks). PowerShell 7.4 LTS retires 2026-11-10, so the gate sits at "7.4+" today and will tighten to "7.5+" in November. Documented here so the gate change is not a surprise.
|
|
79
|
+
|
|
80
|
+
Generated 2026-05-16 alongside the round-2 hard audit. Maintained: when adding a `dot` subcommand, update this matrix in the same PR.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
render_with_liquid: false
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# CI Dependency Pinning Policy
|
|
6
|
+
|
|
7
|
+
Every external dependency the CI pipeline consumes must be pinned by
|
|
8
|
+
40-hex commit SHA. The policy applies to:
|
|
9
|
+
|
|
10
|
+
1. **Third-party actions** — `uses: owner/action@<sha>` (already enforced via Scorecard's `Pinned-Dependencies` check at score ≥ 9).
|
|
11
|
+
2. **Reusable workflows in this repo** — `uses: sebastienrousseau/dotfiles/.github/workflows/reusable-X.yml@<sha>` (added with [#855](https://github.com/sebastienrousseau/dotfiles/issues/855); enforced by `scripts/ci/lint-reusable-pins.sh`).
|
|
12
|
+
3. **Container base images** — `FROM image:tag@sha256:<digest>` (closed by [#886](https://github.com/sebastienrousseau/dotfiles/pull/886)).
|
|
13
|
+
4. **Release binaries downloaded at build time** — `curl … && echo "<sha256> ..." | sha256sum -c` (closed by [#888](https://github.com/sebastienrousseau/dotfiles/pull/888)).
|
|
14
|
+
|
|
15
|
+
## Why SHA-pin reusable workflows
|
|
16
|
+
|
|
17
|
+
When `ci.yml` calls a reusable via `./.github/workflows/reusable-X.yml`,
|
|
18
|
+
GitHub resolves the reusable from the **same ref as the calling
|
|
19
|
+
workflow at run time**. For an in-repo PR that's the PR's branch —
|
|
20
|
+
fine. The risk is the inverse: a malicious push to `master` (or any
|
|
21
|
+
ref the calling workflow might resolve from) can swap reusable
|
|
22
|
+
content under a CI run, with no audit trail in the PR diff.
|
|
23
|
+
|
|
24
|
+
Pinning to a 40-hex SHA freezes the reusable's content at the
|
|
25
|
+
pinned commit. To swap the reusable, you have to bump every call
|
|
26
|
+
site — visible in the PR diff, reviewable, revertible.
|
|
27
|
+
|
|
28
|
+
## Acceptable forms
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
# Acceptable — full SHA pin.
|
|
32
|
+
uses: sebastienrousseau/dotfiles/.github/workflows/reusable-shell-lint.yml@b0615f8fb5c0f3826f58904a5567eff11b6c500e # master
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The trailing comment is a human-readable hint at what the SHA
|
|
36
|
+
represented when it was pinned (typically `master`, sometimes a tag
|
|
37
|
+
like `v0.2.501`). The hint is documentation only — the SHA is what
|
|
38
|
+
GitHub uses.
|
|
39
|
+
|
|
40
|
+
## Rejected forms
|
|
41
|
+
|
|
42
|
+
```yaml
|
|
43
|
+
# Rejected — relative path is a mutable ref.
|
|
44
|
+
uses: ./.github/workflows/reusable-shell-lint.yml
|
|
45
|
+
|
|
46
|
+
# Rejected — branch ref is mutable.
|
|
47
|
+
uses: sebastienrousseau/dotfiles/.github/workflows/reusable-shell-lint.yml@master
|
|
48
|
+
|
|
49
|
+
# Rejected — tag ref is mutable (tags can be moved).
|
|
50
|
+
uses: sebastienrousseau/dotfiles/.github/workflows/reusable-shell-lint.yml@v0.2.501
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The `lint-reusable-pins` job in `ci.yml` runs `scripts/ci/lint-reusable-pins.sh`
|
|
54
|
+
on every workflow change. The lint fails the build on any of the
|
|
55
|
+
rejected forms above.
|
|
56
|
+
|
|
57
|
+
## Refreshing pinned SHAs
|
|
58
|
+
|
|
59
|
+
Reusable workflows in this repo are the only same-repo dependency
|
|
60
|
+
Dependabot doesn't auto-bump. Refresh manually after a change to a
|
|
61
|
+
reusable:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
# 1. Land the change to the reusable on master via a PR.
|
|
65
|
+
# 2. After merge, capture the new master SHA:
|
|
66
|
+
git fetch origin master
|
|
67
|
+
PIN=$(git rev-parse origin/master)
|
|
68
|
+
echo "$PIN"
|
|
69
|
+
|
|
70
|
+
# 3. Bump every call site:
|
|
71
|
+
find .github/workflows -name '*.yml' -exec sed -i.bak -E \
|
|
72
|
+
"s|(/reusable-[a-z0-9-]+\.yml@)[0-9a-f]{40}|\\1${PIN}|g" {} +
|
|
73
|
+
rm -f .github/workflows/*.bak
|
|
74
|
+
|
|
75
|
+
# 4. Verify the lint still passes:
|
|
76
|
+
bash scripts/ci/lint-reusable-pins.sh
|
|
77
|
+
|
|
78
|
+
# 5. Land the bump on a follow-up PR with a single-purpose commit:
|
|
79
|
+
git commit -am "chore(ci): bump reusable-workflow pins to ${PIN:0:10}"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
We treat the manual bump as **deliberate**, not a chore — it forces
|
|
83
|
+
a reviewer to confirm the new reusable content is intentional.
|
|
84
|
+
|
|
85
|
+
## Dependabot
|
|
86
|
+
|
|
87
|
+
Dependabot's `github-actions` ecosystem **does not** support same-repo
|
|
88
|
+
reusable workflow SHA bumps as of May 2026 — it only updates
|
|
89
|
+
references to external actions. Same-repo reusables are tracked
|
|
90
|
+
manually via the recipe above. The Dependabot config in
|
|
91
|
+
`.github/dependabot.yml` covers the external dimension; this
|
|
92
|
+
document covers the in-repo one.
|
|
93
|
+
|
|
94
|
+
If GitHub ships native Dependabot support for reusable workflows,
|
|
95
|
+
delete this section and switch to `package-ecosystem: github-actions`
|
|
96
|
+
with `directory: /.github/workflows`. Track on
|
|
97
|
+
[github/feedback#10539](https://github.com/orgs/community/discussions/10539).
|
|
98
|
+
|
|
99
|
+
## Negative test
|
|
100
|
+
|
|
101
|
+
`tests/unit/ci/test_reusable_pin_lint.sh` deliberately drops an
|
|
102
|
+
unpinned reusable reference into a sandboxed workflow tree and
|
|
103
|
+
asserts that `lint-reusable-pins.sh` exits non-zero with the
|
|
104
|
+
expected error message. The test runs as part of the standard
|
|
105
|
+
test suite — a regression in the lint catches at PR time, not at
|
|
106
|
+
merge time.
|
|
107
|
+
|
|
108
|
+
## See also
|
|
109
|
+
|
|
110
|
+
- [#855](https://github.com/sebastienrousseau/dotfiles/issues/855) — original tracking issue.
|
|
111
|
+
- `scripts/ci/lint-reusable-pins.sh` — the enforcement script.
|
|
112
|
+
- `tests/unit/ci/test_reusable_pin_lint.sh` — the negative test.
|
|
113
|
+
- [GitHub: pinning actions to a full-length commit SHA](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-third-party-actions).
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Commit Signing — Policy & Setup
|
|
2
|
+
|
|
3
|
+
Every commit that reaches `master` in this repository must carry a
|
|
4
|
+
cryptographic signature that GitHub can verify. The policy is enforced
|
|
5
|
+
in three independent layers, so a single bypass does not break the
|
|
6
|
+
chain. This document explains the policy, walks through SSH and GPG
|
|
7
|
+
setup, and lists the verification commands you can run locally before
|
|
8
|
+
pushing.
|
|
9
|
+
|
|
10
|
+
## Why
|
|
11
|
+
|
|
12
|
+
Closes [#853](https://github.com/sebastienrousseau/dotfiles/issues/853).
|
|
13
|
+
Local hooks alone are bypassable — `git commit --no-verify`,
|
|
14
|
+
`git push --no-verify`, or unsetting `DOTFILES_ALLOW_UNSKIPPED_PUSH`
|
|
15
|
+
all let an unsigned commit reach a remote if GitHub-side enforcement
|
|
16
|
+
is missing. The chain below removes every escape hatch.
|
|
17
|
+
|
|
18
|
+
## Enforcement layers
|
|
19
|
+
|
|
20
|
+
1. **Local commit hook** — `dot_config/git/hooks/executable_commit-msg`
|
|
21
|
+
is deployed by chezmoi. It rejects an unsigned commit at the
|
|
22
|
+
`commit-msg` stage on the developer machine.
|
|
23
|
+
2. **Local pre-push hook** — `scripts/git-hooks/pre-push` runs
|
|
24
|
+
`git verify-commit` against every commit in the push range. A
|
|
25
|
+
single unverified commit aborts the push. `--no-verify` skips
|
|
26
|
+
this layer; the next two catch it.
|
|
27
|
+
3. **GitHub Rulesets** — `.github/rulesets/master.json` declares
|
|
28
|
+
`required_signatures` on `refs/heads/master`. The rule is part
|
|
29
|
+
of the repo so it's reproducible across forks. Apply with
|
|
30
|
+
`gh ruleset import .github/rulesets/master.json`.
|
|
31
|
+
4. **`compliance-guard.yml` workflow** — runs on every PR targeting
|
|
32
|
+
`master`. Walks the commit range with `git verify-commit` and
|
|
33
|
+
marks unsigned commits in the PR summary; fails the workflow
|
|
34
|
+
when `unsigned_count > 0`.
|
|
35
|
+
|
|
36
|
+
A merge to `master` therefore requires (Ruleset accepts the push) AND
|
|
37
|
+
(the workflow's signed-commit check passes) AND (the maintainer's
|
|
38
|
+
push key is allowed). The protection holds even if a contributor's
|
|
39
|
+
local hooks are missing or skipped.
|
|
40
|
+
|
|
41
|
+
## Setting up SSH signing (recommended)
|
|
42
|
+
|
|
43
|
+
SSH signing is the 2026 default. It reuses your existing SSH key —
|
|
44
|
+
no new key material, no GPG agent, no Kleopatra UI. GitHub has
|
|
45
|
+
recognised SSH signatures since [2022](https://github.blog/changelog/2022-08-23-ssh-commit-verification-now-supported/).
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
# 1. Tell git to sign with SSH.
|
|
49
|
+
git config --global gpg.format ssh
|
|
50
|
+
|
|
51
|
+
# 2. Point at the SSH key you want git to use.
|
|
52
|
+
git config --global user.signingkey "$HOME/.ssh/id_ed25519.pub"
|
|
53
|
+
|
|
54
|
+
# 3. Turn on auto-signing for every commit and tag.
|
|
55
|
+
git config --global commit.gpgsign true
|
|
56
|
+
git config --global tag.gpgsign true
|
|
57
|
+
|
|
58
|
+
# 4. Tell GitHub which SSH key signs your commits.
|
|
59
|
+
gh ssh-key add ~/.ssh/id_ed25519.pub --type signing --title "$(hostname) signing"
|
|
60
|
+
|
|
61
|
+
# 5. (Optional) Populate the allowed_signers file so
|
|
62
|
+
# `git log --show-signature` can verify locally.
|
|
63
|
+
mkdir -p "$HOME/.config/git"
|
|
64
|
+
echo "$(git config user.email) $(cat ~/.ssh/id_ed25519.pub)" \
|
|
65
|
+
> "$HOME/.config/git/allowed_signers"
|
|
66
|
+
git config --global gpg.ssh.allowedSignersFile \
|
|
67
|
+
"$HOME/.config/git/allowed_signers"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
After this, `git log --show-signature` shows `Good "git" signature
|
|
71
|
+
for you@example.com with ED25519 key SHA256:…` on every new commit.
|
|
72
|
+
|
|
73
|
+
## Setting up GPG signing (legacy / for tag-signing mirrors that don't yet support SSH)
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
# 1. Generate or import a key. ED25519 is the modern recommendation;
|
|
77
|
+
# RSA-4096 is the conservative fallback.
|
|
78
|
+
gpg --quick-generate-key "Your Name <you@example.com>" ed25519 sign 2y
|
|
79
|
+
|
|
80
|
+
# 2. Find the long key ID.
|
|
81
|
+
gpg --list-secret-keys --keyid-format=long
|
|
82
|
+
|
|
83
|
+
# 3. Tell git which key.
|
|
84
|
+
git config --global user.signingkey <LONG_KEY_ID>
|
|
85
|
+
git config --global commit.gpgsign true
|
|
86
|
+
|
|
87
|
+
# 4. Export and upload the public key to GitHub.
|
|
88
|
+
gpg --armor --export <LONG_KEY_ID> | gh gpg-key add -
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Verifying locally before pushing
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
# Every commit in the push range.
|
|
95
|
+
git log "$(git merge-base @{u} HEAD)..HEAD" \
|
|
96
|
+
--pretty='%H %G? %s' \
|
|
97
|
+
| awk '$2 != "G" {print "UNSIGNED:", $0}'
|
|
98
|
+
|
|
99
|
+
# Or the canonical command the pre-push hook uses:
|
|
100
|
+
for c in $(git rev-list "$(git merge-base @{u} HEAD)..HEAD"); do
|
|
101
|
+
git verify-commit "$c" >/dev/null 2>&1 \
|
|
102
|
+
&& echo "✓ $c" \
|
|
103
|
+
|| echo "✗ $c — unsigned, will be rejected by master ruleset"
|
|
104
|
+
done
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Troubleshooting
|
|
108
|
+
|
|
109
|
+
| Symptom | Likely cause | Fix |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| `error: gpg failed to sign the data` | gpg-agent isn't running, or `GPG_TTY` not exported | `export GPG_TTY=$(tty)` in your shell rc; restart agent with `gpgconf --kill gpg-agent` |
|
|
112
|
+
| `error: Load key "/.../id_ed25519": Permission denied` | SSH key permissions too open | `chmod 600 ~/.ssh/id_ed25519` |
|
|
113
|
+
| GitHub shows "Unverified" on a commit signed locally | Signing key not uploaded to GitHub | `gh ssh-key add … --type signing` (SSH) or `gh gpg-key add` (GPG) |
|
|
114
|
+
| Pre-push hook rejects a merge commit you didn't author | Upstream commit lacks a signature | Either pull the rebased branch, or fast-forward instead of merging |
|
|
115
|
+
| Ruleset import via `gh` complains "invalid JSON" | Rulesets API expects the `target` + `rules` envelope, not just the rules array | Use the file as-is — `gh ruleset import .github/rulesets/master.json` |
|
|
116
|
+
|
|
117
|
+
## Re-applying the ruleset after a manual edit
|
|
118
|
+
|
|
119
|
+
If someone edits the ruleset in the GitHub UI by mistake, the
|
|
120
|
+
file-of-truth wins. Re-apply:
|
|
121
|
+
|
|
122
|
+
```sh
|
|
123
|
+
gh api -X POST repos/{owner}/{repo}/rulesets \
|
|
124
|
+
--input .github/rulesets/master.json
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
(or `-X PUT` against the existing ruleset's ID if it already exists).
|
|
128
|
+
|
|
129
|
+
## References
|
|
130
|
+
|
|
131
|
+
- `.github/rulesets/master.json` — the enforced policy.
|
|
132
|
+
- `.github/workflows/compliance-guard.yml` — the workflow that
|
|
133
|
+
fails PRs containing unsigned commits.
|
|
134
|
+
- `scripts/git-hooks/pre-push` — the local pre-push gate.
|
|
135
|
+
- `dot_config/git/hooks/executable_commit-msg` — the local
|
|
136
|
+
commit-msg gate.
|
|
137
|
+
- [GitHub: About commit signature verification](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification)
|
|
138
|
+
- [GitHub: Telling Git about your SSH key](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Encrypted Security Disclosure — Reporter Workflow
|
|
2
|
+
|
|
3
|
+
This document is for **external security researchers** who have found
|
|
4
|
+
an issue in this repository and want to report it without exposing
|
|
5
|
+
sensitive details (PII, exploit chains, internal infrastructure) in
|
|
6
|
+
transit or at rest on third-party servers.
|
|
7
|
+
|
|
8
|
+
If your report doesn't need encryption, please use
|
|
9
|
+
[GitHub Security Advisories](https://github.com/sebastienrousseau/dotfiles/security/advisories)
|
|
10
|
+
instead — it's faster and the GitHub-side audit trail is stronger.
|
|
11
|
+
|
|
12
|
+
## 1. Fetch the maintainer's public key
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
# Web Key Directory is the modern recommendation.
|
|
16
|
+
gpg --auto-key-locate clear,wkd \
|
|
17
|
+
--locate-keys security@sebastienrousseau.com
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
If WKD fails (offline keyserver, corporate proxy), download directly:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
curl -fsSL \
|
|
24
|
+
https://sebastienrousseau.com/.well-known/openpgpkey/hu/qpzqfwauiwxnu1xrf5h47bunsho44p6f \
|
|
25
|
+
| gpg --import
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 2. Verify the fingerprint
|
|
29
|
+
|
|
30
|
+
**Do not skip this step.** A MITM attacker can serve their own key.
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
gpg --fingerprint security@sebastienrousseau.com
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The fingerprint must match the one published in
|
|
37
|
+
[`.github/SECURITY.md`](../../.github/SECURITY.md). Cross-verify
|
|
38
|
+
against the SSH signing key in `dot_config/git/allowed_signers.tmpl`
|
|
39
|
+
— the maintainer signs commits with the same identity, so both
|
|
40
|
+
fingerprints derive from the same identity claim.
|
|
41
|
+
|
|
42
|
+
If the two fingerprints don't match, **stop**. Open a private
|
|
43
|
+
[Security Advisory](https://github.com/sebastienrousseau/dotfiles/security/advisories/new)
|
|
44
|
+
and ask the maintainer to confirm the key out-of-band.
|
|
45
|
+
|
|
46
|
+
## 3. Encrypt your report
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
cat <<EOF | gpg --encrypt --armor \
|
|
50
|
+
--recipient security@sebastienrousseau.com \
|
|
51
|
+
--output disclosure.asc
|
|
52
|
+
Subject: <short description of the issue>
|
|
53
|
+
|
|
54
|
+
<full details — repro steps, affected versions, exploit, screenshots
|
|
55
|
+
referenced by base64-encoded attachments, contact info for follow-up>
|
|
56
|
+
EOF
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Verify the output is an ASCII-armored OpenPGP message before sending:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
head -1 disclosure.asc
|
|
63
|
+
# Expected: -----BEGIN PGP MESSAGE-----
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## 4. Send
|
|
67
|
+
|
|
68
|
+
Attach `disclosure.asc` to an email to
|
|
69
|
+
**<security@sebastienrousseau.com>**.
|
|
70
|
+
|
|
71
|
+
Subject line: `[dotfiles] <severity>: <short description>` (no
|
|
72
|
+
encryption-sensitive content in the subject; mail relays log
|
|
73
|
+
subjects in plain text).
|
|
74
|
+
|
|
75
|
+
## 5. Acknowledgement
|
|
76
|
+
|
|
77
|
+
You should receive an acknowledgement within the SLA in
|
|
78
|
+
[`.github/SECURITY.md`](../../.github/SECURITY.md):
|
|
79
|
+
|
|
80
|
+
| Severity | Initial response |
|
|
81
|
+
|---|---|
|
|
82
|
+
| Critical | 24 hours |
|
|
83
|
+
| High | 72 hours |
|
|
84
|
+
| Medium | 5 business days |
|
|
85
|
+
| Low | 10 business days |
|
|
86
|
+
|
|
87
|
+
The acknowledgement will be signed with the same key you encrypted
|
|
88
|
+
to. Verify the signature:
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
gpg --verify response.asc
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
If you don't receive an acknowledgement in the SLA window, or the
|
|
95
|
+
acknowledgement signature doesn't verify, escalate by:
|
|
96
|
+
|
|
97
|
+
1. Opening a private GitHub Security Advisory.
|
|
98
|
+
2. DM-ing the maintainer on Mastodon: `@sebastienrousseau@hachyderm.io`.
|
|
99
|
+
|
|
100
|
+
## 6. After disclosure
|
|
101
|
+
|
|
102
|
+
The maintainer commits to:
|
|
103
|
+
|
|
104
|
+
- A fix timeline communicated within the SLA.
|
|
105
|
+
- A CVE assignment when severity ≥ Medium and the issue affects
|
|
106
|
+
published releases (npm package, release tarballs).
|
|
107
|
+
- Public credit at fix time in the release notes (unless you
|
|
108
|
+
request anonymity at report time).
|
|
109
|
+
- A backport to any actively-supported release line listed in
|
|
110
|
+
`.github/SECURITY.md`.
|
|
111
|
+
|
|
112
|
+
## What NOT to do
|
|
113
|
+
|
|
114
|
+
- Don't post details in a public issue, even a "I found a bug, ping
|
|
115
|
+
me for details" placeholder — every public reference is a tip-off
|
|
116
|
+
to attackers monitoring the repo.
|
|
117
|
+
- Don't disclose to third parties (other dotfile maintainers,
|
|
118
|
+
security mailing lists, blog posts) before the embargo lifts.
|
|
119
|
+
- Don't access data or systems you don't own.
|
|
120
|
+
- Don't run automated scans against `*.sebastienrousseau.com` that
|
|
121
|
+
exceed normal browser-like traffic.
|
|
122
|
+
|
|
123
|
+
## See also
|
|
124
|
+
|
|
125
|
+
- [`.github/SECURITY.md`](../../.github/SECURITY.md) — policy
|
|
126
|
+
overview + fingerprint.
|
|
127
|
+
- [`docs/security/KEY_ROTATION.md`](KEY_ROTATION.md) — schedule for
|
|
128
|
+
when the disclosure key is rotated.
|
|
129
|
+
- [`docs/security/AUDIT_BYPASS.md`](AUDIT_BYPASS.md) — separate flow
|
|
130
|
+
for the pre-push audit bypass (not for disclosure).
|
|
@@ -4,7 +4,87 @@ render_with_liquid: false
|
|
|
4
4
|
|
|
5
5
|
# Key Rotation Guide
|
|
6
6
|
|
|
7
|
-
This
|
|
7
|
+
This page tracks two distinct key lifecycles:
|
|
8
|
+
|
|
9
|
+
1. **Disclosure key** — the GPG key reporters use to encrypt vulnerability reports to the maintainer. Single key, public, rotated annually or on compromise. Source of truth for the active fingerprint is `.github/SECURITY.md`.
|
|
10
|
+
2. **Secrets encryption keys** — the Age and SOPS keys that protect encrypted dotfiles in this repo. Multiple keys, per-machine, rotated annually or on personnel / device change.
|
|
11
|
+
|
|
12
|
+
Both sections live here so a single audit can confirm the project's
|
|
13
|
+
posture across encrypted-disclosure-in and encrypted-secrets-at-rest.
|
|
14
|
+
|
|
15
|
+
## Disclosure Key (GPG)
|
|
16
|
+
|
|
17
|
+
Closes the rotation half of [#870](https://github.com/sebastienrousseau/dotfiles/issues/870).
|
|
18
|
+
|
|
19
|
+
### Active key
|
|
20
|
+
|
|
21
|
+
| Field | Value |
|
|
22
|
+
|---|---|
|
|
23
|
+
| Identity | `security@sebastienrousseau.com` |
|
|
24
|
+
| Fingerprint | `55AFAD364FD9DB3819E61F0C8D688FAFA9144693` |
|
|
25
|
+
| Algorithm | ED25519 signing primary + CV25519 encryption subkey |
|
|
26
|
+
| Created | 2026-05-16 |
|
|
27
|
+
| Expires | 2029-05-15 (3 years from creation) |
|
|
28
|
+
| WKD URL | <https://sebastienrousseau.com/.well-known/openpgpkey/hu/qpzqfwauiwxnu1xrf5h47bunsho44p6f> |
|
|
29
|
+
| Cross-reference | matches the SSH signing key in `dot_config/git/allowed_signers.tmpl` |
|
|
30
|
+
|
|
31
|
+
### Rotation triggers
|
|
32
|
+
|
|
33
|
+
| Trigger | Response time | Action |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| Annual cadence | Within 30 days of expiry | Generate new key, publish, sign transition statement with the old key, archive old key below. |
|
|
36
|
+
| Suspected compromise | Same day | Revoke immediately, publish revocation cert to WKD, replay in-flight encrypted disclosures with the new key. |
|
|
37
|
+
| Algorithm deprecation | Within 90 days of advisory | Generate replacement on a stronger algorithm; follow annual-cadence steps. |
|
|
38
|
+
| Maintainer change | Same day as handover | Old maintainer signs a transition statement; old key revoked 30 days after handover. |
|
|
39
|
+
|
|
40
|
+
### Rotation procedure
|
|
41
|
+
|
|
42
|
+
1. Generate the new key (air-gapped where possible):
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
gpg --quick-generate-key \
|
|
46
|
+
"Sebastien Rousseau (Security) <security@sebastienrousseau.com>" \
|
|
47
|
+
ed25519 sign 1y
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
2. Sign a transition statement with the OLD key:
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
gpg --clearsign <<EOF
|
|
54
|
+
Transition statement, $(date -u +%Y-%m-%dT%H:%M:%SZ).
|
|
55
|
+
The disclosure key for security@sebastienrousseau.com rotates from:
|
|
56
|
+
OLD: <old-fingerprint>
|
|
57
|
+
NEW: <new-fingerprint>
|
|
58
|
+
Encrypted reports sent to either key during the 30-day overlap
|
|
59
|
+
will be accepted. After <UTC-cutoff>, only the new key is honoured.
|
|
60
|
+
EOF
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
3. Publish the new key to:
|
|
64
|
+
- `sebastienrousseau.com/.well-known/openpgpkey/hu/` (WKD)
|
|
65
|
+
- the maintainer's keyoxide profile if applicable
|
|
66
|
+
|
|
67
|
+
4. Update the "Active key" table above with the new fingerprint and
|
|
68
|
+
move the previous row into the "Historical disclosure keys"
|
|
69
|
+
table below with the actual revocation date.
|
|
70
|
+
|
|
71
|
+
5. Commit, signing with both old and new keys during the overlap:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
git commit -S -m "security(key): rotate disclosure key $(date +%Y-%m)" \
|
|
75
|
+
-m "$(cat transition.asc)"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
6. After the 30-day overlap, revoke the old key and publish the
|
|
79
|
+
revocation cert to the same WKD endpoint.
|
|
80
|
+
|
|
81
|
+
### Historical disclosure keys
|
|
82
|
+
|
|
83
|
+
| From | Until | Fingerprint | Reason for rotation |
|
|
84
|
+
|---|---|---|---|
|
|
85
|
+
| *(none yet — first key)* | *—* | *—* | *—* |
|
|
86
|
+
|
|
87
|
+
---
|
|
8
88
|
|
|
9
89
|
## Age Key Rotation
|
|
10
90
|
|