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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +31 -0
- data/.ruby-version +1 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +99 -0
- data/README.md +557 -0
- data/Rakefile +12 -0
- data/bin/analyze +62 -0
- data/config.yml +16 -0
- data/corpus/automerge_via_action.yml +28 -0
- data/corpus/automerge_via_cli.yml +14 -0
- data/corpus/build-docker-image-run-drc-for-cell-gds-using-magic.yml +170 -0
- data/corpus/cmd.yml +14 -0
- data/corpus/container.yml +19 -0
- data/corpus/container_docker.yml +9 -0
- data/corpus/dispatch_command_injection.yml +17 -0
- data/corpus/inherit_secrets.yml +20 -0
- data/corpus/nameless.yml +11 -0
- data/corpus/permissions.yml +19 -0
- data/corpus/ruby.yml +12 -0
- data/corpus/shellcheck.yml +12 -0
- data/corpus/unsafe_checkout_code_execution.yml +21 -0
- data/corpus/unsafe_checkout_token_leak.yml +33 -0
- data/corpus/unscoped_secrets.yml +16 -0
- data/github_action.yml +36 -0
- data/lib/claws/application.rb +237 -0
- data/lib/claws/base_rule.rb +94 -0
- data/lib/claws/cli/color.rb +30 -0
- data/lib/claws/cli/yaml_with_lines.rb +124 -0
- data/lib/claws/engine.rb +25 -0
- data/lib/claws/formatter/github.rb +17 -0
- data/lib/claws/formatter/stdout.rb +13 -0
- data/lib/claws/formatters.rb +4 -0
- data/lib/claws/rule/automatic_merge.rb +49 -0
- data/lib/claws/rule/bulk_permissions.rb +20 -0
- data/lib/claws/rule/command_injection.rb +14 -0
- data/lib/claws/rule/empty_name.rb +14 -0
- data/lib/claws/rule/inherited_secrets.rb +17 -0
- data/lib/claws/rule/no_containers.rb +28 -0
- data/lib/claws/rule/risky_triggers.rb +32 -0
- data/lib/claws/rule/shellcheck.rb +109 -0
- data/lib/claws/rule/special_permissions.rb +37 -0
- data/lib/claws/rule/unapproved_runners.rb +31 -0
- data/lib/claws/rule/unpinned_action.rb +30 -0
- data/lib/claws/rule/unsafe_checkout.rb +36 -0
- data/lib/claws/rule.rb +13 -0
- data/lib/claws/version.rb +5 -0
- data/lib/claws/violation.rb +11 -0
- data/lib/claws/workflow.rb +221 -0
- data/lib/claws.rb +6 -0
- 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,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
|
+
|
data/corpus/nameless.yml
ADDED
@@ -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,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
|