diff --git a/.github/workflows/verify-npm-security.yml b/.github/workflows/verify-npm-security.yml new file mode 100644 index 0000000..8eb5fbc --- /dev/null +++ b/.github/workflows/verify-npm-security.yml @@ -0,0 +1,36 @@ +name: Verify NPM Security + +on: + workflow_call: + inputs: + working-directory: + description: "Working directory" + required: true + type: string + +jobs: + verify-npm-install: + runs-on: ubuntu-latest + permissions: + contents: read + packages: read + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Log in to GitHub Container Registry + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + + - name: Pull container + run: docker pull ghcr.io/fundwave/npm-security:latest + + - name: Verify npm install + working-directory: ${{ inputs.working-directory }} + run: | + docker run --rm \ + -v $(pwd):/app \ + -e GITHUB_READ_TOKEN=${{ secrets.GITHUB_TOKEN }} \ + ghcr.io/fundwave/npm-security:latest + + - name: Verification complete + run: echo "NPM dependencies verified successfully in ${{ inputs.working-directory }}" diff --git a/npm-security/.npmrc b/npm-security/.npmrc new file mode 100644 index 0000000..13394b5 --- /dev/null +++ b/npm-security/.npmrc @@ -0,0 +1,3 @@ +//npm.pkg.github.com/:_authToken=${GITHUB_READ_TOKEN} +@fundwave:registry=https://npm.pkg.github.com +legacy-peer-deps=true diff --git a/npm-security/Dockerfile b/npm-security/Dockerfile new file mode 100644 index 0000000..13c0d65 --- /dev/null +++ b/npm-security/Dockerfile @@ -0,0 +1,14 @@ +FROM node:22-alpine + +RUN mkdir -p /home/node/test +RUN chown -R node:node /home/node/test + +WORKDIR /home/node/test + +COPY .npmrc /home/node/.npmrc +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +USER node + +ENTRYPOINT ["sh", "/entrypoint.sh"] \ No newline at end of file diff --git a/npm-security/README.md b/npm-security/README.md new file mode 100644 index 0000000..8805d68 --- /dev/null +++ b/npm-security/README.md @@ -0,0 +1,126 @@ +WARNING: Only use read-only GITHUB token as GITHUB_READ_TOKEN. If a WRITE token is supplied, the container will cause malicious packages to be published. + +# NPM Security Container + +A Docker container designed to securely install npm dependencies and perform integrity checks on JavaScript files looking specifically for hashes that indicate infiltration by Sha1-Hulud malware that came out on Nov 24 2025. This container can be used as a precautionary step before `npm install`. + +## Overview + +This container performs the following operations: +1. Sets up authentication for GitHub packages (if `GITHUB_READ_TOKEN` is provided) +2. Runs `npm install` to install dependencies +3. Performs integrity checks on JavaScript files using SHA256 hashes that point to Sha1-Hulud malware files. +4. Returns appropriate exit codes based on the results + +## Exit Codes + +The container uses specific exit codes to indicate different states: + +- **Exit Code 0**: ✅ **Success** - npm install succeeded and all integrity checks passed +- **Exit Code 1**: ❌ **Security Alert** - Integrity check failed, potentially malicious files detected +- **Exit Code 2**: 🔧 **Installation Error** - npm install failed + +## Usage + +### Basic Usage + +```bash +docker pull ghcr.io/fundwave/npm-security:latest +docker run --rm -v $(pwd):/app ghcr.io/fundwave/npm-security:latest +``` + +### With GitHub Read Token (for private packages) + +```bash +docker run --rm -v $(pwd):/app -e GITHUB_READ_TOKEN=your_github_readonly_token ghcr.io/fundwave/npm-security:latest +``` + +### CI/CD Integration + +In your CI/CD pipeline, you can use the exit codes to determine the next steps: + +```yaml +# Example GitHub Actions workflow +- name: Secure NPM Install + run: | + docker run --rm -v $(pwd):/app -e GITHUB_READ_TOKEN=${{ secrets.GITHUB_TOKEN }} ghcr.io/fundwave/npm-security:latest + EXIT_CODE=$? + if [ $EXIT_CODE -eq 0 ]; then + echo "Proceeding with build..." + elif [ $EXIT_CODE -eq 1 ]; then + echo "Security check failed - stopping deployment" + exit 1 + elif [ $EXIT_CODE -eq 2 ]; then + echo "Installation failed - check dependencies" + exit 1 + fi +``` + +## Security Features + +### Integrity Checking + +The container checks for known malicious file hashes: +- Scans all `.js` files in the project +- Compares SHA256 hashes against a database of known threats +- Fails if any malicious patterns are detected + +### Isolated Environment + +- Runs in a containerized environment +- No access to host system beyond mounted volume +- Clean Alpine Linux base with minimal attack surface +- Use only a read-only GITHUB_TOKEN (with package read permissions only). Do not use tokens with write or publish permissions, as this may allow malicious packages to be published. + +### Authentication + +- Supports GitHub Package Registry authentication +- Tokens are handled securely through environment variables +- No credentials stored in the container image + +## Development + +### Building the Container + +```bash +docker build -t npm-security . +``` + +### Testing + +Test with a clean Node.js project: + +```bash +# Create test project +mkdir test-project && cd test-project +npm init -y +echo '{}' > package.json + +# Test the container +docker run --rm -v $(pwd):/app npm-security +echo "Exit code: $?" +``` + +## Environment Variables + +- `GITHUB_READ_TOKEN`: GitHub Personal Access Token for accessing private packages in GitHub Package Registry + +## Troubleshooting + +### Common Issues + +1. **Permission denied**: Ensure the mounted volume has correct permissions +2. **Network issues**: Check Docker network configuration for npm registry access +3. **Token issues**: Verify GitHub token has correct permissions for package registry + +### Debug Mode + +Run the container interactively to debug issues: + +```bash +docker run -it --rm -v $(pwd):/app --entrypoint sh npm-security +``` + +## Contributing + +When modifying the integrity check hashes, ensure you're adding legitimate threat signatures and document the source of the hash information. diff --git a/npm-security/entrypoint.sh b/npm-security/entrypoint.sh new file mode 100644 index 0000000..1a4eeb3 --- /dev/null +++ b/npm-security/entrypoint.sh @@ -0,0 +1,60 @@ +#!/bin/sh +set -e + +# Known malicious SHA256 hashes (Sha1-Hulud malware signatures) +MALICIOUS_HASHES="a3894003ad1d293ba96d77881ccd2071446dc3f65f434669b49b3da92421901a|\ +62ee164b9b306250c1172583f138c9614139264f889fa99614903c12755468d0|\ +c723605455e8667a4c84327cf6b704bbdcb9b4ce3707ddddd927d32b8372ff77|\ +2e44e8d8a8e906fd5bfbb37be08dfe2dcf1ce41bd4ba726987ab516446dfb4f1|\ +fa7df9e9fc5390cc54e0086073fc9b3054087ffddf661bbc9f836b007fa25f20|\ +d66343059793800e72ef17690ce26492dc854c8513905778630ff1ed4e7a81b8|\ +981d3e2f5d7e26c93bd4b758ea722468900894fb2368db5f8399282e2414fe33" + +# Exit codes +EXIT_SUCCESS=0 +EXIT_SECURITY_ALERT=1 +EXIT_INSTALLATION_ERROR=2 + +echo "Validating GitHub Token..." + +if [ -f /app/package.json ] ; then cp /app/package.json ~/test/package.json; fi +if [ -f /app/package-lock.json ] ; then cp /app/package-lock.json ~/test/package-lock.json; fi +if [ -f /.env ] ; then echo ".env file found. Exiting"; exit $EXIT_INSTALLATION_ERROR; fi + +# Skip validation if no token provided +if [ -z "${GITHUB_READ_TOKEN}" ]; then + echo "No GITHUB_READ_TOKEN provided, skipping token validation." +else + + # Fetch GitHub API headers to validate token + GITHUB_TOKEN_HEADERS=$(wget --server-response --spider \ + --header="Authorization: token ${GITHUB_READ_TOKEN}" \ + https://api.github.com 2>&1) + + if echo "${GITHUB_READ_TOKEN}" | grep -q "^ghs_"; then + echo "GitHub Actions token detected - skipping read-only scope check." + elif ! echo "$GITHUB_TOKEN_HEADERS" | grep -qi 'x-oauth-scopes:'; then + echo "ERROR: Cannot determine token scope (may be fine-grained). Only read-only tokens are permitted!" + exit $EXIT_INSTALLATION_ERROR + elif echo "$GITHUB_TOKEN_HEADERS" | grep -i 'x-oauth-scopes:' | grep -qi 'write'; then + echo "ERROR: Token has write access. Only read-only tokens are permitted!" + exit $EXIT_INSTALLATION_ERROR + else + echo "Token validation passed - read-only access confirmed." + fi +fi + +echo "Running npm install..." +npm install || { echo "ERROR: npm install failed!"; exit $EXIT_INSTALLATION_ERROR; } + +echo "Performing integrity checks on JavaScript files..." +SUSPICIOUS_FILES=$(find . -type f -name "*.js" -exec sha256sum {} \; | grep -iE "$MALICIOUS_HASHES" || true) + +# Exit if malicious files found +[ -n "$SUSPICIOUS_FILES" ] && { + echo "SECURITY ALERT: Malicious files detected!" + echo "$SUSPICIOUS_FILES" + exit $EXIT_SECURITY_ALERT +} + +exit $EXIT_SUCCESS