claws-scan 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +31 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +17 -0
  6. data/Gemfile.lock +99 -0
  7. data/README.md +557 -0
  8. data/Rakefile +12 -0
  9. data/bin/analyze +62 -0
  10. data/config.yml +16 -0
  11. data/corpus/automerge_via_action.yml +28 -0
  12. data/corpus/automerge_via_cli.yml +14 -0
  13. data/corpus/build-docker-image-run-drc-for-cell-gds-using-magic.yml +170 -0
  14. data/corpus/cmd.yml +14 -0
  15. data/corpus/container.yml +19 -0
  16. data/corpus/container_docker.yml +9 -0
  17. data/corpus/dispatch_command_injection.yml +17 -0
  18. data/corpus/inherit_secrets.yml +20 -0
  19. data/corpus/nameless.yml +11 -0
  20. data/corpus/permissions.yml +19 -0
  21. data/corpus/ruby.yml +12 -0
  22. data/corpus/shellcheck.yml +12 -0
  23. data/corpus/unsafe_checkout_code_execution.yml +21 -0
  24. data/corpus/unsafe_checkout_token_leak.yml +33 -0
  25. data/corpus/unscoped_secrets.yml +16 -0
  26. data/github_action.yml +36 -0
  27. data/lib/claws/application.rb +237 -0
  28. data/lib/claws/base_rule.rb +94 -0
  29. data/lib/claws/cli/color.rb +30 -0
  30. data/lib/claws/cli/yaml_with_lines.rb +124 -0
  31. data/lib/claws/engine.rb +25 -0
  32. data/lib/claws/formatter/github.rb +17 -0
  33. data/lib/claws/formatter/stdout.rb +13 -0
  34. data/lib/claws/formatters.rb +4 -0
  35. data/lib/claws/rule/automatic_merge.rb +49 -0
  36. data/lib/claws/rule/bulk_permissions.rb +20 -0
  37. data/lib/claws/rule/command_injection.rb +14 -0
  38. data/lib/claws/rule/empty_name.rb +14 -0
  39. data/lib/claws/rule/inherited_secrets.rb +17 -0
  40. data/lib/claws/rule/no_containers.rb +28 -0
  41. data/lib/claws/rule/risky_triggers.rb +32 -0
  42. data/lib/claws/rule/shellcheck.rb +109 -0
  43. data/lib/claws/rule/special_permissions.rb +37 -0
  44. data/lib/claws/rule/unapproved_runners.rb +31 -0
  45. data/lib/claws/rule/unpinned_action.rb +30 -0
  46. data/lib/claws/rule/unsafe_checkout.rb +36 -0
  47. data/lib/claws/rule.rb +13 -0
  48. data/lib/claws/version.rb +5 -0
  49. data/lib/claws/violation.rb +11 -0
  50. data/lib/claws/workflow.rb +221 -0
  51. data/lib/claws.rb +6 -0
  52. metadata +151 -0
@@ -0,0 +1,14 @@
1
+ name: Automerge Non-code Changes
2
+ on:
3
+ push:
4
+ paths: ['**.txt']
5
+
6
+ permissions:
7
+ contents: read
8
+
9
+ jobs:
10
+ merge:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Merge pull request
14
+ run: gh pr merge ${{ steps.create_pull_request.outputs.pull-request-number }} --squash --auto --delete-branch
@@ -0,0 +1,170 @@
1
+ # Copyright 2021 SkyWater PDK Authors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #
15
+ # SPDX-License-Identifier: Apache 2.0
16
+
17
+ name: Build Docker Image for Run DRC for cell GDS (using Magic) Action
18
+
19
+ on:
20
+ workflow_dispatch:
21
+ push:
22
+ pull_request_target:
23
+
24
+
25
+ permissions:
26
+ contents: read
27
+
28
+
29
+ jobs:
30
+
31
+ # FIXME: Remove once GitHub Container Registry is working.
32
+ # docker.pkg.github.com doesn't support buildx built packages, use
33
+ # docker/build-push-action instead.
34
+ build-github-package:
35
+ name: "Building Docker GitHub Package."
36
+
37
+ runs-on: ubuntu-latest
38
+
39
+ permissions:
40
+ packages: write # ${{ github.event_name == "push" || github.event_name == "workflow_dispatch" }}
41
+
42
+ steps:
43
+ - name: Checkout code
44
+ uses: actions/checkout@v2
45
+ with:
46
+ # Always clone the full depth so git-describe works.
47
+ fetch-depth: 0
48
+ submodules: true
49
+
50
+ - name: Set Action Name
51
+ run: echo "ACTION_NAME=run-drc-for-cell-gds-using-magic" >> $GITHUB_ENV
52
+
53
+ - name: Build container image
54
+ uses: docker/build-push-action@v1
55
+ with:
56
+ registry: docker.pkg.github.com
57
+ username: ${{ github.repository_owner }}
58
+ password: ${{ github.token }}
59
+ repository: ${{ github.repository }}/${{ env.ACTION_NAME }}
60
+ path: ${{ env.ACTION_NAME }}
61
+ tag_with_ref: true
62
+ tag_with_sha: true
63
+ add_git_labels: true
64
+ push: ${{ startsWith(github.ref, 'refs/heads/') }}
65
+
66
+
67
+ build-docker-image:
68
+ name: "Building image."
69
+
70
+ runs-on: ubuntu-latest
71
+
72
+ # Run a local registry
73
+ services:
74
+ registry:
75
+ image: registry:2
76
+ ports:
77
+ - 5000:5000
78
+
79
+ steps:
80
+
81
+ - name: Dump context
82
+ uses: crazy-max/ghaction-dump-context@v1
83
+
84
+ - name: Checkout code
85
+ uses: actions/checkout@v2
86
+ with:
87
+ # Always clone the full depth so git-describe works.
88
+ fetch-depth: 0
89
+ submodules: true
90
+
91
+ - name: Set Action Name
92
+ run: echo "ACTION_NAME=run-drc-for-cell-gds-using-magic" >> $GITHUB_ENV
93
+
94
+ - name: Detect Push To Config
95
+ id: push_to
96
+ shell: python
97
+ env:
98
+ HAS_GCR_JSON_KEY: ${{ !!(secrets.GCR_JSON_KEY) }}
99
+ run: |
100
+ import os
101
+ gh_event = os.environ['GITHUB_EVENT_NAME']
102
+
103
+ i = []
104
+ print("Adding local service.")
105
+ i.append("localhost:5000/${{ env.ACTION_NAME }}")
106
+
107
+ if "${{ env.HAS_GCR_JSON_KEY }}":
108
+ print("Adding Google Container Repository (gcr.io)")
109
+ i.append("gcr.io/skywater-pdk/actions/${{ env.ACTION_NAME }}")
110
+
111
+ #print("Adding GitHub Container Repository (ghcr.io)")
112
+ #i.append("ghcr.io/${{ github.repository }}/${{ env.ACTION_NAME }}")
113
+
114
+ l = ",".join(i)
115
+ print("Final locations:", repr(l))
116
+ print("::set-output name=images::{}".format(l))
117
+
118
+ - name: Docker meta
119
+ id: docker_meta
120
+ uses: docker/metadata-action@v3
121
+ with:
122
+ images: ${{ steps.push_to.outputs.images }}
123
+ tags: |
124
+ type=ref,event=tag
125
+ type=ref,event=pr
126
+ type=ref,event=branch
127
+ type=sha
128
+ type=sha,format=long
129
+
130
+ - name: Set up QEMU
131
+ uses: docker/setup-qemu-action@v1
132
+
133
+ - name: Set up Docker Buildx
134
+ uses: docker/setup-buildx-action@v1
135
+ with:
136
+ driver-opts: network=host
137
+
138
+ - name: Login to Google Container Registry
139
+ if: ${{ contains(steps.push_to.outputs.images, 'gcr.io') }}
140
+ uses: docker/login-action@v1
141
+ with:
142
+ registry: gcr.io
143
+ username: _json_key
144
+ password: ${{ secrets.GCR_JSON_KEY }}
145
+
146
+ - name: Login to GitHub Container Registry
147
+ if: ${{ contains(steps.push_to.outputs.images, 'ghcr.io') }}
148
+ uses: docker/login-action@v1
149
+ with:
150
+ username: ${{ github.repository_owner }}
151
+ password: ${{ github.token }}
152
+ registry: ghcr.io
153
+
154
+ - name: Build and push
155
+ uses: docker/build-push-action@v2
156
+ id: docker_build
157
+ with:
158
+ context: ${{ env.ACTION_NAME }}
159
+ file: ${{ env.ACTION_NAME }}/Dockerfile
160
+ push: true
161
+ tags: |
162
+ ${{ steps.docker_meta.outputs.tags }}
163
+ localhost:5000/${{ env.ACTION_NAME }}:latest
164
+ labels: ${{ steps.docker_meta.outputs.labels }}
165
+
166
+ - name: Inspect
167
+ run: docker buildx imagetools inspect localhost:5000/${{ env.ACTION_NAME }}:latest
168
+
169
+ - name: Image digest
170
+ run: echo ${{ steps.docker_build.outputs.digest }}
data/corpus/cmd.yml ADDED
@@ -0,0 +1,14 @@
1
+ # INSECURE
2
+
3
+ on: issue_comment
4
+ name: IssueOps - Demo
5
+ jobs:
6
+ act-on-issue:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - name: Checkout
10
+ uses: actions/checkout@v1
11
+ - name: Reset demo if a demo or reset issue was opened
12
+ run: ./scripts/reset-demo.sh "${{ github.event.issue.body }}" "${{ github.event.issue.number }}"
13
+ env:
14
+ GITHUB_COM_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,19 @@
1
+ name: CI
2
+ on:
3
+ push:
4
+ branches: [ main ]
5
+ jobs:
6
+ container-test-job:
7
+ runs-on: ubuntu-latest
8
+ container:
9
+ image: node:14.16
10
+ env:
11
+ NODE_ENV: development
12
+ ports:
13
+ - 80
14
+ volumes:
15
+ - my_docker_volume:/volume_mount
16
+ options: --cpus 1
17
+ steps:
18
+ - name: Check for dockerenv file
19
+ run: (ls /.dockerenv && echo Found dockerenv) || (echo No dockerenv)
@@ -0,0 +1,9 @@
1
+ name: CI
2
+ on:
3
+ push:
4
+ branches: [ main ]
5
+ jobs:
6
+ use_image:
7
+ steps:
8
+ - name: My first step
9
+ uses: docker://alpine:3.8
@@ -0,0 +1,17 @@
1
+ name: Dispatch Me
2
+ on:
3
+ workflow_dispatch:
4
+ inputs:
5
+ name:
6
+ description: 'Who I should say hello to'
7
+ required: true
8
+
9
+ jobs:
10
+ greet:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Checkout
14
+ uses: actions/checkout@v1
15
+ - name: Reset demo if a demo or reset issue was opened
16
+ run: ./scripts/greet.sh "${{ github.event.inputs.name }}"
17
+
@@ -0,0 +1,20 @@
1
+ on: [workflow_call]
2
+ name: yea
3
+ jobs:
4
+ rake:
5
+ runs-on: ubuntu-latest
6
+ secrets: inherit
7
+ steps:
8
+ - name: Checkout
9
+ uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
10
+ with:
11
+ ref: ${{ github.event.pull_request.head.sha }}
12
+ # ignore: CommandInjection
13
+ - name: test
14
+ run: /bin/ls ${{ github.event.test }}
15
+ - name: Build
16
+ run: rake
17
+ env:
18
+ GITHUB_TOKEN: ${{ github.token }}
19
+ YOINK: ${{ secrets.FLAG }}
20
+
@@ -0,0 +1,11 @@
1
+ on: [push, pull_request, pull_request_target]
2
+ jobs:
3
+ test:
4
+ runs-on: ubuntu-latest
5
+ steps:
6
+ - uses: actions/checkout@v3
7
+ - uses: ruby/setup-ruby@v1
8
+ with:
9
+ ruby-version: '3.0' # Not needed with a .ruby-version file
10
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
11
+ - run: bundle exec rake
@@ -0,0 +1,19 @@
1
+ name: Deploy
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ permissions:
9
+ packages: write
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+ permissions:
15
+ packages: write
16
+ steps:
17
+ - uses: action/checkout@v3
18
+ - name: push
19
+ run: rake release
data/corpus/ruby.yml ADDED
@@ -0,0 +1,12 @@
1
+ name: My workflow
2
+ on: [push, pull_request, pull_request_target]
3
+ jobs:
4
+ test:
5
+ runs-on: ubuntu-latest
6
+ steps:
7
+ - uses: actions/checkout@v3
8
+ - uses: ruby/setup-ruby@v1
9
+ with:
10
+ ruby-version: '3.0' # Not needed with a .ruby-version file
11
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
12
+ - run: bundle exec rake
@@ -0,0 +1,12 @@
1
+ on: [push, pull_request, pull_request_target]
2
+ jobs:
3
+ test:
4
+ runs-on: ubuntu-latest
5
+ steps:
6
+ - uses: actions/checkout@v3
7
+ - uses: ruby/setup-ruby@v1
8
+ - run: |
9
+ x=$(ls -lah)
10
+ if [[ $x == 2 ]]; then
11
+ echo $x
12
+ fi
@@ -0,0 +1,21 @@
1
+ name: Unsafe Checkout that Leads to RCE
2
+
3
+ on: [pull_request_target]
4
+
5
+ jobs:
6
+ build:
7
+ name: Build
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ # check out the attacker controlled branch with their code
11
+ - uses: actions/checkout@v2
12
+ with:
13
+ ref: ${{ github.event.pull_request.head.sha }}
14
+
15
+ # set up the environment and run specs
16
+ # because Rakefile comes from the attacker's branch
17
+ # we end up executing their code, even though they don't
18
+ # control the command here
19
+ - run: |
20
+ rake setup
21
+ rake spec
@@ -0,0 +1,33 @@
1
+ name: Unsafe Checkout that can Leak Tokens
2
+
3
+ on: pull_request_target
4
+
5
+ jobs:
6
+ release:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ # check out the attacker controlled branch
10
+ - name: Checkout (depth 0)
11
+ uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
12
+ with:
13
+ ref: ${{ github.event.pull_request.head.sha }}
14
+
15
+ # grab the version number from the VERSION file
16
+ # however... because we're getting the contents of the file
17
+ # from the attacker's branch, and because git allows symlinks
18
+ # the attacker can symlink VERSION to any other file on the system
19
+ # to leak its contents.
20
+ - name: Get PR Version
21
+ id: version_number
22
+ run: echo "::set-output name=version::$(cat VERSION)"
23
+
24
+ # Dump the version number into a Github comment for everyone to see
25
+ - name: Comment the new version
26
+ uses: peter-evans/create-or-update-comment@v2
27
+ with:
28
+ issue-number: ${{ github.event.pull_request.number }}
29
+ comment-author: 'github-actions[bot]'
30
+ body: |
31
+ Version was updated to
32
+ ```${{ steps.version_number.outputs.version }}```
33
+ bye now...
@@ -0,0 +1,16 @@
1
+ on: [pull_request]
2
+ name: yea
3
+ jobs:
4
+ rake:
5
+ runs-on: ubuntu-latest
6
+ steps:
7
+ - name: Checkout
8
+ uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
9
+ with:
10
+ ref: ${{ github.event.pull_request.head.sha }}
11
+ - name: Build
12
+ run: rake
13
+ env:
14
+ GITHUB_TOKEN: ${{ github.token }}
15
+ YOINK: ${{ secrets.API_KEY }}
16
+
data/github_action.yml ADDED
@@ -0,0 +1,36 @@
1
+ name: Workflow Static Analyzer
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - main
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+ name: Static Analyze
12
+ steps:
13
+ - name: Set Up Ruby
14
+ uses: ruby/setup-ruby@v1
15
+ with:
16
+ ruby-version: '3.0'
17
+ - uses: actions/checkout@v3
18
+ with:
19
+ fetch-depth: 0
20
+ - name: Get PR diff Files
21
+ uses: technote-space/get-diff-action@v5
22
+ id: modified_actions
23
+ with:
24
+ PATTERNS: .github/workflows/*.y*ml
25
+ - name: Set Up Claws
26
+ run: |
27
+ gem install --source "https://${{ secrets.BETTERMENT_GH_PACKAGES_PAT }}@rubygems.pkg.github.com/betterment" claws --version "0.1.4"
28
+ - name: Analyze New/Changed Workflows
29
+ run: |
30
+ bungler_flags=""
31
+ for workflow in ${{ env.GIT_DIFF }}
32
+ do
33
+ echo "$workflow"
34
+ bungler_flags="-t $workflow $bungler_flags"
35
+ done
36
+ analyze -f github -c .claws-config.yml $bungler_flags
@@ -0,0 +1,237 @@
1
+ module Claws
2
+ class Application
3
+ def initialize
4
+ @detections = []
5
+ end
6
+
7
+ def load_detection(detection)
8
+ @detections << detection
9
+ end
10
+
11
+ def analyze(filename, raw_contents)
12
+ workflow = Workflow.load(raw_contents)
13
+
14
+ file_ignores = workflow.ignores
15
+
16
+ # enrich violations with snippets
17
+ # skip violations if specifically ignored
18
+ get_violations(filename, workflow).reject do |v|
19
+ v.snippet = workflow.get_snippet(v.line)
20
+
21
+ line_above = [1, v.line - 1].max
22
+ ignores_for_line = file_ignores.fetch(line_above, [])
23
+ ignores_for_line.include? v.name
24
+ end
25
+ end
26
+
27
+ def get_violations(filename, workflow)
28
+ violations = get_workflow_violations(filename, workflow)
29
+
30
+ workflow.jobs.each do |_job_name, job|
31
+ violations += get_job_violations(filename, workflow, job)
32
+
33
+ job.fetch("steps", []).each do |step|
34
+ violations += get_step_violations(filename, workflow, job, step)
35
+ end
36
+ end
37
+
38
+ violations
39
+ end
40
+
41
+ def get_workflow_violations(filename, workflow)
42
+ violations = []
43
+ @detections.each do |detection|
44
+ detection.on_workflow.each do |rule|
45
+ violation = run_detection(
46
+ filename: filename,
47
+ detection: detection,
48
+ rule: rule,
49
+ workflow: workflow
50
+ )
51
+
52
+ violations << violation if violation
53
+
54
+ next if rule.is_a? Symbol or !rule[:debug]
55
+
56
+ enter_debug(
57
+ result: !violation.nil?,
58
+ expression: rule[:expression],
59
+ values: {
60
+ data: detection.data,
61
+ workflow: workflow
62
+ }
63
+ )
64
+ end
65
+ end
66
+
67
+ violations
68
+ end
69
+
70
+ def get_job_violations(filename, workflow, job)
71
+ violations = []
72
+ @detections.each do |detection|
73
+ detection.on_job.each do |rule|
74
+ violation = run_detection(
75
+ filename: filename,
76
+ detection: detection,
77
+ rule: rule,
78
+ workflow: workflow,
79
+ job: job
80
+ )
81
+
82
+ violations << violation if violation
83
+
84
+ next if rule.is_a? Symbol or !rule[:debug]
85
+
86
+ enter_debug(
87
+ result: !violation.nil?,
88
+ expression: rule[:expression],
89
+ values: {
90
+ data: detection.data,
91
+ workflow: workflow,
92
+ job: job
93
+ }
94
+ )
95
+ end
96
+ end
97
+
98
+ violations
99
+ end
100
+
101
+ def get_step_violations(filename, workflow, job, step)
102
+ violations = []
103
+
104
+ @detections.each do |detection|
105
+ detection.on_step.each do |rule|
106
+ violation = run_detection(
107
+ filename: filename,
108
+ detection: detection,
109
+ rule: rule,
110
+ workflow: workflow,
111
+ job: job,
112
+ step: step
113
+ )
114
+
115
+ violations << violation if violation
116
+
117
+ next if rule.is_a? Symbol or !rule[:debug]
118
+
119
+ enter_debug(
120
+ result: !violation.nil?,
121
+ expression: rule[:expression],
122
+ values: {
123
+ data: detection.data,
124
+ workflow: workflow,
125
+ job: job,
126
+ step: step
127
+ }
128
+ )
129
+ end
130
+ end
131
+
132
+ violations
133
+ end
134
+
135
+ private
136
+
137
+ def run_detection(filename:, detection:, rule:, workflow:, job: nil, step: nil) # rubocop:disable Metrics/ParameterLists
138
+ violation = if rule.is_a? Symbol
139
+ get_dynamic_violation(
140
+ detection: detection,
141
+ method: rule,
142
+ workflow: workflow,
143
+ job: job,
144
+ step: step
145
+ )
146
+ else
147
+ get_static_violations(
148
+ detection: detection,
149
+ rule: rule,
150
+ workflow: workflow,
151
+ job: job,
152
+ step: step
153
+ )
154
+ end
155
+
156
+ if violation
157
+ violation.file = filename
158
+ violation.name = detection.name
159
+ end
160
+
161
+ violation
162
+ end
163
+
164
+ def get_dynamic_violation(detection:, method:, workflow:, job:, step:)
165
+ detection.send(
166
+ method,
167
+ workflow: workflow,
168
+ job: job,
169
+ step: step
170
+ )
171
+ end
172
+
173
+ def get_static_violations(rule:, detection:, workflow:, job:, step:)
174
+ result = rule[:expression].eval_with(values: {
175
+ data: detection.data,
176
+ workflow: workflow,
177
+ job: job,
178
+ step: step
179
+ })
180
+
181
+ return unless result
182
+
183
+ default_target = [step, job, workflow].find(&:itself)
184
+ line_number = default_target.line
185
+ line_number = get_nearest_key(default_target, rule[:highlight]).line if rule[:highlight]
186
+
187
+ Violation.new(
188
+ line: line_number,
189
+ description: detection.description
190
+ )
191
+ end
192
+
193
+ def enter_debug(result:, expression:, values:) # rubocop:disable Metrics/AbcSize
194
+ @debug_values = values
195
+
196
+ require "pry"
197
+ puts "!!! CLAWS DEBUG !!!".red
198
+ puts "#{expression} returned #{result}".red
199
+ puts "Tips:"
200
+ puts "* values available in @debug_values".green
201
+ puts "* eval a test expression: e 'expression'".green
202
+ puts " * e '1 == 2'".green
203
+ puts " * e '$data'".green
204
+ puts " * e '$job.meta'".green
205
+ puts " * e '$job.meta.action.name in $data.automerge_actions'".green
206
+ puts "* ^D to exit".green
207
+
208
+ # no stack trace needed since there's no error
209
+ binding.pry quiet: true # rubocop:disable Lint/Debugger
210
+ end
211
+
212
+ def e(expression)
213
+ expr = BaseRule.parse_rule(expression)
214
+ puts expr.eval_with(
215
+ values: @debug_values
216
+ ).inspect
217
+ end
218
+
219
+ def get_nearest_key(blob, path) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
220
+ cursor = blob
221
+ found_key = nil
222
+ path.split(".").each do |key|
223
+ return found_key unless cursor.is_a? Hash or cursor.is_a? Workflow
224
+
225
+ if cursor.include? key.to_s
226
+ found_key = cursor.keys.filter { |k| k.to_s == key.to_s }.first
227
+ cursor = cursor[key.to_s]
228
+ elsif cursor.include? key.to_sym
229
+ found_key = cursor.keys.filter { |k| k.to_sym == key.to_sym }.first
230
+ cursor = cursor[key.to_sym]
231
+ end
232
+ end
233
+
234
+ found_key
235
+ end
236
+ end
237
+ end