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.
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/PKG-INFO +1 -1
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/_version.py +2 -2
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/commands/pkg.py +20 -2
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/commands/quality.py +8 -1
- boost_skill_cli-1.0.121/boost_cli/core/secretscan.py +128 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_skill_cli.egg-info/PKG-INFO +1 -1
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_skill_cli.egg-info/SOURCES.txt +3 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/integrity-verification-boost-verify.md +3 -3
- {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
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap.html +2 -2
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/functional/test_cli_quality.py +8 -0
- boost_skill_cli-1.0.121/tests/unit/test_pkg_secrets.py +54 -0
- boost_skill_cli-1.0.121/tests/unit/test_secretscan.py +154 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/.gitattributes +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/.gitignore +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/CLAUDE.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/CONTRIBUTING.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/LICENSE +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/MANIFEST.in +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/Makefile +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/README.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/SECURITY.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/__init__.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/__main__.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/cli.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/cliparse.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/commands/__init__.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/commands/bmad.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/commands/configuration.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/commands/discovery.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/commands/hooks.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/commands/info.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/commands/intelligence.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/commands/taps.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/commands/team.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/__init__.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/agents.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/ai.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/catalog.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/claude_settings.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/config.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/dense.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/embed.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/frontmatter.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/gitutil.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/injectscan.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/journal.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/lockfile.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/logs.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/mcp.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/nethttp.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/output.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/paths.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/policy.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/rag.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/registry.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/serve.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/staleness.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/store.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/typosquat.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/updatediff.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/core/util.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/data/registries.json +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/errors.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_cli/spin.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_skill_cli.egg-info/dependency_links.txt +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_skill_cli.egg-info/entry_points.txt +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_skill_cli.egg-info/requires.txt +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/boost_skill_cli.egg-info/top_level.txt +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/.claude/settings.local.json +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/DEBUGGING.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/demo.gif +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/demo.tape +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/design-roadmap.html +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/index.html +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/mcp-hub.html +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/rag-architecture.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D01.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D02.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D03.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D04.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D05.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D06.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D07.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D08.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D09.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D10.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D11.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D12.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D13.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D14.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D15.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D16.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D17.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D18.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D19.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D20.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D21.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D22.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D23.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D24.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D25.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/BOOST-D26.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/accessibility-audit-pa11y-ci-axe-core.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/atomic-corruption-safe-lock-file-writes.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/atomic-skill-install-temp-dir-swap.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/autonomous-ship-workflow-and-isolated-worktree.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/bring-commands-under-mutation-testing.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/broken-link-and-anchor-checking-lychee.md +0 -0
- {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
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/build-provenance-slsa-attestations.md +0 -0
- {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
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/capability-manifest-and-least-privilege-policy.md +0 -0
- {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
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/command-reference-docs-site.md +0 -0
- {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
- {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
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/coverage-dashboard-codecov-free-for-oss.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/coverage-guided-fuzzing-atheris-oss-fuzz.md +0 -0
- {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
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/dependency-cve-gate-pip-audit.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/docstring-coverage-interrogate.md +0 -0
- {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
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/extension-free-tests-core-dense-py.md +0 -0
- {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
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/finish-mutation-hardening-across-core.md +0 -0
- {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
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/fork-safe-network-proxy-handler.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/frontmatter-scalar-over-coercion.md +0 -0
- {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
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/hash-pinned-reproducible-toolchain-uv-lock.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/html-validation-html-validate.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/invocation-pid-logging-for-crash-correlation.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/layering-guard-import-linter.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/lighthouse-ci-on-the-pages-site.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/localize-the-stored-bm25-snippet.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/lockfile-enforcement-and-commit-pinning.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/log-timestamps-mislabeled-utc.md +0 -0
- {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
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/make-the-roadmaps-discoverable.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/markdown-consistency-markdownlint-cli2.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/mcp-hub-diagram-node-text-overflow.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/mcp-launch-objc-fork-safety.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/memoize-config-load-in-process.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/modernization-smells-refurb-pyupgrade.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/mutation-hardening-core-frontmatter-py.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/mutation-hardening-core-gitutil-py.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/mutation-hardening-core-store-py.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/one-command-every-env-nox.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/one-shared-atomic-write-helper.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/package-metadata-validation-twine-check-friends.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/patch-coverage-gate-diff-cover.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/performance-regression-gate-pytest-benchmark.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/post-deploy-smoke-headless-load-check.md +0 -0
- {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
- {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
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/prompt-injection-scanning-of-skill-markdown.md +0 -0
- {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
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/prose-and-terminology-linting-vale.md +0 -0
- {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
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/quality-dashboard-sonarcloud-free-for-oss.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/reconcile-the-theme-drift.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/refresh-the-marketing-surface.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/retrieval-quality-eval-harness.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/reuse-helpers-kill-minor-dead-work.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/robust-tag-argument-parsing.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/rule-install-native-materialization.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/runner-egress-monitoring-stepsecurity-harden-run.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/sbom-aware-scanning-osv-scanner.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/sbom-on-every-release-cyclonedx-syft.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/second-type-checker-pyright.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/secret-scanning-gitleaks-push-protection.md +0 -0
- {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
- {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
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/single-imperative-rule-extractor.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/single-tech-stack-prober.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/split-oversized-command-modules.md +0 -0
- {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
- {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
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/supply-chain-posture-openssf-scorecard.md +0 -0
- {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
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/tap-signing-and-provenance-sigstore-minisign.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/theme-asset-linting-stylelint-eslint.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/typo-detection-codespell.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/typosquat-and-name-confusion-detection.md +0 -0
- {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
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/untrack-generated-build-noise.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/update-diff-before-apply.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/visual-regression-pass-on-the-guide.md +0 -0
- {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
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/windows-in-the-ci-matrix.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/workflow-linting-actionlint.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/workflow-sast-zizmor.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/docs/roadmap/items/workspace-scope-install.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/index.html +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/pyproject.toml +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/scripts/build_registries.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/scripts/build_roadmap.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/scripts/eval_recommend.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/scripts/eval_retrieval.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/scripts/mutation_gate.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/setup.cfg +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/style/README.md +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/style/boost.css +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/style/boost.js +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/style/demo.html +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/conftest.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/eval/baseline.json +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/eval/golden.jsonl +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/eval/recommend.jsonl +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/functional/test_cli_bmad.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/functional/test_cli_configuration.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/functional/test_cli_discovery.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/functional/test_cli_hooks.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/functional/test_cli_info.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/functional/test_cli_intelligence.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/functional/test_cli_pkg.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/functional/test_cli_taps.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/functional/test_cli_team.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/functional/test_everyday_loop.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/make_fixture.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/smoke.sh +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_agents.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_ai.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_catalog.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_claude_settings.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_cliparse.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_config.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_dense.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_dense_fallback.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_docs_pages_linked.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_embed.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_errors_and_cli_table.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_frontmatter.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_gitutil.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_injectscan.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_journal.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_lockfile.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_logs.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_mcp.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_mutation_hardening.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_nethttp.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_output.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_paths.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_pkg_confusions.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_pkg_injection.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_policy.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_rag.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_registries_fresh.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_registry.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_roadmap_fresh.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_serve.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_spin.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_staleness.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_store.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_token_parity.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_typosquat.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_updatediff.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_util.py +0 -0
- {boost_skill_cli-1.0.119 → boost_skill_cli-1.0.121}/tests/unit/test_version.py +0 -0
|
@@ -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.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 0,
|
|
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,
|
|
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
|
-
|
|
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
|
|
@@ -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:
|
|
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:
|
|
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 & 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
|
|
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
|
|
1737
|
+
<div class="head"><span class="pill shipped">Shipped</span><span class="cat">Security · Secrets</span></div>
|
|
1738
1738
|
<h3>Secret & 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"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|