CI/CD provider
Secret leak in pipeline configuration
Control: Pipeline must not leak secrets in configuration · Config key: pipelineMustNotLeakSecretsInConfig
📋 What is this?
The resolved pipeline configuration contains a pattern that matches a hardcoded secret (API token, private key, password, or other credential) embedded directly in the YAML. Plumber resolves the full merged .gitlab-ci.yml (with every include: followed) and pipes the result through [gitleaks](https://gitleaks.io); any high-confidence match against the built-in rule catalog — or a custom rule set provided via gitleaksConfigPath — surfaces as an ISSUE-301 finding. The detected value never leaves the scanner: each finding's preview carries a redacted form with the first and last four characters visible and the middle replaced with asterisks.
⚠️ Impact
Secrets committed to pipeline configuration are exposed to everyone with read access to the repository, appear in version history forever (rotation is the only fix, not deletion), and are forwarded to every runner that executes the pipeline. A leaked API key can enable unauthorised access to cloud services, data exfiltration, billing abuse, or lateral movement into production systems.
🔧 How to fix
Revoke and rotate the exposed secret immediately. Remove it from the YAML, then inject it securely as a masked, protected CI/CD variable (Settings > CI/CD > Variables) and reference it as $MY_SECRET in the pipeline. For production workloads, prefer an external secrets manager (HashiCorp Vault, AWS Secrets Manager, Doppler) over GitLab-managed variables.
# .gitlab-ci.yml — ❌ Hardcoded secrets (CRITICAL)deploy: stage: deploy script: - export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE - export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY - aws s3 sync . s3://my-bucket
api-call: variables: API_TOKEN: "ghp_exampleTokenHardcodedHere123" script: - curl -H "Authorization: token $API_TOKEN" https://api.example.com# .gitlab-ci.yml — ✅ Secrets injected via CI/CD variablesdeploy: stage: deploy script: # AWS credentials injected from protected CI/CD variables - aws s3 sync . s3://my-bucket
api-call: script: - curl -H "Authorization: token $API_TOKEN" https://api.example.com
# In GitLab: Settings > CI/CD > Variables# Add: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, API_TOKEN# Set Protected: true, Masked: true for each
# .plumber.yamlgitlab: controls: pipelineMustNotLeakSecretsInConfig: enabled: true # gitleaksPath: /usr/local/bin/gitleaks # default: $PATH # gitleaksConfigPath: .gitleaks.toml # default: built-in rules💡 Tips
- Detection is opt-in: the control requires [gitleaks](https://github.com/gitleaks/gitleaks) on
$PATH(or setgitleaksPath). If the binary is missing, plumber emits a warning and the control routes through the SKIPPED lane rather than failing the run or silently passing. - If a secret was ever committed, treat it as compromised and rotate it immediately — deletion does not remove it from history.
- Use a custom
.gitleaks.tomlviagitleaksConfigPathto narrow rules to your stack, or to allowlist legitimate matches (e.g. dummy values in test pipelines). - Pair with pre-commit hooks (
gitleaks protect --staged) so the next leak is caught before it lands in git.
⚙️ Configuration
This control is configured in .plumber.yaml under the gitlab section:
gitlab:
controls:
pipelineMustNotLeakSecretsInConfig:
enabled: trueSee the CLI documentation for the full configuration reference.
Secret leak in pipeline configuration
Control: Workflow must not leak secrets in configuration · Config key: pipelineMustNotLeakSecretsInConfig
📋 What is this?
A file under .github/workflows/ contains a pattern that matches a hardcoded secret (API token, private key, password, or other credential) embedded directly in the YAML. Plumber pipes every workflow file through [gitleaks](https://gitleaks.io); any high-confidence match against the built-in rule catalog — or a custom rule set provided via gitleaksConfigPath — surfaces as an ISSUE-301 finding. The detected value never leaves the scanner: each finding's preview carries a redacted form with the first and last four characters visible and the middle replaced with asterisks.
⚠️ Impact
Secrets committed to workflow files are exposed to every collaborator, every fork, and the entire commit history. Public repositories make the leak instantly indexable by attacker tooling; private repositories still expose it to every member of the org and every workflow that runs against the repo. Rotation is the only fix.
🔧 How to fix
Revoke and rotate the exposed secret immediately. Remove the literal from the workflow YAML, then inject it via repository or environment secrets and reference it as ${{ secrets.MY_SECRET }}. Scope the reference to the smallest step that needs it; avoid passing it through job-level env: if a single step suffices.
# .github/workflows/release.yml — ❌ Hardcoded token (CRITICAL)jobs: publish: runs-on: ubuntu-latest env: SLACK_WEBHOOK: xoxb-EXAMPLE-EXAMPLE-redactedfortestingonly STRIPE_KEY: "sk_test_REDACTED_DOC_EXAMPLE_NOT_A_REAL_KEY" steps: - run: ./scripts/publish.sh# .github/workflows/release.yml — ✅ Repository / environment secretsjobs: publish: runs-on: ubuntu-latest steps: - env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} STRIPE_KEY: ${{ secrets.STRIPE_KEY }} run: ./scripts/publish.sh
# In GitHub: Settings > Secrets and variables > Actions# Add: SLACK_WEBHOOK, STRIPE_KEY (repository or environment-scoped)
# .plumber.yamlgithub: controls: pipelineMustNotLeakSecretsInConfig: enabled: true # gitleaksPath: /usr/local/bin/gitleaks # default: $PATH # gitleaksConfigPath: .gitleaks.toml # default: built-in rules💡 Tips
- Detection is opt-in: the control requires [gitleaks](https://github.com/gitleaks/gitleaks) on
$PATH(or setgitleaksPath). When the binary is missing or the scan fails, plumber emits a warning and routes the control through the SKIPPED lane rather than silently passing. - Public repos are scanned by gitleaks-as-a-service on every push by GitHub itself, but that is detection-after-the-fact — this control catches the leak before it merges.
- Use a custom
.gitleaks.tomlviagitleaksConfigPathto allowlist synthetic test values (the slack/stripe patterns in plumber's own scenario battery are deliberately allowed in a local scratch config). - Pair with pre-commit hooks (
gitleaks protect --staged) and GitHub's push-protection rules so the next leak is caught before it lands on a branch.
⚙️ Configuration
This control is configured in .plumber.yaml under the github section:
github:
controls:
pipelineMustNotLeakSecretsInConfig:
enabled: trueSee the CLI documentation for the full configuration reference.