boost-skill-cli 1.0.119__tar.gz → 1.0.121__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/PKG-INFO +1 -1
  2. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/_version.py +2 -2
  3. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/commands/pkg.py +20 -2
  4. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/commands/quality.py +8 -1
  5. boost_skill_cli-1.0.121/boost_cli/core/secretscan.py +128 -0
  6. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_skill_cli.egg-info/PKG-INFO +1 -1
  7. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_skill_cli.egg-info/SOURCES.txt +3 -0
  8. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/integrity-verification-boost-verify.md +3 -3
  9. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/secret-and-pii-scanning-of-installed-skills.md +3 -3
  10. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap.html +2 -2
  11. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/functional/test_cli_quality.py +8 -0
  12. boost_skill_cli-1.0.121/tests/unit/test_pkg_secrets.py +54 -0
  13. boost_skill_cli-1.0.121/tests/unit/test_secretscan.py +154 -0
  14. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/.gitattributes +0 -0
  15. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/.gitignore +0 -0
  16. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/CLAUDE.md +0 -0
  17. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/CONTRIBUTING.md +0 -0
  18. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/LICENSE +0 -0
  19. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/MANIFEST.in +0 -0
  20. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/Makefile +0 -0
  21. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/README.md +0 -0
  22. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/SECURITY.md +0 -0
  23. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost +0 -0
  24. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/__init__.py +0 -0
  25. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/__main__.py +0 -0
  26. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/cli.py +0 -0
  27. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/cliparse.py +0 -0
  28. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/commands/__init__.py +0 -0
  29. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/commands/bmad.py +0 -0
  30. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/commands/configuration.py +0 -0
  31. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/commands/discovery.py +0 -0
  32. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/commands/hooks.py +0 -0
  33. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/commands/info.py +0 -0
  34. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/commands/intelligence.py +0 -0
  35. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/commands/taps.py +0 -0
  36. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/commands/team.py +0 -0
  37. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/__init__.py +0 -0
  38. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/agents.py +0 -0
  39. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/ai.py +0 -0
  40. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/catalog.py +0 -0
  41. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/claude_settings.py +0 -0
  42. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/config.py +0 -0
  43. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/dense.py +0 -0
  44. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/embed.py +0 -0
  45. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/frontmatter.py +0 -0
  46. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/gitutil.py +0 -0
  47. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/injectscan.py +0 -0
  48. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/journal.py +0 -0
  49. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/lockfile.py +0 -0
  50. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/logs.py +0 -0
  51. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/mcp.py +0 -0
  52. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/nethttp.py +0 -0
  53. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/output.py +0 -0
  54. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/paths.py +0 -0
  55. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/policy.py +0 -0
  56. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/rag.py +0 -0
  57. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/registry.py +0 -0
  58. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/serve.py +0 -0
  59. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/staleness.py +0 -0
  60. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/store.py +0 -0
  61. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/typosquat.py +0 -0
  62. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/updatediff.py +0 -0
  63. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/util.py +0 -0
  64. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/data/registries.json +0 -0
  65. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/errors.py +0 -0
  66. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/spin.py +0 -0
  67. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_skill_cli.egg-info/dependency_links.txt +0 -0
  68. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_skill_cli.egg-info/entry_points.txt +0 -0
  69. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_skill_cli.egg-info/requires.txt +0 -0
  70. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_skill_cli.egg-info/top_level.txt +0 -0
  71. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/.claude/settings.local.json +0 -0
  72. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/DEBUGGING.md +0 -0
  73. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/demo.gif +0 -0
  74. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/demo.tape +0 -0
  75. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/design-roadmap.html +0 -0
  76. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/index.html +0 -0
  77. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/mcp-hub.html +0 -0
  78. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/rag-architecture.md +0 -0
  79. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D01.md +0 -0
  80. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D02.md +0 -0
  81. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D03.md +0 -0
  82. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D04.md +0 -0
  83. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D05.md +0 -0
  84. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D06.md +0 -0
  85. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D07.md +0 -0
  86. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D08.md +0 -0
  87. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D09.md +0 -0
  88. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D10.md +0 -0
  89. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D11.md +0 -0
  90. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D12.md +0 -0
  91. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D13.md +0 -0
  92. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D14.md +0 -0
  93. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D15.md +0 -0
  94. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D16.md +0 -0
  95. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D17.md +0 -0
  96. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D18.md +0 -0
  97. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D19.md +0 -0
  98. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D20.md +0 -0
  99. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D21.md +0 -0
  100. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D22.md +0 -0
  101. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D23.md +0 -0
  102. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D24.md +0 -0
  103. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D25.md +0 -0
  104. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D26.md +0 -0
  105. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/accessibility-audit-pa11y-ci-axe-core.md +0 -0
  106. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/atomic-corruption-safe-lock-file-writes.md +0 -0
  107. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/atomic-skill-install-temp-dir-swap.md +0 -0
  108. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/autonomous-ship-workflow-and-isolated-worktree.md +0 -0
  109. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/bring-commands-under-mutation-testing.md +0 -0
  110. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/broken-link-and-anchor-checking-lychee.md +0 -0
  111. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/browse-crashes-selecting-a-rule-or-workflow.md +0 -0
  112. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/build-provenance-slsa-attestations.md +0 -0
  113. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/cache-the-catalog-entry-set-across-rag-queries.md +0 -0
  114. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/capability-manifest-and-least-privilege-policy.md +0 -0
  115. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/clean-env-install-smoke-pip-and-pipx.md +0 -0
  116. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/command-reference-docs-site.md +0 -0
  117. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/complexity-and-dead-code-radar-xenon-vulture.md +0 -0
  118. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/consolidate-skill-staleness-drift-logic-into-cor.md +0 -0
  119. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/coverage-dashboard-codecov-free-for-oss.md +0 -0
  120. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/coverage-guided-fuzzing-atheris-oss-fuzz.md +0 -0
  121. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/crash-recorder-error-paths-core-logs-py.md +0 -0
  122. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/dependency-cve-gate-pip-audit.md +0 -0
  123. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/docstring-coverage-interrogate.md +0 -0
  124. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/end-of-options-guard-on-git-commands.md +0 -0
  125. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/extension-free-tests-core-dense-py.md +0 -0
  126. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/extract-mcp-http-servers-out-of-configuration-py.md +0 -0
  127. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/finish-mutation-hardening-across-core.md +0 -0
  128. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/fix-self-update-version-detection-dead-branch.md +0 -0
  129. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/fork-safe-network-proxy-handler.md +0 -0
  130. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/frontmatter-scalar-over-coercion.md +0 -0
  131. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/github-pages-deploy-is-broken-every-push.md +0 -0
  132. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/hash-pinned-reproducible-toolchain-uv-lock.md +0 -0
  133. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/html-validation-html-validate.md +0 -0
  134. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/invocation-pid-logging-for-crash-correlation.md +0 -0
  135. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/layering-guard-import-linter.md +0 -0
  136. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/lighthouse-ci-on-the-pages-site.md +0 -0
  137. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/localize-the-stored-bm25-snippet.md +0 -0
  138. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/lockfile-enforcement-and-commit-pinning.md +0 -0
  139. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/log-timestamps-mislabeled-utc.md +0 -0
  140. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/lowest-version-resolution-uv-resolution-lowest-d.md +0 -0
  141. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/make-the-roadmaps-discoverable.md +0 -0
  142. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/markdown-consistency-markdownlint-cli2.md +0 -0
  143. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/mcp-hub-diagram-node-text-overflow.md +0 -0
  144. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/mcp-launch-objc-fork-safety.md +0 -0
  145. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/memoize-config-load-in-process.md +0 -0
  146. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/modernization-smells-refurb-pyupgrade.md +0 -0
  147. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/mutation-hardening-core-frontmatter-py.md +0 -0
  148. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/mutation-hardening-core-gitutil-py.md +0 -0
  149. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/mutation-hardening-core-store-py.md +0 -0
  150. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/one-command-every-env-nox.md +0 -0
  151. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/one-shared-atomic-write-helper.md +0 -0
  152. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/package-metadata-validation-twine-check-friends.md +0 -0
  153. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/patch-coverage-gate-diff-cover.md +0 -0
  154. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/performance-regression-gate-pytest-benchmark.md +0 -0
  155. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/post-deploy-smoke-headless-load-check.md +0 -0
  156. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/pre-release-python-canary-3-14t-free-threaded.md +0 -0
  157. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/promote-nav-footer-into-the-shared-style-system.md +0 -0
  158. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/prompt-injection-scanning-of-skill-markdown.md +0 -0
  159. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/property-based-tests-hypothesis-on-the-parsers.md +0 -0
  160. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/prose-and-terminology-linting-vale.md +0 -0
  161. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/prune-ignored-dirs-during-scan-dir-walk.md +0 -0
  162. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/quality-dashboard-sonarcloud-free-for-oss.md +0 -0
  163. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/reconcile-the-theme-drift.md +0 -0
  164. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/refresh-the-marketing-surface.md +0 -0
  165. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/retrieval-quality-eval-harness.md +0 -0
  166. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/reuse-helpers-kill-minor-dead-work.md +0 -0
  167. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/robust-tag-argument-parsing.md +0 -0
  168. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/rule-install-native-materialization.md +0 -0
  169. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/runner-egress-monitoring-stepsecurity-harden-run.md +0 -0
  170. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/sbom-aware-scanning-osv-scanner.md +0 -0
  171. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/sbom-on-every-release-cyclonedx-syft.md +0 -0
  172. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/second-type-checker-pyright.md +0 -0
  173. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/secret-scanning-gitleaks-push-protection.md +0 -0
  174. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/security-linting-bandit-via-ruff-s-rules.md +0 -0
  175. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/shift-left-gate-pre-commit-pre-commit-ci.md +0 -0
  176. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/single-imperative-rule-extractor.md +0 -0
  177. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/single-tech-stack-prober.md +0 -0
  178. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/split-oversized-command-modules.md +0 -0
  179. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/startup-and-import-time-budget-x-importtime.md +0 -0
  180. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/stop-re-serializing-entry-meta-on-every-search.md +0 -0
  181. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/supply-chain-posture-openssf-scorecard.md +0 -0
  182. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/surface-every-docs-page-from-the-guide.md +0 -0
  183. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/tap-signing-and-provenance-sigstore-minisign.md +0 -0
  184. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/theme-asset-linting-stylelint-eslint.md +0 -0
  185. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/typo-detection-codespell.md +0 -0
  186. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/typosquat-and-name-confusion-detection.md +0 -0
  187. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/unify-tilde-two-copies-have-a-boundary-bug.md +0 -0
  188. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/untrack-generated-build-noise.md +0 -0
  189. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/update-diff-before-apply.md +0 -0
  190. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/visual-regression-pass-on-the-guide.md +0 -0
  191. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/widen-the-ruff-rule-surface-b-sim-c4-perf-ruf.md +0 -0
  192. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/windows-in-the-ci-matrix.md +0 -0
  193. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/workflow-linting-actionlint.md +0 -0
  194. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/workflow-sast-zizmor.md +0 -0
  195. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/workspace-scope-install.md +0 -0
  196. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/index.html +0 -0
  197. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/pyproject.toml +0 -0
  198. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/scripts/build_registries.py +0 -0
  199. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/scripts/build_roadmap.py +0 -0
  200. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/scripts/eval_recommend.py +0 -0
  201. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/scripts/eval_retrieval.py +0 -0
  202. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/scripts/mutation_gate.py +0 -0
  203. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/setup.cfg +0 -0
  204. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/style/README.md +0 -0
  205. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/style/boost.css +0 -0
  206. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/style/boost.js +0 -0
  207. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/style/demo.html +0 -0
  208. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/conftest.py +0 -0
  209. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/eval/baseline.json +0 -0
  210. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/eval/golden.jsonl +0 -0
  211. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/eval/recommend.jsonl +0 -0
  212. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/functional/test_cli_bmad.py +0 -0
  213. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/functional/test_cli_configuration.py +0 -0
  214. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/functional/test_cli_discovery.py +0 -0
  215. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/functional/test_cli_hooks.py +0 -0
  216. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/functional/test_cli_info.py +0 -0
  217. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/functional/test_cli_intelligence.py +0 -0
  218. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/functional/test_cli_pkg.py +0 -0
  219. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/functional/test_cli_taps.py +0 -0
  220. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/functional/test_cli_team.py +0 -0
  221. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/functional/test_everyday_loop.py +0 -0
  222. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/make_fixture.py +0 -0
  223. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/smoke.sh +0 -0
  224. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_agents.py +0 -0
  225. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_ai.py +0 -0
  226. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_catalog.py +0 -0
  227. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_claude_settings.py +0 -0
  228. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_cliparse.py +0 -0
  229. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_config.py +0 -0
  230. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_dense.py +0 -0
  231. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_dense_fallback.py +0 -0
  232. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_docs_pages_linked.py +0 -0
  233. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_embed.py +0 -0
  234. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_errors_and_cli_table.py +0 -0
  235. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_frontmatter.py +0 -0
  236. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_gitutil.py +0 -0
  237. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_injectscan.py +0 -0
  238. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_journal.py +0 -0
  239. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_lockfile.py +0 -0
  240. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_logs.py +0 -0
  241. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_mcp.py +0 -0
  242. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_mutation_hardening.py +0 -0
  243. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_nethttp.py +0 -0
  244. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_output.py +0 -0
  245. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_paths.py +0 -0
  246. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_pkg_confusions.py +0 -0
  247. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_pkg_injection.py +0 -0
  248. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_policy.py +0 -0
  249. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_rag.py +0 -0
  250. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_registries_fresh.py +0 -0
  251. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_registry.py +0 -0
  252. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_roadmap_fresh.py +0 -0
  253. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_serve.py +0 -0
  254. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_spin.py +0 -0
  255. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_staleness.py +0 -0
  256. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_store.py +0 -0
  257. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_token_parity.py +0 -0
  258. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_typosquat.py +0 -0
  259. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_updatediff.py +0 -0
  260. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_util.py +0 -0
  261. {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boost-skill-cli
3
- Version: 1.0.119
3
+ Version: 1.0.121
4
4
  Summary: boost — Homebrew for AI coding skills
5
5
  Author: Jonathan Reyes
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '1.0.119'
22
- __version_tuple__ = version_tuple = (1, 0, 119)
21
+ __version__ = version = '1.0.121'
22
+ __version_tuple__ = version_tuple = (1, 0, 121)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -16,8 +16,8 @@ from typing import Dict, List, Optional
16
16
 
17
17
  from .. import cliparse
18
18
  from ..core import (agents, catalog, gitutil, injectscan, journal, lockfile,
19
- paths, registry, staleness, store, typosquat, updatediff,
20
- util)
19
+ paths, registry, secretscan, staleness, store, typosquat,
20
+ updatediff, util)
21
21
  from ..core import output as out
22
22
  from ..errors import BoostError
23
23
 
@@ -83,6 +83,23 @@ def _warn_injection(res: store.InstallResult) -> None:
83
83
  out.warn(" L%d [%s] %s" % (f.line, f.severity, f.description))
84
84
 
85
85
 
86
+ def _warn_secrets(res: store.InstallResult) -> None:
87
+ """Flag embedded credentials / secrets in the skill just copied.
88
+
89
+ Scans the installed ``SKILL.md`` (core.secretscan) for known secret shapes
90
+ and surfaces any hits worst-first, with the secret value redacted. Advisory
91
+ only — it warns, it never blocks the install.
92
+ """
93
+ findings = secretscan.scan_file(res.dest / "SKILL.md")
94
+ if not findings:
95
+ return
96
+ out.warn("%s: %d possible secret%s in SKILL.md (%s) — do not commit"
97
+ % (res.name, len(findings), "" if len(findings) == 1 else "s",
98
+ secretscan.worst_severity(findings)))
99
+ for f in findings[:3]:
100
+ out.warn(" L%d [%s] %s" % (f.line, f.severity, f.description))
101
+
102
+
86
103
  def _report_result(res: store.InstallResult) -> None:
87
104
  """The canonical three-line install report."""
88
105
  out.ok("copied to %s" % _tilde(res.dest))
@@ -94,6 +111,7 @@ def _report_result(res: store.InstallResult) -> None:
94
111
  out.warn("not linked: %s exists and is not managed by boost" % _tilde(path))
95
112
  out.ok("lock updated (.skill-lock.json)")
96
113
  _warn_injection(res)
114
+ _warn_secrets(res)
97
115
 
98
116
 
99
117
  def _boostfile_text(skills: Dict[str, dict], via: str = "boost bundle dump") -> str:
@@ -286,12 +286,19 @@ def cmd_doctor(argv):
286
286
  enabled = agents.enabled_agents()
287
287
  skill_issues = 0
288
288
  for name, entry in sorted(skills.items()):
289
- if not store.skill_store_dir(name).is_dir():
289
+ sdir = store.skill_store_dir(name)
290
+ if not sdir.is_dir():
290
291
  bad("skill %s missing from store — run `boost heal`" % name)
291
292
  skill_issues += 1
292
293
  continue
293
294
  if entry.get("quarantined"):
294
295
  continue
296
+ # tamper detection: the lock file records a sha256 at install time, but
297
+ # only `boost verify` ever re-checked it — surface content drift here too.
298
+ locked = entry.get("sha256")
299
+ if locked and util.sha256_dir(sdir) != locked:
300
+ bad("skill %s modified since install — run `boost verify`" % name)
301
+ skill_issues += 1
295
302
  for agent in entry.get("agents", []):
296
303
  adir = enabled.get(agent)
297
304
  if adir is None:
@@ -0,0 +1,128 @@
1
+ """Scan installed skill content for embedded secrets & credentials (stdlib).
2
+
3
+ A third-party skill can ship a hard-coded credential — an AWS key, a GitHub
4
+ token, a private-key block — that then lands in the user's tree, or leak one the
5
+ agent was handed. This module carries a small, high-precision set of secret
6
+ *shape* patterns (known token prefixes and key blocks, not entropy guessing) so
7
+ boost can flag them at install time without any external scanner.
8
+
9
+ Findings never echo the raw secret: the matched value is redacted to a short
10
+ prefix so a warning can point at the offending line without re-leaking it.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import re
15
+ from pathlib import Path
16
+ from typing import List, NamedTuple
17
+
18
+ _SEVERITY_RANK = {"high": 3, "medium": 2, "low": 1}
19
+
20
+
21
+ class Rule(NamedTuple):
22
+ id: str
23
+ severity: str
24
+ pattern: "re.Pattern[str]"
25
+ group: int # capture group holding the secret value to redact
26
+ description: str
27
+
28
+
29
+ class Finding(NamedTuple):
30
+ rule_id: str
31
+ severity: str
32
+ line: int # 1-based line number
33
+ snippet: str # offending line, secret redacted, trimmed
34
+ description: str
35
+
36
+
37
+ def _rx(pattern: str, flags: int = 0) -> "re.Pattern[str]":
38
+ return re.compile(pattern, flags)
39
+
40
+
41
+ # High-precision rules keyed on well-known token shapes — chosen so a benign
42
+ # skill almost never trips them. ``group`` marks the span to redact (0 = whole
43
+ # match).
44
+ RULES: tuple = (
45
+ Rule("private-key-block", "high",
46
+ _rx(r"-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----"),
47
+ 0, "embedded private key block"),
48
+ Rule("aws-access-key-id", "high",
49
+ _rx(r"\b(AKIA[0-9A-Z]{16})\b"), 1, "AWS access key id"),
50
+ Rule("github-token", "high",
51
+ _rx(r"\b((?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36}"
52
+ r"|github_pat_[A-Za-z0-9_]{22,})\b"), 1, "GitHub token"),
53
+ Rule("slack-token", "high",
54
+ _rx(r"\b(xox[baprs]-[A-Za-z0-9-]{10,})\b"), 1, "Slack token"),
55
+ Rule("google-api-key", "high",
56
+ _rx(r"\b(AIza[0-9A-Za-z_\-]{35})\b"), 1, "Google API key"),
57
+ Rule("stripe-secret-key", "high",
58
+ _rx(r"\b((?:r|s)k_live_[A-Za-z0-9]{16,})\b"), 1, "Stripe live key"),
59
+ Rule("openai-key", "high",
60
+ _rx(r"\b(sk-(?:proj-)?[A-Za-z0-9]{20,})\b"), 1, "OpenAI-style key"),
61
+ Rule("jwt", "medium",
62
+ _rx(r"\b(eyJ[A-Za-z0-9_\-]{8,}\.[A-Za-z0-9_\-]{8,}"
63
+ r"\.[A-Za-z0-9_\-]{8,})\b"), 1, "JSON Web Token"),
64
+ Rule("generic-secret-assignment", "medium",
65
+ _rx(r"(?i)\b(?:api[_\-]?key|secret|token|passwd|password|"
66
+ r"access[_\-]?key)\b\s*[=:]\s*[\"']([^\"'\n]{8,})[\"']"),
67
+ 1, "hard-coded secret assignment"),
68
+ Rule("us-ssn", "medium",
69
+ _rx(r"\b([0-9]{3}-[0-9]{2}-[0-9]{4})\b"), 1, "US Social Security number"),
70
+ )
71
+
72
+
73
+ def redact(value: str) -> str:
74
+ """Mask a secret to a short, non-recoverable hint (keeps ≤4 leading chars)."""
75
+ if len(value) <= 4:
76
+ return "****"
77
+ return value[:4] + "****"
78
+
79
+
80
+ def _redacted_line(line: str, rule: Rule, match) -> str:
81
+ """The offending line with this match's secret span replaced by a hint."""
82
+ start, end = match.span(rule.group)
83
+ if start < 0: # optional group didn't participate
84
+ return line.strip()[:200]
85
+ masked = line[:start] + redact(match.group(rule.group)) + line[end:]
86
+ return masked.strip()[:200]
87
+
88
+
89
+ def scan_text(text: str) -> List[Finding]:
90
+ """Findings for every secret-shape rule that matches a line of ``text``.
91
+
92
+ One finding per (rule, line), using that line's first match. The raw secret
93
+ is never included — the snippet carries a redacted line. Ordered worst-first
94
+ (severity desc, then line, then rule id).
95
+ """
96
+ findings: List[Finding] = []
97
+ for lineno, raw in enumerate(text.splitlines(), start=1):
98
+ for rule in RULES:
99
+ m = rule.pattern.search(raw)
100
+ if m:
101
+ findings.append(Finding(rule.id, rule.severity, lineno,
102
+ _redacted_line(raw, rule, m),
103
+ rule.description))
104
+ findings.sort(key=lambda f: (-_SEVERITY_RANK[f.severity], f.line,
105
+ f.rule_id))
106
+ return findings
107
+
108
+
109
+ def scan_file(path) -> List[Finding]:
110
+ """Scan the file at ``path``; a missing/unreadable file yields ``[]``."""
111
+ p = Path(path)
112
+ try:
113
+ text = p.read_text(encoding="utf-8", errors="replace")
114
+ except (OSError, ValueError):
115
+ return []
116
+ return scan_text(text)
117
+
118
+
119
+ def worst_severity(findings) -> str:
120
+ """Highest severity among ``findings`` ("high" > "medium" > "low"), or ""."""
121
+ best = ""
122
+ best_rank = 0
123
+ for f in findings:
124
+ rank = _SEVERITY_RANK.get(f.severity, 0)
125
+ if rank > best_rank:
126
+ best_rank = rank
127
+ best = f.severity
128
+ return best
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boost-skill-cli
3
- Version: 1.0.119
3
+ Version: 1.0.121
4
4
  Summary: boost — Homebrew for AI coding skills
5
5
  Author: Jonathan Reyes
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -50,6 +50,7 @@ boost_cli/core/paths.py
50
50
  boost_cli/core/policy.py
51
51
  boost_cli/core/rag.py
52
52
  boost_cli/core/registry.py
53
+ boost_cli/core/secretscan.py
53
54
  boost_cli/core/serve.py
54
55
  boost_cli/core/staleness.py
55
56
  boost_cli/core/store.py
@@ -241,11 +242,13 @@ tests/unit/test_output.py
241
242
  tests/unit/test_paths.py
242
243
  tests/unit/test_pkg_confusions.py
243
244
  tests/unit/test_pkg_injection.py
245
+ tests/unit/test_pkg_secrets.py
244
246
  tests/unit/test_policy.py
245
247
  tests/unit/test_rag.py
246
248
  tests/unit/test_registries_fresh.py
247
249
  tests/unit/test_registry.py
248
250
  tests/unit/test_roadmap_fresh.py
251
+ tests/unit/test_secretscan.py
249
252
  tests/unit/test_serve.py
250
253
  tests/unit/test_spin.py
251
254
  tests/unit/test_staleness.py
@@ -2,15 +2,15 @@
2
2
  id: integrity-verification-boost-verify
3
3
  board: code
4
4
  section: trust
5
- status: planned
5
+ status: shipped
6
6
  category: Security · Integrity
7
7
  complexity: S
8
8
  impact: High
9
9
  wow: 4
10
10
  note: reuses sha256_dir
11
11
  order: 2
12
- owner:
13
- pr:
12
+ owner: loop/doctor-integrity
13
+ pr: 139
14
14
  title: Integrity verification — <code>boost verify</code>
15
15
  ---
16
16
  The lockfile already records a <code>sha256</code> per install via
@@ -2,15 +2,15 @@
2
2
  id: secret-and-pii-scanning-of-installed-skills
3
3
  board: code
4
4
  section: trust
5
- status: planned
5
+ status: shipped
6
6
  category: Security · Secrets
7
7
  complexity: S
8
8
  impact: Med
9
9
  wow: 3
10
10
  note: installed content
11
11
  order: 6
12
- owner:
13
- pr:
12
+ owner: loop/secret-scan
13
+ pr: 138
14
14
  title: Secret &amp; PII scanning of installed skills
15
15
  ---
16
16
  Point the round-2 secret scanners (<code>gitleaks</code>/<code>trufflehog</code>)
@@ -1663,7 +1663,7 @@ and the work stays visible.</p>
1663
1663
  </article>
1664
1664
 
1665
1665
  <article class="cap rcard">
1666
- <div class="head"><span class="pill planned">Planned</span><span class="cat">Security · Integrity</span></div>
1666
+ <div class="head"><span class="pill shipped">Shipped</span><span class="cat">Security · Integrity</span></div>
1667
1667
  <h3>Integrity verification — <code>boost verify</code></h3>
1668
1668
  <p>The lockfile already records a <code>sha256</code> per install via
1669
1669
  <code>util.sha256_dir</code>, but nothing ever re-checks it. A
@@ -1734,7 +1734,7 @@ paths.</p>
1734
1734
  </article>
1735
1735
 
1736
1736
  <article class="cap rcard">
1737
- <div class="head"><span class="pill planned">Planned</span><span class="cat">Security · Secrets</span></div>
1737
+ <div class="head"><span class="pill shipped">Shipped</span><span class="cat">Security · Secrets</span></div>
1738
1738
  <h3>Secret &amp; PII scanning of installed skills</h3>
1739
1739
  <p>Point the round-2 secret scanners (<code>gitleaks</code>/<code>trufflehog</code>)
1740
1740
  at <em>third-party</em> skill content, not just boost's own repo — catching
@@ -82,6 +82,14 @@ class TestDoctor:
82
82
  assert "no taps configured — add one with `boost tap owner/repo`" in r.out
83
83
  assert "0 skills installed · 0 taps synced · 0 broken links" in r.out
84
84
 
85
+ def test_tampered_content_rc1(self, boost, installed):
86
+ # doctor must re-hash installed skills, not just check they exist:
87
+ # editing SKILL.md content after install is drift from the lock digest.
88
+ _tamper("brainstorming")
89
+ r = boost("doctor", expect=1)
90
+ assert "skill brainstorming modified since install — run `boost verify`" in r.out
91
+ assert "need attention" in r.out
92
+
85
93
 
86
94
  # ── lint ─────────────────────────────────────────────────────────────────
87
95
 
@@ -0,0 +1,54 @@
1
+ """Unit tests: pkg._warn_secrets — the install-time secret-scan wiring."""
2
+ from __future__ import annotations
3
+
4
+ from boost_cli.commands import pkg
5
+ from boost_cli.core import output as out
6
+ from boost_cli.core.store import InstallResult
7
+
8
+
9
+ def _capture(monkeypatch):
10
+ warnings = []
11
+ monkeypatch.setattr(out, "warn", lambda m: warnings.append(m))
12
+ return warnings
13
+
14
+
15
+ def _result(tmp_path, body):
16
+ dest = tmp_path / "leaky-skill"
17
+ dest.mkdir()
18
+ (dest / "SKILL.md").write_text(body, encoding="utf-8")
19
+ return InstallResult(name="leaky-skill", dest=dest)
20
+
21
+
22
+ def test_warns_on_embedded_secret(monkeypatch, tmp_path):
23
+ warnings = _capture(monkeypatch)
24
+ pkg._warn_secrets(_result(tmp_path, "aws = AKIAIOSFODNN7EXAMPLE"))
25
+ assert any("possible secret" in w for w in warnings)
26
+ assert any("high" in w for w in warnings)
27
+
28
+
29
+ def test_never_prints_raw_secret(monkeypatch, tmp_path):
30
+ leaked = "AKIAIOSFODNN7EXAMPLE"
31
+ warnings = _capture(monkeypatch)
32
+ pkg._warn_secrets(_result(tmp_path, "aws = " + leaked))
33
+ assert all(leaked not in w for w in warnings)
34
+
35
+
36
+ def test_quiet_on_clean_skill(monkeypatch, tmp_path):
37
+ warnings = _capture(monkeypatch)
38
+ pkg._warn_secrets(_result(tmp_path, "# Clean\nRuns pytest.\n"))
39
+ assert warnings == []
40
+
41
+
42
+ def test_missing_skill_md_quiet(monkeypatch, tmp_path):
43
+ dest = tmp_path / "no-md"
44
+ dest.mkdir()
45
+ warnings = _capture(monkeypatch)
46
+ pkg._warn_secrets(InstallResult(name="no-md", dest=dest))
47
+ assert warnings == []
48
+
49
+
50
+ def test_singular_wording(monkeypatch, tmp_path):
51
+ warnings = _capture(monkeypatch)
52
+ pkg._warn_secrets(_result(tmp_path, "aws = AKIAIOSFODNN7EXAMPLE"))
53
+ summary = next(w for w in warnings if "possible secret" in w)
54
+ assert "1 possible secret in" in summary
@@ -0,0 +1,154 @@
1
+ """Unit tests: boost_cli/core/secretscan.py — embedded-secret detection."""
2
+ from __future__ import annotations
3
+
4
+ from boost_cli.core import secretscan
5
+
6
+
7
+ def _ids(findings):
8
+ return [f.rule_id for f in findings]
9
+
10
+
11
+ class TestRuleMatches:
12
+ def test_private_key_block(self):
13
+ got = secretscan.scan_text("-----BEGIN RSA PRIVATE KEY-----")
14
+ assert _ids(got) == ["private-key-block"]
15
+ assert got[0].severity == "high"
16
+
17
+ def test_openssh_private_key_block(self):
18
+ assert _ids(secretscan.scan_text(
19
+ "-----BEGIN OPENSSH PRIVATE KEY-----")) == ["private-key-block"]
20
+
21
+ def test_aws_access_key_id(self):
22
+ got = secretscan.scan_text("key = AKIAIOSFODNN7EXAMPLE")
23
+ assert "aws-access-key-id" in _ids(got)
24
+
25
+ def test_github_token(self):
26
+ tok = "ghp_" + "a" * 36
27
+ assert "github-token" in _ids(secretscan.scan_text("token: " + tok))
28
+
29
+ def test_github_fine_grained_pat(self):
30
+ tok = "github_pat_" + "A" * 30
31
+ assert "github-token" in _ids(secretscan.scan_text(tok))
32
+
33
+ def test_slack_token(self):
34
+ assert "slack-token" in _ids(
35
+ secretscan.scan_text("xoxb-123456789012-abcdefghijkl"))
36
+
37
+ def test_google_api_key(self):
38
+ key = "AIza" + "b" * 35
39
+ assert "google-api-key" in _ids(secretscan.scan_text(key))
40
+
41
+ def test_stripe_live_key(self):
42
+ assert "stripe-secret-key" in _ids(
43
+ secretscan.scan_text("sk_live_" + "c" * 20))
44
+
45
+ def test_openai_key(self):
46
+ assert "openai-key" in _ids(secretscan.scan_text("sk-" + "d" * 32))
47
+
48
+ def test_jwt(self):
49
+ jwt = "eyJhbGciOiJIUzI1.eyJzdWIiOiIxMjM0NTY.SflKxwRJSMeKKF2QT4"
50
+ assert "jwt" in _ids(secretscan.scan_text(jwt))
51
+
52
+ def test_generic_assignment(self):
53
+ got = secretscan.scan_text('api_key = "s3cr3t-value-here"')
54
+ assert "generic-secret-assignment" in _ids(got)
55
+ assert got[0].severity == "medium"
56
+
57
+ def test_us_ssn(self):
58
+ assert "us-ssn" in _ids(secretscan.scan_text("SSN: 123-45-6789"))
59
+
60
+
61
+ class TestNonMatches:
62
+ def test_benign_prose_clean(self):
63
+ text = ("# A skill\n\nRuns `pytest` and reports coverage. See the "
64
+ "README for setup.\n")
65
+ assert secretscan.scan_text(text) == []
66
+
67
+ def test_short_akia_lookalike_not_matched(self):
68
+ # too short to be a real AWS key id
69
+ assert secretscan.scan_text("AKIA123") == []
70
+
71
+ def test_unquoted_assignment_not_matched(self):
72
+ # rule requires a quoted literal to avoid matching prose
73
+ assert secretscan.scan_text("api_key = see the vault") == []
74
+
75
+ def test_empty_text(self):
76
+ assert secretscan.scan_text("") == []
77
+
78
+
79
+ class TestRedaction:
80
+ def test_secret_value_never_appears_in_snippet(self):
81
+ leaked = "AKIAIOSFODNN7EXAMPLE"
82
+ got = secretscan.scan_text("aws = " + leaked)
83
+ assert leaked not in got[0].snippet
84
+
85
+ def test_snippet_keeps_short_prefix_hint(self):
86
+ got = secretscan.scan_text("aws = AKIAIOSFODNN7EXAMPLE")
87
+ assert "AKIA****" in got[0].snippet
88
+
89
+ def test_generic_assignment_value_redacted(self):
90
+ got = secretscan.scan_text('password = "hunter2-very-secret"')
91
+ assert "hunter2-very-secret" not in got[0].snippet
92
+ assert "hunt****" in got[0].snippet
93
+
94
+ def test_redact_masks_short_values(self):
95
+ assert secretscan.redact("abcd") == "****"
96
+
97
+ def test_redact_keeps_four_char_prefix(self):
98
+ assert secretscan.redact("abcdefghij") == "abcd****"
99
+
100
+ def test_private_key_block_group0_redacts_whole_match(self):
101
+ got = secretscan.scan_text("-----BEGIN PRIVATE KEY-----")
102
+ # group 0 = whole match; redaction keeps a 4-char prefix hint
103
+ assert got[0].snippet.startswith("----")
104
+ assert "PRIVATE KEY" not in got[0].snippet
105
+
106
+
107
+ class TestScanMechanics:
108
+ def test_line_numbers_one_based(self):
109
+ text = "clean\nclean\nAKIAIOSFODNN7EXAMPLE"
110
+ got = secretscan.scan_text(text)
111
+ assert got[0].line == 3
112
+
113
+ def test_sorted_high_before_medium(self):
114
+ text = 'password = "abcdefgh"\n-----BEGIN PRIVATE KEY-----'
115
+ got = secretscan.scan_text(text)
116
+ assert [f.severity for f in got] == ["high", "medium"]
117
+ assert got[0].rule_id == "private-key-block"
118
+
119
+ def test_ties_broken_by_line(self):
120
+ tok = "ghp_" + "a" * 36
121
+ text = "AKIAIOSFODNN7EXAMPLE\n" + tok
122
+ got = secretscan.scan_text(text)
123
+ assert [f.line for f in got] == [1, 2]
124
+
125
+ def test_description_populated(self):
126
+ got = secretscan.scan_text("AKIAIOSFODNN7EXAMPLE")
127
+ assert got[0].description
128
+
129
+
130
+ class TestScanFile:
131
+ def test_reads_existing_file(self, tmp_path):
132
+ p = tmp_path / "SKILL.md"
133
+ p.write_text("token: ghp_" + "a" * 36, encoding="utf-8")
134
+ assert "github-token" in _ids(secretscan.scan_file(p))
135
+
136
+ def test_missing_file_empty(self, tmp_path):
137
+ assert secretscan.scan_file(tmp_path / "nope.md") == []
138
+
139
+ def test_directory_path_empty(self, tmp_path):
140
+ assert secretscan.scan_file(tmp_path) == []
141
+
142
+
143
+ class TestWorstSeverity:
144
+ def test_high_wins(self):
145
+ findings = secretscan.scan_text(
146
+ 'password = "abcdefgh"\nAKIAIOSFODNN7EXAMPLE')
147
+ assert secretscan.worst_severity(findings) == "high"
148
+
149
+ def test_empty_blank(self):
150
+ assert secretscan.worst_severity([]) == ""
151
+
152
+ def test_only_medium(self):
153
+ findings = secretscan.scan_text('token = "abcdefgh12"')
154
+ assert secretscan.worst_severity(findings) == "medium"