Back to Cheat Sheets

πŸ“Š Insufficient Logging and Visibility

OWASP Top 10 CI/CD Security Risks - CICD-SEC-10

MEDIUM RISK

πŸ“‹ What Is It?

Insufficient Logging and Visibility describes a CI/CD environment that lacks the logging, monitoring, and detection needed to notice an attack in the pipeline. The vulnerability is not a misconfigured switch - it is the absence of telemetry and alerting that would let a defender see tampering, credential abuse, or misuse of the build system while it happens, or reconstruct it afterwards. The danger is not that an attack succeeds (every other CI/CD risk covers a way in) but that it proceeds undetected. Pipeline tampering, secret access, new tokens, and unusual deploys all leave traces; here, nobody collects, correlates, or watches them. It is a force multiplier for every other risk.

CICD-SEC-10 OWASP Rank
Detection Focus Area
Dwell Time The Harm

⚠️ Common Exploits

  • Audit Logging Disabled: Off, too coarse, or gated behind a higher tier
  • Logs Not Centralised: Each tool logs to its own console, nobody aggregates
  • No Correlation: Can't tie a commit to the build, token, and deploy
  • No Alerting: Config changes, secret reads, new tokens fire nothing
  • Short / No Retention: Runner logs vanish, CI logs rotate in days
  • Mutable Logs: Pipeline identities can edit or delete their own trail
  • No Runner Visibility: Exfiltration hides inside an ordinary build

πŸ”΄ Attack Flow

1. Enter via another risk (leaked token, poisoned PR)
↓
2. Act: change config, read secrets, mint tokens, deploy
↓
3. Blend: each action resembles routine automation
↓
4. Persist / exfiltrate from runners, ship a backdoor
↓
5. IMPACT: Objective achieved undetected - found later, by an outsider!

❌ Vulnerable Code

# GitHub org - audit log trapped in the UI, key events uncaptured org: audit_log_streaming: disabled ← events stay in the console, never exported # Attacker disables branch protection at 3am - no alert fires # GitLab CI - only mutable job console output exists ci_cd: audit_events: disabled ← variable reads, runner registration invisible # Registry - mutable tags, no push logging: a released tag silently replaced registry: immutable_tags: false audit: off # AWS - single-region trail, no integrity validation, editable logs resource "aws_cloudtrail" "main" { is_multi_region_trail = false ← other regions blind enable_log_file_validation = false ← logs alterable undetectably } # SIEM - nothing forwarded, no correlation, no alerts (collection β‰  detection) siem: { sources: [], correlation: none, alerts: [] } # Runner - persistent, allow-all egress; a step can curl secrets anywhere runner: { type: shared-persistent, egress: allow-all, host_monitoring: none }

βœ… Secure Code

# GitHub org - stream the audit log off-host, capture high-signal events org: audit_log_streaming: { enabled: true, destination: s3://acme-ci-audit-logs } captured_events: [repo.branch_protection.destroy, org.update_member, deploy_key.create, personal_access_token.create, workflows.updated] # Registry - immutable tags; log every mutation and publish-token change registry: immutable_tags: true ← a released tag can never be overwritten audit: { enabled: true, events: [image_push, tag_mutation_attempt, new_publish_token] } # AWS - multi-region, integrity-validated trail, object-locked, separate account resource "aws_cloudtrail" "main" { is_multi_region_trail = true is_organization_trail = true enable_log_file_validation = true ← tamper-evident } # SIEM - all sources normalised, correlated by trace_id, alerting on risk events siem: sources: [github_audit, gitlab_ci, registry, secrets_manager, cloudtrail, runners] correlation: { join_on: trace_id } # commitβ†’buildβ†’secret_readβ†’pushβ†’deploy alerts: [pipeline_config_change, new_pipeline_identity, secret_read_anomaly, off_hours_or_out_of_band_deploy, fork_pr_workflow_run, protection_rule_change] # Runner - ephemeral, default-deny egress, host + network telemetry to SIEM runner: { type: ephemeral-single-use, egress: { default: deny, allow: [...] } }

βœ“ Prevention Checklist

  • Enable audit logging across SCM, CI, registry, secrets, cloud
  • Confirm the audit tier is actually available on your plan
  • Centralise every tool's log stream into one SIEM (forward, don't just store)
  • Propagate a correlation/trace ID through every pipeline run
  • Alert on pipeline-config changes and workflow-file edits
  • Alert on secret reads, new tokens/keys/service accounts
  • Alert on permission/protection-rule changes
  • Alert on off-hours, out-of-band, and fork-PR runs
  • Store logs off-host, write-once (WORM), long retention
  • Monitor runner processes and network egress; default-deny
  • Baseline normal pipeline behaviour to surface anomalies
  • Wire pipeline alerts into the incident-response process

πŸ” Detection & Tools

Logging & SIEM:

SCM audit-log streaming AWS CloudTrail (validated) Jenkins Audit Trail plugin Splunk / Elastic / SIEM S3 Object Lock (WORM)

Runner & Anomaly:

Falco (runtime) StepSecurity Harden-Runner (egress) Host EDR Ephemeral runners Behavioural baselines

How to Test:

  • Confirm SCM/CI/registry audit logs stream to a central platform
  • Trace one commit β†’ build β†’ secret use β†’ deploy end to end
  • Edit a workflow file; confirm an alert fires with the identity
  • Read a secret from a job; confirm the read is logged
  • Verify logs are off-host, immutable, and long-retained
  • Send runner egress to a novel host; confirm it's flagged

🌍 Real-World Incident Classes

  • Supply-chain build compromise discovered downstream: An attacker modifies the build process so published artifacts are backdoored while source looks clean; the tampering is frequently discovered not by the origin org's own monitoring but by an external party noticing anomalous behaviour in the shipped product - and insufficient build/runner logging meant the producer could not detect it or reconstruct which builds were affected.
  • Stolen CI/CD token used undetected: A leaked long-lived token or deploy key clones private repos and pushes changes; with no token-use monitoring or access baseline, the abuse continues until the attacker acts overtly or a third party reports it.
  • Poisoned fork-PR workflow: A fork PR triggers a workflow running attacker-influenced code with secret access; with no fork-run alerting or runner egress monitoring, the malicious job's outbound connection blends into ordinary CI traffic.

Note: these are described as classes of real events, not attributed breaches with invented figures. Across all of them the root cause is the same - too little trustworthy, centralised, correlated telemetry for anyone to notice the attack or reconstruct it.

πŸ“Œ Quick Tips

  • DO NOT treat build/job logs as an audit trail
  • DO NOT leave logs where the pipeline can edit them
  • DO NOT collect without correlating and alerting
  • DO enable audit logging across the whole toolchain
  • DO centralise and correlate by trace ID
  • DO monitor runner process and network egress
  • DO baseline normal and alert on off-hours activity

πŸ“œ Compliance

Related Standards:

  • SOC 2 CC7.2/CC7.3 - Monitoring & detection
  • NIST 800-53 AU family - Audit & Accountability
  • PCI-DSS Req 10 - Logging & monitoring
  • SSDF (SP 800-218) - Provenance & audit
  • SLSA Auditable build records