CtrlK
BlogDocsLog inGet started
Tessl Logo

pantheon-ai/jenkinsfile-validator

Comprehensive toolkit for validating, linting, testing, and automating Jenkinsfile pipelines (both Declarative and Scripted). Use this skill when working with Jenkins pipeline files, validating pipeline syntax, checking best practices, debugging pipeline issues, or working with custom plugins.

Overall
score

93%

Does it follow best practices?

Validation for skill structure

Overview
Skills
Evals
Files

common_validation.shscripts/

#!/bin/bash

# Common Validation Functions
# Shared validation functions for both Declarative and Scripted pipelines

# Note: We use set -u (undefined variable check) but not -e (exit on error)
# because grep returns exit code 1 when no match is found, which would cause
# premature script exit in conditional statements
set -uo pipefail

# Colors for output
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Counters
ERRORS=0
WARNINGS=0
INFO=0

# Validation result arrays
declare -a ERROR_MESSAGES=()
declare -a WARNING_MESSAGES=()
declare -a INFO_MESSAGES=()

log_error() {
    local line=$1
    local message=$2
    ERROR_MESSAGES+=("ERROR [Line $line]: $message")
    ((ERRORS++))
}

log_warning() {
    local line=$1
    local message=$2
    WARNING_MESSAGES+=("WARNING [Line $line]: $message")
    ((WARNINGS++))
}

log_info() {
    local line=$1
    local message=$2
    INFO_MESSAGES+=("INFO [Line $line]: $message")
    ((INFO++))
}

# Detect pipeline type
detect_pipeline_type() {
    local file=$1

    # Remove comments and empty lines
    local first_meaningful=$(grep -v '^\s*//' "$file" | grep -v '^\s*$' | head -1)

    if echo "$first_meaningful" | grep -q '^\s*pipeline\s*{'; then
        echo "declarative"
    elif echo "$first_meaningful" | grep -qE '^\s*node\s*(\(|{)'; then
        echo "scripted"
    elif grep -q '^\s*pipeline\s*{' "$file"; then
        echo "declarative"
    elif grep -qE '^\s*node\s*(\(|{)' "$file"; then
        echo "scripted"
    else
        echo "unknown"
    fi
}

# Check for hardcoded credentials
check_credentials() {
    local file=$1
    local line_num=0

    echo -e "${BLUE}=== Checking for Hardcoded Credentials ===${NC}"

    while IFS= read -r line; do
        ((++line_num))

        # Skip comments
        if echo "$line" | grep -qE '^\s*//'; then
            continue
        fi

        # Skip lines that are just comment markers in multi-line comments
        if echo "$line" | grep -qE '^\s*\*'; then
            continue
        fi

        # ============================================
        # PASSWORD DETECTION
        # ============================================

        # Check for password variable assignments: password = "value"
        if echo "$line" | grep -qiE '(password|passwd|pwd)\s*=\s*["\047]' && ! echo "$line" | grep -qi 'credentials'; then
            log_error "$line_num" "Potential hardcoded password detected"
            log_error "$line_num" "  → Use credentials() or withCredentials instead"
        fi

        # Check for command-line password flags: -p password, --password=secret
        # Look for -p followed by a non-variable value (not starting with $)
        # Exclude common false positives: mkdir -p, cp -p, tar -p, install -p, etc.
        if echo "$line" | grep -qiE '\s-p\s+[a-zA-Z0-9_]+' && ! echo "$line" | grep -qE '\s-p\s+\$'; then
            # Skip common commands that use -p for other purposes
            if ! echo "$line" | grep -qiE '(mkdir|cp|tar|install|rsync|scp|chmod)\s+-p'; then
                log_error "$line_num" "Password passed as command-line argument (-p)"
                log_error "$line_num" "  → Use withCredentials to inject secrets securely"
            fi
        fi

        # Check for --password flag with value
        if echo "$line" | grep -qiE -e '--password[=\s]+[a-zA-Z0-9_]+' && ! echo "$line" | grep -qE '\$'; then
            log_error "$line_num" "Password passed as command-line argument (--password)"
            log_error "$line_num" "  → Use withCredentials to inject secrets securely"
        fi

        # ============================================
        # API KEY / TOKEN DETECTION
        # ============================================

        # Check for API key variable assignments
        if echo "$line" | grep -qiE '(api[_-]?key|apikey)\s*=\s*["\047]' && ! echo "$line" | grep -qi 'credentials'; then
            log_error "$line_num" "Potential hardcoded API key detected"
            log_error "$line_num" "  → Use credentials() or withCredentials instead"
        fi

        # Check for token/secret variable assignments with long values
        if echo "$line" | grep -qiE '(token|secret)\s*=\s*["\047][a-zA-Z0-9]{20,}'; then
            log_warning "$line_num" "Potential hardcoded token/secret detected"
            log_warning "$line_num" "  → Use Jenkins Credentials Manager"
        fi

        # ============================================
        # BEARER TOKEN / AUTH HEADER DETECTION
        # ============================================

        # Check for Bearer tokens in Authorization headers
        if echo "$line" | grep -qiE 'Bearer\s+[a-zA-Z0-9_.-]{10,}' && ! echo "$line" | grep -qE '\$'; then
            log_error "$line_num" "Hardcoded Bearer token detected"
            log_error "$line_num" "  → Use withCredentials for token management"
        fi

        # Check for Basic auth with inline credentials (base64 or plain)
        if echo "$line" | grep -qiE 'Basic\s+[a-zA-Z0-9+/=]{10,}' && ! echo "$line" | grep -qE '\$'; then
            log_error "$line_num" "Hardcoded Basic auth credentials detected"
            log_error "$line_num" "  → Use withCredentials([usernamePassword(...)]) instead"
        fi

        # Check for X-API-Key, X-Auth-Token, and similar headers with inline values
        if echo "$line" | grep -qiE '(X-API-Key|X-Auth-Token|X-Access-Token|Authorization):\s*[a-zA-Z0-9_.-]{8,}' && ! echo "$line" | grep -qE '\$'; then
            log_warning "$line_num" "Hardcoded auth header value detected"
            log_warning "$line_num" "  → Use withCredentials for secure header injection"
        fi

        # ============================================
        # CLOUD PROVIDER CREDENTIALS
        # ============================================

        # Check for AWS Access Key IDs
        if echo "$line" | grep -qE 'AKIA[0-9A-Z]{16}'; then
            log_error "$line_num" "AWS Access Key ID detected - NEVER hardcode AWS credentials!"
            log_error "$line_num" "  → Use withCredentials or AWS credentials plugin"
        fi

        # Check for AWS Secret Access Keys (40 char base64-like string after aws_secret)
        if echo "$line" | grep -qiE 'aws_secret[_-]?(access[_-]?key)?\s*=\s*["\047][A-Za-z0-9/+=]{40}'; then
            log_error "$line_num" "AWS Secret Access Key detected"
            log_error "$line_num" "  → Use withCredentials or AWS credentials plugin"
        fi

        # Check for Azure credentials patterns
        if echo "$line" | grep -qiE '(azure[_-]?client[_-]?secret|azure[_-]?tenant)\s*=\s*["\047][a-zA-Z0-9-]{20,}'; then
            log_error "$line_num" "Azure credential detected"
            log_error "$line_num" "  → Use Azure Credentials plugin with withCredentials"
        fi

        # Check for GCP service account key indicators
        if echo "$line" | grep -qE '"type"\s*:\s*"service_account"'; then
            log_error "$line_num" "GCP service account key detected"
            log_error "$line_num" "  → Use GCP Credentials plugin or secret file credentials"
        fi

        # ============================================
        # VERSION CONTROL TOKENS
        # ============================================

        # Check for GitHub tokens (ghp_, gho_, ghu_, ghs_, ghr_)
        if echo "$line" | grep -qE 'gh[pousr]_[a-zA-Z0-9]{36,}'; then
            log_error "$line_num" "GitHub token detected"
            log_error "$line_num" "  → Use credentials() with secret text"
        fi

        # Check for GitLab tokens (glpat-)
        if echo "$line" | grep -qE 'glpat-[a-zA-Z0-9_-]{20,}'; then
            log_error "$line_num" "GitLab personal access token detected"
            log_error "$line_num" "  → Use credentials() with secret text"
        fi

        # Check for Bitbucket app passwords
        if echo "$line" | grep -qiE 'bitbucket.*["\047][a-zA-Z0-9]{20,}["\047]'; then
            log_warning "$line_num" "Potential Bitbucket credential detected"
            log_warning "$line_num" "  → Use credentials() with username/password"
        fi

        # ============================================
        # DOCKER / REGISTRY CREDENTIALS
        # ============================================

        # Check for docker login with inline password
        if echo "$line" | grep -qiE 'docker\s+login.*-p\s+[^\$]' && ! echo "$line" | grep -qE '\$'; then
            log_error "$line_num" "Docker login with hardcoded password"
            log_error "$line_num" "  → Use withCredentials([usernamePassword(credentialsId: 'docker-creds', ...)])"
        fi

        # Check for docker login with --password
        if echo "$line" | grep -qiE 'docker\s+login.*--password[=\s]+[a-zA-Z0-9]' && ! echo "$line" | grep -qE '\$'; then
            log_error "$line_num" "Docker login with hardcoded password"
            log_error "$line_num" "  → Use docker.withRegistry() with credentials"
        fi

        # ============================================
        # SSH / PRIVATE KEY DETECTION
        # ============================================

        # Check for private key content (use -e to avoid -- being interpreted as option)
        if echo "$line" | grep -qE -e 'BEGIN.*(RSA\s+)?PRIVATE\s+KEY'; then
            log_error "$line_num" "Private key content detected in pipeline"
            log_error "$line_num" "  → Use SSH credentials in Jenkins Credentials Manager"
        fi

        # Check for SSH key file paths with hardcoded values
        if echo "$line" | grep -qiE 'ssh.*-i\s+["\047]?(/[^\$]+|~[^\$]+)["\047]?' && ! echo "$line" | grep -qE '\$'; then
            log_warning "$line_num" "Hardcoded SSH key path detected"
            log_warning "$line_num" "  → Use sshUserPrivateKey credentials"
        fi

        # ============================================
        # DATABASE CREDENTIALS
        # ============================================

        # Check for database connection strings with credentials
        if echo "$line" | grep -qiE '(mysql|postgresql|postgres|mongodb|redis|jdbc)://[a-zA-Z0-9_]+:[a-zA-Z0-9_]+@'; then
            log_error "$line_num" "Database connection string with embedded credentials"
            log_error "$line_num" "  → Use withCredentials to inject database credentials"
        fi

        # Check for database password parameters
        if echo "$line" | grep -qiE '(db[_-]?pass|database[_-]?password|mysql[_-]?pwd)\s*=\s*["\047][a-zA-Z0-9]'; then
            log_error "$line_num" "Hardcoded database password"
            log_error "$line_num" "  → Use credentials() or withCredentials"
        fi

        # ============================================
        # GENERIC PATTERNS
        # ============================================

        # Check for common credential variable names with suspicious values
        if echo "$line" | grep -qiE '(username|user)\s*=\s*["\047](admin|root|sa|administrator)'; then
            log_warning "$line_num" "Hardcoded username detected - consider using credentials"
        fi

        # Check for slack/webhook tokens
        if echo "$line" | grep -qE 'xox[baprs]-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-9]{24}'; then
            log_error "$line_num" "Slack token detected"
            log_error "$line_num" "  → Use credentials() with secret text"
        fi

        # Check for generic webhook URLs with tokens
        if echo "$line" | grep -qiE 'hooks\.(slack|discord)\.com/services/[A-Z0-9/]+'; then
            log_warning "$line_num" "Webhook URL with token detected"
            log_warning "$line_num" "  → Store webhook URL in credentials"
        fi

        # Check for npm tokens
        if echo "$line" | grep -qE 'npm_[a-zA-Z0-9]{36}'; then
            log_error "$line_num" "NPM token detected"
            log_error "$line_num" "  → Use credentials() with secret text"
        fi

        # Check for PyPI tokens
        if echo "$line" | grep -qE 'pypi-[a-zA-Z0-9_-]{50,}'; then
            log_error "$line_num" "PyPI token detected"
            log_error "$line_num" "  → Use credentials() with secret text"
        fi

        # Check for base64 encoded credentials (common mistake) - long base64 strings
        if echo "$line" | grep -qE '["\047][A-Za-z0-9+/]{40,}={0,2}["\047]'; then
            log_info "$line_num" "Potential base64 encoded value - ensure this is not a credential"
        fi

        # Check for hex-encoded secrets (32+ hex chars often indicate secrets)
        if echo "$line" | grep -qE '["\047][a-fA-F0-9]{32,}["\047]' && ! echo "$line" | grep -qiE '(commit|sha|hash|checksum|md5|sha1|sha256)'; then
            log_info "$line_num" "Long hex string detected - verify this is not a secret"
        fi

    done < "$file"

    echo ""
}

# Check variable usage
check_variable_usage() {
    local file=$1
    local line_num=0

    echo -e "${BLUE}=== Checking Variable Usage ===${NC}"

    declare -A defined_vars
    declare -A used_vars

    while IFS= read -r line; do
        ((++line_num))

        # Skip comments
        if echo "$line" | grep -qE '^\s*//'; then
            continue
        fi

        # Track variable definitions
        if echo "$line" | grep -qE '(def|String|Integer|Boolean)\s+[a-zA-Z_][a-zA-Z0-9_]*\s*='; then
            local var_name=$(echo "$line" | grep -oE '[a-zA-Z_][a-zA-Z0-9_]*\s*=' | sed 's/\s*=//')
            defined_vars["$var_name"]=$line_num
        fi

        # Track variable usage (simple heuristic)
        if echo "$line" | grep -qE '\$\{?[a-zA-Z_][a-zA-Z0-9_]*\}?'; then
            local vars=$(echo "$line" | grep -oE '\$\{?[a-zA-Z_][a-zA-Z0-9_]*\}?' | sed 's/[\${}]//g')
            for var in $vars; do
                used_vars["$var"]=$line_num
            done
        fi

        # Check for undefined variables being used
        if echo "$line" | grep -qE '\$[a-zA-Z_][a-zA-Z0-9_]*' && ! echo "$line" | grep -qE '(def|String|params\.|env\.)'; then
            local var=$(echo "$line" | grep -oE '\$[a-zA-Z_][a-zA-Z0-9_]*' | sed 's/\$//' | head -1)
            if [[ ! -v defined_vars["$var"] ]] && [[ "$var" != "WORKSPACE" ]] && [[ "$var" != "BUILD_NUMBER" ]] && [[ "$var" != "JOB_NAME" ]]; then
                log_info "$line_num" "Variable '\$$var' used but not explicitly defined - ensure it's set by environment"
            fi
        fi
    done < "$file"

    echo ""
}

# Detect plugin usage
detect_plugins() {
    local file=$1

    echo -e "${BLUE}=== Detecting Plugin Usage ===${NC}"

    declare -A plugins

    # Common plugin steps
    local plugin_patterns=(
        "docker\."
        "kubernetes\."
        "withCredentials"
        "git "
        "checkout"
        "junit"
        "archiveArtifacts"
        "publishHTML"
        "mail"
        "emailext"
        "slackSend"
        "build job:"
        "input "
        "timeout"
        "retry"
        "script"
    )

    for pattern in "${plugin_patterns[@]}"; do
        if grep -q "$pattern" "$file"; then
            local plugin_name=$(echo "$pattern" | sed 's/[\.\\].*$//' | sed 's/ $//')
            plugins["$plugin_name"]=1
        fi
    done

    if [ ${#plugins[@]} -gt 0 ]; then
        echo "Plugins detected:"
        for plugin in "${!plugins[@]}"; do
            echo "  - $plugin"
        done
    else
        echo "No common plugins detected"
    fi

    echo ""
}

# Check for multiple consecutive sh/bat steps
check_multiple_sh_steps() {
    local file=$1
    local line_num=0
    local consecutive_sh=0
    local first_sh_line=0

    echo -e "${BLUE}=== Checking for Multiple Shell Steps ===${NC}"

    while IFS= read -r line; do
        ((++line_num))

        # Check for sh/bat steps
        if echo "$line" | grep -qE '^\s*(sh|bat)\s+["\047]' || echo "$line" | grep -qE '^\s*(sh|bat)\s*\('; then
            if [ $consecutive_sh -eq 0 ]; then
                first_sh_line=$line_num
            fi
            ((consecutive_sh++))
        else
            # Not a sh/bat line
            if [ $consecutive_sh -gt 2 ]; then
                log_warning "$first_sh_line" "Found $consecutive_sh consecutive sh/bat steps"
                log_warning "$first_sh_line" "  → Combine into single sh step with triple-quoted string (sh '''...''')"
                log_warning "$first_sh_line" "  → See: references/best_practices.md#combine-shell-commands"
            fi
            consecutive_sh=0
        fi
    done < "$file"

    # Check at end of file
    if [ $consecutive_sh -gt 2 ]; then
        log_warning "$first_sh_line" "Found $consecutive_sh consecutive sh/bat steps"
        log_warning "$first_sh_line" "  → Combine into single sh step with triple-quoted string"
    fi

    echo ""
}

# Check for timeout usage
check_timeout_usage() {
    local file=$1

    echo -e "${BLUE}=== Checking Timeout Configuration ===${NC}"

    if ! grep -qE '(timeout|Timeout)' "$file"; then
        log_info 1 "No timeout configuration found - consider adding timeout to prevent hung builds"
        log_info 1 "  → Declarative: options { timeout(time: 1, unit: 'HOURS') }"
        log_info 1 "  → Scripted: timeout(time: 1, unit: 'HOURS') { ... }"
    fi

    echo ""
}

# Check for proper workspace cleanup
check_workspace_cleanup() {
    local file=$1

    echo -e "${BLUE}=== Checking Workspace Cleanup ===${NC}"

    if ! grep -qE '(cleanWs|deleteDir|ws)' "$file"; then
        log_info 1 "No workspace cleanup detected - consider cleaning workspace for reproducible builds"
        log_info 1 "  → Add cleanWs() in post section or use deleteDir()"
    fi

    echo ""
}

# Print validation results
print_results() {
    echo -e "${BLUE}=== Validation Results ===${NC}"
    echo ""

    if [ ${#ERROR_MESSAGES[@]} -gt 0 ]; then
        echo -e "${RED}ERRORS (${ERRORS}):${NC}"
        for msg in "${ERROR_MESSAGES[@]}"; do
            echo -e "${RED}$msg${NC}"
        done
        echo ""
    fi

    if [ ${#WARNING_MESSAGES[@]} -gt 0 ]; then
        echo -e "${YELLOW}WARNINGS (${WARNINGS}):${NC}"
        for msg in "${WARNING_MESSAGES[@]}"; do
            echo -e "${YELLOW}$msg${NC}"
        done
        echo ""
    fi

    if [ ${#INFO_MESSAGES[@]} -gt 0 ]; then
        echo -e "${BLUE}INFO (${INFO}):${NC}"
        for msg in "${INFO_MESSAGES[@]}"; do
            echo -e "${BLUE}$msg${NC}"
        done
        echo ""
    fi

    # Summary
    echo -e "${BLUE}=== Summary ===${NC}"
    if [ $ERRORS -eq 0 ] && [ $WARNINGS -eq 0 ]; then
        echo -e "${GREEN}✓ Validation passed with no errors or warnings${NC}"
        return 0
    elif [ $ERRORS -eq 0 ]; then
        echo -e "${YELLOW}✓ Validation passed with $WARNINGS warning(s)${NC}"
        return 0
    else
        echo -e "${RED}✗ Validation failed with $ERRORS error(s) and $WARNINGS warning(s)${NC}"
        return 1
    fi
}

# Main function - run all common checks
run_common_checks() {
    local file=$1

    echo -e "${BLUE}=== Running Common Validation Checks ===${NC}"
    echo "File: $file"
    echo ""

    check_credentials "$file" || true
    check_variable_usage "$file" || true
    detect_plugins "$file" || true
    check_multiple_sh_steps "$file" || true
    check_timeout_usage "$file" || true
    check_workspace_cleanup "$file" || true

    print_results
}

# If script is executed directly
if [ "${BASH_SOURCE[0]}" == "${0}" ]; then
    if [ $# -lt 2 ]; then
        echo "Usage: $0 <command> <jenkinsfile>"
        echo "Commands:"
        echo "  detect_type       - Detect pipeline type (declarative/scripted)"
        echo "  check_credentials - Check for hardcoded credentials"
        echo "  check_all        - Run all common validation checks"
        exit 1
    fi

    COMMAND=$1
    JENKINSFILE=$2

    if [ ! -f "$JENKINSFILE" ]; then
        echo -e "${RED}Error: File '$JENKINSFILE' not found${NC}"
        exit 1
    fi

    case "$COMMAND" in
        detect_type)
            TYPE=$(detect_pipeline_type "$JENKINSFILE")
            echo "Pipeline type: $TYPE"
            ;;
        check_credentials)
            check_credentials "$JENKINSFILE"
            print_results
            ;;
        check_all)
            run_common_checks "$JENKINSFILE"
            ;;
        *)
            echo "Unknown command: $COMMAND"
            exit 1
            ;;
    esac
fi

Install with Tessl CLI

npx tessl i pantheon-ai/jenkinsfile-validator@0.1.0

SKILL.md

tile.json