DevToolBoxGRATIS
Blog

GitHub Actions Secrets e Sicurezza: Ambienti, OIDC e Best Practices

12 mindi DevToolBox

GitHub Actions secrets allow you to store sensitive data — API keys, passwords, tokens, certificates — securely in your repository settings and use them in workflows without exposing them in your code. This guide covers everything from basic secret usage to advanced patterns like OIDC federation, environment-scoped secrets, and automated rotation.

Basic Secret Usage

Secrets are defined in your repository settings (Settings > Secrets and variables > Actions) and referenced in workflows using the secrets context. They are automatically masked in workflow logs.

# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Deploy to server
        env:
          # Reference a repository secret
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
          API_TOKEN: ${{ secrets.API_TOKEN }}
        run: |
          echo "Deploying with secure credentials..."
          ./deploy.sh

Environment-Scoped Secrets

GitHub Environments let you define secrets that are specific to a deployment environment (staging, production) and add protection rules like required reviewers.

# Environment-specific secrets (staging vs production)
name: Deploy

on:
  push:
    branches: [main, staging]

jobs:
  deploy:
    runs-on: ubuntu-latest
    # Pick environment based on branch
    environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}

    steps:
      - uses: actions/checkout@v4

      - name: Deploy
        env:
          # These come from the selected environment's secrets
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        run: ./deploy.sh --env ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}

OIDC Federation: No Long-Lived Secrets

The best practice in 2026 is to use OIDC (OpenID Connect) federation to authenticate with cloud providers instead of storing long-lived access keys. GitHub Actions can exchange a JWT token for temporary cloud credentials.

# OIDC: No long-lived credentials needed!
name: Deploy to AWS (OIDC)

on:
  push:
    branches: [main]

permissions:
  id-token: write   # Required for OIDC
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
          aws-region: us-east-1
          # No ACCESS_KEY or SECRET_KEY secrets needed!

      - name: Deploy to S3
        run: aws s3 sync ./dist s3://my-bucket --delete

Secret Masking and Safe Usage

GitHub automatically masks secret values in logs, but there are important caveats to understand to avoid accidental exposure.

# Secrets are automatically masked in logs
# But be careful with derived values!

jobs:
  careful:
    runs-on: ubuntu-latest
    steps:
      - name: Use secret safely
        env:
          MY_SECRET: ${{ secrets.MY_SECRET }}
        run: |
          # This output will be masked: ***
          echo "Secret value: $MY_SECRET"

          # DANGER: This may expose the secret!
          # If MY_SECRET="abc", base64 gives "YWJj" which is NOT masked
          echo $MY_SECRET | base64

          # Safe: always use secrets directly, not derived forms
          curl -H "Authorization: Bearer $MY_SECRET" https://api.example.com/endpoint

      - name: Add to mask (for dynamic secrets)
        run: |
          # Manually mask a dynamically generated value
          TOKEN=$(generate-token.sh)
          echo "::add-mask::${TOKEN}"
          echo "TOKEN=${TOKEN}" >> $GITHUB_ENV

Secrets in Reusable Workflows

When using reusable workflows (workflow_call), secrets must be explicitly passed through. They are not inherited automatically.

# .github/workflows/reusable-deploy.yml
name: Reusable Deploy Workflow

on:
  workflow_call:
    secrets:
      DEPLOY_KEY:
        required: true
      DATABASE_URL:
        required: true
      SLACK_WEBHOOK:
        required: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Deploy
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
        run: ./deploy.sh

      - name: Notify Slack
        if: ${{ secrets.SLACK_WEBHOOK != '' }}
        run: |
          curl -X POST -H 'Content-type: application/json' \
            --data '{"text":"Deployment complete!"}' \
            ${{ secrets.SLACK_WEBHOOK }}

# .github/workflows/production.yml — caller
name: Production Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    uses: ./.github/workflows/reusable-deploy.yml
    secrets:
      DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
      DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }}
      SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

Automated Secret Rotation

Long-lived secrets are a security risk. Set up automated rotation using scheduled workflows.

# Automated secret rotation pattern
name: Rotate API Token

on:
  schedule:
    - cron: '0 0 1 * *'  # Monthly rotation
  workflow_dispatch:       # Manual trigger

jobs:
  rotate:
    runs-on: ubuntu-latest
    steps:
      - name: Generate new token
        id: generate
        env:
          OLD_TOKEN: ${{ secrets.API_TOKEN }}
        run: |
          # Call API to generate new token using old token
          NEW_TOKEN=$(curl -s -X POST \
            -H "Authorization: Bearer $OLD_TOKEN" \
            https://api.example.com/tokens/rotate | jq -r '.token')
          echo "::add-mask::${NEW_TOKEN}"
          echo "new_token=${NEW_TOKEN}" >> $GITHUB_OUTPUT

      - name: Update GitHub secret
        uses: gliech/create-github-secret-action@v1
        with:
          name: API_TOKEN
          value: ${{ steps.generate.outputs.new_token }}
          github_token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}

Secret Storage Options Comparison

OptionScopeVisibilityRotationCostBest For
Repository SecretsSingle repoRepo adminsManualFreeSimple projects
Environment SecretsSingle repo + envRepo adminsManualFreeMulti-stage deploys
Organization SecretsMultiple reposOrg adminsManualFreeShared credentials
OIDC FederationSingle repoNo stored secretAutomaticFreeCloud deployments
HashiCorp VaultAnyConfigurableAutomaticPaidEnterprise

Security Best Practices

  1. Use OIDC federation for cloud providers (AWS, GCP, Azure) instead of static access keys whenever possible.
  2. Scope secrets to environments. Production secrets should require environment protection rules (required reviewers, deployment branches).
  3. Never print secrets in workflow steps, even for debugging. GitHub masks the exact string, but encoding/transformations bypass masking.
  4. Rotate secrets regularly. Set calendar reminders or use automated rotation workflows for API keys.
  5. Use the minimum required permissions. Create service accounts with narrow scopes specifically for GitHub Actions.
  6. Audit secret access via GitHub's audit log. Review which workflows are using which secrets.

Frequently Asked Questions

Can forked repositories access secrets?

No. By default, secrets are not passed to workflows triggered by pull requests from forks. This prevents forked repos from exfiltrating your secrets. For public repos, you can configure which events allow secrets for external contributors, but this requires explicit approval.

What is the difference between repository secrets and organization secrets?

Repository secrets are only available to workflows in that specific repository. Organization secrets can be shared across multiple repositories and have a policy setting that controls which repos can access them (all repos, selected repos, or only private repos).

How many secrets can I store?

GitHub allows up to 100 secrets per repository and 1,000 per organization. Each secret can be at most 64 KB in size. For larger secrets (like certificates), consider storing a base64-encoded version and decoding it in the workflow.

Can I read a secret value after it is stored?

No. Once a secret is created, its value cannot be read through the GitHub UI or API. You can only update or delete it. This is by design — if you lose track of a secret, create a new one and rotate the credentials.

How do I pass secrets between jobs in a workflow?

Secrets cannot be directly passed between jobs as job outputs (outputs are visible in logs). Instead, re-reference the secret in each job that needs it using secrets.MY_SECRET, or generate a temporary credential in one job and pass it through encrypted artifact storage using GPG.

Related Tools

𝕏 Twitterin LinkedIn
È stato utile?

Resta aggiornato

Ricevi consigli dev e nuovi strumenti ogni settimana.

Niente spam. Cancella quando vuoi.

Prova questi strumenti correlati

#Hash GeneratorB→Base64 Encode Online🔐JWT Generator

Articoli correlati

Strategie di branching Git: GitFlow vs Trunk-Based vs GitHub Flow

Confronto tra strategie GitFlow, Trunk-Based Development e GitHub Flow. Strutture di branch, workflow di merge, integrazione CI/CD e scelta della strategia giusta.

Kubernetes per principianti: Tutorial completo (2026)

Impara Kubernetes da zero. Pod, Servizi, Deployment e altro.