@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.
Files changed (65) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/README.md +38 -8
  3. package/docs/COPYRIGHT +1 -1
  4. package/docs/index.md +2 -2
  5. package/docs/manual/00-introduction.md +1 -1
  6. package/docs/manual/03-reference/01-dot-cli.md +63 -45
  7. package/docs/manual/03-reference/02-config-files.md +2 -2
  8. package/docs/manual/03-reference/05-feature-flags.md +62 -3
  9. package/docs/manual/_toc.yml +1 -1
  10. package/docs/manual/command-index.md +12 -8
  11. package/docs/manual/index.md +66 -0
  12. package/docs/operations/COVERAGE.md +36 -2
  13. package/docs/operations/HARD_AUDIT_2026.md +631 -0
  14. package/docs/operations/REGISTRY.md +89 -0
  15. package/docs/operations/ROADMAP_2026.md +665 -0
  16. package/docs/operations/TRACEABILITY.md +3 -0
  17. package/docs/operations/VERSION_SYNC.md +4 -4
  18. package/docs/reference/POWERSHELL_PARITY.md +80 -0
  19. package/docs/registry.json +6 -0
  20. package/docs/security/CI_PINNING.md +113 -0
  21. package/docs/security/COMMIT_SIGNING.md +138 -0
  22. package/docs/security/DISCLOSURE.md +130 -0
  23. package/docs/security/KEY_ROTATION.md +81 -1
  24. package/docs/security/SCORECARD.md +74 -14
  25. package/docs/security/security-pubkey.asc +15 -0
  26. package/dot_config/fish/conf.d/direnv.fish +4 -0
  27. package/dot_config/fish/conf.d/mise-activate.fish +5 -0
  28. package/dot_config/git/hooks/executable_commit-msg +1 -1
  29. package/dot_config/shell/00-core-paths.sh.tmpl +8 -1
  30. package/dot_config/shell/README.md +1 -1
  31. package/dot_config/zsh/dot_zshrc.tmpl +57 -4
  32. package/dot_config/zsh/rc.d/30-options.zsh.tmpl +1 -1
  33. package/dot_local/bin/executable_dot +49 -9
  34. package/dot_local/bin/executable_dot-bootstrap +0 -1
  35. package/dot_local/bin/executable_dot-theme-sync +8 -8
  36. package/dot_local/bin/executable_tour +2 -2
  37. package/dot_local/share/man/man1/dot.1 +1 -1
  38. package/dot_local/share/zsh/completions/_dot +4 -0
  39. package/install.sh +44 -37
  40. package/package.json +1 -1
  41. package/scripts/ci/dot-cli-startup-bench.sh +126 -0
  42. package/scripts/ci/install-chezmoi-verified.sh +4 -1
  43. package/scripts/ci/lint-reusable-pins.sh +78 -0
  44. package/scripts/ci/run-coverage.sh +89 -0
  45. package/scripts/ci/windows-smoke-test.ps1 +136 -0
  46. package/scripts/diagnostics/doctor.sh +39 -9
  47. package/scripts/dot/commands/agent.sh +19 -22
  48. package/scripts/dot/commands/agents.sh +325 -0
  49. package/scripts/dot/commands/aliases.sh +10 -8
  50. package/scripts/dot/commands/core.sh +10 -4
  51. package/scripts/dot/commands/fleet.sh +278 -3
  52. package/scripts/dot/commands/init.sh +184 -0
  53. package/scripts/dot/commands/meta.sh +7 -4
  54. package/scripts/dot/commands/registry.sh +263 -0
  55. package/scripts/dot/commands/tools.sh +49 -1
  56. package/scripts/dot/lib/bento.sh +1 -1
  57. package/scripts/dot/lib/platform.sh +21 -8
  58. package/scripts/dot/lib/ui.sh +134 -2
  59. package/scripts/dot/lib/utils.sh +1 -1
  60. package/scripts/git-hooks/pre-commit-audit.sh +1 -1
  61. package/scripts/lib/secrets_provider.sh +32 -6
  62. package/scripts/ops/rollback.sh +14 -0
  63. package/scripts/security/check-disclosure-key-expiry.sh +110 -0
  64. package/scripts/security/lock-configs.sh +11 -2
  65. 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.501
97
- **Dotfiles Version**: v0.2.501
98
- Version: v0.2.501
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.501
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,6 @@
1
+ {
2
+ "version": 1,
3
+ "updated": "2026-05-15T16:30:00Z",
4
+ "registry": "sebastienrousseau/dotfiles",
5
+ "modules": []
6
+ }
@@ -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 guide covers rotating encryption keys for secrets managed by Age and SOPS.
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