Credentials Rule

Credentials Rule Overview #

This rule detects hardcoded credentials in GitHub Actions workflows, specifically focusing on passwords within container and service definitions. Hardcoding sensitive information like passwords directly in workflow files is a critical security risk that can lead to credential exposure.

Vulnerable Example:

on: push
jobs:
  test:
    runs-on: ubuntu-latest
    container:
      image: "example.com/owner/image"
      credentials:
        username: user
        password: "hardcodedPassword123"  # Hardcoded credential detected
    services:
      redis:
        image: redis
        credentials:
          username: user
          password: "anotherHardcodedPassword456"  # Hardcoded credential detected
    steps:
      - run: echo 'hello'

Detection Output:

credentials.yaml:9:19: "Container" section: Password found in container section, do not paste password direct hardcode [credentials]
      9 πŸ‘ˆ|        password: "hardcodedPassword123"

credentials.yaml:15:21: "Service" section for service redis: Password found in container section, do not paste password direct hardcode [credentials]
      15 πŸ‘ˆ|          password: "anotherHardcodedPassword456"

Security Background #

Why is this dangerous? #

Hardcoded credentials in workflow files pose significant security risks:

  1. Version Control Exposure: Credentials committed to version control are visible to anyone with repository access
  2. Fork Exposure: Forked repositories inherit hardcoded credentials
  3. Log Exposure: Hardcoded passwords may appear in CI/CD logs
  4. Rotation Difficulty: Changing hardcoded credentials requires code changes and redeployment

Attack Scenario #

1. Developer hardcodes password in workflow file
   └── Password committed to repository

2. Repository is forked or accessed by unauthorized user
   └── Credentials are visible in workflow file

3. Attacker uses credentials to access container registry
   └── Pulls or pushes malicious container images

4. Supply chain compromise
   └── Malicious images used in CI/CD pipeline

OWASP and CWE Mapping #

  • CWE-798: Use of Hard-coded Credentials
  • CWE-259: Use of Hard-coded Password
  • OWASP CI/CD Security Risks:
    • CICD-SEC-6: Insufficient Credential Hygiene

Technical Detection Mechanism #

The rule analyzes YAML workflow files and checks container/service credential definitions:

// Detection pattern
var isExpr = regexp.MustCompile(`^\$\{.+\}$`)

func (rule *CredentialRule) checkCredentials(where string, node *ast.Container) {
    if node.Credentials != nil &&
       node.Credentials.Password != nil &&
       !isExpr.MatchString(node.Credentials.Password.Value) {
        // Password is hardcoded - not a GitHub Actions expression
        rule.Errorf(node.Credentials.Password.Pos,
            "Password found in %s, do not paste password direct hardcode", where)
    }
}

Detection Logic Explanation #

What the Rule Checks #

  1. Container Section: Validates passwords in job container definitions
  2. Service Definitions: Validates passwords in service container credentials
  3. Expression Detection: Uses regex ^\$\{.+\}$ to identify GitHub Actions expressions

Safe vs Hardcoded Patterns #

Safe (will NOT trigger an error):

credentials:
  username: user
  password: ${{ secrets.REGISTRY_PASSWORD }}  # Uses secrets - safe

Hardcoded (will trigger an error):

credentials:
  username: user
  password: "myPassword123"  # Literal string - unsafe
  password: myPassword123    # Unquoted literal - unsafe

Safe Patterns #

on: push
jobs:
  test:
    runs-on: ubuntu-latest
    container:
      image: "example.com/owner/image"
      credentials:
        username: ${{ secrets.REGISTRY_USERNAME }}
        password: ${{ secrets.REGISTRY_PASSWORD }}  # Safe: Uses secrets
    steps:
      - run: echo 'hello'

Pattern 2: Using Environment Variables #

on: push
jobs:
  test:
    runs-on: ubuntu-latest
    container:
      image: "example.com/owner/image"
      credentials:
        username: ${{ vars.REGISTRY_USERNAME }}
        password: ${{ secrets.REGISTRY_PASSWORD }}  # Safe
    steps:
      - run: echo 'hello'

Auto-Fix Support #

The credentials rule supports auto-fixing by removing the hardcoded password field:

# Preview changes without applying
sisakulint -fix dry-run

# Apply fixes
sisakulint -fix on

Before (Vulnerable):

container:
  image: "example.com/image"
  credentials:
    username: user
    password: "hardcodedPassword123"

After Auto-Fix:

container:
  image: "example.com/image"
  credentials:
    username: user
    # password field removed - you must add secrets reference manually

Note: Auto-fix removes the password field entirely. You must manually add a proper secrets reference (${{ secrets.PASSWORD }}) after the fix.

Best Practices #

1. Always Use GitHub Secrets #

Store sensitive credentials in GitHub Secrets and reference them using expressions:

credentials:
  password: ${{ secrets.MY_PASSWORD }}

2. Use Organization-Level Secrets #

For credentials used across multiple repositories, use organization-level secrets:

credentials:
  password: ${{ secrets.ORG_REGISTRY_PASSWORD }}

3. Rotate Credentials Regularly #

Even when using secrets, implement regular credential rotation policies.

4. Limit Secret Access #

Use environment-scoped secrets for production credentials:

jobs:
  deploy:
    environment: production
    steps:
      - name: Deploy
        env:
          DEPLOY_TOKEN: ${{ secrets.PRODUCTION_DEPLOY_TOKEN }}
  • permissions: Ensures workflows follow least-privilege principle
  • commit-sha: Pins actions to prevent supply chain attacks

References #

GitHub Documentation #

Security Resources #

Using secrets in GitHub Actions - GitHub Docs

favicon

docs.github.com

favicon

cwe.mitre.org

Testing #

To test this rule:

# Detect hardcoded credentials
sisakulint .github/workflows/*.yml

# Apply auto-fix
sisakulint -fix on .github/workflows/*.yml

Configuration #

This rule is enabled by default. To disable it:

sisakulint -ignore credentials