Detection rules › Splunk

Circle CI Disable Security Step

Status
experimental
Severity
low
Group by
"owners{}", "vcs.committer_name", "vcs.subject", "vcs.url", job_id, job_name, step_names
Author
Patrick Bareiss, Splunk
Source
github.com/splunk/security_content

The following analytic detects the disablement of security steps in a CircleCI pipeline. It leverages CircleCI logs, using field renaming, joining, and statistical analysis to identify instances where mandatory security steps are not executed. This activity is significant because disabling security steps can introduce vulnerabilities, unauthorized changes, or malicious code into the pipeline. If confirmed malicious, this could lead to potential attacks, data breaches, or compromised infrastructure. Investigate by reviewing job names, commit details, and user information associated with the disablement, and examine any relevant artifacts and concurrent processes.

MITRE ATT&CK coverage

TacticTechniques
PersistenceT1554 Compromise Host Software Binary

Rule body splunk

name: Circle CI Disable Security Step
id: 72cb9de9-e98b-4ac9-80b2-5331bba6ea97
version: 9
creation_date: '2021-09-01'
modification_date: '2026-05-13'
author: Patrick Bareiss, Splunk
status: experimental
type: Anomaly
description: The following analytic detects the disablement of security steps in a CircleCI pipeline. It leverages CircleCI logs, using field renaming, joining, and statistical analysis to identify instances where mandatory security steps are not executed. This activity is significant because disabling security steps can introduce vulnerabilities, unauthorized changes, or malicious code into the pipeline. If confirmed malicious, this could lead to potential attacks, data breaches, or compromised infrastructure. Investigate by reviewing job names, commit details, and user information associated with the disablement, and examine any relevant artifacts and concurrent processes.
data_source:
    - CircleCI
search: |-
    `circleci`
      | rename workflows.job_id AS job_id
      | join job_id [
      | search `circleci`
      | stats values(name) as step_names count
        BY job_id job_name ]
      | stats count
        BY step_names job_id job_name
           vcs.committer_name vcs.subject vcs.url
           owners{}
      | rename vcs.* as * , owners{} as user
      | lookup mandatory_step_for_job job_name OUTPUTNEW step_name AS mandatory_step
      | search mandatory_step=*
      | eval mandatory_step_executed=if(like(step_names, "%".mandatory_step."%"), 1, 0)
      | where mandatory_step_executed=0
      | rex field=url "(?<repository>[^\/]*\/[^\/]*)$"
      | eval phase="build"
      | `security_content_ctime(firstTime)`
      | `security_content_ctime(lastTime)`
      | `circle_ci_disable_security_step_filter`
how_to_implement: You must index CircleCI logs.
known_false_positives: No false positives have been identified at this time.
references: []
intermediate_findings:
    entities:
        - field: user
          type: user
          score: 20
          message: Disable security step $mandatory_step$ in job $job_name$ from user $user$
analytic_story:
    - Dev Sec Ops
asset_type: CircleCI
mitre_attack_id:
    - T1554
product:
    - Splunk Enterprise
    - Splunk Enterprise Security
    - Splunk Cloud
category: cloud
security_domain: network
tests:
    - name: True Positive Test
      attack_data:
        - data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1554/circle_ci_disable_security_step/circle_ci_disable_security_step.json
          sourcetype: circleci
          source: circleci
      test_type: experimental
      description: This test is a legacy experimental test and may not be accurate.

Stages and Predicates

Stage 1: search

`circleci`

Stage 2: rename

| rename workflows.job_id AS job_id

Stage 3: join

| join job_id [
  | search `circleci`
  | stats values(name) as step_names count
    BY job_id job_name ]

Stage 4: stats

| stats count
    BY step_names job_id job_name
       vcs.committer_name vcs.subject vcs.url
       owners{}

Stage 5: rename

| rename vcs.* as * , owners{} as user

Stage 6: lookup

| lookup mandatory_step_for_job job_name OUTPUTNEW step_name AS mandatory_step
Lookup table
mandatory_step_for_job
Key field
job_name
Output columns
['step_name', 'mandatory_step']

Stage 7: search

| search mandatory_step=*

Stage 8: eval

| eval mandatory_step_executed=if(like(step_names, "%".mandatory_step."%"), 1, 0)
mandatory_step_executed =
iflike(step_names, concat("%", mandatory_step, "%"))1
else0

Stage 9: where

| where mandatory_step_executed=0

Stage 10: rex

| rex field=url "(?<repository>[^\/]*\/[^\/]*)$"

Stage 11: eval

| eval phase="build"

Stage 12: search

| `security_content_ctime(firstTime)`

Stage 13: search

| `security_content_ctime(lastTime)`

Stage 14: search

| `circle_ci_disable_security_step_filter`

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.