diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 5a66701..53e67c8 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -5,11 +5,23 @@ on: branches: ["main"] pull_request: +permissions: + contents: read + issues: write + pull-requests: write + jobs: security: runs-on: ubuntu-latest + timeout-minutes: 15 + steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Prepare artifacts directory + run: mkdir -p artifacts/security - name: Set up Go uses: actions/setup-go@v5 @@ -22,3 +34,132 @@ jobs: uses: returntocorp/semgrep-action@v1 with: config: "p/ci" + - name: Run Gitleaks + uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Run Trivy FS Scan + uses: aquasecurity/trivy-action@master + with: + scan-type: fs + format: json + output: artifacts/security/trivy-fs.json + severity: HIGH,CRITICAL + + - name: Generate security summary (JSON) + if: always() + run: | + echo "Generating security summary..." + mkdir -p artifacts/security + + # Gitleaks: count total findings (supports array or {findings:[...]}) + if [ -f artifacts/security/gitleaks-report.json ]; then + GITLEAKS_COUNT=$(jq 'if type=="array" then length else (.findings | length // 0) end' artifacts/security/gitleaks-report.json) + else + GITLEAKS_COUNT=0 + fi + + # Trivy: group vulnerabilities by severity (CRITICAL/HIGH/etc.) + if [ -f artifacts/security/trivy-fs.json ]; then + TRIVY_SUMMARY=$(jq ' + ( [ .Results[].Vulnerabilities[]? | .Severity ] + | group_by(.) + | map({ (.[0]): length }) + | add + ) // {}' artifacts/security/trivy-fs.json) + else + TRIVY_SUMMARY='{}' + fi + + jq -n \ + --argjson gitleaks_count "$GITLEAKS_COUNT" \ + --argjson trivy "$TRIVY_SUMMARY" \ + '{ gitleaks: { total: $gitleaks_count }, trivy: $trivy }' \ + > artifacts/security/summary.json + + echo "Summary written to artifacts/security/summary.json" + + - name: Post security summary as PR comment + if: always() && github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const fs = require('fs'); + const path = 'artifacts/security/summary.json'; + const marker = ''; + + let summary; + try { + const raw = fs.readFileSync(path, 'utf8'); + summary = JSON.parse(raw); + } catch (err) { + core.warning(`Could not read ${path}: ${err}`); + summary = null; + } + + let body = `${marker}\n`; + body += '### πŸ” DevSecOps Kit Security Summary\n\n'; + + if (!summary) { + body += '_No summary.json available. Check workflow logs._\n'; + } else { + const gitleaksTotal = summary?.gitleaks?.total ?? 0; + const trivy = summary?.trivy || {}; + + body += `- **Gitleaks:** ${gitleaksTotal} leak(s)\n`; + + const severities = Object.keys(trivy); + if (severities.length > 0) { + body += '- **Trivy vulnerabilities:**\n'; + for (const sev of severities.sort()) { + body += ` - ${sev}: ${trivy[sev]}\n`; + } + } else { + body += '- **Trivy vulnerabilities:** none counted in summary\n'; + } + + const hasBlocking = + gitleaksTotal > 0 || + (trivy.CRITICAL ?? 0) > 0 || + (trivy.HIGH ?? 0) > 0; + + body += '\n'; + body += hasBlocking + ? '🚨 _Status: Potential blocking issues detected._\n' + : 'βœ… _Status: No blocking issues detected (HIGH/CRITICAL)._ \n'; + } + + const { owner, repo } = context.repo; + const issue_number = context.issue.number; + + const comments = await github.rest.issues.listComments({ + owner, + repo, + issue_number, + }); + + const existing = comments.data.find(c => c.body && c.body.includes(marker)); + + if (existing) { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner, + repo, + issue_number, + body, + }); + } + + - name: Upload security artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: security-reports + path: artifacts/security/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2763a61 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,100 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres (loosely) to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +--- + +## [Unreleased] + +- TBD + +--- + +## [0.2.0] - 2025-11-21 + +### Added + +- **`devsecops diagnose` command** to verify environment readiness: + - Checks installed scanners (Semgrep, Gitleaks, Trivy) + - Verifies Docker availability (for Trivy) + - Confirms project type detection +- **Interactive wizard** for initialization: + - `devsecops init --wizard` + - Guides users through selecting tools, severity thresholds, and configuration choices. +- **Automated PR security summary comment**: + - GitHub Actions workflow posts (and updates) a comment on pull requests. + - Comment includes Gitleaks leak counts, Trivy vulnerability counts, and a pass/fail recommendation. +- **JSON summary output**: + - New `artifacts/security/summary.json` file generated on each run. + - Includes: + - Total number of leaks from Gitleaks (when available) + - Aggregated Trivy vulnerabilities per severity (CRITICAL/HIGH/MEDIUM/LOW) +- **Security artifacts upload**: + - Workflow now uploads a `security-reports` artifact containing: + - `summary.json` + - `trivy-fs.json` (when Trivy is enabled) + - `gitleaks-report.json` (reserved for future enhancements) +- **Expanded `security-config.yml` schema**: + - New fields added: + - `version`: configuration schema version (starting at `"0.2.0"`) + - `exclude_paths`: reserved list for future path exclusions + - `fail_on`: reserved map for future per-tool fail gates + - `notifications`: + - `pr_comment`: enabled by default + - `slack`, `email`: placeholders for future integrations + +### Changed + +- **GitHub Actions workflows hardened**: + - Explicit `permissions` block: + - `contents: read` + - `issues: write` + - `pull-requests: write` + - Added job-level `timeout-minutes` to avoid hanging runs. + - Ensured artifacts folder (`artifacts/security/`) is created early in the job. +- **Workflow templates updated** to: + - Always generate `summary.json` even if some tools are disabled. + - Use `actions/github-script@v7` to manage PR comments with an idempotent marker. +- **README** refreshed to reflect v0.2.0 features: + - Added sections for wizard, diagnose, JSON summary, PR comments, and updated config format. + - Clarified installation and quick-start flows. + +### Fixed + +- Resolved issues with: + - Gitleaks GitHub token requirements on pull requests by setting `GITHUB_TOKEN` env in the workflow. + - Incompatible `with:` inputs for the Gitleaks action (removed unsupported inputs). + - `github-script` step script errors related to re-declaring `core` and insufficient `GITHUB_TOKEN` permissions. + +--- + +## [0.1.0] - 2025-11-20 + +### Added + +- Initial release of **DevSecOps Kit** CLI: + - `devsecops` binary. +- **Project type detection**: + - Node.js via `package.json` + - Go via `go.mod` +- **GitHub Actions workflow generation**: + - Node.js and Go workflows with: + - Semgrep (SAST) + - Gitleaks (secrets scanning) + - Trivy (filesystem/dependency scanning) +- **`security-config.yml` generation**: + - Base fields: + - `language` + - `framework` + - `severity_threshold` + - `tools` (Semgrep, Gitleaks, Trivy flags) +- **CLI flags for configuration**: + - `--severity` to set severity threshold. + - `--no-semgrep`, `--no-gitleaks`, `--no-trivy` to toggle tools. +- **Version subcommand**: + - `devsecops version` prints the current CLI version. + +--- diff --git a/README.md b/README.md index 9bc827a..b8c39ad 100644 --- a/README.md +++ b/README.md @@ -1,321 +1,217 @@ -# DevSecOps Kit +# πŸ“˜ DevSecOps Kit -Opinionated CLI to bootstrap a complete security pipeline for small teams. +Modern, opinionated CLI to bootstrap a complete security pipeline for small teams β€” instantly. +DevSecOps Kit detects your project (Node.js or Go), generates a hardened GitHub Actions workflow, and produces a centralized security configuration that evolves with your needs. -DevSecOps Kit automatically detects your project type and generates a ready-to-use GitHub Actions security workflow along with a centralized `security-config.yml`. +Designed for small teams, freelancers, and agencies who need practical DevSecOps without complexity. -It’s designed for small teams, freelancers, and agencies that need practical DevSecOps pipelines β€” without spending days wiring scanners manually. - ---- - -## ✨ Features (v0.1.0) +## πŸš€ Key Features (v0.2.0) ### πŸ” Automatic Project Detection -- Node.js (via `package.json`) -- Go (via `go.mod`) - -### βš™οΈ Security Workflow Generation -Creates a tailored GitHub Actions workflow: -- Node.js workflow -- Go workflow - -### πŸ›‘οΈ Security Tools Integration -Enable tools individually via CLI flags: -- **Semgrep** β€” static code analysis -- **Gitleaks** β€” secrets scanning -- **Trivy** β€” dependency & container scanning - -### 🎚️ Configurable Severity Threshold -Controls what fails the pipeline: -`low | medium | high | critical` +Works out-of-the-box with: -### πŸ“„ Centralized Security Configuration -Automatically creates a `security-config.yml` with: -- Language / framework -- Enabled tools -- Severity threshold -- Metadata (CLI version, timestamp) - -### πŸ“¦ Single Self-Contained Binary -- No external templates needed -- All assets embedded via `go:embed` - ---- - -## πŸ› οΈ Installation +- Node.js (`package.json`) +- Go (`go.mod`) -You can install DevSecOps Kit in two ways: +### βš™οΈ Auto-Generated Security Pipeline +Generates a ready-to-run GitHub Actions workflow including: -### **1. Install via Go (recommended)** +- Semgrep (SAST) +- Gitleaks (Secrets detection) +- Trivy (FS + dependency scanning) +- Hardenered permissions +- Artifact uploads +- Timeout protections +### πŸ§™ Interactive Wizard ```bash -go install github.com/edgarposada/devsecops-kit@latest +devsecops init --wizard ``` -This installs the binary globally into `$GOPATH/bin/`. +A guided setup for new users: -Check that it works: +- Select tools +- Choose severity gates +- Preview settings before generating +### 🩺 Environment Diagnose Command ```bash -devsecops version +devsecops diagnose ``` -### **2. Build from source** +Checks system readiness: -```bash -git clone https://github.com/edgarposada/devsecops-kit.git -cd devsecops-kit +- Installed scanners +- Docker availability +- Project detection +- CI/CD compatibility -# Build binary (VERSION defaults to 0.1.0) -make build +### πŸ“¦ Artifacts + JSON Summary (CI) +Each workflow produces: -# Check version -./devsecops-kit version +``` +artifacts/security/ + gitleaks-report.json + trivy-fs.json + summary.json ``` ---- +The `summary.json` contains: -## πŸš€ Usage +- Total secrets leaks +- Vulnerability counts by severity +- Ready for dashboards or fail-gates in future releases -### Initialize security settings +### πŸ’¬ Automated PR Security Comment +Every pull request receives a concise, updated comment summarizing: -```bash -devsecops init -``` +- Secrets found +- Vulnerabilities +- PASS/FAIL recommendation -This will: +### πŸ“„ Expanded Configuration (v0.2.0) -- Detect Node.js or Go -- Generate `security-config.yml` -- Create `.github/workflows/security.yml` -- Enable default scanners (Semgrep + Gitleaks) +Generated automatically as: ---- +```yaml +version: "0.2.0" -## πŸ”§ CLI Flags +language: "golang" +framework: "" -| Flag | Description | Default | -|------|-------------|---------| -| `--tools` | Comma-separated list: `semgrep`, `gitleaks`, `trivy` | `semgrep,gitleaks` | -| `--severity` | Minimum severity to fail CI | `high` | -| `--output` | Output directory | `./` | -| `--dry-run` | Preview without generating files | `false` | -| `-y, --yes` | Skip confirmation prompts | `false` | -| `--version` | Show version | β€” | +severity_threshold: "high" ---- +tools: + semgrep: true + trivy: true + gitleaks: true -## πŸ“ Usage Examples +exclude_paths: [] +fail_on: {} -### 1. Basic initialization -```bash -devsecops init +notifications: + pr_comment: true + slack: false + email: false ``` -### 2. Enable all tools -```bash -devsecops init --tools semgrep,gitleaks,trivy -``` +## πŸ› οΈ Installation -### 3. Fail on ANY severity +### Option A β€” Install via Go ```bash -devsecops init --severity low +go install github.com/edgarpsda/devsecops-kit/cmd/devsecops@latest ``` -### 4. Strict performance mode (fail only critical) +Verify: + ```bash -devsecops init --severity critical +devsecops version ``` -### 5. Preview before generating (dry run) +### Option B β€” Build from source ```bash -devsecops init --dry-run +git clone https://github.com/edgarpsda/devsecops-kit.git +cd devsecops-kit +make build +./devsecops version ``` -### 6. Custom output folder +## 🚦 Quick Start + +### 1. Run the wizard (recommended) ```bash -devsecops init --output ./ci/security +devsecops init --wizard ``` -### 7. Non-interactive mode (CI-friendly) +### 2. Or non-interactive: ```bash -devsecops init -y +devsecops init ``` ---- +This generates: -## πŸ“„ Example Configurations - -### Node.js example (`security-config.yml`) - -```yaml -language: node -tools: - semgrep: true - gitleaks: true - trivy: true -severity_threshold: high +``` +security-config.yml +.github/workflows/security.yml +``` -metadata: - generated_at: 2025-01-01T12:34:56Z - version: 0.1.0 +### 3. Diagnose environment +```bash +devsecops diagnose ``` -### Go example +## πŸ”§ CLI Flags -```yaml -language: go -tools: - semgrep: true - gitleaks: true - trivy: true -severity_threshold: high +| Flag | Description | +|----------------|-------------------------------------| +| `--wizard` | Launch interactive configuration | +| `--severity` | Set severity threshold | +| `--no-semgrep` | Disable Semgrep | +| `--no-gitleaks`| Disable Gitleaks | +| `--no-trivy` | Disable Trivy | +| `--verbose` | Verbose mode | -metadata: - generated_at: 2025-01-01T12:34:56Z - version: 0.1.0 -``` +## πŸ“„ Example Security Summary Comment (PR) ---- +```markdown +### πŸ” DevSecOps Kit Security Summary -## βš™οΈ Example GitHub Actions Workflow +- **Gitleaks:** 0 leaks +- **Trivy vulnerabilities:** + - CRITICAL: 0 + - HIGH: 2 + - MEDIUM: 7 -### Node.js Version - -```yaml -name: Security Scan - -on: - push: - branches: ["main"] - pull_request: - -jobs: - security: - name: Run Security Scans - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: "18" - - - name: Install dependencies - run: npm install --legacy-peer-deps - - - name: Semgrep Code Scan - uses: returntocorp/semgrep-action@v1 - with: - config: "p/ci" - - - name: Secrets Scan (Gitleaks) - uses: gitleaks/gitleaks-action@v2 - with: - args: "--config-path=.gitleaks.toml --verbosity=info" - - - name: Dependency Scan (Trivy) - uses: aquasecurity/trivy-action@master - with: - scan-type: "fs" - severity: "HIGH,CRITICAL" +βœ… Status: No blocking issues detected. ``` ---- +## πŸ“ Example Artifacts -### Go Version - -```yaml -name: Security Scan - -on: - push: - branches: ["main"] - pull_request: - -jobs: - security: - name: Run Security Scans - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: "1.22" - - - name: Semgrep Code Scan - uses: returntocorp/semgrep-action@v1 - with: - config: "p/ci" - - - name: Secrets Scan (Gitleaks) - uses: gitleaks/gitleaks-action@v2 - - - name: Dependency Scan (Trivy) - uses: aquasecurity/trivy-action@master - with: - scan-type: "fs" - severity: "HIGH,CRITICAL" ``` - ---- +security-reports/ + trivy-fs.json + gitleaks-report.json + summary.json +``` ## 🧭 Roadmap -| Version | Planned Feature | -|---------|-----------------| -| **v0.2.0** | Prebuilt binaries for Mac, Linux, Windows | -| **v0.3.0** | Python, Java & Dockerfile detection | -| **v0.4.0** | Local CLI command to run all scans | -| **v0.5.0** | VS Code extension for workflow generation | -| **v1.0.0** | Fully interactive onboarding wizard | - ---- +| Version | Features | +|---------|----------| +| **0.3.0** | Fail-on logic, exclude-paths integration, Semgrep JSON support | +| **0.4.0** | Local CLI scans (`devsecops scan`) | +| **0.5.0** | Expanded detection: Python, Java, Dockerfiles | +| **1.0.0** | Full onboarding experience + multi-CI support | ## 🀝 Contributing -Contributions are welcome. - -1. Fork the repo -2. Create a feature branch -3. Run `make build` before submitting -4. Open a PR following conventional commits +Contributions are welcome! ---- +- Fork the repository +- Create a feature branch +- Run `make build` before submitting +- Follow conventional commits +- Open a PR πŸŽ‰ -## πŸ§ͺ Development +## πŸ“œ License -### Build: -```bash -make build -``` - -### Tests: -```bash -make test -``` +MIT β€” free for personal and commercial use. -### Formatting: -```bash -make fmt -``` +## πŸ›‘οΈ Security & Privacy ---- +- No telemetry +- No tracking +- No code uploads +- All scans run locally or in your own CI +- OSS tools with strong community support ## ❓ FAQ -### Is it only for GitHub Actions? -For now, yes. GitLab & Jenkins support are planned. +### Does it overwrite existing CI workflows? +No β€” unless you explicitly approve it. -### Will it overwrite my workflows? -No. It creates new files unless you pass `--yes`. +### Does it support GitLab or Jenkins? +Coming soon (planned in v0.4.x). -### Does it send telemetry? -Never. No tracking. 100% local. +### Will more languages be supported? +Yes, Python, Java, Dockerfile detection is planned for v0.5.0. diff --git a/cli/cmd/diagnose.go b/cli/cmd/diagnose.go new file mode 100644 index 0000000..67010a0 --- /dev/null +++ b/cli/cmd/diagnose.go @@ -0,0 +1,111 @@ +package cmd + +import ( + "fmt" + "os/exec" + "path/filepath" + + "github.com/spf13/cobra" + + "github.com/edgarpsda/devsecops-kit/cli/detectors" +) + +var diagnosePath string + +// diagnoseCmd defines the `devsecops diagnose` command. +var diagnoseCmd = &cobra.Command{ + Use: "diagnose", + Short: "Check DevSecOps environment and project readiness", + Long: `Run a series of checks to verify that your environment and project +are ready to use DevSecOps Kit. + +It checks: +- Project type detection (Node.js / Go) +- Availability of Semgrep, Gitleaks, Trivy +- Docker CLI presence (for container-based scanners in the future)`, + RunE: func(cmd *cobra.Command, args []string) error { + return runDiagnose() + }, +} + +func init() { + // Register the diagnose command with the root command. + rootCmd.AddCommand(diagnoseCmd) + + diagnoseCmd.Flags().StringVar( + &diagnosePath, + "path", + ".", + "Project root path (default: current directory)", + ) +} + +func runDiagnose() error { + root := diagnosePath + if root == "" { + root = "." + } + + absRoot, err := filepath.Abs(root) + if err != nil { + return fmt.Errorf("failed to resolve path %q: %w", root, err) + } + + fmt.Println("🩺 DevSecOps Kit Diagnose") + fmt.Println("-------------------------") + fmt.Printf("Project root: %s\n\n", absRoot) + + // 1) Project detection + fmt.Println("πŸ” Project detection") + det, err := detectors.DetectProject(absRoot) + if err != nil { + fmt.Printf(" ❌ Failed to detect project: %v\n\n", err) + } else { + fmt.Println(" βœ… Detection succeeded:") + fmt.Printf(" β€’ Language: %s\n", det.Language) + fmt.Printf(" β€’ Framework: %s\n", det.Framework) + fmt.Printf(" β€’ Package: %s\n", det.PackageFile) + fmt.Printf(" β€’ RootDir: %s\n\n", det.RootDir) + } + + // 2) Tool checks + fmt.Println("πŸ›  Tool checks") + checkAndPrintTool("Semgrep", "semgrep") + checkAndPrintTool("Gitleaks", "gitleaks") + checkAndPrintTool("Trivy", "trivy") + checkAndPrintTool("Docker", "docker") + + fmt.Println() + fmt.Println("βœ… Diagnose finished.") + fmt.Println("If some tools are missing (⚠️ / ❌), install them or adjust your pipeline configuration.") + + // Missing tools are informational, not a hard error. + return nil +} + +func checkAndPrintTool(label, binary string) { + found, path, err := checkBinary(binary) + if err != nil { + fmt.Printf(" ❌ %s: error while checking: %v\n", label, err) + return + } + + if !found { + fmt.Printf(" ⚠️ %s: NOT found on PATH\n", label) + return + } + + fmt.Printf(" βœ… %s: found at %s\n", label, path) +} + +func checkBinary(name string) (bool, string, error) { + path, err := exec.LookPath(name) + if err != nil { + // Not found is not a "real" error; just report false. + if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound { + return false, "", nil + } + return false, "", err + } + return true, path, nil +} diff --git a/cli/cmd/init.go b/cli/cmd/init.go index ce6e066..7bf2bce 100644 --- a/cli/cmd/init.go +++ b/cli/cmd/init.go @@ -1,6 +1,7 @@ package cmd import ( + "bufio" "fmt" "os" "path/filepath" @@ -16,6 +17,7 @@ var ( noSemgrepFlag bool noTrivyFlag bool noGitleaksFlag bool + wizardFlag bool ) var initCmd = &cobra.Command{ @@ -27,9 +29,16 @@ This command detects your project type and generates: - .github/workflows/security.yml - security-config.yml -You can customize severity threshold and enabled tools with flags. +You can customize severity threshold and enabled tools with flags, +or run interactively with --wizard. `, RunE: func(cmd *cobra.Command, args []string) error { + + if wizardFlag { + return runInitWizard() + } + + // Non-interactive mode (original behavior) fmt.Println("πŸ” Detecting project type...") dir, err := os.Getwd() @@ -94,4 +103,130 @@ func init() { initCmd.Flags().BoolVar(&noSemgrepFlag, "no-semgrep", false, "Disable Semgrep in generated workflow") initCmd.Flags().BoolVar(&noTrivyFlag, "no-trivy", false, "Disable Trivy in generated workflow") initCmd.Flags().BoolVar(&noGitleaksFlag, "no-gitleaks", false, "Disable Gitleaks in generated workflow") + initCmd.Flags().BoolVar(&wizardFlag, "wizard", false, "Run interactive guided setup") +} + +// +// ----------------------------- +// INTERACTIVE WIZARD MODE +// ----------------------------- +// + +func runInitWizard() error { + reader := bufio.NewReader(os.Stdin) + + fmt.Println("πŸ§™ Welcome to DevSecOps Kit Wizard!") + fmt.Println("-----------------------------------") + + // Detect project type automatically + dir, _ := os.Getwd() + project, err := detectors.DetectProject(dir) + if err != nil { + return fmt.Errorf("project detection failed: %w", err) + } + + fmt.Printf("πŸ” Detected project: %s (%s)\n", project.Language, project.Framework) + if !askYesNo(reader, "Is this correct? (Y/n): ", true) { + fmt.Println("❌ Aborted by user.") + return nil + } + + // Choose tools + fmt.Println("\nπŸ›  Select tools to enable:") + + enableSemgrep := askYesNo(reader, "Enable Semgrep? (Y/n): ", true) + enableGitleaks := askYesNo(reader, "Enable Gitleaks? (Y/n): ", true) + enableTrivy := askYesNo(reader, "Enable Trivy? (Y/n): ", true) + + // Select severity + fmt.Println("\n🎚 Choose severity threshold:") + severity := askChoice(reader, "low | medium | high | critical [default: high]: ", + []string{"low", "medium", "high", "critical"}, + "high", + ) + + // Show summary + fmt.Println("\nπŸ“‹ Summary") + fmt.Println("----------------------------------") + fmt.Printf("Language: %s\n", project.Language) + fmt.Printf("Framework: %s\n", project.Framework) + fmt.Printf("Severity: %s\n", severity) + fmt.Printf("Tools:\n") + fmt.Printf(" - Semgrep: %v\n", enableSemgrep) + fmt.Printf(" - Gitleaks: %v\n", enableGitleaks) + fmt.Printf(" - Trivy: %v\n", enableTrivy) + + if !askYesNo(reader, "\nProceed and generate files? (Y/n): ", true) { + fmt.Println("❌ Aborted by user.") + return nil + } + + // Generate config + cfg := &generators.InitConfig{ + Project: project, + SeverityThreshold: severity, + Tools: generators.ToolsConfig{ + Semgrep: enableSemgrep, + Gitleaks: enableGitleaks, + Trivy: enableTrivy, + }, + } + + wfDir := filepath.Join(dir, ".github", "workflows") + if err := os.MkdirAll(wfDir, 0o755); err != nil { + return fmt.Errorf("failed to create workflows directory: %w", err) + } + + fmt.Println("\nβš™οΈ Generating workflow + config files...") + + if err := generators.GenerateGithubActions(cfg); err != nil { + return err + } + if err := generators.GenerateSecurityConfig(cfg); err != nil { + return err + } + + fmt.Println("\nπŸŽ‰ Setup complete!") + fmt.Println("Generated:") + fmt.Println(" - .github/workflows/security.yml") + fmt.Println(" - security-config.yml") + + return nil +} + +// +// ----------------------------- +// HELPER FUNCTIONS +// ----------------------------- +// + +func askYesNo(reader *bufio.Reader, prompt string, def bool) bool { + fmt.Print(prompt) + resp, _ := reader.ReadString('\n') + resp = strings.TrimSpace(strings.ToLower(resp)) + + if resp == "" { + return def + } + return resp == "y" || resp == "yes" +} + +func askChoice(reader *bufio.Reader, prompt string, valid []string, def string) string { + for { + fmt.Print(prompt) + resp, _ := reader.ReadString('\n') + resp = strings.TrimSpace(strings.ToLower(resp)) + + if resp == "" { + return def + } + + for _, v := range valid { + if resp == v { + return resp + } + } + + fmt.Println("❌ Invalid choice, try again.") + } } diff --git a/cli/templates/security-config.yml.tmpl b/cli/templates/security-config.yml.tmpl index 513b9e7..fbe2ff2 100644 --- a/cli/templates/security-config.yml.tmpl +++ b/cli/templates/security-config.yml.tmpl @@ -1,10 +1,25 @@ # Security config generated by DevSecOps Kit +version: "0.2.0" + language: "{{ .Project.Language }}" framework: "{{ .Project.Framework }}" + severity_threshold: "{{ .SeverityThreshold }}" tools: semgrep: {{ .Tools.Semgrep }} - gitleaks: {{ .Tools.Gitleaks }} trivy: {{ .Tools.Trivy }} + gitleaks: {{ .Tools.Gitleaks }} + +# Reserved for future versions β€” currently unused in execution +exclude_paths: [] + +# Reserved for future: per-tool fail-on behavior +fail_on: {} + +# Notification settings (PR comment enabled by default) +notifications: + pr_comment: true + slack: false + email: false diff --git a/cli/templates/workflows/go_security.yml.tmpl b/cli/templates/workflows/go_security.yml.tmpl index f64ecaf..0635236 100644 --- a/cli/templates/workflows/go_security.yml.tmpl +++ b/cli/templates/workflows/go_security.yml.tmpl @@ -5,11 +5,23 @@ on: branches: ["main"] pull_request: +permissions: + contents: read + issues: write + pull-requests: write + jobs: security: runs-on: ubuntu-latest + timeout-minutes: 15 + steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Prepare artifacts directory + run: mkdir -p artifacts/security - name: Set up Go uses: actions/setup-go@v5 @@ -28,13 +40,135 @@ jobs: {{- if .Tools.Gitleaks }} - name: Run Gitleaks - uses: zricethezav/gitleaks-action@v2 + uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{"{{"}} secrets.GITHUB_TOKEN {{ "}}" }} {{- end }} {{- if .Tools.Trivy }} - - name: Trivy FS Scan + - name: Run Trivy FS Scan uses: aquasecurity/trivy-action@master with: scan-type: fs + format: json + output: artifacts/security/trivy-fs.json severity: HIGH,CRITICAL {{- end }} + + - name: Generate security summary (JSON) + if: always() + run: | + echo "Generating security summary..." + mkdir -p artifacts/security + + # Gitleaks: count total findings (supports array or {findings:[...]}) + if [ -f artifacts/security/gitleaks-report.json ]; then + GITLEAKS_COUNT=$(jq 'if type=="array" then length else (.findings | length // 0) end' artifacts/security/gitleaks-report.json) + else + GITLEAKS_COUNT=0 + fi + + # Trivy: group vulnerabilities by severity (CRITICAL/HIGH/etc.) + if [ -f artifacts/security/trivy-fs.json ]; then + TRIVY_SUMMARY=$(jq ' + ( [ .Results[].Vulnerabilities[]? | .Severity ] + | group_by(.) + | map({ (.[0]): length }) + | add + ) // {}' artifacts/security/trivy-fs.json) + else + TRIVY_SUMMARY='{}' + fi + + jq -n \ + --argjson gitleaks_count "$GITLEAKS_COUNT" \ + --argjson trivy "$TRIVY_SUMMARY" \ + '{ gitleaks: { total: $gitleaks_count }, trivy: $trivy }' \ + > artifacts/security/summary.json + + echo "Summary written to artifacts/security/summary.json" + + - name: Post security summary as PR comment + if: always() && github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + github-token: ${{"{{"}} secrets.GITHUB_TOKEN {{ "}}" }} + script: | + const fs = require('fs'); + const path = 'artifacts/security/summary.json'; + const marker = ''; + + let summary; + try { + const raw = fs.readFileSync(path, 'utf8'); + summary = JSON.parse(raw); + } catch (err) { + core.warning(`Could not read ${path}: ${err}`); + summary = null; + } + + let body = `${marker}\n`; + body += '### πŸ” DevSecOps Kit Security Summary\n\n'; + + if (!summary) { + body += '_No summary.json available. Check workflow logs._\n'; + } else { + const gitleaksTotal = summary?.gitleaks?.total ?? 0; + const trivy = summary?.trivy || {}; + + body += `- **Gitleaks:** ${gitleaksTotal} leak(s)\n`; + + const severities = Object.keys(trivy); + if (severities.length > 0) { + body += '- **Trivy vulnerabilities:**\n'; + for (const sev of severities.sort()) { + body += ` - ${sev}: ${trivy[sev]}\n`; + } + } else { + body += '- **Trivy vulnerabilities:** none counted in summary\n'; + } + + const hasBlocking = + gitleaksTotal > 0 || + (trivy.CRITICAL ?? 0) > 0 || + (trivy.HIGH ?? 0) > 0; + + body += '\n'; + body += hasBlocking + ? '🚨 _Status: Potential blocking issues detected._\n' + : 'βœ… _Status: No blocking issues detected (HIGH/CRITICAL)._ \n'; + } + + const { owner, repo } = context.repo; + const issue_number = context.issue.number; + + const comments = await github.rest.issues.listComments({ + owner, + repo, + issue_number, + }); + + const existing = comments.data.find(c => c.body && c.body.includes(marker)); + + if (existing) { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner, + repo, + issue_number, + body, + }); + } + + - name: Upload security artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: security-reports + path: artifacts/security/ diff --git a/cli/templates/workflows/node_security.yml.tmpl b/cli/templates/workflows/node_security.yml.tmpl index bf412db..835d727 100644 --- a/cli/templates/workflows/node_security.yml.tmpl +++ b/cli/templates/workflows/node_security.yml.tmpl @@ -5,11 +5,23 @@ on: branches: ["main"] pull_request: +permissions: + contents: read + issues: write + pull-requests: write + jobs: security: runs-on: ubuntu-latest + timeout-minutes: 15 + steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Prepare artifacts directory + run: mkdir -p artifacts/security - name: Set up Node uses: actions/setup-node@v4 @@ -28,7 +40,9 @@ jobs: {{- if .Tools.Gitleaks }} - name: Run Gitleaks - uses: zricethezav/gitleaks-action@v2 + uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{"{{"}} secrets.GITHUB_TOKEN {{ "}}" }} {{- end }} {{- if .Tools.Trivy }} @@ -36,5 +50,125 @@ jobs: uses: aquasecurity/trivy-action@master with: scan-type: fs + format: json + output: artifacts/security/trivy-fs.json severity: HIGH,CRITICAL {{- end }} + + - name: Generate security summary (JSON) + if: always() + run: | + echo "Generating security summary..." + mkdir -p artifacts/security + + # Gitleaks: count total findings (supports array or {findings:[...]}) + if [ -f artifacts/security/gitleaks-report.json ]; then + GITLEAKS_COUNT=$(jq 'if type=="array" then length else (.findings | length // 0) end' artifacts/security/gitleaks-report.json) + else + GITLEAKS_COUNT=0 + fi + + # Trivy: group vulnerabilities by severity (CRITICAL/HIGH/etc.) + if [ -f artifacts/security/trivy-fs.json ]; then + TRIVY_SUMMARY=$(jq ' + ( [ .Results[].Vulnerabilities[]? | .Severity ] + | group_by(.) + | map({ (.[0]): length }) + | add + ) // {}' artifacts/security/trivy-fs.json) + else + TRIVY_SUMMARY='{}' + fi + + jq -n \ + --argjson gitleaks_count "$GITLEAKS_COUNT" \ + --argjson trivy "$TRIVY_SUMMARY" \ + '{ gitleaks: { total: $gitleaks_count }, trivy: $trivy }' \ + > artifacts/security/summary.json + + echo "Summary written to artifacts/security/summary.json" + + - name: Post security summary as PR comment + if: always() && github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + github-token: ${{"{{"}} secrets.GITHUB_TOKEN {{ "}}" }} + script: | + const fs = require('fs'); + const path = 'artifacts/security/summary.json'; + const marker = ''; + + let summary; + try { + const raw = fs.readFileSync(path, 'utf8'); + summary = JSON.parse(raw); + } catch (err) { + core.warning(`Could not read ${path}: ${err}`); + summary = null; + } + + let body = `${marker}\n`; + body += '### πŸ” DevSecOps Kit Security Summary\n\n'; + + if (!summary) { + body += '_No summary.json available. Check workflow logs._\n'; + } else { + const gitleaksTotal = summary?.gitleaks?.total ?? 0; + const trivy = summary?.trivy || {}; + + body += `- **Gitleaks:** ${gitleaksTotal} leak(s)\n`; + + const severities = Object.keys(trivy); + if (severities.length > 0) { + body += '- **Trivy vulnerabilities:**\n'; + for (const sev of severities.sort()) { + body += ` - ${sev}: ${trivy[sev]}\n`; + } + } else { + body += '- **Trivy vulnerabilities:** none counted in summary\n'; + } + + const hasBlocking = + gitleaksTotal > 0 || + (trivy.CRITICAL ?? 0) > 0 || + (trivy.HIGH ?? 0) > 0; + + body += '\n'; + body += hasBlocking + ? '🚨 _Status: Potential blocking issues detected._\n' + : 'βœ… _Status: No blocking issues detected (HIGH/CRITICAL)._ \n'; + } + + const { owner, repo } = context.repo; + const issue_number = context.issue.number; + + const comments = await github.rest.issues.listComments({ + owner, + repo, + issue_number, + }); + + const existing = comments.data.find(c => c.body && c.body.includes(marker)); + + if (existing) { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner, + repo, + issue_number, + body, + }); + } + + - name: Upload security artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: security-reports + path: artifacts/security/ diff --git a/devsecops b/devsecops index 8bcefb0..e64688f 100755 Binary files a/devsecops and b/devsecops differ diff --git a/security-config.yml b/security-config.yml index 1f5247e..76a1c00 100644 --- a/security-config.yml +++ b/security-config.yml @@ -1,10 +1,25 @@ # Security config generated by DevSecOps Kit +version: "0.2.0" + language: "golang" framework: "" -severity_threshold: "critical" + +severity_threshold: "high" tools: semgrep: true - gitleaks: false - trivy: false + trivy: true + gitleaks: true + +# Reserved for future versions β€” currently unused in execution +exclude_paths: [] + +# Reserved for future: per-tool fail-on behavior +fail_on: {} + +# Notification settings (PR comment enabled by default) +notifications: + pr_comment: true + slack: false + email: false