diff --git a/.gitignore b/.gitignore index e91907f..fd19df5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,7 @@ node_modules *.swo data *.DS_Store -test-data/ \ No newline at end of file +test-data/ +bin/ +sbom*.txt +sbom*.json \ No newline at end of file diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000..ace9163 --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,124 @@ +labels: + location: manager + +clone: + git: + image: woodpeckerci/plugin-git + settings: + partial: false + depth: 1 + +steps: + # Run Tests + test: + name: test + image: node:22-alpine + commands: + - echo "nameserver 1.1.1.1" > /etc/resolv.conf + - echo "nameserver 1.0.0.1" >> /etc/resolv.conf + - npm --version | cat + - node --version | cat + - npm ci + - npm test + when: + branch: main + event: [push, pull_request, cron] + + # SBOM for source code + sbom-source: + name: sbom-source + image: alpine:3.20 + commands: + - echo "nameserver 1.1.1.1" > /etc/resolv.conf + - echo "nameserver 1.0.0.1" >> /etc/resolv.conf + - apk add --no-cache curl tar + - curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin + - syft version | cat + - syft dir:. -o table | tee sbom.txt + - syft dir:. -o spdx-json > sbom.spdx.json + - echo "SBOM generated successfully" + - ls -lh sbom.* | cat + when: + branch: main + event: [push, pull_request, cron] + + # Trivy filesystem scan + trivy-fs: + name: trivy-fs + image: aquasec/trivy:latest + commands: + - echo "nameserver 1.1.1.1" > /etc/resolv.conf + - echo "nameserver 1.0.0.1" >> /etc/resolv.conf + - trivy --version | cat + - trivy fs --scanners vuln,misconfig --severity HIGH,CRITICAL --exit-code 0 . + - trivy fs --scanners vuln,misconfig --severity HIGH,CRITICAL --exit-code 0 Dockerfile + when: + branch: main + event: [push, pull_request, cron] + + # Build Docker image for scanning + build-image: + name: build-image + image: woodpeckerci/plugin-docker-buildx + depends_on: [ "test" ] + environment: + REGISTRY_USER: + from_secret: REGISTRY_USER + REGISTRY_PASSWORD: + from_secret: REGISTRY_PASSWORD + DOCKER_REGISTRY_USER: + from_secret: DOCKER_REGISTRY_USER + DOCKER_REGISTRY_PASSWORD: + from_secret: DOCKER_REGISTRY_PASSWORD + volumes: + - /var/run/docker.sock:/var/run/docker.sock + commands: + - echo "nameserver 1.1.1.1" > /etc/resolv.conf + - echo "nameserver 1.0.0.1" >> /etc/resolv.conf + - HOSTNAME=$(docker info --format "{{.Name}}") + - echo "Building on $HOSTNAME" + - echo "$${DOCKER_REGISTRY_PASSWORD}" | docker login -u "$${DOCKER_REGISTRY_USER}" --password-stdin || echo "Docker registry login skipped" + - echo "$${REGISTRY_PASSWORD}" | docker login -u "$${REGISTRY_USER}" --password-stdin git.nixc.us || echo "Registry login skipped" + - docker build -t hastebin:test --no-cache . + - docker tag hastebin:test git.nixc.us/hastebin:latest || echo "Image tagging skipped" + - docker push git.nixc.us/hastebin:latest || echo "Image push skipped (may need registry credentials)" + when: + branch: main + event: [push, cron] + + # Scan Docker image with Trivy + trivy-image: + name: trivy-image + image: aquasec/trivy:latest + depends_on: [ "build-image" ] + volumes: + - /var/run/docker.sock:/var/run/docker.sock + commands: + - echo "nameserver 1.1.1.1" > /etc/resolv.conf + - echo "nameserver 1.0.0.1" >> /etc/resolv.conf + - trivy --version | cat + - trivy image --timeout 10m --scanners vuln --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 hastebin:test + when: + branch: main + event: [push, cron] + + # Generate SBOM for Docker image + sbom-image: + name: sbom-image + image: alpine:3.20 + depends_on: [ "build-image" ] + volumes: + - /var/run/docker.sock:/var/run/docker.sock + commands: + - echo "nameserver 1.1.1.1" > /etc/resolv.conf + - echo "nameserver 1.0.0.1" >> /etc/resolv.conf + - apk add --no-cache curl docker-cli + - curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin + - syft version | cat + - syft docker:hastebin:test -o table | tee sbom-image.txt + - syft docker:hastebin:test -o spdx-json > sbom-image.spdx.json + - echo "Image SBOM generated successfully" + - ls -lh sbom-image.* | cat + when: + branch: main + event: [push, cron] diff --git a/Dockerfile b/Dockerfile index 74cc7bf..f022765 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,6 +21,10 @@ RUN node update-js.js || echo "No update-js.js script found" RUN echo "var config = require('./config.js'); config.storage = { type: 'file', path: '/app/data' }; module.exports = config;" > /app/config.override.js && \ sed -i '1s/^/var config = require(".\/config.override.js"); /' server.js +# Change ownership of /app to node user (non-root) +# The node user already exists in node:22-alpine (UID 1000) +RUN chown -R node:node /app + # Set environment variables ENV NODE_ENV=production \ HASTEBIN_STORAGE_TYPE=file \ @@ -34,5 +38,8 @@ ENV NODE_ENV=production \ # Expose port EXPOSE 7777 +# Switch to non-root user +USER node + # Use app.sh script as entry point CMD ["/app/app.sh"] \ No newline at end of file diff --git a/lib/key_generators/phonetic.js b/lib/key_generators/phonetic.js index 68da747..2b87199 100644 --- a/lib/key_generators/phonetic.js +++ b/lib/key_generators/phonetic.js @@ -12,7 +12,7 @@ module.exports = class PhoneticKeyGenerator { createKey(keyLength) { let text = ''; - let chooseConsonant = Math.random() < 0.5; // Randomly start with consonant or vowel + let chooseConsonant = true; // Always start with consonant for (let i = 0; i < keyLength; i++) { text += chooseConsonant ? randConsonant() : randVowel(); diff --git a/package.json b/package.json index fa4d077..1d1de61 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "start": "node server.js", "start:file": "NODE_ENV=development HASTEBIN_STORAGE_TYPE=file node server.js", "start:dev": "NODE_ENV=development HASTEBIN_STORAGE_TYPE=file node server.js", - "test": "mocha --recursive", + "test": "mocha --recursive --ignore 'test/security/**'", "test:core": "mocha test/core/core_functionality_spec.js", "test:security": "node test/security/security_spec.js --test=basic,csp,noCsp,cors,hsts,devMode,devBypass,combinedSecurity", "test:all": "npm run test && npm run test:security", @@ -63,7 +63,12 @@ "test:security:csp": "node test/security/security_spec.js --test=csp", "test:security:cors": "node test/security/security_spec.js --test=cors", "test:security:combined": "node test/security/security_spec.js --test=combinedSecurity", - "build": "node update-js.js" + "build": "node update-js.js", + "scan:sbom": "./scripts/scan-sbom.sh", + "scan:trivy": "./scripts/scan-trivy-fs.sh", + "scan:trivy:image": "./scripts/scan-trivy-image.sh", + "scan:sbom:image": "./scripts/scan-sbom-image.sh", + "scan:all": "npm run scan:sbom && npm run scan:trivy" }, "repository": { "type": "git", diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..f4ab924 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,138 @@ +# Scripts + +This directory contains utility scripts for the Hastebin project. + +## Git Hooks Installation + +### `install-git-hooks.sh` + +Installs Git pre-commit hooks to prevent pushing broken code. The hook runs core tests before each commit. + +**Usage:** + +```bash +./scripts/install-git-hooks.sh +``` + +**What it does:** + +1. Creates a pre-commit hook in `.git/hooks/pre-commit` +2. The hook runs `npm run test:core` before each commit +3. If tests fail, the commit is aborted +4. Automatically installs dependencies if `node_modules` is missing + +**Skipping the hook:** + +If you need to skip the pre-commit hook (not recommended), use: + +```bash +git commit --no-verify +``` + +**Note:** The hook runs core tests only (faster than the full test suite) to keep commit times reasonable. Full tests are still run in CI/CD via Woodpecker. + +## Security Scanning + +### SBOM Generation + +#### `scan-sbom.sh` + +Generates a Software Bill of Materials (SBOM) for the source code using Syft. Creates SBOM files in multiple formats (table, SPDX JSON, CycloneDX JSON). + +**Usage:** + +```bash +./scripts/scan-sbom.sh +# or +npm run scan:sbom +``` + +**Output files:** +- `sbom.txt` - Human-readable table format +- `sbom.spdx.json` - SPDX JSON format +- `sbom.cyclonedx.json` - CycloneDX JSON format + +**Requirements:** +- Syft (automatically installed if not present) + +### Trivy Security Scans + +#### `scan-trivy-fs.sh` + +Runs Trivy filesystem security scan to detect vulnerabilities and misconfigurations in the codebase and Dockerfile. + +**Usage:** + +```bash +./scripts/scan-trivy-fs.sh +# or +npm run scan:trivy +``` + +**What it scans:** +- Filesystem for vulnerabilities (HIGH and CRITICAL severity) +- Dockerfile for misconfigurations +- Reports findings but doesn't fail (exit code 0) + +**Requirements:** +- Trivy installed (`brew install trivy` or see [Trivy installation guide](https://aquasecurity.github.io/trivy/latest/getting-started/installation/)) + +#### `scan-trivy-image.sh` + +Builds the Docker image and scans it for vulnerabilities using Trivy. + +**Usage:** + +```bash +./scripts/scan-trivy-image.sh [image-name] +# or +npm run scan:trivy:image +``` + +**Default image name:** `hastebin:test` + +**What it does:** +1. Builds the Docker image +2. Scans the image for vulnerabilities (HIGH and CRITICAL severity) +3. Fails if unfixed vulnerabilities are found (exit code 1) + +**Requirements:** +- Docker +- Trivy installed + +### Image SBOM Generation + +#### `scan-sbom-image.sh` + +Builds the Docker image and generates an SBOM for it. + +**Usage:** + +```bash +./scripts/scan-sbom-image.sh [image-name] +# or +npm run scan:sbom:image +``` + +**Default image name:** `hastebin:test` + +**Output files:** +- `sbom-image.txt` - Human-readable table format +- `sbom-image.spdx.json` - SPDX JSON format +- `sbom-image.cyclonedx.json` - CycloneDX JSON format + +**Requirements:** +- Docker +- Syft (automatically installed if not present) + +### Running All Scans + +To run both SBOM generation and Trivy filesystem scan: + +```bash +npm run scan:all +``` + +This runs: +1. SBOM generation for source code +2. Trivy filesystem security scan diff --git a/scripts/install-git-hooks.sh b/scripts/install-git-hooks.sh new file mode 100755 index 0000000..a49ce51 --- /dev/null +++ b/scripts/install-git-hooks.sh @@ -0,0 +1,110 @@ +#!/bin/bash + +# Installation script for Git hooks in Hastebin +# This script sets up pre-commit hooks to prevent pushing broken code + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Get the repository root +REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || echo "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)") +HOOKS_DIR="$REPO_ROOT/.git/hooks" +PRE_COMMIT_HOOK="$HOOKS_DIR/pre-commit" +SCRIPT_DIR="$REPO_ROOT/scripts" + +echo -e "${BLUE}🔧 Installing Git hooks for Hastebin${NC}" +echo "" + +# Check if we're in a git repository +if [ ! -d "$REPO_ROOT/.git" ]; then + echo -e "${RED}❌ Error: Not a git repository${NC}" + exit 1 +fi + +# Create hooks directory if it doesn't exist +mkdir -p "$HOOKS_DIR" + +# Check if pre-commit hook already exists +if [ -f "$PRE_COMMIT_HOOK" ]; then + echo -e "${YELLOW}⚠️ Pre-commit hook already exists${NC}" + read -p "Do you want to overwrite it? (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo -e "${YELLOW}Skipping hook installation${NC}" + exit 0 + fi + # Backup existing hook + mv "$PRE_COMMIT_HOOK" "$PRE_COMMIT_HOOK.backup.$(date +%Y%m%d_%H%M%S)" + echo -e "${YELLOW}Backed up existing hook${NC}" +fi + +# Create the pre-commit hook +cat > "$PRE_COMMIT_HOOK" << 'HOOK_EOF' +#!/bin/bash + +# Git pre-commit hook for Hastebin +# Runs tests before allowing commits to prevent pushing broken code + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +NC='\033[0m' # No Color + +echo -e "${YELLOW}Running pre-commit checks...${NC}" + +# Get the repository root +REPO_ROOT=$(git rev-parse --show-toplevel) +cd "$REPO_ROOT" + +# Check if node_modules exists, if not, install dependencies +if [ ! -d "node_modules" ]; then + echo -e "${YELLOW}Installing dependencies...${NC}" + npm ci +fi + +# Run the core tests (faster than full test suite) +echo -e "${YELLOW}Running core tests...${NC}" +if npm run test:core; then + echo -e "${GREEN}✅ Core tests passed${NC}" +else + echo -e "${RED}❌ Core tests failed. Commit aborted.${NC}" + echo -e "${YELLOW}To skip this check, use: git commit --no-verify${NC}" + exit 1 +fi + +# Clean up test artifacts +if [ -d "test-data" ]; then + rm -rf test-data +fi + +echo -e "${GREEN}✅ Pre-commit checks passed${NC}" +exit 0 +HOOK_EOF + +# Make the hook executable +chmod +x "$PRE_COMMIT_HOOK" + +echo -e "${GREEN}✅ Pre-commit hook installed successfully${NC}" +echo "" +echo -e "${BLUE}The hook will now run tests before each commit.${NC}" +echo -e "${YELLOW}To skip the hook, use: git commit --no-verify${NC}" +echo "" + +# Check if dependencies are installed +if [ ! -d "$REPO_ROOT/node_modules" ]; then + echo -e "${YELLOW}⚠️ Dependencies not found. Installing...${NC}" + cd "$REPO_ROOT" + npm ci + echo -e "${GREEN}✅ Dependencies installed${NC}" +fi + +echo -e "${GREEN}🎉 Git hooks installation complete!${NC}" diff --git a/scripts/scan-sbom-image.sh b/scripts/scan-sbom-image.sh new file mode 100755 index 0000000..7fe3e85 --- /dev/null +++ b/scripts/scan-sbom-image.sh @@ -0,0 +1,54 @@ +#!/bin/bash +# Generate SBOM for Docker image +# Builds the image and generates SBOM in multiple formats + +set -e + +IMAGE_NAME="${1:-hastebin:test}" + +echo "🐳 Building Docker image: $IMAGE_NAME" +docker build -t "$IMAGE_NAME" --no-cache . + +echo "" +echo "🔍 Generating SBOM for Docker image..." + +# Check if syft is installed +if ! command -v syft &> /dev/null; then + echo "Syft not found. Attempting to install to ./bin..." + mkdir -p ./bin + curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b ./bin + export PATH="./bin:$PATH" + + # Verify installation + if ! command -v syft &> /dev/null; then + echo "❌ Failed to install syft automatically." + echo "Please install manually:" + echo " brew install syft" + echo " or visit: https://github.com/anchore/syft#installation" + exit 1 + fi +fi + +# Show version +syft version + +# Generate SBOM in table format +echo "" +echo "📋 Generating SBOM table..." +syft docker:"$IMAGE_NAME" -o table | tee sbom-image.txt + +# Generate SBOM in SPDX JSON format +echo "" +echo "📦 Generating SBOM in SPDX JSON format..." +syft docker:"$IMAGE_NAME" -o spdx-json > sbom-image.spdx.json + +# Generate SBOM in CycloneDX JSON format +echo "" +echo "🌀 Generating SBOM in CycloneDX JSON format..." +syft docker:"$IMAGE_NAME" -o cyclonedx-json > sbom-image.cyclonedx.json + +echo "" +echo "✅ Image SBOM generated successfully!" +echo "" +echo "Generated files:" +ls -lh sbom-image.* | cat diff --git a/scripts/scan-sbom.sh b/scripts/scan-sbom.sh new file mode 100755 index 0000000..68adc12 --- /dev/null +++ b/scripts/scan-sbom.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# Generate SBOM (Software Bill of Materials) for source code +# Uses Syft to generate SBOM in multiple formats + +set -e + +echo "🔍 Generating SBOM for source code..." + +# Check if syft is installed +if ! command -v syft &> /dev/null; then + echo "Syft not found. Attempting to install to ./bin..." + mkdir -p ./bin + curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b ./bin + export PATH="./bin:$PATH" + + # Verify installation + if ! command -v syft &> /dev/null; then + echo "❌ Failed to install syft automatically." + echo "Please install manually:" + echo " brew install syft" + echo " or visit: https://github.com/anchore/syft#installation" + exit 1 + fi +fi + +# Show version +syft version + +# Generate SBOM in table format +echo "" +echo "📋 Generating SBOM table..." +syft dir:. -o table | tee sbom.txt + +# Generate SBOM in SPDX JSON format +echo "" +echo "📦 Generating SBOM in SPDX JSON format..." +syft dir:. -o spdx-json > sbom.spdx.json + +# Generate SBOM in CycloneDX JSON format +echo "" +echo "🌀 Generating SBOM in CycloneDX JSON format..." +syft dir:. -o cyclonedx-json > sbom.cyclonedx.json + +echo "" +echo "✅ SBOM generated successfully!" +echo "" +echo "Generated files:" +ls -lh sbom.* | cat diff --git a/scripts/scan-trivy-fs.sh b/scripts/scan-trivy-fs.sh new file mode 100755 index 0000000..f849b93 --- /dev/null +++ b/scripts/scan-trivy-fs.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# Run Trivy filesystem security scan +# Scans for vulnerabilities and misconfigurations + +set -e + +echo "🔒 Running Trivy filesystem security scan..." + +# Check if trivy is installed +if ! command -v trivy &> /dev/null; then + echo "Trivy not found. Please install it:" + echo " brew install trivy" + echo " or visit: https://aquasecurity.github.io/trivy/latest/getting-started/installation/" + exit 1 +fi + +# Show version +trivy --version + +echo "" +echo "📁 Scanning filesystem for vulnerabilities and misconfigurations..." +echo "" + +# Scan filesystem with exit code 0 (don't fail on findings, just report) +trivy fs \ + --scanners vuln,misconfig \ + --severity HIGH,CRITICAL \ + --exit-code 0 \ + --format table \ + . + +echo "" +echo "🐳 Scanning Dockerfile..." +echo "" + +# Scan Dockerfile +trivy fs \ + --scanners vuln,misconfig \ + --severity HIGH,CRITICAL \ + --exit-code 0 \ + --format table \ + Dockerfile + +echo "" +echo "✅ Trivy filesystem scan completed!" diff --git a/scripts/scan-trivy-image.sh b/scripts/scan-trivy-image.sh new file mode 100755 index 0000000..e93500c --- /dev/null +++ b/scripts/scan-trivy-image.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# Run Trivy image security scan +# Builds the Docker image and scans it for vulnerabilities + +set -e + +IMAGE_NAME="${1:-hastebin:test}" + +echo "🐳 Building Docker image: $IMAGE_NAME" +docker build -t "$IMAGE_NAME" --no-cache . + +echo "" +echo "🔒 Running Trivy image security scan..." + +# Check if trivy is installed +if ! command -v trivy &> /dev/null; then + echo "Trivy not found. Please install it:" + echo " brew install trivy" + echo " or visit: https://aquasecurity.github.io/trivy/latest/getting-started/installation/" + exit 1 +fi + +# Show version +trivy --version + +echo "" +echo "📦 Scanning Docker image for vulnerabilities..." +echo "" + +# Scan image with exit code 1 (fail on HIGH/CRITICAL vulnerabilities) +trivy image \ + --timeout 10m \ + --scanners vuln \ + --severity HIGH,CRITICAL \ + --ignore-unfixed \ + --exit-code 1 \ + --format table \ + "$IMAGE_NAME" + +echo "" +echo "✅ Trivy image scan completed!"