@sebastienrousseau/dotfiles 0.2.500 → 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 (237) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/README.md +113 -45
  3. package/docs/.vitepress/reports/localization-readability-audit.md +4 -0
  4. package/docs/AI.md +8 -2
  5. package/docs/CNAME +1 -0
  6. package/docs/COPYRIGHT +1 -1
  7. package/docs/NAMING_CONVENTIONS.md +7 -0
  8. package/docs/README.md +4 -0
  9. package/docs/_config.yml +59 -0
  10. package/docs/adr/ADR-001-ci-cd-pipeline.md +17 -0
  11. package/docs/adr/ADR-002-shell-performance.md +9 -0
  12. package/docs/adr/ADR-003-security-first.md +18 -0
  13. package/docs/adr/ADR-004-cli-architecture.md +14 -0
  14. package/docs/adr/ADR-005-chezmoi-choice.md +10 -0
  15. package/docs/adr/ADR-006-shell-selection.md +9 -0
  16. package/docs/adr/ADR-007-multi-shell-parity.md +10 -1
  17. package/docs/adr/ADR-008-alias-system-architecture.md +10 -0
  18. package/docs/adr/ADR-009-wallpaper-driven-theming.md +131 -0
  19. package/docs/adr/ADR-010-starship-transient-prompt.md +144 -0
  20. package/docs/adr/ADR-011-nushell-tier3-keep.md +144 -0
  21. package/docs/adr/README.md +7 -0
  22. package/docs/architecture/ARCHITECTURE.md +4 -0
  23. package/docs/architecture/INTEROP.md +8 -0
  24. package/docs/architecture/REPO_LAYOUT.md +5 -1
  25. package/docs/architecture/WALKTHROUGH.md +4 -0
  26. package/docs/architecture/fleet-deployment.md +4 -0
  27. package/docs/archive/EUXIS_2026_REVIEW.md +11 -3
  28. package/docs/archive/LEGACY_ROADMAP.md +45 -27
  29. package/docs/archive/MILESTONE_v0.2.493.md +4 -0
  30. package/docs/archive/PLAN.md +46 -8
  31. package/docs/archive/REPO_AUDIT.md +8 -0
  32. package/docs/guides/INSTALL.md +4 -0
  33. package/docs/guides/NEOVIM_IDE_GUIDE.md +9 -0
  34. package/docs/guides/THEMING.md +8 -0
  35. package/docs/guides/TROUBLESHOOTING.md +28 -0
  36. package/docs/guides/WSL2_NIX_TROUBLESHOOTING.md +70 -1
  37. package/docs/index.md +6 -2
  38. package/docs/interop/A2A.md +7 -0
  39. package/docs/interop/POWERSHELL.md +102 -0
  40. package/docs/manual/00-introduction.md +5 -1
  41. package/docs/manual/01-concepts/01-architecture.md +4 -0
  42. package/docs/manual/01-concepts/02-trust-model.md +6 -2
  43. package/docs/manual/01-concepts/03-theme-engine.md +4 -0
  44. package/docs/manual/01-concepts/04-fleet.md +7 -1
  45. package/docs/manual/01-concepts/05-self-healing.md +8 -1
  46. package/docs/manual/02-tutorials/01-first-install.md +5 -0
  47. package/docs/manual/02-tutorials/02-add-wallpaper.md +5 -0
  48. package/docs/manual/02-tutorials/03-create-profile.md +6 -0
  49. package/docs/manual/02-tutorials/04-encrypt-secret.md +6 -0
  50. package/docs/manual/02-tutorials/05-deploy-fleet.md +5 -1
  51. package/docs/manual/03-reference/01-dot-cli.md +67 -45
  52. package/docs/manual/03-reference/02-config-files.md +8 -2
  53. package/docs/manual/03-reference/03-environment.md +4 -0
  54. package/docs/manual/03-reference/04-templates.md +7 -1
  55. package/docs/manual/03-reference/05-feature-flags.md +72 -3
  56. package/docs/manual/04-cookbook/01-recipes.md +4 -0
  57. package/docs/manual/04-cookbook/02-troubleshooting.md +32 -0
  58. package/docs/manual/04-cookbook/03-faq.md +8 -0
  59. package/docs/manual/05-appendices/A-platform-matrix.md +4 -0
  60. package/docs/manual/05-appendices/B-security-checklist.md +6 -0
  61. package/docs/manual/05-appendices/C-glossary.md +4 -0
  62. package/docs/manual/05-appendices/D-bibliography.md +5 -1
  63. package/docs/manual/05-appendices/E-license.md +4 -0
  64. package/docs/manual/_toc.yml +1 -1
  65. package/docs/manual/command-index.md +16 -8
  66. package/docs/manual/concept-index.md +4 -0
  67. package/docs/manual/index.md +66 -0
  68. package/docs/operations/ATTESTATION.md +5 -0
  69. package/docs/operations/CI_CADENCE.md +107 -0
  70. package/docs/operations/CI_COMPOSITES.md +156 -0
  71. package/docs/operations/COMPLETIONS.md +123 -0
  72. package/docs/operations/COVERAGE.md +182 -0
  73. package/docs/operations/DRIFT.md +107 -0
  74. package/docs/operations/HARD_AUDIT_2026.md +631 -0
  75. package/docs/operations/MAINTENANCE.md +5 -1
  76. package/docs/operations/MIGRATION.md +14 -6
  77. package/docs/operations/OPERATIONS.md +24 -0
  78. package/docs/operations/PERFORMANCE.md +133 -0
  79. package/docs/operations/REGISTRY.md +89 -0
  80. package/docs/operations/RELIABILITY.md +6 -0
  81. package/docs/operations/ROADMAP.md +36 -18
  82. package/docs/operations/ROADMAP_2026.md +665 -0
  83. package/docs/operations/TESTING.md +4 -0
  84. package/docs/operations/TRACEABILITY.md +7 -0
  85. package/docs/operations/TRUSTED_AGENT_WORKSTATION.md +4 -0
  86. package/docs/operations/VERSION_SYNC.md +54 -5
  87. package/docs/reference/ALIASES.md +7 -0
  88. package/docs/reference/ALIASES_CHEATSHEET.md +5 -1
  89. package/docs/reference/ALIASES_DEPRECATIONS.md +5 -1
  90. package/docs/reference/FEATURES.md +4 -0
  91. package/docs/reference/FONTS.md +4 -0
  92. package/docs/reference/POWERSHELL_PARITY.md +80 -0
  93. package/docs/reference/PROFILES.md +4 -0
  94. package/docs/reference/SCREENSHOTS.md +4 -0
  95. package/docs/reference/SCRIPTS.md +4 -0
  96. package/docs/reference/SUPPORT_MATRIX.md +10 -4
  97. package/docs/reference/THEMES.md +4 -0
  98. package/docs/reference/TOOLS.md +4 -0
  99. package/docs/reference/UTILS.md +16 -12
  100. package/docs/registry.json +6 -0
  101. package/docs/security/AI_ACT_COMPLIANCE.md +4 -0
  102. package/docs/security/AUDIT_BYPASS.md +103 -0
  103. package/docs/security/AUTOMATION_SECRETS.md +4 -0
  104. package/docs/security/CI_EGRESS_ALLOWLIST.md +127 -0
  105. package/docs/security/CI_PINNING.md +113 -0
  106. package/docs/security/COMMIT_SIGNING.md +138 -0
  107. package/docs/security/COMPLIANCE.md +5 -0
  108. package/docs/security/DEPS_DEV_EXCEPTIONS.md +86 -0
  109. package/docs/security/DISCLOSURE.md +130 -0
  110. package/docs/security/ENCRYPTION.md +5 -0
  111. package/docs/security/FMEA.md +4 -0
  112. package/docs/security/HISTORY_FILTERING.md +132 -0
  113. package/docs/security/INCIDENT_RESPONSE.md +4 -0
  114. package/docs/security/INSTALL_VERIFICATION.md +122 -0
  115. package/docs/security/KEYS.md +4 -0
  116. package/docs/security/KEY_ROTATION.md +85 -1
  117. package/docs/security/MCP_POLICY.md +9 -0
  118. package/docs/security/POLICY_RELEASES.md +4 -0
  119. package/docs/security/README.md +4 -0
  120. package/docs/security/SCORECARD.md +140 -0
  121. package/docs/security/SECRETS.md +12 -0
  122. package/docs/security/SECURITY.md +4 -0
  123. package/docs/security/SECURITY_CHECKLIST.md +11 -0
  124. package/docs/security/SHELL_EXEMPTIONS.md +145 -0
  125. package/docs/security/SOUP_REGISTER.md +4 -0
  126. package/docs/security/THREAT_MODEL.md +10 -0
  127. package/docs/security/VERIFICATION_VALIDATION.md +5 -1
  128. package/docs/security/security-pubkey.asc +15 -0
  129. package/docs/themes/README.md +4 -0
  130. package/docs/themes/VISUAL_INTEGRITY_REPORT.md +4 -0
  131. package/dot_config/ai/identity.md +3 -0
  132. package/dot_config/ai/patterns/architect.md +2 -0
  133. package/dot_config/ai/patterns/hardener.md +2 -0
  134. package/dot_config/ai/patterns/refactor.md +2 -0
  135. package/dot_config/alacritty/alacritty.toml.tmpl +3 -3
  136. package/dot_config/atuin/config.toml.tmpl +47 -0
  137. package/dot_config/dotfiles/agent-card.json +1 -1
  138. package/dot_config/dotfiles/boot/README.md +2 -0
  139. package/dot_config/dotfiles/grub/README.md +2 -0
  140. package/dot_config/dotfiles/lock/README.md +2 -0
  141. package/dot_config/fish/conf.d/direnv.fish +4 -0
  142. package/dot_config/fish/conf.d/init.fish.tmpl +21 -0
  143. package/dot_config/fish/conf.d/mise-activate.fish +5 -0
  144. package/dot_config/fish/functions/_cached_eval.fish +84 -11
  145. package/dot_config/fish/functions/_cached_eval_clear.fish +17 -0
  146. package/dot_config/foot/foot.ini.tmpl +3 -3
  147. package/dot_config/fuzzel/fuzzel.ini.tmpl +2 -2
  148. package/dot_config/ghostty/config.tmpl +3 -3
  149. package/dot_config/git/hooks/executable_commit-msg +146 -0
  150. package/dot_config/goose/config.yaml +2 -2
  151. package/dot_config/gtk-3.0/gtk.css.tmpl +2 -2
  152. package/dot_config/gtk-3.0/settings.ini.tmpl +2 -2
  153. package/dot_config/gtk-4.0/gtk.css.tmpl +2 -2
  154. package/dot_config/gtk-4.0/settings.ini.tmpl +2 -2
  155. package/dot_config/kitty/kitty.conf.tmpl +3 -3
  156. package/dot_config/mise/config.toml +1 -1
  157. package/dot_config/niri/config.kdl.tmpl +2 -2
  158. package/dot_config/nushell/cached_eval.nu +80 -0
  159. package/dot_config/nushell/env.nu.tmpl +21 -13
  160. package/dot_config/shell/00-core-paths.sh.tmpl +9 -1
  161. package/dot_config/shell/05-core-safety.sh +1 -0
  162. package/dot_config/shell/10-secrets.sh +1 -0
  163. package/dot_config/shell/40-fzf-defaults.sh.tmpl +1 -0
  164. package/dot_config/shell/40-ls-colors.sh +1 -0
  165. package/dot_config/shell/50-logic-functions-core.sh.tmpl +1 -0
  166. package/dot_config/shell/50-logic-functions.sh.tmpl +1 -0
  167. package/dot_config/shell/51-logic-functions-extra.sh.tmpl +1 -0
  168. package/dot_config/shell/90-ux-aliases.sh.tmpl +1 -0
  169. package/dot_config/shell/91-ux-aliases-lazy.sh.tmpl +1 -0
  170. package/dot_config/shell/README.md +25 -8
  171. package/dot_config/starship.toml.tmpl +2 -2
  172. package/dot_config/tmux/tmux.conf.tmpl +3 -3
  173. package/dot_config/user-dirs.dirs +1 -0
  174. package/dot_config/vscode/settings.json.tmpl +2 -2
  175. package/dot_config/waybar/config.jsonc.tmpl +2 -2
  176. package/dot_config/waybar/style.css.tmpl +2 -2
  177. package/dot_config/wezterm/wezterm.lua.tmpl +3 -3
  178. package/dot_config/zsh/dot_zshrc.tmpl +207 -17
  179. package/dot_config/zsh/rc.d/00-alias-shims.zsh +28 -6
  180. package/dot_config/zsh/rc.d/30-options.zsh.tmpl +27 -7
  181. package/dot_local/bin/executable_bm +2 -0
  182. package/dot_local/bin/executable_dot +56 -9
  183. package/dot_local/bin/executable_dot-bootstrap +0 -1
  184. package/dot_local/bin/executable_dot-load-benchmark +1 -1
  185. package/dot_local/bin/executable_dot-theme-sync +8 -8
  186. package/dot_local/bin/executable_notify +2 -0
  187. package/dot_local/bin/executable_open +2 -0
  188. package/dot_local/bin/executable_tour +4 -2
  189. package/dot_local/bin/executable_up +3 -1
  190. package/dot_local/share/man/man1/dot.1 +1 -1
  191. package/dot_local/share/zsh/completions/_dot +4 -0
  192. package/install.sh +53 -38
  193. package/package.json +1 -1
  194. package/scripts/ci/check-dangerous-chmod.sh +19 -0
  195. package/scripts/ci/check-deps-dev.sh +236 -0
  196. package/scripts/ci/check-insecure-tls.sh +61 -0
  197. package/scripts/ci/check-regression-traceability.sh +67 -0
  198. package/scripts/ci/check-shell-preamble.sh +106 -0
  199. package/scripts/ci/dot-cli-startup-bench.sh +126 -0
  200. package/scripts/ci/install-chezmoi-verified.sh +4 -1
  201. package/scripts/ci/lint-reusable-pins.sh +78 -0
  202. package/scripts/ci/run-coverage.sh +451 -0
  203. package/scripts/ci/validate-chezmoidata.sh +25 -0
  204. package/scripts/ci/windows-smoke-test.ps1 +136 -0
  205. package/scripts/diagnostics/doctor.sh +203 -5
  206. package/scripts/diagnostics/drift-dashboard.sh +177 -13
  207. package/scripts/diagnostics/health.sh +21 -4
  208. package/scripts/diagnostics/perf.sh +304 -77
  209. package/scripts/diagnostics/workstation-attestation.sh +6 -1
  210. package/scripts/dot/commands/agent.sh +33 -27
  211. package/scripts/dot/commands/agents.sh +325 -0
  212. package/scripts/dot/commands/ai.sh +75 -10
  213. package/scripts/dot/commands/aliases.sh +10 -8
  214. package/scripts/dot/commands/core.sh +10 -4
  215. package/scripts/dot/commands/fleet.sh +278 -3
  216. package/scripts/dot/commands/init.sh +184 -0
  217. package/scripts/dot/commands/meta.sh +7 -4
  218. package/scripts/dot/commands/registry.sh +263 -0
  219. package/scripts/dot/commands/tools.sh +49 -1
  220. package/scripts/dot/lib/bento.sh +2 -1
  221. package/scripts/dot/lib/log.sh +6 -0
  222. package/scripts/dot/lib/platform.sh +22 -8
  223. package/scripts/dot/lib/ui.sh +145 -2
  224. package/scripts/dot/lib/utils.sh +6 -1
  225. package/scripts/git-hooks/pre-commit-audit.sh +1 -1
  226. package/scripts/git-hooks/pre-push +66 -3
  227. package/scripts/lib/secrets_provider.sh +32 -6
  228. package/scripts/ops/heal-chezmoi.sh +41 -6
  229. package/scripts/ops/rollback.sh +14 -0
  230. package/scripts/qa/powershell-contract.ps1 +95 -0
  231. package/scripts/security/check-disclosure-key-expiry.sh +110 -0
  232. package/scripts/security/lock-configs.sh +11 -2
  233. package/scripts/theme/merge-wallpaper.sh +4 -0
  234. package/scripts/theme/switch.sh +20 -10
  235. package/scripts/version-sync.sh +3 -0
  236. package/dot_config/atuin/config.toml +0 -40
  237. package/dot_local/bin/__pycache__/executable_dot-load-benchmark-ptycpython-312.pyc +0 -0
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env bash
2
+ # Copyright (c) 2015-2026 Dotfiles. All rights reserved.
3
+ # =============================================================================
4
+ # check-deps-dev.sh — Validate direct dependencies against the deps.dev
5
+ # Insights API. Catches supply-chain risk a Grype-only scan misses:
6
+ # stale-but-CVE-free packages, unattested artifacts, and packages with
7
+ # advisories that haven't yet been mapped to a CVE.
8
+ #
9
+ # Scans:
10
+ # * `package.json` — direct `dependencies` + `devDependencies` (npm).
11
+ # * `pyproject.toml` — direct `[project.dependencies]` (Python).
12
+ # * `.github/workflows/*.yml` — `uses:` action references (GitHub Actions).
13
+ #
14
+ # Skips ecosystems with no matching manifest.
15
+ #
16
+ # Exit codes:
17
+ # 0 no advisories found (or all under threshold).
18
+ # 1 one or more direct deps have advisories at or above the threshold.
19
+ # 2 prerequisites missing (jq / curl / python3).
20
+ #
21
+ # Closes part of #877.
22
+ # =============================================================================
23
+
24
+ set -uo pipefail
25
+
26
+ REPO_ROOT="${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
27
+ THRESHOLD="${DEPS_DEV_SEVERITY_THRESHOLD:-HIGH}" # LOW | MEDIUM | HIGH | CRITICAL
28
+ DEPS_DEV_BASE="${DEPS_DEV_BASE:-https://api.deps.dev/v3}"
29
+ EXCEPTIONS_FILE="$REPO_ROOT/docs/security/DEPS_DEV_EXCEPTIONS.md"
30
+ SARIF_OUT="${DEPS_DEV_SARIF_OUT:-}"
31
+
32
+ # Strict mode: bail on prerequisites.
33
+ for cmd in jq curl python3; do
34
+ if ! command -v "$cmd" >/dev/null 2>&1; then
35
+ echo "::warning::check-deps-dev: $cmd not available — skipping." >&2
36
+ exit 0
37
+ fi
38
+ done
39
+
40
+ # -----------------------------------------------------------------------------
41
+ # Helpers
42
+ # -----------------------------------------------------------------------------
43
+
44
+ # severity_rank — map deps.dev severity strings to integers so we can
45
+ # compare against THRESHOLD numerically.
46
+ severity_rank() {
47
+ case "${1:-}" in
48
+ CRITICAL) echo 4 ;;
49
+ HIGH) echo 3 ;;
50
+ MEDIUM | MODERATE) echo 2 ;;
51
+ LOW) echo 1 ;;
52
+ *) echo 0 ;;
53
+ esac
54
+ }
55
+
56
+ THRESHOLD_RANK=$(severity_rank "$THRESHOLD")
57
+
58
+ # is_excepted — check if a (system, name) pair has an active exception
59
+ # entry in DEPS_DEV_EXCEPTIONS.md. Exceptions look like:
60
+ # `npm:lodash` (expires 2026-12-31): <reason>
61
+ is_excepted() {
62
+ local system="$1" name="$2"
63
+ [[ -f "$EXCEPTIONS_FILE" ]] || return 1
64
+ grep -Eq "^\s*\`${system}:${name}\`" "$EXCEPTIONS_FILE"
65
+ }
66
+
67
+ # fetch_advisories — query deps.dev for advisories on a (system, name,
68
+ # version) triple. Prints one advisory-key per line; empty if none.
69
+ fetch_advisories() {
70
+ local system="$1" name="$2" version="$3"
71
+ local encoded_name
72
+ encoded_name=$(python3 -c 'import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1], safe=""))' "$name")
73
+ local url="$DEPS_DEV_BASE/systems/$system/packages/$encoded_name/versions/$version"
74
+ curl -fsSL --max-time 15 "$url" 2>/dev/null |
75
+ jq -r '.advisoryKeys[]?.id // empty' 2>/dev/null ||
76
+ true
77
+ }
78
+
79
+ # fetch_advisory_severity — given an advisory ID, return its highest
80
+ # severity rating. Returns empty string when the API doesn't expose
81
+ # severity (older advisories sometimes lack it).
82
+ fetch_advisory_severity() {
83
+ local advisory_id="$1"
84
+ local encoded
85
+ encoded=$(python3 -c 'import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1], safe=""))' "$advisory_id")
86
+ curl -fsSL --max-time 15 "$DEPS_DEV_BASE/advisories/$encoded" 2>/dev/null |
87
+ jq -r '.severity[]?.type // empty' 2>/dev/null |
88
+ head -1 ||
89
+ true
90
+ }
91
+
92
+ # -----------------------------------------------------------------------------
93
+ # Manifest readers
94
+ # -----------------------------------------------------------------------------
95
+
96
+ # Each reader prints one "system\tname\tversion" line per direct dep.
97
+
98
+ read_npm() {
99
+ local pkg="$REPO_ROOT/package.json"
100
+ [[ -f "$pkg" ]] || return 0
101
+ jq -r '
102
+ [.dependencies // {}, .devDependencies // {}]
103
+ | add // {}
104
+ | to_entries[]
105
+ | "NPM\t\(.key)\t\(.value | sub("^[~^>=<]+"; ""))"
106
+ ' "$pkg"
107
+ }
108
+
109
+ read_pypi() {
110
+ local pyproj="$REPO_ROOT/pyproject.toml"
111
+ [[ -f "$pyproj" ]] || return 0
112
+ python3 - "$pyproj" <<'PY'
113
+ import sys
114
+ try:
115
+ import tomllib
116
+ except ImportError:
117
+ try:
118
+ import tomli as tomllib # type: ignore
119
+ except ImportError:
120
+ sys.exit(0)
121
+ data = tomllib.load(open(sys.argv[1], "rb"))
122
+ deps = data.get("project", {}).get("dependencies", [])
123
+ for spec in deps:
124
+ # spec is like "requests>=2.28.0"; pull name + version.
125
+ name = spec.split("[")[0].split(">")[0].split("<")[0].split("=")[0].strip()
126
+ version = ""
127
+ for op in (">=", "<=", "==", ">", "<"):
128
+ if op in spec:
129
+ version = spec.split(op, 1)[1].strip().split(",")[0]
130
+ break
131
+ print(f"PYPI\t{name}\t{version}")
132
+ PY
133
+ }
134
+
135
+ read_actions() {
136
+ local f
137
+ for f in "$REPO_ROOT"/.github/workflows/*.yml; do
138
+ [[ -f "$f" ]] || continue
139
+ # Match `uses: owner/repo@<40-hex-sha> # vX.Y.Z`
140
+ grep -Eo 'uses:[[:space:]]+[a-zA-Z0-9._-]+/[a-zA-Z0-9._/-]+@[a-f0-9]{40}[[:space:]]*#[[:space:]]*v[0-9.]+' "$f" |
141
+ sed -E 's/uses:[[:space:]]+([^@]+)@[a-f0-9]{40}[[:space:]]+#[[:space:]]+v([0-9.]+).*/GITHUB_ACTIONS\t\1\t\2/' ||
142
+ true
143
+ done | sort -u
144
+ }
145
+
146
+ # -----------------------------------------------------------------------------
147
+ # Main scan
148
+ # -----------------------------------------------------------------------------
149
+
150
+ findings=()
151
+ total_scanned=0
152
+
153
+ scan_one() {
154
+ local system="$1" name="$2" version="$3"
155
+ [[ -z "$name" || -z "$version" ]] && return 0
156
+ total_scanned=$((total_scanned + 1))
157
+
158
+ if is_excepted "$system" "$name"; then
159
+ echo " - skip $system:$name@$version (excepted)" >&2
160
+ return 0
161
+ fi
162
+
163
+ local advs
164
+ advs=$(fetch_advisories "$system" "$name" "$version")
165
+ [[ -z "$advs" ]] && return 0
166
+
167
+ while IFS= read -r adv; do
168
+ [[ -z "$adv" ]] && continue
169
+ local sev
170
+ sev=$(fetch_advisory_severity "$adv")
171
+ local rank
172
+ rank=$(severity_rank "$sev")
173
+ if ((rank >= THRESHOLD_RANK)); then
174
+ findings+=("$system:$name@$version :: $adv ($sev)")
175
+ echo "::warning::deps.dev advisory $sev: $system $name $version → $adv" >&2
176
+ fi
177
+ done <<<"$advs"
178
+ }
179
+
180
+ echo "Scanning manifests under $REPO_ROOT ..." >&2
181
+
182
+ # 1. npm
183
+ while IFS=$'\t' read -r system name version; do
184
+ scan_one "$system" "$name" "$version"
185
+ done < <(read_npm)
186
+
187
+ # 2. pypi
188
+ while IFS=$'\t' read -r system name version; do
189
+ scan_one "$system" "$name" "$version"
190
+ done < <(read_pypi)
191
+
192
+ # 3. GitHub Actions (best-effort — deps.dev coverage is partial)
193
+ while IFS=$'\t' read -r system name version; do
194
+ scan_one "$system" "$name" "$version"
195
+ done < <(read_actions)
196
+
197
+ echo "Scanned $total_scanned direct dependencies." >&2
198
+
199
+ # Optional SARIF emission for Code Scanning upload.
200
+ if [[ -n "$SARIF_OUT" ]]; then
201
+ python3 - "$SARIF_OUT" "${findings[@]+"${findings[@]}"}" <<'PY'
202
+ import json, sys
203
+ out = sys.argv[1]
204
+ findings = sys.argv[2:]
205
+ runs = [{
206
+ "tool": {"driver": {
207
+ "name": "check-deps-dev",
208
+ "version": "1.0.0",
209
+ "informationUri": "https://deps.dev",
210
+ }},
211
+ "results": [
212
+ {
213
+ "ruleId": "deps-dev-advisory",
214
+ "level": "error",
215
+ "message": {"text": f},
216
+ }
217
+ for f in findings
218
+ ],
219
+ }]
220
+ with open(out, "w") as fp:
221
+ json.dump({"version": "2.1.0", "$schema": "https://json.schemastore.org/sarif-2.1.0.json", "runs": runs}, fp)
222
+ print(f"SARIF written: {out}")
223
+ PY
224
+ fi
225
+
226
+ if [[ "${#findings[@]}" -gt 0 ]]; then
227
+ echo "" >&2
228
+ echo "::error::${#findings[@]} dep(s) flagged at or above ${THRESHOLD}." >&2
229
+ printf ' %s\n' "${findings[@]}" >&2
230
+ echo "" >&2
231
+ echo "Document exceptions at docs/security/DEPS_DEV_EXCEPTIONS.md with expiry dates." >&2
232
+ exit 1
233
+ fi
234
+
235
+ echo "deps.dev validation: clean." >&2
236
+ exit 0
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env bash
2
+ # Copyright (c) 2015-2026 Dotfiles. All rights reserved.
3
+ # =============================================================================
4
+ # check-insecure-tls.sh — block `curl -k` / `curl --insecure` /
5
+ # `wget --no-check-certificate` patterns from landing.
6
+ #
7
+ # Invoked as a pre-commit local hook (no filename args; scans the repo).
8
+ # Extracted from `config/pre-commit-config.yaml` under #866 so the line
9
+ # is yamllint-clean and the matcher can be tested in isolation.
10
+ #
11
+ # Matcher uses awk for proper tokenization (the previous inline grep
12
+ # regex false-positive'd `-k` inside package names like `-keyring`).
13
+ # =============================================================================
14
+
15
+ set -euo pipefail
16
+
17
+ ROOT="${1:-.}"
18
+
19
+ # Awk body intentionally lives in a variable. The $i / $NF / FILENAME /
20
+ # NR references inside are awk's own field/record variables — NOT shell
21
+ # variables — so the whole body must stay literal (single-quoted). The
22
+ # SC2016 suppression is the canonical signal to readers + linters that
23
+ # this is intentional.
24
+ # shellcheck disable=SC2016
25
+ AWK_BODY='
26
+ # Each input line tokenized on whitespace. Flag if the line uses
27
+ # curl/wget and a known insecure flag appears as a standalone token.
28
+ {
29
+ has_curl = 0; has_wget = 0
30
+ bad = ""
31
+ for (i = 1; i <= NF; i++) {
32
+ tok = $i
33
+ if (tok == "curl") has_curl = 1
34
+ if (tok == "wget") has_wget = 1
35
+ if (has_curl && (tok == "-k" || tok == "--insecure" || tok ~ /^--insecure=/)) {
36
+ bad = tok
37
+ }
38
+ if (has_wget && (tok == "--no-check-certificate" || tok ~ /^--no-check-certificate=/)) {
39
+ bad = tok
40
+ }
41
+ }
42
+ if (bad != "") printf "%s:%d:%s\n", FILENAME, NR, bad
43
+ }
44
+ '
45
+
46
+ matches=$(
47
+ grep -rln \
48
+ --include='*.sh' --include='*.bash' --include='*.zsh' --include='*.tmpl' \
49
+ --exclude-dir='.git' \
50
+ --exclude='check-insecure-tls.sh' \
51
+ -E '\b(curl|wget)\b' \
52
+ "$ROOT" 2>/dev/null |
53
+ xargs -I{} awk "$AWK_BODY" {} 2>/dev/null
54
+ )
55
+
56
+ if [[ -n "$matches" ]]; then
57
+ echo "ERROR: Insecure TLS patterns found." >&2
58
+ echo "$matches" >&2
59
+ echo "Remove the flag or, if absolutely required, pin the cert and document the why." >&2
60
+ exit 1
61
+ fi
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env bash
2
+ # Copyright (c) 2015-2026 Dotfiles. All rights reserved.
3
+ # =============================================================================
4
+ # check-regression-traceability.sh — enforce a `# Regression for:` header on
5
+ # every file under `tests/regression/`, with one of three accepted forms:
6
+ #
7
+ # # Regression for: GH-1234
8
+ # # Regression for: <7-or-more-char-sha-prefix>
9
+ # # Regression for: pre-history
10
+ #
11
+ # The first two are required for any test introduced as part of fixing a
12
+ # known issue or shipping a known commit. The third is an explicit "we
13
+ # couldn't trace it" marker — still trackable, just not auto-resolvable.
14
+ #
15
+ # Invocation:
16
+ # * pre-commit local hook (files passed as args)
17
+ # * standalone: ./scripts/ci/check-regression-traceability.sh
18
+ # =============================================================================
19
+
20
+ set -euo pipefail
21
+
22
+ PATTERN='^# *Regression for: *(GH-[0-9]+|[0-9a-f]{7,40}|pre-history)\b'
23
+
24
+ check_one() {
25
+ local f="$1"
26
+ [[ -f "$f" ]] || return 0
27
+
28
+ # Only check files under tests/regression/
29
+ case "$f" in
30
+ tests/regression/*) ;;
31
+ */tests/regression/*) ;;
32
+ *) return 0 ;;
33
+ esac
34
+
35
+ if ! head -15 "$f" | grep -Eq "$PATTERN"; then
36
+ echo "MISSING trace header: $f"
37
+ return 1
38
+ fi
39
+ }
40
+
41
+ fail=0
42
+ if [[ $# -gt 0 ]]; then
43
+ for f in "$@"; do
44
+ check_one "$f" || fail=1
45
+ done
46
+ else
47
+ while IFS= read -r f; do
48
+ check_one "$f" || fail=1
49
+ done < <(git ls-files "tests/regression/*.sh")
50
+ fi
51
+
52
+ if [[ $fail -ne 0 ]]; then
53
+ cat >&2 <<'MSG'
54
+
55
+ Every file under tests/regression/ must include a trace header within
56
+ the first 15 lines, in one of these forms:
57
+
58
+ # Regression for: GH-1234 (preferred — link to GitHub issue)
59
+ # Regression for: <sha-prefix> (link to introducing commit)
60
+ # Regression for: pre-history (no traceable origin)
61
+
62
+ Why: regressions pinned years ago without context are forensic dead-ends
63
+ when they fire. See docs/operations/TESTING.md and #868.
64
+
65
+ MSG
66
+ exit 1
67
+ fi
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env bash
2
+ # Copyright (c) 2015-2026 Dotfiles. All rights reserved.
3
+ # =============================================================================
4
+ # check-shell-preamble.sh — Enforce `set -euo pipefail` (bash) / `set -eu` (sh)
5
+ # on every executable shell script in the repo.
6
+ #
7
+ # Skip-rules:
8
+ # * Sourced libraries that declare themselves as such via a "Sourced by ..."
9
+ # header comment in the first 15 lines.
10
+ # * Explicit opt-out via `# preamble:skip` marker in the first 15 lines
11
+ # (used by completion files, init fragments, and other library code that
12
+ # is loaded into the caller's shell context).
13
+ # * Files under tests/ (the test runner sets shell options for its children).
14
+ #
15
+ # Invocation:
16
+ # * pre-commit local hook (files passed as args)
17
+ # * standalone: ./scripts/ci/check-shell-preamble.sh # full repo
18
+ # ./scripts/ci/check-shell-preamble.sh file.sh # explicit
19
+ # =============================================================================
20
+
21
+ set -euo pipefail
22
+
23
+ check_one() {
24
+ local f="$1"
25
+
26
+ [[ -f "$f" ]] || return 0
27
+
28
+ local shebang
29
+ shebang="$(head -1 "$f" 2>/dev/null || true)"
30
+
31
+ # Only check files with a bash or sh shebang.
32
+ case "$shebang" in
33
+ *bash*) ;;
34
+ *sh) ;;
35
+ *) return 0 ;;
36
+ esac
37
+
38
+ # Skip files declaring themselves as sourced libraries or opting out.
39
+ if head -15 "$f" | grep -Eq '^# *(Sourced by |preamble:skip|shellcheck source-only)'; then
40
+ return 0
41
+ fi
42
+
43
+ # Skip the test suite — tests are sourced by tests/framework/test_runner.sh
44
+ # which manages shell options for its children.
45
+ case "$f" in
46
+ tests/*) return 0 ;;
47
+ # Bulk-sourced fragments. These directories contain hundreds of
48
+ # alias / function / PATH snippets that are sourced into the
49
+ # caller's shell context — adding `set -e` locally would corrupt
50
+ # the caller. Path-skipped en masse rather than annotated per-file
51
+ # (would be 300+ marker comments).
52
+ .chezmoitemplates/aliases/*) return 0 ;;
53
+ .chezmoitemplates/functions/*) return 0 ;;
54
+ .chezmoitemplates/paths/*) return 0 ;;
55
+ # dot_config/shell/* and scripts/dot/lib/* are also sourced but
56
+ # have explicit `# Sourced by` headers as of #854 (defense in
57
+ # depth — the lint also accepts them on header alone).
58
+ dot_local/bin/executable_dot_completion) return 0 ;; # zsh completion, sourced into shell
59
+ esac
60
+
61
+ # Required preamble lives within the first 50 lines (after optional docstring).
62
+ local preamble_re
63
+ case "$shebang" in
64
+ *bash*)
65
+ preamble_re='^[[:space:]]*set[[:space:]]+-([eu]+o[[:space:]]+pipefail|euo[[:space:]]+pipefail|eu[[:space:]]*$)'
66
+ ;;
67
+ *sh)
68
+ preamble_re='^[[:space:]]*set[[:space:]]+-eu[o]?'
69
+ ;;
70
+ esac
71
+
72
+ if ! head -50 "$f" | grep -Eq "$preamble_re"; then
73
+ echo "MISSING preamble: $f"
74
+ return 1
75
+ fi
76
+ }
77
+
78
+ fail=0
79
+ if [[ $# -gt 0 ]]; then
80
+ for f in "$@"; do
81
+ check_one "$f" || fail=1
82
+ done
83
+ else
84
+ # Full-repo scan
85
+ while IFS= read -r f; do
86
+ check_one "$f" || fail=1
87
+ done < <(git ls-files | grep -E '\.sh$|^dot_local/bin/executable_|^scripts/.+\.sh$')
88
+ fi
89
+
90
+ if [[ $fail -ne 0 ]]; then
91
+ cat >&2 <<'MSG'
92
+
93
+ Some shell scripts are missing the safety preamble.
94
+ * bash → `set -euo pipefail`
95
+ * sh → `set -eu`
96
+
97
+ If the file is a sourced library, add this header to its first 15 lines:
98
+ # Sourced by <parent>.sh; inherits set -euo pipefail
99
+
100
+ If the file is a shell init fragment / completion that must NOT enforce its own
101
+ options on the caller, add this marker instead:
102
+ # preamble:skip
103
+
104
+ MSG
105
+ exit 1
106
+ fi
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env bash
2
+ # Copyright (c) 2015-2026 Dotfiles. All rights reserved.
3
+ #
4
+ # scripts/ci/dot-cli-startup-bench.sh
5
+ #
6
+ # Sub-100ms cold-start gate for the `dot` CLI dispatcher.
7
+ #
8
+ # The §3 audit roadmap calls for measuring `dot help` / `dot version`
9
+ # cold-start time and gating PRs on a regression budget. This script
10
+ # runs the dispatcher N times under a clean shell, computes the median
11
+ # elapsed time, and exits non-zero when the median exceeds the budget.
12
+ #
13
+ # Usage:
14
+ # bash scripts/ci/dot-cli-startup-bench.sh [--budget-ms <n>] [--runs <n>] [--cmd <argv>]
15
+ #
16
+ # Env overrides:
17
+ # DOT_BENCH_BUDGET_MS Default budget in ms (default: 250).
18
+ # DOT_BENCH_RUNS Number of runs (default: 11; median-of-N).
19
+ # DOT_BENCH_CMD Argv passed to `dot` (default: "version").
20
+ #
21
+ # The default budget is 250ms — looser than the §3 aspirational 100ms
22
+ # while we lazy-load mise/asdf/etc. CI ratchets this number down as
23
+ # improvements land. Bench *failure* is a hard build failure; bench
24
+ # *regression* (median > previous baseline + 15%) is a warning.
25
+
26
+ set -euo pipefail
27
+
28
+ BUDGET_MS="${DOT_BENCH_BUDGET_MS:-250}"
29
+ RUNS="${DOT_BENCH_RUNS:-11}"
30
+ DOT_ARGV_RAW="${DOT_BENCH_CMD:-version}"
31
+
32
+ while [[ $# -gt 0 ]]; do
33
+ case "$1" in
34
+ --budget-ms)
35
+ BUDGET_MS="$2"
36
+ shift 2
37
+ ;;
38
+ --runs)
39
+ RUNS="$2"
40
+ shift 2
41
+ ;;
42
+ --cmd)
43
+ DOT_ARGV_RAW="$2"
44
+ shift 2
45
+ ;;
46
+ -h | --help)
47
+ sed -n '5,25p' "${BASH_SOURCE[0]}"
48
+ exit 0
49
+ ;;
50
+ *)
51
+ echo "Unknown arg: $1" >&2
52
+ exit 2
53
+ ;;
54
+ esac
55
+ done
56
+
57
+ # Locate the dot dispatcher binary. Prefer the repo-local copy so a
58
+ # user-installed `dot` on PATH can't sneak in.
59
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
60
+ DOT_BIN="$REPO_ROOT/dot_local/bin/executable_dot"
61
+ if [[ ! -x "$DOT_BIN" ]] && command -v dot >/dev/null 2>&1; then
62
+ DOT_BIN="$(command -v dot)"
63
+ fi
64
+ if [[ ! -f "$DOT_BIN" ]]; then
65
+ echo "::error::dot binary not found at $DOT_BIN" >&2
66
+ exit 2
67
+ fi
68
+
69
+ # Portable wall-clock to ms. macOS bash 3.2 has no $EPOCHREALTIME.
70
+ _now_ms() {
71
+ if [[ -n "${EPOCHREALTIME:-}" ]]; then
72
+ # zsh / bash 5+: floating-point seconds.
73
+ awk -v t="$EPOCHREALTIME" 'BEGIN{printf "%d\n", t*1000}'
74
+ elif date +%s%N 2>/dev/null | grep -qE '^[0-9]+$'; then
75
+ # GNU date: nanoseconds.
76
+ echo $(($(date +%s%N) / 1000000))
77
+ elif command -v python3 >/dev/null 2>&1; then
78
+ python3 -c 'import time; print(int(time.time()*1000))'
79
+ else
80
+ # Fall back to seconds × 1000 (low resolution; warn).
81
+ echo "::warning::no high-resolution clock; using whole-second precision" >&2
82
+ echo $(($(date +%s) * 1000))
83
+ fi
84
+ }
85
+
86
+ # Parse argv string (space-separated).
87
+ read -r -a DOT_ARGV <<<"$DOT_ARGV_RAW"
88
+
89
+ echo "dot CLI cold-start benchmark"
90
+ echo " binary : $DOT_BIN"
91
+ echo " argv : ${DOT_ARGV[*]}"
92
+ echo " runs : $RUNS"
93
+ echo " budget : ${BUDGET_MS}ms"
94
+ echo
95
+
96
+ samples=()
97
+ for ((i = 1; i <= RUNS; i++)); do
98
+ start_ms="$(_now_ms)"
99
+ # `env -i` strips inherited cached state so we measure cold-start, not
100
+ # warm-cache. Keep PATH so the dispatcher can find git/awk/etc.
101
+ env -i HOME="$HOME" PATH="$PATH" "$DOT_BIN" "${DOT_ARGV[@]}" >/dev/null 2>&1 || true
102
+ end_ms="$(_now_ms)"
103
+ elapsed=$((end_ms - start_ms))
104
+ samples+=("$elapsed")
105
+ printf ' run %2d: %dms\n' "$i" "$elapsed"
106
+ done
107
+
108
+ # Median of samples (odd RUNS → middle value).
109
+ median="$(printf '%s\n' "${samples[@]}" | sort -n | awk -v n="$RUNS" 'NR==int(n/2)+1{print; exit}')"
110
+ echo
111
+ echo "median: ${median}ms budget: ${BUDGET_MS}ms"
112
+
113
+ # Optional baseline record for trend tracking.
114
+ baseline_file="${DOT_BENCH_BASELINE:-$REPO_ROOT/.cache/dot-cli-startup-baseline.txt}"
115
+ mkdir -p "$(dirname "$baseline_file")" 2>/dev/null || true
116
+ printf '%s\n' "$median" >"$baseline_file"
117
+
118
+ if ((median > BUDGET_MS)); then
119
+ echo "::error::dot CLI cold-start regression: median ${median}ms > budget ${BUDGET_MS}ms" >&2
120
+ echo " Possible causes: a slow source-time helper in dot_local/bin/executable_dot," >&2
121
+ echo " an unconditional ${BUDGET_MS}ms+ tool init at top of a sourced lib, or" >&2
122
+ echo " unnecessary jq/awk calls before the dispatcher case statement." >&2
123
+ exit 1
124
+ fi
125
+
126
+ echo "✓ dot CLI cold-start within budget"
@@ -26,7 +26,10 @@ case "$ARCH" in
26
26
  x86_64 | amd64) ARCH="amd64" ;;
27
27
  arm64 | aarch64) ARCH="arm64" ;;
28
28
  *)
29
- echo "Unsupported architecture: $ARCH" >&2
29
+ # chezmoi ships amd64 + arm64 builds only. Other architectures
30
+ # (ppc64le, s390x, riscv64, armv7) would need a source build.
31
+ echo "Unsupported architecture: $ARCH (only x86_64/amd64 and arm64/aarch64 are supported)" >&2
32
+ echo "See https://github.com/twpayne/chezmoi/releases for the full asset list." >&2
30
33
  exit 1
31
34
  ;;
32
35
  esac
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env bash
2
+ # Copyright (c) 2015-2026 Dotfiles. All rights reserved.
3
+ # shellcheck disable=SC2155
4
+ #
5
+ # lint-reusable-pins.sh — fail if any workflow references a reusable
6
+ # workflow via a mutable ref (relative path, branch name, tag).
7
+ #
8
+ # Closes the lint-rule half of #855. Acceptable form for a reusable
9
+ # workflow reference:
10
+ #
11
+ # uses: sebastienrousseau/dotfiles/.github/workflows/reusable-X.yml@<40-hex-sha>
12
+ #
13
+ # Rejected forms:
14
+ #
15
+ # uses: ./.github/workflows/reusable-X.yml # relative path → mutable
16
+ # uses: org/repo/.github/workflows/reusable-X.yml@master # branch ref → mutable
17
+ # uses: org/repo/.github/workflows/reusable-X.yml@v1 # tag ref → mutable
18
+ #
19
+ # The full-SHA constraint is what prevents a TOCTOU swap where a
20
+ # malicious push to the reusable's branch redirects the calling
21
+ # workflow at run time.
22
+
23
+ set -euo pipefail
24
+
25
+ REPO_ROOT="${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
26
+ cd "$REPO_ROOT"
27
+
28
+ WORKFLOWS_DIR=".github/workflows"
29
+ fail_count=0
30
+ checked_count=0
31
+
32
+ if [[ ! -d "$WORKFLOWS_DIR" ]]; then
33
+ echo "::error::workflows directory not found at $WORKFLOWS_DIR"
34
+ exit 1
35
+ fi
36
+
37
+ # Walk every .yml under .github/workflows/ and inspect each `uses:`
38
+ # line whose target ends with `reusable-*.yml`.
39
+ while IFS= read -r workflow; do
40
+ while IFS=: read -r line_no _; do
41
+ line=$(sed -n "${line_no}p" "$workflow")
42
+ # Extract the ref expression after `uses:`.
43
+ # Acceptable: <owner>/<repo>/.github/workflows/reusable-X.yml@<40-hex>
44
+ if echo "$line" | grep -qE '^\s*uses:\s*\./'; then
45
+ echo "::error file=$workflow,line=$line_no::reusable workflow referenced by relative path (mutable). Pin to <owner>/<repo>/.github/workflows/<file>@<40-hex-sha>."
46
+ echo " $line"
47
+ fail_count=$((fail_count + 1))
48
+ continue
49
+ fi
50
+ if echo "$line" | grep -qE 'reusable-[a-z0-9-]+\.yml@[0-9a-f]{40}\b'; then
51
+ checked_count=$((checked_count + 1))
52
+ continue
53
+ fi
54
+ if echo "$line" | grep -qE 'reusable-[a-z0-9-]+\.yml@'; then
55
+ ref=$(echo "$line" | sed -E 's|.*reusable-[a-z0-9-]+\.yml@([^ #]+).*|\1|')
56
+ echo "::error file=$workflow,line=$line_no::reusable workflow pinned to mutable ref '$ref'. Pin to a 40-hex commit SHA instead."
57
+ echo " $line"
58
+ fail_count=$((fail_count + 1))
59
+ continue
60
+ fi
61
+ done < <(grep -nE 'reusable-[a-z0-9-]+\.yml' "$workflow" || true)
62
+ done < <(find "$WORKFLOWS_DIR" -maxdepth 1 -type f -name '*.yml')
63
+
64
+ echo "reusable-pin lint: checked $checked_count call site(s), $fail_count failure(s)"
65
+
66
+ if [[ "$fail_count" -gt 0 ]]; then
67
+ echo ""
68
+ echo "Refresh pinned SHAs with:"
69
+ echo " git fetch origin master"
70
+ echo " PIN=\$(git rev-parse origin/master)"
71
+ echo " # Apply per call site, then verify:"
72
+ echo " bash scripts/ci/lint-reusable-pins.sh"
73
+ echo ""
74
+ echo "Policy: docs/security/CI_PINNING.md"
75
+ exit 1
76
+ fi
77
+
78
+ exit 0