Skip to content

Commit b971b01

Browse files
committed
feat: Implement automated release process and enhance security features
1 parent da84873 commit b971b01

8 files changed

Lines changed: 176 additions & 20 deletions

File tree

.github/workflows/release.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Automated releases for SecKit, driven by Conventional Commits.
2+
#
3+
# How it works (release-please):
4+
# - Every push to main is scanned for conventional commits (feat:, fix:, ...).
5+
# - release-please keeps an open "release PR" that bumps version.txt + the
6+
# manifest and rewrites CHANGELOG.md from those commits.
7+
# - Merging that PR tags the commit (vX.Y.Z) and publishes a GitHub Release.
8+
#
9+
# Pre-1.0 bumping: feat -> minor, fix -> patch (see release-please-config.json).
10+
#
11+
# Repo setting required once: Settings > Actions > General >
12+
# "Allow GitHub Actions to create and approve pull requests" must be ON,
13+
# otherwise the release PR cannot be opened with the default GITHUB_TOKEN.
14+
name: release
15+
16+
on:
17+
push:
18+
branches: [main]
19+
workflow_dispatch:
20+
21+
permissions:
22+
contents: write
23+
pull-requests: write
24+
25+
jobs:
26+
release-please:
27+
runs-on: ubuntu-latest
28+
steps:
29+
- name: Run release-please
30+
uses: googleapis/release-please-action@v4
31+
with:
32+
token: ${{ secrets.GITHUB_TOKEN }}
33+
config-file: release-please-config.json
34+
manifest-file: .release-please-manifest.json

.release-please-manifest.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
".": "0.1.0"
3+
}

CHANGELOG.md

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,36 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [Unreleased]
9-
10-
### Added
11-
12-
- Added xyz [@your_username](https://github.com/your_username)
13-
14-
### Fixed
8+
From v0.1.0 onward this file is generated automatically by
9+
[release-please](https://github.com/googleapis/release-please) from
10+
[Conventional Commits](https://www.conventionalcommits.org/). Do not edit
11+
released sections by hand; write a good commit message instead.
1512

16-
- Fixed zyx [@your_username](https://github.com/your_username)
17-
18-
## [1.1.0] - 2021-06-23
13+
## [Unreleased]
1914

2015
### Added
2116

22-
- Added x [@your_username](https://github.com/your_username)
17+
- `harden` now drops `ignore-scripts=true` into `.npmrc` on Node repos to block
18+
install-time script execution (the vector used by self-propagating npm worms
19+
such as the May 2026 `redhat-cloud-services` worm). The payload runs from a
20+
package's `preinstall`/`install`/`postinstall` hook during `npm install`,
21+
before any of your own code; this stops it even for variants no scanner has
22+
catalogued yet.
23+
- `harden` surfaces the trade-off concretely: it lists the installed deps in the
24+
target repo that legitimately build via install scripts (e.g. `esbuild`,
25+
`sharp`), so they can be allowlisted with `@lavamoat/allow-scripts` instead of
26+
disabling the protection. The generated `.npmrc` documents the allowlist flow
27+
inline.
28+
- `seckit version` (`--version`/`-v`) prints the installed version, sourced from
29+
`version.txt`.
2330

2431
### Changed
2532

26-
- Changed y [@your_username](https://github.com/your_username)
33+
- Introduced automated releases via release-please and a `version.txt` source of
34+
truth. This is the first tracked release.
2735

28-
## [1.0.0] - 2021-06-20
29-
30-
### Added
36+
## [0.1.0] - 2026-06-02
3137

32-
- Inititated y [@your_username](https://github.com/your_username)
33-
- Inititated z [@your_username](https://github.com/your_username)
38+
Baseline release. Establishes versioning and the automated release flow for the
39+
existing toolkit (`install`, `doctor`, `scan`, `scan-skill`, `harden`, `agent`,
40+
`mcp`, `audit`, `enforce`, `reminders`, `startup`).

README.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pwsh ./seckit.ps1 # Windows
2424
| **install** | Installs every missing scanner and client via brew/npm/pipx/scoop. Run this once on a fresh machine. |
2525
| **scan** | Sweeps a folder of repos for vulnerable dependencies, code/IaC flaws, malware and secrets. Pick all scanners or a subset (osv, gitleaks, trufflehog, semgrep, checkov, socket). |
2626
| **scan-skill** | Statically vets an AI agent skill or MCP server (directory, `.zip` or git URL) before you install it: prompt-injection, data exfiltration, credential theft, supply-chain RCE, obfuscation, over-broad agency and MCP tool poisoning. Never executes the target; prints a 0-100 risk verdict and a markdown report. |
27-
| **harden** | Drops pre-commit, gitleaks, SECURITY.md, CODEOWNERS, dependabot, CodeQL and PR templates into a repo so the next commit is clean. |
27+
| **harden** | Drops pre-commit, gitleaks, SECURITY.md, CODEOWNERS, dependabot, CodeQL and PR templates into a repo so the next commit is clean. On Node repos it also sets `ignore-scripts=true` in `.npmrc` to block install-time supply-chain worms (see below). |
2828
| **agent** | Installs the SecKit prompt as a Claude subagent, Copilot chat mode, Cursor rule or `AGENTS.md` section so any AI assistant runs the same playbook. |
2929
| **mcp** | Wires the official MCP servers (Semgrep, Snyk, OSV, Trivy, Scorecard, GitHub, ADO, Atlassian, Microsoft Learn, Terraform, Foundry) into Claude/Copilot/Cursor. |
3030
| **audit** | Read-only posture check against a GitHub org/repo or Azure DevOps project/repo. Safe to run anywhere because every call is a `GET`. |
@@ -40,5 +40,31 @@ Drop-in pipelines that run the same flow on every push: `seckit install` provisi
4040

4141
Both are soft-fail by default (findings produce a warning plus the report artifact, not a red build); flip the gate step to `exit 1` / remove `continueOnError` to block merges on findings.
4242

43-
More: [`docs/`](docs/) · [`CONTRIBUTING.md`](CONTRIBUTING.md) · [`LICENSE`](LICENSE)
43+
## Blocking npm install-script worms
44+
45+
Self-propagating npm worms (for example the May 2026 `redhat-cloud-services` worm that hit 90+ packages) run their payload from a package's `preinstall`/`install`/`postinstall` hook **during `npm install`, before any of your own code runs**. A scanner only helps if it runs before install, and only catches what it has catalogued; by the time a brand-new variant has an advisory, the hook has already executed and exfiltrated your npm/GitHub/cloud/SSH tokens.
46+
47+
`seckit harden` closes the vector at the source on any repo with a `package.json`: it appends `ignore-scripts=true` to `.npmrc`, so no dependency lifecycle script executes on install. This holds even for a variant no scanner knows about yet.
48+
49+
**The trade-off, handled.** A few legitimate deps build native code in those hooks (`esbuild`, `sharp`, `bcrypt`, ...), and your own root `postinstall`/`prepare` (husky, prisma) is skipped too. `harden` does not leave you to discover this the hard way: it scans `node_modules` and prints the exact deps in your repo that build via install scripts, and the generated `.npmrc` documents how to allowlist them rather than disabling the protection:
50+
51+
```bash
52+
npx --yes @lavamoat/allow-scripts auto # write a vetted allowlist into package.json
53+
npx --yes @lavamoat/allow-scripts # run ONLY allowlisted scripts, after install
54+
# or, dependency-free, for a single vetted native dep:
55+
npm rebuild <pkg> --ignore-scripts=false
56+
```
57+
58+
A hardened install flow then looks like `npm ci && npx --yes @lavamoat/allow-scripts && npm run prepare`.
59+
60+
## Releases
61+
62+
Releases are automated with [release-please](https://github.com/googleapis/release-please), driven by [Conventional Commits](https://www.conventionalcommits.org/) on `main`:
63+
64+
- Push a `feat:`/`fix:` commit to `main`. The [`release`](.github/workflows/release.yml) workflow opens (or updates) a **release PR** that bumps `version.txt` + `.release-please-manifest.json` and rewrites `CHANGELOG.md` from your commits.
65+
- Merge that PR. release-please tags the commit `vX.Y.Z` and publishes a GitHub Release.
66+
67+
Pre-1.0, `feat:` bumps the minor and `fix:` bumps the patch (configured in [`release-please-config.json`](release-please-config.json)). `seckit version` prints the current version. One-time repo setting: **Settings > Actions > General > "Allow GitHub Actions to create and approve pull requests"** must be enabled so the release PR can be opened with the default token.
68+
69+
More: [`docs/`](docs/) · [`CONTRIBUTING.md`](CONTRIBUTING.md) · [`CHANGELOG.md`](CHANGELOG.md) · [`LICENSE`](LICENSE)
4470

release-please-config.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
3+
"bump-minor-pre-major": true,
4+
"bump-patch-for-minor-pre-major": true,
5+
"include-component-in-tag": false,
6+
"changelog-sections": [
7+
{ "type": "feat", "section": "Added" },
8+
{ "type": "fix", "section": "Fixed" },
9+
{ "type": "perf", "section": "Performance" },
10+
{ "type": "refactor", "section": "Changed" },
11+
{ "type": "docs", "section": "Documentation" },
12+
{ "type": "chore", "section": "Miscellaneous", "hidden": true }
13+
],
14+
"packages": {
15+
".": {
16+
"release-type": "simple",
17+
"package-name": "seckit"
18+
}
19+
}
20+
}

seckit.sh

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# seckit enforce <p> <s> write the missing settings (dry-run by default)
1515
# seckit reminders print all security reminders
1616
# seckit startup animated banner + one rotating reminder + tool health
17+
# seckit version print the installed SecKit version
1718
# seckit help this help
1819
#
1920
# Run it before you start work in any repo. Reminders live in reminders.txt.
@@ -22,6 +23,9 @@ set -uo pipefail
2223

2324
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
2425

26+
# Single source of truth, maintained by release-please (see version.txt).
27+
SECKIT_VERSION="$(cat "$HERE/version.txt" 2>/dev/null || echo dev)"
28+
2529
# Optional animated banner (defines banner(); does not auto-play here because
2630
# this script runs non-interactively).
2731
[[ -f "$HERE/banner.sh" ]] && source "$HERE/banner.sh"
@@ -230,7 +234,7 @@ print_status() {
230234
fi
231235
}
232236

233-
cmd_help() { sed -n '3,20p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; }
237+
cmd_help() { sed -n '3,21p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; }
234238

235239
# Interactive menu: shown when seckit is run with no arguments on a terminal.
236240
cmd_menu() {
@@ -378,6 +382,39 @@ _h_block() { # append SecKit block to dest if marker not already present
378382
echo " ${GRN}block ->${RST} ${dest#$root/}"
379383
}
380384

385+
# Node repos: append ignore-scripts=true to .npmrc, then make the catch concrete
386+
# by listing the installed deps that actually declare a build hook (so they get
387+
# allowlisted, not used as an excuse to disable the protection).
388+
_h_npm_hardening() {
389+
[[ -f "$root/package.json" ]] || return 0 # Node projects only
390+
local rc="$root/.npmrc"
391+
if [[ -f "$rc" ]] && grep -q 'ignore-scripts' "$rc" 2>/dev/null; then
392+
echo " ${DIM}ok (already set): .npmrc ignore-scripts${RST}"
393+
else
394+
[[ -f "$rc" ]] && printf '\n' >> "$rc"
395+
cat "$TPL/repo/npmrc-hardened" >> "$rc"
396+
echo " ${GRN}block ->${RST} .npmrc ${DIM}(ignore-scripts=true)${RST}"
397+
fi
398+
# The catch, surfaced for THIS repo: which installed deps build via a hook?
399+
if [[ -d "$root/node_modules" ]] && have jq; then
400+
local hooked
401+
hooked="$(find "$root/node_modules" -maxdepth 3 -name package.json -print0 2>/dev/null \
402+
| xargs -0 jq -r 'select(.scripts.preinstall or .scripts.install or .scripts.postinstall) | .name // empty' 2>/dev/null \
403+
| sort -u)"
404+
if [[ -n "$hooked" ]]; then
405+
local n; n="$(printf '%s\n' "$hooked" | grep -c .)"
406+
echo " ${YEL}heads-up${RST} ${n} installed dep(s) build via install scripts and will NOT run with ignore-scripts on:"
407+
printf '%s\n' "$hooked" | sed 's/^/ /'
408+
echo " ${DIM}allowlist: npx --yes @lavamoat/allow-scripts auto && npx --yes @lavamoat/allow-scripts${RST}"
409+
echo " ${DIM}or one-off: npm rebuild <pkg> --ignore-scripts=false${RST}"
410+
else
411+
echo " ${DIM}ok: no installed dep declares an install script - nothing to allowlist${RST}"
412+
fi
413+
else
414+
echo " ${DIM}tip: after 'npm ci --ignore-scripts', re-run to list deps that need a build (needs jq + node_modules)${RST}"
415+
fi
416+
}
417+
381418
# Confirm before writing (skipped with --yes, or when non-interactive).
382419
_h_confirm() {
383420
(( yes )) && return 0
@@ -471,6 +508,7 @@ cmd_harden() {
471508
echo " .github/copilot-instructions.md ${DIM}agent instructions${RST}"
472509
echo " .github/copilot-content-exclusion.yml ${DIM}paste into GitHub${RST}"
473510
echo " .pre-commit-config.yaml, .gitleaks.toml ${DIM}gitleaks gate${RST}"
511+
echo " .npmrc ${DIM}ignore-scripts (Node repos)${RST}"
474512
echo " SECURITY.md, CODEOWNERS ${DIM}repo hygiene${RST}"
475513
echo " .github/pull_request_template.md ${DIM}PR checklist${RST}"
476514
echo " .github/dependabot.yml ${DIM}dependency updates${RST}"
@@ -496,6 +534,8 @@ cmd_harden() {
496534
# gitleaks pre-commit gate (blocks any secret before it commits).
497535
_h_put "$root/.pre-commit-config.yaml" "$TPL/pre-commit-config.yaml"
498536
_h_put "$root/.gitleaks.toml" "$TPL/gitleaks.toml"
537+
# Block install-time script execution on Node repos + surface the build-script catch.
538+
_h_npm_hardening
499539
# Repo hygiene + PR/CODEOWNERS + dependabot + codeql + ADO PR template.
500540
_h_put "$root/SECURITY.md" "$TPL/repo/SECURITY.md"
501541
_h_put "$root/CODEOWNERS" "$TPL/repo/CODEOWNERS"
@@ -642,6 +682,7 @@ case "$cmd" in
642682
enforce) exec bash "$HERE/enforce.sh" "$@" ;;
643683
reminders|tips) cmd_reminders ;;
644684
startup|hello) cmd_startup ;;
685+
version|--version|-v) echo "seckit $SECKIT_VERSION" ;;
645686
help|-h|--help) cmd_help ;;
646687
*) echo "Unknown command: $cmd" >&2; cmd_help; exit 2 ;;
647688
esac

templates/repo/npmrc-hardened

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# --- SecKit: block install-time script execution ---------------------------
2+
# Supply-chain worms (e.g. the redhat-cloud-services npm worm, May 2026) run
3+
# their payload from a package's preinstall / install / postinstall hook during
4+
# `npm install`, BEFORE any of your own code runs. This setting stops every
5+
# lifecycle script from executing on install, which neutralises that vector
6+
# even for a brand-new variant no scanner has catalogued yet.
7+
#
8+
# THE CATCH: a few legitimate deps compile native code in these hooks
9+
# (esbuild, sharp, bcrypt, better-sqlite3, node-gyp builds, ...). With this on
10+
# they will NOT build, and your OWN root postinstall/prepare (husky, prisma
11+
# generate) is skipped too. Do NOT turn this back off to fix that. Allowlist
12+
# the specific packages you trust instead:
13+
#
14+
# npx --yes @lavamoat/allow-scripts auto # write an allowlist into package.json
15+
# npx --yes @lavamoat/allow-scripts # run ONLY allowlisted scripts, post-install
16+
#
17+
# Dependency-free alternative for one vetted native dep:
18+
# npm rebuild <pkg> --ignore-scripts=false
19+
#
20+
# For your own root scripts, run them explicitly in your setup/CI step:
21+
# npm ci && npx --yes @lavamoat/allow-scripts && npm run prepare
22+
#
23+
# Vet anything new with `seckit scan` / `socket` BEFORE adding it.
24+
ignore-scripts=true

version.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.1.0

0 commit comments

Comments
 (0)