Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 41 additions & 5 deletions docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
#!/bin/sh
set -e
set -euo pipefail

if ! command -v curl >/dev/null 2>&1; then
echo "Error: curl is required but was not found in PATH." >&2
exit 1
fi

if ! command -v jq >/dev/null 2>&1; then
echo "Error: jq is required but was not found in PATH." >&2
exit 1
fi

# Allow operators to override the genesis URL and expected checksum.
GENESIS_URL=${GENESIS_URL:-"https://raw.githubusercontent.com/bettergovph/govchain/refs/heads/main/genesis.json"}
GENESIS_SHA256=${GENESIS_SHA256:-""}

# Exit if the MONIKER environment variable is not set.
if [ -z "$MONIKER" ]; then
if [ -z "${MONIKER:-}" ]; then
echo "Error: The MONIKER environment variable is not set."
echo "Please set it using -e MONIKER='your-moniker' with docker run, or in your docker-compose.yml."
exit 1
fi

EXTERNAL_IP=${EXTERNAL_IP:-""}

# Ensure the config directory exists
mkdir -p "/home/nonroot/.govchain/config"
chown nonroot:nonroot "/home/nonroot/.govchain/config"
Expand All @@ -16,11 +32,30 @@ chown nonroot:nonroot "/home/nonroot/.govchain/config"
if [ ! -f "/home/nonroot/.govchain/config/genesis.json" ]; then
# Define the path for the actual genesis.json
ACTUAL_GENESIS_PATH="/home/nonroot/.govchain/config/genesis.json"
TEMP_GENESIS_PATH="/tmp/genesis.json" # Use a temporary path for download
TEMP_GENESIS_PATH=$(mktemp /tmp/genesis.XXXXXX)

cleanup() {
[ -f "$TEMP_GENESIS_PATH" ] && rm -f "$TEMP_GENESIS_PATH"
}
trap cleanup EXIT

# Download the actual genesis.json to a temporary location
echo "📥 Downloading actual genesis file to extract chain-id..."
env 'HOME=/home/nonroot' curl -sL "https://raw.githubusercontent.com/bettergovph/govchain/refs/heads/main/genesis.json" -o "$TEMP_GENESIS_PATH"
echo "📥 Downloading actual genesis file from $GENESIS_URL to extract chain-id..."
if ! env 'HOME=/home/nonroot' curl -fSL --retry 3 --retry-delay 2 "$GENESIS_URL" -o "$TEMP_GENESIS_PATH"; then
echo "Error: failed to download genesis file from $GENESIS_URL" >&2
exit 1
fi

if [ -n "$GENESIS_SHA256" ]; then
if ! command -v sha256sum >/dev/null 2>&1; then
echo "Error: sha256sum is required when GENESIS_SHA256 is provided." >&2
exit 1
fi
echo "🔐 Verifying genesis checksum..."
echo "$GENESIS_SHA256 $TEMP_GENESIS_PATH" | sha256sum -c -
else
echo "⚠️ Warning: GENESIS_SHA256 not provided. Skipping checksum verification."
fi

# Extract chain-id from the downloaded genesis.json
CHAIN_ID=$(jq -r '.chain_id' "$TEMP_GENESIS_PATH")
Expand All @@ -37,6 +72,7 @@ if [ ! -f "/home/nonroot/.govchain/config/genesis.json" ]; then
# Replace the dummy genesis.json created by init with the actual downloaded one
echo "Replacing dummy genesis.json with the actual genesis file..."
mv "$TEMP_GENESIS_PATH" "$ACTUAL_GENESIS_PATH"
trap - EXIT

# Set the minimum gas price in app.toml
env 'HOME=/home/nonroot' sed -i 's/minimum-gas-prices = ""/minimum-gas-prices = "0stake"/' "/home/nonroot/.govchain/config/app.toml"
Expand Down
27 changes: 27 additions & 0 deletions docs/security_assessment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Security Assessment Summary

This document captures the primary security weaknesses identified during the review and the remediation steps implemented in this change-set. A hands-on proof-of-concept that recreates the original vulnerability and demonstrates the hardened mitigation is available in [`docs/poc/genesis_poisoning.md`](poc/genesis_poisoning.md).

## 1. Insecure Genesis File Bootstrap

### Issue
- Both the Docker entrypoint (`docker-entrypoint.sh`) and the volunteer helper script (`scripts/join-as-volunteer.sh`) downloaded the network genesis file directly from the `main` branch without integrity checks.
- The downloads used silent `curl` invocations that ignored HTTP errors and offered no tamper detection. Any man-in-the-middle attack, compromised CDN, or unexpected upstream change could silently deliver a malicious genesis file. A poisoned genesis file allows an attacker to modify the validator set or bootstrap nodes onto a hostile fork.

### Remediation
- Added strict download options (`curl -fSL --retry 3 --retry-delay 2`) so HTTP failures surface immediately.
- Introduced optional `GENESIS_URL` and `GENESIS_SHA256` overrides and checksum verification in both scripts.
- Added dependency checks for `curl`, `jq`, and `sha256sum` to fail fast when prerequisites are missing.
- Hardened scripts with `set -euo pipefail`, temporary-file handling, and cleanup traps to avoid partial writes.

### Operator Action Items
- Publish an official `GENESIS_SHA256` alongside every release and configure the environment variables (or script arguments) accordingly.
- When hosting alternative genesis mirrors, ensure the checksum matches the canonical release artifact.
- Consider pinning `GENESIS_URL` to an immutable tag or release asset rather than a moving branch reference.

## 2. General Operational Guidance

- Monitor CI/CD pipelines to ensure scripts remain executable and dependencies such as `jq` and `sha256sum` stay available in container images.
- Document the expected checksum management process in validator onboarding materials so volunteers consistently verify genesis integrity.

The combination of these mitigations closes the immediate loophole around silent genesis tampering and establishes a clearer operational posture for secure node provisioning.
38 changes: 34 additions & 4 deletions scripts/join-as-volunteer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,30 @@
# govchain Volunteer Node Setup
# Allows volunteers to join the network as validators without tokens

set -e
set -euo pipefail

if ! command -v curl >/dev/null 2>&1; then
echo "❌ Error: curl is required but was not found in PATH." >&2
exit 1
fi

echo "================================"
echo "govchain Volunteer Node Setup"
echo "================================"
echo ""

if [ "$#" -lt 2 ]; then
echo "Usage: $0 <node-name> <genesis-file-url>"
echo "Usage: $0 <node-name> <genesis-file-url> [genesis-sha256]"
echo ""
echo "Example:"
echo " $0 volunteer-node-1 https://raw.githubusercontent.com/org/govchain/main/genesis.json"
echo " $0 volunteer-node-1 https://raw.githubusercontent.com/org/govchain/main/genesis.json <sha256>"
echo ""
exit 1
fi

NODE_NAME="$1"
GENESIS_URL="$2"
GENESIS_SHA256="${3:-}"

echo "📝 Node Name: $NODE_NAME"
echo "🌐 Genesis URL: $GENESIS_URL"
Expand Down Expand Up @@ -48,7 +54,31 @@ echo "🔧 Initializing volunteer node..."

# Download genesis file
echo "📥 Downloading genesis file..."
curl -s "$GENESIS_URL" -o "$HOME/.govchain/config/genesis.json"
TEMP_GENESIS=$(mktemp /tmp/genesis.XXXXXX)

cleanup() {
[ -f "$TEMP_GENESIS" ] && rm -f "$TEMP_GENESIS"
}
trap cleanup EXIT

if ! curl -fSL --retry 3 --retry-delay 2 "$GENESIS_URL" -o "$TEMP_GENESIS"; then
echo "❌ Error: failed to download genesis from $GENESIS_URL" >&2
exit 1
fi

if [ -n "$GENESIS_SHA256" ]; then
if ! command -v sha256sum >/dev/null 2>&1; then
echo "❌ Error: sha256sum is required when providing a genesis checksum." >&2
exit 1
fi
echo "🔐 Verifying genesis checksum..."
echo "$GENESIS_SHA256 $TEMP_GENESIS" | sha256sum -c -
else
echo "⚠️ Warning: No genesis checksum provided. Skipping integrity verification."
fi

mv "$TEMP_GENESIS" "$HOME/.govchain/config/genesis.json"
trap - EXIT

# Create validator key
echo "🔑 Creating validator key..."
Expand Down