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
25 changes: 15 additions & 10 deletions .github/.env.base
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ ENABLE_BENCHMARKS=true # Run benchmark tests
ENABLE_CACHE_WARMING=true # Warm Go module and build caches
ENABLE_CODE_COVERAGE=true # Generate coverage reports via go-coverage
ENABLE_FUZZ_TESTING=true # Run fuzz tests (Go 1.18+)
ENABLE_GO_TESTS=true # Run Go test suite (unit, integration, matrix)
ENABLE_RACE_DETECTION=true # Enable Go race detector
ENABLE_STATIC_ANALYSIS=true # Run go vet analysis
ENABLE_VERBOSE_TEST_OUTPUT=false # Verbose test output (can slow CI)
Expand All @@ -106,7 +107,7 @@ ENABLE_GODOCS_PUBLISHING=true # Publish to pkg.go.dev on tag/releases
ARTIFACT_DOWNLOAD_RETRIES=3 # Number of retry attempts for failed downloads
ARTIFACT_DOWNLOAD_RETRY_DELAY=10 # Initial retry delay in seconds (uses exponential backoff)
ARTIFACT_DOWNLOAD_TIMEOUT=300 # Download timeout in seconds (5 minutes)
ARTIFACT_DOWNLOAD_CONTINUE_ON_ERROR=false # Continue workflow execution even if artifact download fails
ARTIFACT_DOWNLOAD_CONTINUE_ON_ERROR=true # Continue workflow execution even if artifact download fails (required for fork PRs)

# ================================================================================================
# ⚙️ BENCHMARK & TEST CONFIGURATION
Expand Down Expand Up @@ -231,14 +232,14 @@ REDIS_CACHE_FORCE_PULL=false # Force pull Redis images even when cache
# 🪄 MAGE-X CONFIGURATION
# ================================================================================================

MAGE_X_VERSION=v1.7.9 # https://github.com/mrz1836/mage-x/releases
MAGE_X_VERSION=v1.7.12 # https://github.com/mrz1836/mage-x/releases
MAGE_X_USE_LOCAL=false # Use local version for development
MAGE_X_AUTO_DISCOVER_BUILD_TAGS=true # Enable auto-discovery of build tags
MAGE_X_AUTO_DISCOVER_BUILD_TAGS_EXCLUDE=race,custom # Comma-separated list of tags to exclude
MAGE_X_FORMAT_EXCLUDE_PATHS=vendor,node_modules,.git,.idea # Format exclusion paths (comma-separated directories to exclude from formatting)
MAGE_X_GITLEAKS_VERSION=8.28.0 # https://github.com/gitleaks/gitleaks/releases
MAGE_X_GOFUMPT_VERSION=v0.9.1 # https://github.com/mvdan/gofumpt/releases
MAGE_X_GOLANGCI_LINT_VERSION=v2.6.0 # https://github.com/golangci/golangci-lint/releases
MAGE_X_GOFUMPT_VERSION=v0.9.2 # https://github.com/mvdan/gofumpt/releases
MAGE_X_GOLANGCI_LINT_VERSION=v2.6.1 # https://github.com/golangci/golangci-lint/releases
MAGE_X_GORELEASER_VERSION=v2.12.7 # https://github.com/goreleaser/goreleaser/releases
MAGE_X_GOVULNCHECK_VERSION=v1.1.4 # https://pkg.go.dev/golang.org/x/vuln
MAGE_X_GO_SECONDARY_VERSION=1.24.x # Secondary Go version for MAGE-X (also our secondary)
Expand All @@ -247,7 +248,7 @@ MAGE_X_MOCKGEN_VERSION=v0.6.0 # https://github.c
MAGE_X_NANCY_VERSION=v1.0.52 # https://github.com/sonatype-nexus-community/nancy/releases
MAGE_X_STATICCHECK_VERSION=2025.1.1 # https://github.com/dominikh/go-tools/releases
MAGE_X_SWAG_VERSION=v1.16.6 # https://github.com/swaggo/swag/releases
MAGE_X_YAMLFMT_VERSION=v0.17.2 # https://github.com/google/yamlfmt/releases
MAGE_X_YAMLFMT_VERSION=v0.20.0 # https://github.com/google/yamlfmt/releases

# Runtime variables (set by setup-goreleaser action):
# MAGE_X_GORELEASER_PATH - Path to installed goreleaser binary
Expand Down Expand Up @@ -299,12 +300,12 @@ NANCY_VERSION=v1.0.51 # https://github.com/sonatype-nexus-commu
# ================================================================================================

# Pre-Commit System
GO_PRE_COMMIT_VERSION=v1.3.4 # https://github.com/mrz1836/go-pre-commit
GO_PRE_COMMIT_VERSION=v1.3.5 # https://github.com/mrz1836/go-pre-commit
GO_PRE_COMMIT_USE_LOCAL=false # Use local version for development

# System Settings
GO_PRE_COMMIT_FAIL_FAST=false
GO_PRE_COMMIT_TIMEOUT_SECONDS=300
GO_PRE_COMMIT_TIMEOUT_SECONDS=720
GO_PRE_COMMIT_TOOL_INSTALL_TIMEOUT=300
GO_PRE_COMMIT_AUTO_ADJUST_CI_TIMEOUTS=true
GO_PRE_COMMIT_PARALLEL_WORKERS=2
Expand All @@ -318,8 +319,8 @@ GO_PRE_COMMIT_MAX_FILES_OPEN=100
GO_PRE_COMMIT_ALL_FILES=true

# Tool Versions
GO_PRE_COMMIT_GOLANGCI_LINT_VERSION=v2.6.0 # https://github.com/golangci/golangci-lint
GO_PRE_COMMIT_FUMPT_VERSION=v0.9.1 # https://github.com/mvdan/gofumpt
GO_PRE_COMMIT_GOLANGCI_LINT_VERSION=v2.6.1 # https://github.com/golangci/golangci-lint/releases
GO_PRE_COMMIT_FUMPT_VERSION=v0.9.2 # https://github.com/mvdan/gofumpt/releases
GO_PRE_COMMIT_GOIMPORTS_VERSION=latest # https://github.com/golang/tools

# Build tags for golangci-lint and other tools
Expand Down Expand Up @@ -347,7 +348,7 @@ GO_PRE_COMMIT_AI_DETECTION_AUTO_FIX=false
GO_PRE_COMMIT_FMT_TIMEOUT=30
GO_PRE_COMMIT_FUMPT_TIMEOUT=30
GO_PRE_COMMIT_GOIMPORTS_TIMEOUT=30
GO_PRE_COMMIT_LINT_TIMEOUT=60
GO_PRE_COMMIT_LINT_TIMEOUT=600
GO_PRE_COMMIT_MOD_TIDY_TIMEOUT=60
GO_PRE_COMMIT_WHITESPACE_TIMEOUT=30
GO_PRE_COMMIT_EOF_TIMEOUT=30
Expand Down Expand Up @@ -409,6 +410,10 @@ AUTO_MERGE_COMMENT_ON_ENABLE=true
AUTO_MERGE_COMMENT_ON_DISABLE=true
AUTO_MERGE_LABELS_TO_ADD=automerge-enabled
AUTO_MERGE_SKIP_BOT_PRS=true
AUTO_MERGE_SKIP_FORK_PRS=true
# Note: Fork PRs receive welcome comments from pull-request-management-fork.yml instead
# This setting only affects same-repo PRs (fork PRs use read-only GITHUB_TOKEN)
AUTO_MERGE_COMMENT_ON_FORK_SKIP=true

# ================================================================================================
# 📝 PULL REQUEST MANAGEMENT CONFIGURATION
Expand Down
50 changes: 50 additions & 0 deletions .github/actions/load-env/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,50 @@ runs:
fi
}

# Function to validate environment variable names and values
validate_env_vars() {
local json="$1"
local source="$2"

echo "🔒 Validating environment variables from $source..."

# Extract all keys and values
local keys=$(echo "$json" | jq -r 'keys[]')

while IFS= read -r key; do
# Skip empty keys
[[ -z "$key" ]] && continue

# Validate key name: must match ^[A-Z_][A-Z0-9_]*$
if ! echo "$key" | grep -qE '^[A-Z_][A-Z0-9_]*$'; then
echo "❌ ERROR: Invalid environment variable name in $source: '$key'" >&2
echo " Variable names must start with uppercase letter or underscore" >&2
echo " and contain only uppercase letters, numbers, and underscores" >&2
exit 1
fi

# Get the value for this key
local value=$(echo "$json" | jq -r --arg k "$key" '.[$k]')

# Validate value length (max 10000 chars to prevent DoS)
if [[ ${#value} -gt 10000 ]]; then
echo "❌ ERROR: Environment variable value too long in $source: '$key'" >&2
echo " Maximum length is 10000 characters, got ${#value}" >&2
exit 1
fi

# Check for suspicious command injection patterns
if echo "$value" | grep -qE '`|\$\(|\$\{|;|&|\||<\(|>|<|\\|'"'"'|"|\x00|[[:cntrl:]]'; then
echo "⚠️ WARNING: Potentially unsafe characters in $source variable '$key'" >&2
echo " Value contains backticks, command substitution, or shell metacharacters" >&2
echo " Value will be treated as a literal string during extraction" >&2
fi

done <<< "$keys"

echo "✅ All variables in $source passed validation"
}

# Load configuration files in order of precedence
BASE_JSON="{}"
CUSTOM_JSON="{}"
Expand All @@ -83,6 +127,9 @@ runs:
BASE_JSON=$(parse_env_file ".github/.env.base")
BASE_COUNT=$(echo "$BASE_JSON" | jq 'keys | length')
echo "✅ Loaded $BASE_COUNT base configuration variables"

# Validate base configuration
validate_env_vars "$BASE_JSON" ".env.base"
else
echo "❌ ERROR: Required .env.base file not found!" >&2
exit 1
Expand All @@ -94,6 +141,9 @@ runs:
CUSTOM_JSON=$(parse_env_file ".github/.env.custom")
CUSTOM_COUNT=$(echo "$CUSTOM_JSON" | jq 'keys | length')
echo "✅ Loaded $CUSTOM_COUNT custom override variables"

# Validate custom configuration
validate_env_vars "$CUSTOM_JSON" ".env.custom"
else
echo "ℹ️ No custom configuration file found (this is optional)"
fi
Expand Down
3 changes: 3 additions & 0 deletions .github/labels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
- name: "feature"
description: "Any new significant addition"
color: 0e8a16
- name: "fork-pr"
description: "PR originated from a forked repository"
color: 5319e7
- name: "github-actions"
description: "Used for referencing GitHub Actions"
color: 006b75
Expand Down
107 changes: 89 additions & 18 deletions .github/workflows/auto-merge-on-approval.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ jobs:
id: config
env:
ENV_JSON: ${{ needs.load-env.outputs.env-json }}
GH_PAT_TOKEN: ${{ secrets.GH_PAT_TOKEN }}
run: |
echo "📋 Extracting auto-merge configuration from environment..."

Expand All @@ -116,6 +115,8 @@ jobs:
COMMENT_ON_DISABLE=$(echo "$ENV_JSON" | jq -r '.AUTO_MERGE_COMMENT_ON_DISABLE')
LABELS_TO_ADD=$(echo "$ENV_JSON" | jq -r '.AUTO_MERGE_LABELS_TO_ADD')
SKIP_BOT_PRS=$(echo "$ENV_JSON" | jq -r '.AUTO_MERGE_SKIP_BOT_PRS')
SKIP_FORK_PRS=$(echo "$ENV_JSON" | jq -r '.AUTO_MERGE_SKIP_FORK_PRS')
COMMENT_ON_FORK_SKIP=$(echo "$ENV_JSON" | jq -r '.AUTO_MERGE_COMMENT_ON_FORK_SKIP')
PREFERRED_TOKEN=$(echo "$ENV_JSON" | jq -r '.PREFERRED_GITHUB_TOKEN')

# Validate required configuration
Expand All @@ -135,6 +136,8 @@ jobs:
echo "COMMENT_ON_DISABLE=$COMMENT_ON_DISABLE" >> $GITHUB_ENV
echo "LABELS_TO_ADD=$LABELS_TO_ADD" >> $GITHUB_ENV
echo "SKIP_BOT_PRS=$SKIP_BOT_PRS" >> $GITHUB_ENV
echo "SKIP_FORK_PRS=$SKIP_FORK_PRS" >> $GITHUB_ENV
echo "COMMENT_ON_FORK_SKIP=$COMMENT_ON_FORK_SKIP" >> $GITHUB_ENV

# Determine default merge type
DEFAULT_MERGE_TYPE=$(echo "$MERGE_TYPES" | cut -d',' -f1)
Expand All @@ -156,12 +159,9 @@ jobs:
echo " 💬 Comment on disable: $COMMENT_ON_DISABLE"
echo " 🏷️ Labels to add: $LABELS_TO_ADD"
echo " 🤖 Skip bot PRs: $SKIP_BOT_PRS"

if [[ "$PREFERRED_TOKEN" == "GH_PAT_TOKEN" && -n "$GH_PAT_TOKEN" ]]; then
echo " 🔑 Token: Personal Access Token (PAT)"
else
echo " 🔑 Token: Default GITHUB_TOKEN"
fi
echo " 🍴 Skip fork PRs: $SKIP_FORK_PRS"
echo " 💬 Comment on fork skip: $COMMENT_ON_FORK_SKIP"
echo " 🔑 Token: Selected via github-script action"

# --------------------------------------------------------------------
# Process the PR for auto-merge
Expand Down Expand Up @@ -198,6 +198,43 @@ jobs:
return;
}

// ————————————————————————————————————————————————————————————————
// Check if we should skip fork PRs
// ————————————————————————————————————————————————————————————————

// Handle edge case: fork repository deleted/inaccessible (pr.head.repo is null)
if (!pr.head.repo) {
console.log('⚠️ PR head repository is null (fork may have been deleted)');
if (process.env.SKIP_FORK_PRS === 'true') {
console.log('🍴 Skipping PR with deleted fork source (security policy)');
core.setOutput('action', 'skip-deleted-fork');
return;
}
// If not skipping forks, log and continue (will be treated as same-repo PR)
console.log('⚠️ Continuing with auto-merge processing (null repo treated as same-repo)');
} else {
// Safe to access pr.head.repo.full_name now
const headRepoFullName = pr.head.repo.full_name;
const baseRepoFullName = `${owner}/${repo}`;
const isForkPR = headRepoFullName !== baseRepoFullName;

if (isForkPR && process.env.SKIP_FORK_PRS === 'true') {
console.log('🍴 Skipping fork PR (security policy: fork PRs are not auto-merged)');
console.log(` Fork source: ${headRepoFullName}`);
console.log(` Base repository: ${baseRepoFullName}`);
console.log(' Security reason: Fork PRs require manual maintainer review before merge');

// Note: Comments are not posted to fork PRs due to read-only GITHUB_TOKEN permissions
// Fork PR handling is already managed by pull-request-management-fork.yml workflow
if (process.env.COMMENT_ON_FORK_SKIP === 'true') {
console.log(' ℹ️ Comment posting skipped for fork PR (handled by fork PR workflow)');
}

core.setOutput('action', 'skip-fork');
return;
}
}

// ————————————————————————————————————————————————————————————————
// Check basic PR conditions
// ————————————————————————————————————————————————————————————————
Expand Down Expand Up @@ -287,7 +324,7 @@ jobs:
execSync(`gh pr merge --disable-auto "${pr.html_url}"`, {
env: {
...process.env,
GH_TOKEN: '${{ secrets.GH_PAT_TOKEN || secrets.GITHUB_TOKEN }}'
GH_TOKEN: process.env.GITHUB_TOKEN
},
stdio: 'inherit'
});
Expand All @@ -304,8 +341,17 @@ jobs:
}

core.setOutput('action', 'disabled-changes-requested');
} catch (error) {
console.log('ℹ️ Could not disable auto-merge (may not have been enabled)');
} catch (disableError) {
// Differentiate between "not enabled" and actual failures
if (disableError.message && (
disableError.message.includes('not enabled') ||
disableError.message.includes('auto-merge is not enabled')
)) {
console.log('ℹ️ Auto-merge was not enabled, no action needed');
} else {
console.error(`❌ Failed to disable auto-merge: ${disableError.message}`);
// Don't fail workflow, but log the error properly
}
}
return;
}
Expand Down Expand Up @@ -342,15 +388,29 @@ jobs:

console.log(`🚀 Enabling auto-merge with command: ${mergeCommand}`);

execSync(mergeCommand, {
env: {
...process.env,
GH_TOKEN: '${{ secrets.GH_PAT_TOKEN || secrets.GITHUB_TOKEN }}'
},
stdio: 'inherit'
});
try {
execSync(mergeCommand, {
env: {
...process.env,
GH_TOKEN: process.env.GITHUB_TOKEN
},
stdio: 'inherit'
});

console.log('✅ Auto-merge enabled! PR will merge when all status checks pass.');
console.log('✅ Auto-merge enabled! PR will merge when all status checks pass.');
} catch (enableError) {
// Handle race condition: another workflow run may have enabled auto-merge
if (enableError.message && (
enableError.message.includes('already enabled') ||
enableError.message.includes('auto-merge is already enabled')
)) {
console.log('ℹ️ Auto-merge already enabled by another workflow run');
core.setOutput('action', 'already-enabled');
return;
}
// Re-throw other errors to be caught by outer catch block
throw enableError;
}

// Add comment if configured
if (process.env.COMMENT_ON_ENABLE === 'true') {
Expand Down Expand Up @@ -445,6 +505,12 @@ jobs:
"skip-bot")
ACTION_DESC="🤖 Skipped (bot PR)"
;;
"skip-fork")
ACTION_DESC="🍴 Skipped (fork PR - security policy)"
;;
"skip-deleted-fork")
ACTION_DESC="🍴 Skipped (deleted fork PR)"
;;
"skip-draft")
ACTION_DESC="📝 Skipped (draft PR)"
;;
Expand Down Expand Up @@ -477,6 +543,7 @@ jobs:
SKIP_DRAFT=$(echo "$ENV_JSON" | jq -r '.AUTO_MERGE_SKIP_DRAFT')
SKIP_WIP=$(echo "$ENV_JSON" | jq -r '.AUTO_MERGE_SKIP_WIP')
SKIP_BOT_PRS=$(echo "$ENV_JSON" | jq -r '.AUTO_MERGE_SKIP_BOT_PRS')
SKIP_FORK_PRS=$(echo "$ENV_JSON" | jq -r '.AUTO_MERGE_SKIP_FORK_PRS')

echo "| Setting | Value |" >> $GITHUB_STEP_SUMMARY
echo "|---------|-------|" >> $GITHUB_STEP_SUMMARY
Expand All @@ -486,6 +553,7 @@ jobs:
echo "| Skip draft PRs | $SKIP_DRAFT |" >> $GITHUB_STEP_SUMMARY
echo "| Skip WIP PRs | $SKIP_WIP |" >> $GITHUB_STEP_SUMMARY
echo "| Skip bot PRs | $SKIP_BOT_PRS |" >> $GITHUB_STEP_SUMMARY
echo "| Skip fork PRs | $SKIP_FORK_PRS |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY
echo "🤖 _Automated by GitHub Actions_" >> $GITHUB_STEP_SUMMARY
Expand All @@ -509,6 +577,9 @@ jobs:
disabled-changes-requested)
echo "🛑 Action: Auto-merge disabled due to changes requested"
;;
skip-fork)
echo "🍴 Action: Skipped - Fork PR (security policy)"
;;
skip-*)
echo "⏭️ Action: Skipped - $ACTION"
;;
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/fortress-code-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ jobs:
# ----------------------------------------------------------------------------------
lint:
name: ✨ Lint Code
timeout-minutes: 20
if: ${{ inputs.go-lint-enabled == 'true' }}
runs-on: ${{ inputs.primary-runner }}
outputs:
Expand Down
Loading