- name
- security-setup
- description
- Install local-first security hardening: pre-commit secret detection, offline dependency scans, static analysis, reports, and gated free CI. Use when hardening repos or adding security hooks. Don't use for incident response or cloud security reviews.
- license
- MIT
- compatibility
- Cross-platform (macOS, Linux, Windows). Requires git, Python 3.8+, and project write access. Uses pre-commit plus free local tools such as gitleaks, trivy, semgrep, bandit, or cargo-audit when appropriate. Semgrep on Windows requires WSL2.
- effort
- high
- metadata
- {"version":"1.5.0","author":"Luong NGUYEN <luongnv89@gmail.com>"}
# Security Setup
Install a local-first security hardening stack for a project. Favor checks that run
offline at hook time, produce machine-readable output, and give developers a clear
summary before code leaves their machine.
<!-- Maintainer note — not runtime guidance: keep this orchestrator short for the
agent's context budget; detailed matrices, templates, and long verification
scenarios live in `references/`. Link, don't inline. -->
This skill may trigger automatically per its description, but it never writes
silently: Phase 1 always dry-runs the planned changes and waits for explicit
user confirmation before touching files, and Phase 2 (CI) only runs when the
user explicitly asks for it (e.g. `--ci`).
## Prerequisites
- Git repo with a branch you can commit to.
- Python 3.8+ and pip.
- Permission to create `.pre-commit-config.yaml`, `scripts/`, `security/`, `SECURITY.md`.
- (Optional for `--ci`) GitHub Actions enabled on the repo.
- Network for initial tool/db installs (subsequent hook runs are offline).
Check these before Phase 1:
1. Run `git rev-parse --git-dir`. If it exits non-zero, stop and end with a `BLOCKED` final report.
2. Run `python3 --version` (`python --version` on Windows). If no interpreter answers, or the version is below 3.8, stop and end with a `BLOCKED` final report.
3. If a selected tool cannot run offline, record the gap for `SECURITY.md` and the final report. Do not claim offline coverage for it.
## Repo Sync Before Edits (mandatory)
Before creating/updating/deleting files in an existing repository, sync the current
branch with remote:
```bash
branch="$(git rev-parse --abbrev-ref HEAD)"
git fetch origin
git pull --rebase origin "$branch"
```
If the working tree is not clean, stash first as a backup, sync, then restore:
```bash
git stash push -u -m "pre-sync" # backup local changes
branch="$(git rev-parse --abbrev-ref HEAD)"
git fetch origin && git pull --rebase origin "$branch"
git stash pop # rollback by restoring the backup
```
If `origin` is missing, pull is unavailable, or rebase/stash conflicts occur, stop
and ask the user before continuing. Never use `--force` rollback options without
confirmation.
## Operating Model
Work in two gated phases:
1. **Local security baseline** - install a pre-commit hook that checks secrets,
dependency vulnerabilities, and static analysis issues locally.
2. **CI/CD mirror** - only when the user asks for `--ci`, create a free-tier
GitHub Actions workflow that runs the same local runner on pull requests.
Do not create CI files until Phase 1 is installed and passing.
## Phase 1 - Local Security Baseline
### 1. Detect the Project
Inspect the repo before choosing tools. The runner and skill instructions work
on macOS, Linux, and Windows; pick the matching shell snippet.
**macOS / Linux (bash, zsh):**
```bash
ls -la package.json package-lock.json pnpm-lock.yaml yarn.lock pyproject.toml requirements.txt Cargo.toml Cargo.lock go.mod pom.xml build.gradle 2>/dev/null
ls -la .pre-commit-config.yaml SECURITY.md .github/workflows/security.yml 2>/dev/null
command -v gitleaks trivy semgrep detect-secrets bandit cargo-audit pre-commit 2>/dev/null
```
**Windows PowerShell:**
```powershell
Get-ChildItem -Force -ErrorAction SilentlyContinue package.json,package-lock.json,pnpm-lock.yaml,yarn.lock,pyproject.toml,requirements.txt,Cargo.toml,Cargo.lock,go.mod,pom.xml,build.gradle
Get-ChildItem -Force -ErrorAction SilentlyContinue .pre-commit-config.yaml,SECURITY.md,.github\workflows\security.yml
foreach ($t in 'gitleaks','trivy','semgrep','detect-secrets','bandit','cargo-audit','pre-commit') { Get-Command $t -ErrorAction SilentlyContinue }
```
Identify:
- Languages and lockfiles
- Existing pre-commit config and security docs
- Available local tools
- Whether the repo is public or private, if CI/SARIF upload is requested
Use `references/tool-selection.md` for the tool matrix and install commands.
### 2. Select the Minimal Tool Set
Choose the smallest useful set:
- **Secrets**: prefer `gitleaks`; use `detect-secrets` when it already exists in
a Python-heavy repo.
- **Dependencies**: prefer `trivy fs --skip-db-update` for offline hook runtime;
add `cargo-audit` only for Rust repos that already use Cargo.
- **Static analysis**: prefer `semgrep` with local rules under `security/`.
Add language-native scanners only when the language is present (`bandit` for
Python, `gosec` for Go, `cargo clippy`/`cargo audit` for Rust).
#### Supply-chain install guardrail
When the repo uses package managers, recommend Socket Firewall as a lightweight
local guardrail for day-to-day dependency installs. This does not replace lockfile
scanning with `trivy`/`cargo-audit`; it shifts risk left by making risky installs
harder before a new dependency reaches the repo.
For macOS/Linux users on `zsh` or `bash`, suggest adding these aliases to the
developer shell profile:
```bash
alias npm="sfw npm"
alias yarn="sfw yarn"
alias pnpm="sfw pnpm"
alias pip="sfw pip"
alias uv="sfw uv"
alias cargo="sfw cargo"
```
Only include aliases for package managers the project actually uses, and document
the guardrail in `SECURITY.md` as optional local developer setup. If Socket
Firewall is not installed, print the official install instructions or link to
the official docs; do not add failing hooks that require `sfw`.
The hook must not call cloud services at runtime. If a scanner needs a local
database, warm that database during setup and run with offline flags in the hook.
If offline dependency scanning cannot be configured for an ecosystem, document the
gap in `SECURITY.md` and do not pretend the criterion is satisfied.
#### File-aware scoping (per-check triggers)
Pre-commit must be fast on small commits without losing coverage. Each check
declares **its own relevance rule** — never a global "skip on .md only" filter.
Trigger semantics in `security/security-tools.json`:
| Trigger | Behavior |
|---|---|
| `"always": true` | Always run. Use for secret scanners — secrets land in `.md`, `.json`, `.env.example`, `Dockerfile`, anywhere. |
| `"paths": [globs]` | Run only when at least one staged file matches a glob. Use for lockfile-driven (`trivy`, `cargo-audit`) or language-driven (`semgrep`, `bandit`) checks. |
| (omitted) | Runner falls back to the per-tool defaults baked into `scripts/security_check.py` for the recognized tool name. |
A repo-wide `trip_all_paths` list (default: `.pre-commit-config.yaml`, `security/**`,
`.github/workflows/**`, `Dockerfile*`, `.dockerignore`, `scripts/security_check.py`)
forces every applicable check to run when any of those files is staged. This
catches workflow-injection edits, hook-tampering, and Dockerfile RCE that would
otherwise slip past purely category-based scoping.
CI runs full scans (`--all`). Scoping only applies on developer commits; the CI
mirror is the safety net.
**Mandatory invariant:** secret scanning runs on every commit. Do not move
gitleaks (or its replacement) into a path-restricted trigger.
### 3. Generate Local Files
Dry-run the changes before writing any file:
1. List every target path below and mark each one `new` or `exists`.
2. For each `exists` target, show the diff between the current file and the planned content.
3. Ask the user to confirm the list. Name `.pre-commit-config.yaml` and `SECURITY.md` explicitly when they exist: overwriting either one is destructive.
4. If the user declines every file, write nothing and end with a `BLOCKED` final report. If the user declines some files, write only the confirmed ones and record each declined file for the final report.
5. Before changing an `exists` target, copy it to `<path>.bak` so the user can roll back.
Create or update these files:
- `.pre-commit-config.yaml` - merge a local `security-check` hook into existing
config; do not overwrite user hooks.
- `scripts/security_check.py` - copy from `scripts/security_check.py` in this
skill, then adjust tool config if needed.
- `security/semgrep-rules.yml` - local Semgrep rules so runtime scans are offline.
- `security/security-tools.json` - selected tools and command overrides.
- `SECURITY.md` - summary of selected tools, why they were chosen, and how to run
or bypass checks.
Use `references/templates.md` for starter snippets.
### 4. Bypass Policy
Never add a silent bypass. Bypass cannot be performed through `git commit`
because `pre-commit` redirects hook stdin to `/dev/null`, so the runner's
TTY check refuses `--force` from inside the hook. The approved bypass is a
two-step, explicit override:
1. Run the runner directly with `--force` and type `YES` at the prompt:
```bash
# macOS / Linux
SECURITY_CHECK_ARGS=--force python3 scripts/security_check.py
```
```powershell
# Windows PowerShell
$env:SECURITY_CHECK_ARGS = "--force"; python scripts\security_check.py; Remove-Item Env:SECURITY_CHECK_ARGS
```
```bat
:: Windows cmd.exe
set SECURITY_CHECK_ARGS=--force && python scripts\security_check.py && set SECURITY_CHECK_ARGS=
```
The runner prints the prompt:
```text
Type YES to override security checks and force-push:
```
It accepts only the literal string `YES`. Any other input, EOF, or a
non-TTY context exits non-zero and refuses the bypass.
2. After the override is recorded in the report, commit with
`git commit --no-verify`. Document the bypass in `SECURITY.md` (date,
reason, link to the recorded report) so the override is auditable.
Do not add `--force` to the pre-commit hook entry, and do not wrap
`git commit` in a script that opens `/dev/tty` for the hook — both routes
hide the bypass from review.
### 5. Verify Locally
Run the local checks after writing files. Use `python3` on macOS/Linux and
`python` on Windows (the Python launcher routes to the active interpreter).
Verification uses `--all` so every configured check runs regardless of what
happens to be staged.
First run `pre-commit --version`. If it exits non-zero, print the install
commands below, run only the `security_check.py` command, skip
`pre-commit run`, and end with a `PARTIAL` final report that names the
missing `pre-commit`:
```bash
python3 -m pip install pre-commit # use `python` on Windows
pre-commit install
```
If it exits 0, run both verification commands and record each exit code:
```bash
# macOS / Linux
python3 scripts/security_check.py --all --no-fail-on-missing-tools
pre-commit run security-check --all-files
```
```powershell
# Windows PowerShell
python scripts\security_check.py --all --no-fail-on-missing-tools
pre-commit run security-check --all-files
```
When run from a `pre-commit` hook with no flags, the runner inspects
`git diff --cached` and scopes checks to the staged file set per the trigger
table in §2. `--all` overrides this for verification or one-off full scans;
`--staged-only` errors if no staged files are found (useful for guarded
hooks).
Exit codes: `0` means no failing finding (or an accepted bypass), `1` means
at least one `fail_on` finding, and `2` means the runner could not run (an
invalid `security-tools.json`, `--staged-only` with nothing staged, or an
unwritable report path); its stderr message names the input and the fix.
#### Expected output
A successful first run prints a summary to stdout and exits 0. See
"Report Requirements" below for the exact shape and content of that summary.
Assert: a clean run exits 0, both report paths exist, and
`security-report.json` parses as valid JSON with a top-level `summary`
object. Any `HIGH` or `CRITICAL` finding exits non-zero unless the bypass
in §"4. Bypass Policy" was completed.
## Phase 2 - CI/CD Mirror (`--ci`)
Only run this phase when the user asks for CI/CD, for example
`/security-setup --ci`.
Preconditions:
- Phase 1 files exist
- `python3 scripts/security_check.py --all --no-fail-on-missing-tools` exits 0 locally
- `.pre-commit-config.yaml` contains the `security-check` hook
If any precondition fails, do not create the workflow; record `CI not created`
and the failing precondition for the final report.
Otherwise create `.github/workflows/security.yml` using `references/templates.md`.
If that file already exists, apply the Step 3 dry-run steps to it first
(diff, confirm, `.bak` backup). Keep the workflow free-tier friendly:
- Trigger on `pull_request` and `push` to the default branch.
- Install only the selected tools.
- Cache scanner databases where supported.
- Run `python3 scripts/security_check.py --all` (CI always full-scans).
- Upload SARIF only when the repo can use GitHub Code Scanning; otherwise keep
the job summary only.
## Report Requirements
The local runner must print a concise report with:
- Total checks run
- Findings by category: secrets, dependencies, static analysis, tool errors
- Severity breakdown: critical, high, medium, low, info
- Actionable fix hints
- Paths to JSON and Markdown report artifacts
The default exit behavior is strict: any `HIGH` or `CRITICAL` finding exits
non-zero.
A successful full run looks like this (exact counts vary):
```text
Security Check Summary
======================
Mode: full
Checks run: 3 of 3 (skipped: 0)
Findings: 1
Severity: HIGH=1
Categories: dependencies=1
JSON report: security/security-report.json
Markdown report: security/security-report.md
Top findings:
- HIGH [dependencies/trivy] CVE-XXXX-XXXX in <pkg> (<lockfile>)
Hint: Upgrade to <version>.
```
A scoped run on a docs-only commit looks like:
```text
Mode: staged
Staged files: 2
Checks run: 1 of 3 (skipped: 2)
Skipped: trivy, semgrep
```
Skipped checks are recorded in both reports with their scope reason — they
are not silent.
## Acceptance Criteria
A completed setup passes when:
- [ ] `.pre-commit-config.yaml` contains a local `security-check` hook.
- [ ] `scripts/security_check.py` exists and prints the required summary.
- [ ] Secret detection, dependency scanning, and static analysis are configured
for the detected project where offline local tooling is available.
- [ ] Hook runtime uses offline flags and does not require cloud credentials.
- [ ] `--force` requires the exact `YES` confirmation before bypassing failures.
- [ ] `SECURITY.md` documents selected tools, omissions, run commands, and CI
status.
- [ ] `--ci` creates `.github/workflows/security.yml` only after Phase 1 passes.
- [ ] File-aware scoping has been walked through manually against the
no-blindspot scenarios in `references/verification-scenarios.md`.
- [ ] The final report passes the reader checks in `references/final-report.md`:
the status is on the first line, verified checks are separated from
assumptions, each claim cites a command or file, and the next decision is
named. Human understanding stays unconfirmed until the user gives feedback.
## Edge Cases
- **Existing hooks**: merge the new hook; preserve all existing hooks and revs.
- **Monorepo**: use path filters in `security/security-tools.json` and document
per-package coverage.
- **Missing tools**: print the install command from
`references/tool-selection.md` for each missing selected tool, and install it
only after the user confirms. A category whose tool stays missing is listed
in the final report; never skip it silently.
- **Private repo SARIF**: GitHub Code Scanning may require a paid plan. Keep the
summary report and skip SARIF upload unless available.
- **Network-restricted setup**: create configs and report the commands that must
be run later to warm vulnerability databases.
## Step Completion Report
After each phase, report:
```text
◆ Security Setup ([phase] - [context])
................................................................
Project detection: pass | fail - detail
Tool selection: pass | fail - detail
Local hook: pass | fail - detail
Security report: pass | fail - detail
CI mirror: pass | skipped | fail - detail
Criteria: N/M met
____________________________
Result: PASS | PARTIAL | FAIL
```
## Final Report
End every run, including a stop, with one final report in the shape
Auf GitHub ansehen