Security improvements and local testing scripts
ci/woodpecker/push/woodpecker Pipeline failed Details

- Fix Dockerfile to run as non-root user (node) for security
- Fix phonetic key generator to always start with consonant (test fix)
- Add local security scanning scripts (SBOM, Trivy)
- Update test script to exclude security tests from mocha
- Add npm scripts for security scans
- Update .gitignore for generated files
- Update Woodpecker CI to use modern Trivy syntax and push images
This commit is contained in:
Colin 2026-01-21 10:21:55 -05:00
parent 69db5d56a5
commit 618a2c1ff7
Signed by: colin
SSH Key Fingerprint: SHA256:nRPCQTeMFLdGytxRQmPVK9VXY3/ePKQ5lGRyJhT5DY8
11 changed files with 579 additions and 4 deletions

5
.gitignore vendored
View File

@ -4,4 +4,7 @@ node_modules
*.swo
data
*.DS_Store
test-data/
test-data/
bin/
sbom*.txt
sbom*.json

124
.woodpecker.yml Normal file
View File

@ -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]

View File

@ -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"]

View File

@ -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();

View File

@ -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",

138
scripts/README.md Normal file
View File

@ -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

110
scripts/install-git-hooks.sh Executable file
View File

@ -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}"

54
scripts/scan-sbom-image.sh Executable file
View File

@ -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

48
scripts/scan-sbom.sh Executable file
View File

@ -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

45
scripts/scan-trivy-fs.sh Executable file
View File

@ -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!"

41
scripts/scan-trivy-image.sh Executable file
View File

@ -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!"