claws-scan 0.8.0 → 0.9.5
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 +4 -4
- data/.rubocop.yml +1 -1
- data/Gemfile.lock +17 -11
- data/README.md +209 -0
- data/bin/analyze +1 -1
- data/example-config.yml +18 -0
- data/lib/claws/application.rb +36 -36
- data/lib/claws/base_rule.rb +29 -7
- data/lib/claws/cli/yaml_with_lines.rb +6 -2
- data/lib/claws/rule/checkout_with_static_credentials.rb +38 -0
- data/lib/claws/rule/command_injection.rb +1 -1
- data/lib/claws/rule/global_permissions_block.rb +34 -0
- data/lib/claws/rule/implicit_persist_credentials.rb +27 -0
- data/lib/claws/rule/static_aws_credentials.rb +54 -0
- data/lib/claws/rule.rb +4 -0
- data/lib/claws/version.rb +1 -1
- data/lib/claws/workflow.rb +10 -4
- metadata +8 -4
- data/.policy.yml +0 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 99e68a15fab38b583b1b9c34717ef25f2a1612f0da8b0bf50d5b0d4530829ce5
|
|
4
|
+
data.tar.gz: ca653965f7c90d93e2122d70ceb1a54e0c1a326d13d8cbbfbfaa8cadc8e01273
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c1fbbe050665049b3a913681debe61e374d825197e3be5883e7bcbf3e0ea7818937700e576feba9fdd5fd0edb16798a9b2ce1c6a157781036324ff8b6ab4aa59
|
|
7
|
+
data.tar.gz: f715f4ea43b80cdc3d25a41554d30b0fcf88a3055e500f325c93f31a3d35020083cf45bcbff14cf10f9373952a8fb918da674fec2bb9aebf1b6c341a522a7eec
|
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
claws-scan (0.
|
|
4
|
+
claws-scan (0.9.5)
|
|
5
5
|
equation (~> 0.6)
|
|
6
6
|
pry
|
|
7
7
|
slop (~> 4.9)
|
|
@@ -10,21 +10,26 @@ PATH
|
|
|
10
10
|
GEM
|
|
11
11
|
remote: https://rubygems.org/
|
|
12
12
|
specs:
|
|
13
|
-
addressable (2.
|
|
14
|
-
public_suffix (>= 2.0.2, <
|
|
13
|
+
addressable (2.9.0)
|
|
14
|
+
public_suffix (>= 2.0.2, < 8.0)
|
|
15
15
|
ast (2.4.2)
|
|
16
16
|
coderay (1.1.3)
|
|
17
17
|
diff-lcs (1.5.0)
|
|
18
18
|
equation (0.6.2)
|
|
19
19
|
fakefs (2.3.0)
|
|
20
|
-
faraday (2.
|
|
21
|
-
faraday-net_http (>= 2.0, < 3.
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
faraday (2.14.3)
|
|
21
|
+
faraday-net_http (>= 2.0, < 3.5)
|
|
22
|
+
json
|
|
23
|
+
logger
|
|
24
|
+
faraday-net_http (3.4.4)
|
|
25
|
+
net-http (~> 0.5)
|
|
24
26
|
faraday-retry (2.0.0)
|
|
25
27
|
faraday (~> 2.0)
|
|
26
|
-
json (2.
|
|
28
|
+
json (2.21.2)
|
|
29
|
+
logger (1.7.0)
|
|
27
30
|
method_source (1.0.0)
|
|
31
|
+
net-http (0.9.1)
|
|
32
|
+
uri (>= 0.11.1)
|
|
28
33
|
octokit (6.0.1)
|
|
29
34
|
faraday (>= 1, < 3)
|
|
30
35
|
sawyer (~> 0.9)
|
|
@@ -35,11 +40,11 @@ GEM
|
|
|
35
40
|
pry (0.14.2)
|
|
36
41
|
coderay (~> 1.1)
|
|
37
42
|
method_source (~> 1.0)
|
|
38
|
-
public_suffix (
|
|
43
|
+
public_suffix (7.0.5)
|
|
39
44
|
rainbow (3.1.1)
|
|
40
45
|
rake (13.0.6)
|
|
41
46
|
regexp_parser (2.6.0)
|
|
42
|
-
rexml (3.
|
|
47
|
+
rexml (3.4.2)
|
|
43
48
|
rspec (3.11.0)
|
|
44
49
|
rspec-core (~> 3.11.0)
|
|
45
50
|
rspec-expectations (~> 3.11.0)
|
|
@@ -66,7 +71,6 @@ GEM
|
|
|
66
71
|
rubocop-ast (1.23.0)
|
|
67
72
|
parser (>= 3.1.1.0)
|
|
68
73
|
ruby-progressbar (1.11.0)
|
|
69
|
-
ruby2_keywords (0.0.5)
|
|
70
74
|
sawyer (0.9.2)
|
|
71
75
|
addressable (>= 2.3.5)
|
|
72
76
|
faraday (>= 0.17.3, < 3)
|
|
@@ -74,12 +78,14 @@ GEM
|
|
|
74
78
|
treetop (1.6.12)
|
|
75
79
|
polyglot (~> 0.3)
|
|
76
80
|
unicode-display_width (2.3.0)
|
|
81
|
+
uri (1.1.1)
|
|
77
82
|
|
|
78
83
|
PLATFORMS
|
|
79
84
|
arm64-darwin-21
|
|
80
85
|
arm64-darwin-22
|
|
81
86
|
arm64-darwin-23
|
|
82
87
|
arm64-darwin-24
|
|
88
|
+
arm64-darwin-25
|
|
83
89
|
x86_64-linux
|
|
84
90
|
|
|
85
91
|
DEPENDENCIES
|
data/README.md
CHANGED
|
@@ -8,6 +8,82 @@ This is in contrast to common static analysis tools that achieve this by requiri
|
|
|
8
8
|
|
|
9
9
|
While it's important to be able to easily write a Rule, it's just as important (if not more!) to write good tests for them. Like with Rubocop, Claws comes with a couple RSpec helpers that makes it easy to write test cases. Test cases are simply example Workflows that exercise a Rule's expressions, ensuring that a modification to a Rule can't accidentally affect its ability to detect known bad content.
|
|
10
10
|
|
|
11
|
+
## Getting Started
|
|
12
|
+
|
|
13
|
+
Claws is written in Ruby and distributed as a Ruby Gem, so you can install it using `gem`. Just point it at the [example configuration](example-config.yml) file and you should be good to go.
|
|
14
|
+
|
|
15
|
+
For one off scans, you can just follow these commands:
|
|
16
|
+
```
|
|
17
|
+
# Install claws
|
|
18
|
+
$ gem install claws-scan
|
|
19
|
+
|
|
20
|
+
# Optionally, specify a version
|
|
21
|
+
$ gem install claws-scan -v 0.7.5
|
|
22
|
+
|
|
23
|
+
# Scan a Github Action file
|
|
24
|
+
analyze -c example_config.yml -t .github/workflows/ci.yml
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
If you'd like to integrate this into your workflow like we have, this should be enough to get you started.
|
|
28
|
+
|
|
29
|
+
```yaml
|
|
30
|
+
name: Workflow Static Analyzer
|
|
31
|
+
|
|
32
|
+
on:
|
|
33
|
+
pull_request:
|
|
34
|
+
branches:
|
|
35
|
+
- main
|
|
36
|
+
|
|
37
|
+
jobs:
|
|
38
|
+
build:
|
|
39
|
+
name: Analyze Github Workflows
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
steps:
|
|
42
|
+
- name: Set Up Ruby
|
|
43
|
+
uses: ruby/setup-ruby@d8d83c3960843afb664e821fed6be52f37da5267 # v1.231.0
|
|
44
|
+
with:
|
|
45
|
+
ruby-version: '3.0'
|
|
46
|
+
# Grab your configuration file however makes sense for you
|
|
47
|
+
# We keep ours in a separate Github repo.
|
|
48
|
+
- name: Set Up Claws Config
|
|
49
|
+
run: |
|
|
50
|
+
echo ... > /tmp/claws-config.yml
|
|
51
|
+
# Optional, useful if you want Claws to run shellcheck for you
|
|
52
|
+
- name: Set Up Shellcheck
|
|
53
|
+
run: |
|
|
54
|
+
sudo apt-get update
|
|
55
|
+
sudo apt-get install -y shellcheck
|
|
56
|
+
- uses: actions/checkout@v4
|
|
57
|
+
with:
|
|
58
|
+
fetch-depth: 0
|
|
59
|
+
- name: Set Up Claws
|
|
60
|
+
run: |
|
|
61
|
+
gem install claws-scan -v 0.7.5
|
|
62
|
+
- name: Analyze Workflows
|
|
63
|
+
run: |
|
|
64
|
+
#!/bin/bash
|
|
65
|
+
|
|
66
|
+
# Collect all files in the .github/workflows directory
|
|
67
|
+
workflow_files=$(find .github/workflows -type f)
|
|
68
|
+
|
|
69
|
+
# Exit early if there are no workflow files
|
|
70
|
+
if [[ -z "$workflow_files" ]]; then
|
|
71
|
+
echo "No workflow files found in .github/workflows"
|
|
72
|
+
exit 0
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
flags=()
|
|
76
|
+
|
|
77
|
+
# Iterate over each workflow file
|
|
78
|
+
while IFS= read -r file; do
|
|
79
|
+
echo "Processing $file"
|
|
80
|
+
flags+=("-t" "$file")
|
|
81
|
+
done <<< "$workflow_files"
|
|
82
|
+
|
|
83
|
+
# Run the analyze command with all gathered flags
|
|
84
|
+
analyze -f github -c /tmp/claws-config.yml "${flags[@]}"
|
|
85
|
+
```
|
|
86
|
+
|
|
11
87
|
## Built In Rules
|
|
12
88
|
|
|
13
89
|
These are all the rules that come out of the box with Claws. They can all be found in [the rules subdirectory](https://github.com/Betterment/claws/tree/main/lib/claws/rule), and some of them have configuration options.
|
|
@@ -204,6 +280,24 @@ Shellcheck is a great tool for dealing with bugs or otherwise unintended effects
|
|
|
204
280
|
|
|
205
281
|
This rule flags workflows that request write access to specific unusual permissions. While this rule cannot flag how these permissions are exercised, it serves as a warning to code reviewers that if these permissions are requested, the way they are used should be scrutinized. A reviewer may find that a permission is left over from testing and no longer needed, or that a specific permission was never needed.
|
|
206
282
|
|
|
283
|
+
### CheckoutWithStaticCredentials
|
|
284
|
+
|
|
285
|
+
This rule flags any uses of `actions/checkout` using static credentials. Using static credentials can pose a risk because these credentials are typically not auditable and can be tricky to rotate. In the event of an incident where they are leaked, incident response to determine the scope of impact may be tough.
|
|
286
|
+
|
|
287
|
+
Where possible, the default `$GITHUB_TOKEN` should be used. Its settings can be configured directly from within the workflow. Check [the official documentation](https://docs.github.com/en/actions/tutorials/authenticate-with-github_token#modifying-the-permissions-for-the-github_token) for more information on how to do this.
|
|
288
|
+
|
|
289
|
+
If you are using a deploy key via SSH to access a package or otherwise an artifact from another repository, you can instead configure the repository to grant it access explicitly to that other repository. This will give the default `$GITHUB_TOKEN` access to that repository without needing to use a deploy key. To learn more about this, [check out the official documentation](https://docs.github.com/en/packages/learn-github-packages/configuring-a-packages-access-control-and-visibility). This is safer than a static deploy key because the credential is short lived and access can be audited.
|
|
290
|
+
|
|
291
|
+
If you need a Github Token to perform some authenticated action where the default `$GITHUB_TOKEN` doesn't do what you need, consider setting up a Github App and using [`actions/create-github-app-token`](https://github.com/actions/create-github-app-token). This will generate a short lived access token, and using an app creates a useful audit trail for what this access token can actually do. Then, use this token in just the build steps where it's needed.
|
|
292
|
+
|
|
293
|
+
In some cases, a static access token or deploy key may still be necessary, especially for APIs that are not yet supported by Github App Tokens. In these cases, make sure to limit the scope of the access token to the bare minimum necessary to function.
|
|
294
|
+
|
|
295
|
+
### StaticAwsCredentials
|
|
296
|
+
|
|
297
|
+
This rule flags long-lived AWS credentials in Github workflows — not just `aws-actions/configure-aws-credentials`, but also `env:` blocks and common shell patterns (`export`, `aws configure set`). Static credentials stored in GitHub secrets, variables, or environment variables can be tricky to audit and rotate. In the event of an incident where they are leaked, incident response may be tough.
|
|
298
|
+
|
|
299
|
+
Where possible, configure AWS to trust GitHub's OIDC provider and use `role-to-assume` to get short-lived credentials for each workflow run. Check [GitHub's documentation on configuring OIDC in AWS](https://docs.github.com/en/actions/security-for-github-actions/security-hardening-for-github-actions/configuring-openid-connect-in-amazon-web-services) for setup instructions.
|
|
300
|
+
|
|
207
301
|
### UnapprovedRunners
|
|
208
302
|
|
|
209
303
|
This rule flags workflows that use runners that they might not need or should not use. This can come in handy when an organization has available self hosted or otherwise expensive runners but wants to be particular about when they're used.
|
|
@@ -307,6 +401,121 @@ This rule only looks for user supplied branches being checked out for `pull_requ
|
|
|
307
401
|
|-------------|------------------------|---------------------------------------------------------------|
|
|
308
402
|
| `risky_events` | ["pull_request_target", "workflow_dispatch"] | An array of Github events you consider risky. |
|
|
309
403
|
|
|
404
|
+
### ImplicitPersistCredentials
|
|
405
|
+
|
|
406
|
+
By default, all versions of the official [`actions/checkout`](https://github.com/actions/checkout) have a default value of `true` for the `persist-credentials` setting. This means the temporary credentials generated to clone a repository's source code will be written to disk. This is necessary if you intend on using the `git` command line tool to further interact with this repository (e.g. check out a specific branch or push new commits to it). However, if you don't intend on doing this, this opens up an unnecessary risk. Untrusted or otherwise malicious code can find these temporary credentials and use them to access source code and other resources that would otherwise not have been exposed by your workflow. For example, if during a supply chain attack someone steals your job's environment variables, they may be able to use these credentials from their own system to access private repositories.
|
|
407
|
+
|
|
408
|
+
For example, take the following workflow:
|
|
409
|
+
|
|
410
|
+
```yaml
|
|
411
|
+
name: Ruby CI (PR)
|
|
412
|
+
|
|
413
|
+
on: [pull_request]
|
|
414
|
+
|
|
415
|
+
jobs:
|
|
416
|
+
bundle-install:
|
|
417
|
+
name: Bundle install
|
|
418
|
+
runs-on: ubuntu-latest
|
|
419
|
+
|
|
420
|
+
steps:
|
|
421
|
+
- name: Checkout repository
|
|
422
|
+
uses: actions/checkout@v6
|
|
423
|
+
|
|
424
|
+
- name: Set up Ruby
|
|
425
|
+
uses: ruby/setup-ruby@v1
|
|
426
|
+
|
|
427
|
+
- name: Install dependencies
|
|
428
|
+
run: bundle install
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
Because `persist-credentials` is not specified, its value is `true` by default. That means the temporary Github credentials used to clone the repository will be either written to `.git/config` or to a temporary directory. Both of these locations will be accessible to subsequent steps, so if for example we were installing a malicious dependency with the `bundle install` command, it could find those credentials on disk and send them to someone via the network, letting them clone this repository and potentially access even other repositories. In this workflow, we aren't interacting with git beyond that checkout, so we don't need to persist the credential. We can fix this by setting `persist-credentials` to `false`:
|
|
432
|
+
|
|
433
|
+
```yaml
|
|
434
|
+
# ...
|
|
435
|
+
steps:
|
|
436
|
+
- name: Checkout repository
|
|
437
|
+
uses: actions/checkout@v6
|
|
438
|
+
with:
|
|
439
|
+
persist-credentials: false
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
However, if you do need to interact with the repository via git, you should explicitly set `persist-credentials` to `true`. This way, you signal to your reviewers that your workflow interacts with the local checkout in a way that needs this credential.
|
|
443
|
+
|
|
444
|
+
Note, there is [a Github Issue tracking this](https://github.com/actions/checkout/issues/485) that has been open for a few years by now.
|
|
445
|
+
|
|
446
|
+
### GlobalPermissionsBlock
|
|
447
|
+
|
|
448
|
+
[The permissions block](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#permissions) dictates which permissions a workflow or a job have or don't have. When permissions are defined at the workflow level, each job gets the same set of permissions, potentially giving more access than a job or a step truly needs. Instead, permissions should be defined at the job level, making sure each job and step has access only to the permissions it needs. This can minimize the impact of untrusted code using an overly permissive `$GITHUB_TOKEN`, such as during a supply chain attack.
|
|
449
|
+
|
|
450
|
+
For example, take the following workflow:
|
|
451
|
+
|
|
452
|
+
```yaml
|
|
453
|
+
name: Build and Deploy
|
|
454
|
+
|
|
455
|
+
on: [push]
|
|
456
|
+
|
|
457
|
+
permissions:
|
|
458
|
+
id-token: write
|
|
459
|
+
|
|
460
|
+
jobs:
|
|
461
|
+
build:
|
|
462
|
+
name: Build
|
|
463
|
+
|
|
464
|
+
steps:
|
|
465
|
+
- name: Checkout
|
|
466
|
+
uses: actions/checkout@v6
|
|
467
|
+
|
|
468
|
+
- name: Install dependencies
|
|
469
|
+
run: bundle install
|
|
470
|
+
|
|
471
|
+
- name: Upload build artifact to GitHub
|
|
472
|
+
uses: actions/upload-artifact@v4
|
|
473
|
+
with:
|
|
474
|
+
name: ${{ env.ARTIFACT_NAME }}-${{ steps.meta.outputs.version }}
|
|
475
|
+
path: ${{ env.ARTIFACT_NAME }}.tgz
|
|
476
|
+
|
|
477
|
+
deploy:
|
|
478
|
+
name: Deploy
|
|
479
|
+
needs: build
|
|
480
|
+
|
|
481
|
+
steps:
|
|
482
|
+
- name: Configure AWS credentials (OIDC)
|
|
483
|
+
uses: aws-actions/configure-aws-credentials@v4
|
|
484
|
+
with:
|
|
485
|
+
role-to-assume: ${{ env.ROLE_TO_ASSUME }}
|
|
486
|
+
aws-region: ${{ env.AWS_REGION }}
|
|
487
|
+
|
|
488
|
+
- name: Upload release artifact to S3
|
|
489
|
+
run: |
|
|
490
|
+
aws s3 cp "${ARTIFACT_NAME}.tgz" "s3://${S3_BUCKET}/${{ needs.build.outputs.s3_key }}"
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
This sample workflow deploys to AWS using OIDC, so it needs the `id-token: write` permission to generate temporary AWS credentials. However, because this permission is defined at the workflow level, the `build` job can also use OIDC to grab temporary AWS credentials. This means if this job has some kind of vulnerability, an attacker could use it to gain access to an AWS environment that would otherwise have been entirely inaccessible.
|
|
494
|
+
|
|
495
|
+
To remedy this, we should move the `permissions:` block into the specific job that actually needs this permission:
|
|
496
|
+
|
|
497
|
+
```yaml
|
|
498
|
+
# ...
|
|
499
|
+
deploy:
|
|
500
|
+
name: Deploy
|
|
501
|
+
needs: build
|
|
502
|
+
permissions:
|
|
503
|
+
id-token: write
|
|
504
|
+
|
|
505
|
+
steps:
|
|
506
|
+
- name: Configure AWS credentials (OIDC)
|
|
507
|
+
uses: aws-actions/configure-aws-credentials@v4
|
|
508
|
+
with:
|
|
509
|
+
role-to-assume: ${{ env.ROLE_TO_ASSUME }}
|
|
510
|
+
aws-region: ${{ env.AWS_REGION }}
|
|
511
|
+
|
|
512
|
+
- name: Upload release artifact to S3
|
|
513
|
+
run: |
|
|
514
|
+
aws s3 cp "${ARTIFACT_NAME}.tgz" "s3://${S3_BUCKET}/${{ needs.build.outputs.s3_key }}"
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
This limits AWS OIDC access to just `deploy`.
|
|
518
|
+
|
|
310
519
|
## Walkthrough
|
|
311
520
|
|
|
312
521
|
Let's start with a minimal configuration file that enables some basic Rules.
|
data/bin/analyze
CHANGED
data/example-config.yml
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Enabled:
|
|
2
|
+
NoContainers:
|
|
3
|
+
approved_images: ["ubuntu-latest"]
|
|
4
|
+
SpecialPermissions:
|
|
5
|
+
EmptyName:
|
|
6
|
+
RiskyTriggers:
|
|
7
|
+
UnapprovedRunners:
|
|
8
|
+
allowed_runners: ["ubuntu-latest", "macos-latest"]
|
|
9
|
+
CommandInjection:
|
|
10
|
+
StaticAwsCredentials:
|
|
11
|
+
AutomaticMerge:
|
|
12
|
+
UnpinnedAction:
|
|
13
|
+
trusted_authors: ["actions"]
|
|
14
|
+
UnsafeCheckout:
|
|
15
|
+
InheritedSecrets:
|
|
16
|
+
BulkPermissions:
|
|
17
|
+
Shellcheck:
|
|
18
|
+
shellcheck_bin: "/usr/bin/shellcheck"
|
data/lib/claws/application.rb
CHANGED
|
@@ -43,10 +43,10 @@ module Claws
|
|
|
43
43
|
@detections.each do |detection|
|
|
44
44
|
detection.on_workflow.each do |rule|
|
|
45
45
|
violation = run_detection(
|
|
46
|
-
filename
|
|
47
|
-
detection
|
|
48
|
-
rule
|
|
49
|
-
workflow:
|
|
46
|
+
filename:,
|
|
47
|
+
detection:,
|
|
48
|
+
rule:,
|
|
49
|
+
workflow:
|
|
50
50
|
)
|
|
51
51
|
|
|
52
52
|
violations << violation if violation
|
|
@@ -58,7 +58,7 @@ module Claws
|
|
|
58
58
|
expression: rule[:expression],
|
|
59
59
|
values: {
|
|
60
60
|
data: detection.data,
|
|
61
|
-
workflow:
|
|
61
|
+
workflow:
|
|
62
62
|
}
|
|
63
63
|
)
|
|
64
64
|
end
|
|
@@ -72,11 +72,11 @@ module Claws
|
|
|
72
72
|
@detections.each do |detection|
|
|
73
73
|
detection.on_job.each do |rule|
|
|
74
74
|
violation = run_detection(
|
|
75
|
-
filename
|
|
76
|
-
detection
|
|
77
|
-
rule
|
|
78
|
-
workflow
|
|
79
|
-
job:
|
|
75
|
+
filename:,
|
|
76
|
+
detection:,
|
|
77
|
+
rule:,
|
|
78
|
+
workflow:,
|
|
79
|
+
job:
|
|
80
80
|
)
|
|
81
81
|
|
|
82
82
|
violations << violation if violation
|
|
@@ -88,8 +88,8 @@ module Claws
|
|
|
88
88
|
expression: rule[:expression],
|
|
89
89
|
values: {
|
|
90
90
|
data: detection.data,
|
|
91
|
-
workflow
|
|
92
|
-
job:
|
|
91
|
+
workflow:,
|
|
92
|
+
job:
|
|
93
93
|
}
|
|
94
94
|
)
|
|
95
95
|
end
|
|
@@ -104,12 +104,12 @@ module Claws
|
|
|
104
104
|
@detections.each do |detection|
|
|
105
105
|
detection.on_step.each do |rule|
|
|
106
106
|
violation = run_detection(
|
|
107
|
-
filename
|
|
108
|
-
detection
|
|
109
|
-
rule
|
|
110
|
-
workflow
|
|
111
|
-
job
|
|
112
|
-
step:
|
|
107
|
+
filename:,
|
|
108
|
+
detection:,
|
|
109
|
+
rule:,
|
|
110
|
+
workflow:,
|
|
111
|
+
job:,
|
|
112
|
+
step:
|
|
113
113
|
)
|
|
114
114
|
|
|
115
115
|
violations << violation if violation
|
|
@@ -121,9 +121,9 @@ module Claws
|
|
|
121
121
|
expression: rule[:expression],
|
|
122
122
|
values: {
|
|
123
123
|
data: detection.data,
|
|
124
|
-
workflow
|
|
125
|
-
job
|
|
126
|
-
step:
|
|
124
|
+
workflow:,
|
|
125
|
+
job:,
|
|
126
|
+
step:
|
|
127
127
|
}
|
|
128
128
|
)
|
|
129
129
|
end
|
|
@@ -137,19 +137,19 @@ module Claws
|
|
|
137
137
|
def run_detection(filename:, detection:, rule:, workflow:, job: nil, step: nil) # rubocop:disable Metrics/ParameterLists
|
|
138
138
|
violation = if rule.is_a? Symbol
|
|
139
139
|
get_dynamic_violation(
|
|
140
|
-
detection
|
|
140
|
+
detection:,
|
|
141
141
|
method: rule,
|
|
142
|
-
workflow
|
|
143
|
-
job
|
|
144
|
-
step:
|
|
142
|
+
workflow:,
|
|
143
|
+
job:,
|
|
144
|
+
step:
|
|
145
145
|
)
|
|
146
146
|
else
|
|
147
147
|
get_static_violations(
|
|
148
|
-
detection
|
|
149
|
-
rule
|
|
150
|
-
workflow
|
|
151
|
-
job
|
|
152
|
-
step:
|
|
148
|
+
detection:,
|
|
149
|
+
rule:,
|
|
150
|
+
workflow:,
|
|
151
|
+
job:,
|
|
152
|
+
step:
|
|
153
153
|
)
|
|
154
154
|
end
|
|
155
155
|
|
|
@@ -164,18 +164,18 @@ module Claws
|
|
|
164
164
|
def get_dynamic_violation(detection:, method:, workflow:, job:, step:)
|
|
165
165
|
detection.send(
|
|
166
166
|
method,
|
|
167
|
-
workflow
|
|
168
|
-
job
|
|
169
|
-
step:
|
|
167
|
+
workflow:,
|
|
168
|
+
job:,
|
|
169
|
+
step:
|
|
170
170
|
)
|
|
171
171
|
end
|
|
172
172
|
|
|
173
173
|
def get_static_violations(rule:, detection:, workflow:, job:, step:)
|
|
174
174
|
result = rule[:expression].eval_with(values: {
|
|
175
175
|
data: detection.data,
|
|
176
|
-
workflow
|
|
177
|
-
job
|
|
178
|
-
step:
|
|
176
|
+
workflow:,
|
|
177
|
+
job:,
|
|
178
|
+
step:
|
|
179
179
|
})
|
|
180
180
|
|
|
181
181
|
return unless result
|
data/lib/claws/base_rule.rb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
class BaseRule
|
|
2
2
|
attr_accessor :on_workflow, :on_job, :on_step, :configuration
|
|
3
3
|
|
|
4
|
-
def self.parse_rule(rule) # rubocop:disable Metrics/AbcSize
|
|
4
|
+
def self.parse_rule(rule) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
5
5
|
ExpressionParser.parse_expression(rule).tap do |expression|
|
|
6
6
|
expression.instance_eval do
|
|
7
|
-
def ctx # rubocop:disable Metrics/AbcSize
|
|
7
|
+
def ctx # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
8
8
|
@ctx ||= Context.new(
|
|
9
9
|
default: {},
|
|
10
10
|
methods: {
|
|
@@ -14,7 +14,29 @@ class BaseRule
|
|
|
14
14
|
endswith: ->(string, needle) { string.to_s.end_with? needle },
|
|
15
15
|
difference: ->(arr1, arr2) { arr1.difference arr2 },
|
|
16
16
|
intersection: ->(arr1, arr2) { arr1.intersection arr2 },
|
|
17
|
-
|
|
17
|
+
get_key: ->(arr, key) { (arr || {}).fetch(key, nil) },
|
|
18
|
+
count: ->(n) { n.length },
|
|
19
|
+
dig: lambda { |object, path, default = nil|
|
|
20
|
+
# sometimes we might want to traverse the object as if it were a hash
|
|
21
|
+
# sometimes we might want to traverse it as a Ruby object
|
|
22
|
+
# annoying up front, but the edge cases are few and keeps expressions simple
|
|
23
|
+
path.to_s.split(".").reduce(object) do |current, part|
|
|
24
|
+
return default if current.nil?
|
|
25
|
+
|
|
26
|
+
if current.is_a?(Hash)
|
|
27
|
+
# Prefer exact string key, then symbol key
|
|
28
|
+
if current.key?(part)
|
|
29
|
+
current[part]
|
|
30
|
+
elsif current.key?(part.to_sym)
|
|
31
|
+
current[part.to_sym]
|
|
32
|
+
else
|
|
33
|
+
default
|
|
34
|
+
end
|
|
35
|
+
else
|
|
36
|
+
current.respond_to?(part) ? current.public_send(part) : default
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
}
|
|
18
40
|
}
|
|
19
41
|
)
|
|
20
42
|
end
|
|
@@ -45,23 +67,23 @@ class BaseRule
|
|
|
45
67
|
end
|
|
46
68
|
|
|
47
69
|
def self.on_workflow(value, highlight: nil, debug: false)
|
|
48
|
-
(@on_workflow ||= []) << extract_value(value, highlight
|
|
70
|
+
(@on_workflow ||= []) << extract_value(value, highlight:, debug:)
|
|
49
71
|
end
|
|
50
72
|
|
|
51
73
|
def self.on_job(value, highlight: nil, debug: false)
|
|
52
74
|
highlight = highlight.to_s unless highlight.nil?
|
|
53
|
-
(@on_job ||= []) << extract_value(value, highlight
|
|
75
|
+
(@on_job ||= []) << extract_value(value, highlight:, debug:)
|
|
54
76
|
end
|
|
55
77
|
|
|
56
78
|
def self.on_step(value, highlight: nil, debug: false)
|
|
57
79
|
highlight = highlight.to_s unless highlight.nil?
|
|
58
|
-
(@on_step ||= []) << extract_value(value, highlight
|
|
80
|
+
(@on_step ||= []) << extract_value(value, highlight:, debug:)
|
|
59
81
|
end
|
|
60
82
|
|
|
61
83
|
def self.extract_value(value, highlight: nil, debug: false)
|
|
62
84
|
case value
|
|
63
85
|
when String
|
|
64
|
-
{ expression: parse_rule(value), highlight
|
|
86
|
+
{ expression: parse_rule(value), highlight:, debug: }
|
|
65
87
|
when Symbol
|
|
66
88
|
value
|
|
67
89
|
else
|
|
@@ -18,7 +18,9 @@ module Psych
|
|
|
18
18
|
class ToRuby
|
|
19
19
|
def accept(target)
|
|
20
20
|
s = super(target)
|
|
21
|
-
|
|
21
|
+
|
|
22
|
+
# types that we cannot monkey patch into holding line information
|
|
23
|
+
if target.respond_to?(:line) and ![TrueClass, FalseClass, NilClass, Integer, Float].include? s.class
|
|
22
24
|
s.instance_eval do
|
|
23
25
|
extend(Locatable)
|
|
24
26
|
end
|
|
@@ -49,7 +51,9 @@ module Psych
|
|
|
49
51
|
key.line = 0 if key.respond_to? :line and key.line.nil?
|
|
50
52
|
key.line += 1 if key.respond_to? :line
|
|
51
53
|
key.freeze
|
|
52
|
-
|
|
54
|
+
|
|
55
|
+
# types that we cannot monkey patch into holding line information
|
|
56
|
+
if [TrueClass, FalseClass, NilClass, Integer, Float].include? key.class
|
|
53
57
|
val.line = 0 if val.respond_to? :line and val.line.nil?
|
|
54
58
|
val.line += 1 if val.respond_to? :line
|
|
55
59
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Claws
|
|
2
|
+
module Rule
|
|
3
|
+
class CheckoutWithStaticCredentials < BaseRule
|
|
4
|
+
description <<~DESC
|
|
5
|
+
Avoid using static credentials like deploy keys, SSH keys, or personal access
|
|
6
|
+
tokens to clone other repositories. Static credentials can be tricky to audit
|
|
7
|
+
and rotate, making them risky to hold onto, especially in the event of an
|
|
8
|
+
incident where they may be leaked.
|
|
9
|
+
|
|
10
|
+
Either grant your repository access directly to other repositories, or use a
|
|
11
|
+
Github App to generate a short lived access token.
|
|
12
|
+
|
|
13
|
+
For more information:
|
|
14
|
+
https://github.com/betterment/claws/blob/main/README.md#checkoutwithstaticcredentials
|
|
15
|
+
DESC
|
|
16
|
+
|
|
17
|
+
on_step %(
|
|
18
|
+
$step.meta.action.name == "actions/checkout" &&
|
|
19
|
+
(
|
|
20
|
+
get_key($step.with, "ssh-key") =~ "{{.*secrets\..*" ||
|
|
21
|
+
get_key($step.with, "ssh-key") =~ "{{.*env\..*" ||
|
|
22
|
+
get_key($step.with, "ssh-key") =~ "{{.*vars\..*" ||
|
|
23
|
+
get_key($step.with, "ssh-key") =~ ".*-----BEGIN.*"
|
|
24
|
+
)
|
|
25
|
+
), highlight: "with.ssh-key"
|
|
26
|
+
|
|
27
|
+
on_step %(
|
|
28
|
+
$step.meta.action.name == "actions/checkout" &&
|
|
29
|
+
(
|
|
30
|
+
get_key($step.with, "token") =~ "{{.*secrets\..*" ||
|
|
31
|
+
get_key($step.with, "token") =~ "{{.*env\..*" ||
|
|
32
|
+
get_key($step.with, "token") =~ "{{.*vars\..*" ||
|
|
33
|
+
get_key($step.with, "token") =~ "gh[a-z]_.*"
|
|
34
|
+
)
|
|
35
|
+
), highlight: "with.token"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -8,7 +8,7 @@ module Claws
|
|
|
8
8
|
https://github.com/betterment/claws/blob/main/README.md#commandinjection
|
|
9
9
|
DESC
|
|
10
10
|
|
|
11
|
-
on_step '$step.run =~ ".*{{
|
|
11
|
+
on_step '$step.run =~ ".*{{.*(github\.event|inputs)\..*}}.*"', highlight: "run"
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Claws
|
|
2
|
+
module Rule
|
|
3
|
+
class GlobalPermissionsBlock < BaseRule
|
|
4
|
+
description <<~DESC
|
|
5
|
+
Permissions should be set at the job level, not globally at the workflow level.
|
|
6
|
+
Because jobs will often need varying permissions, it's better to specify a set
|
|
7
|
+
of permissions for each individual job, minimizing potential misuse from
|
|
8
|
+
untrusted code in a job with permissions it never needed in the first place.
|
|
9
|
+
|
|
10
|
+
This rule will flag workflows that have multiple jobs and a root level
|
|
11
|
+
permissions block. If there is a root level permissions block but just one job,
|
|
12
|
+
it will not be flagged.
|
|
13
|
+
|
|
14
|
+
For more information:
|
|
15
|
+
https://github.com/betterment/claws/blob/main/README.md#globalpermissionsblock
|
|
16
|
+
DESC
|
|
17
|
+
|
|
18
|
+
on_workflow :test_root_level_permissions
|
|
19
|
+
|
|
20
|
+
def test_root_level_permissions(workflow:, job:, step:) # rubocop:disable Lint/UnusedMethodArgument
|
|
21
|
+
root_permission_block_line = workflow.keys.filter { |x| x == "permissions" }.first&.line
|
|
22
|
+
return if root_permission_block_line.nil?
|
|
23
|
+
|
|
24
|
+
job_count = workflow["jobs"]&.count || 0
|
|
25
|
+
return if job_count < 2
|
|
26
|
+
|
|
27
|
+
Violation.new(
|
|
28
|
+
line: root_permission_block_line,
|
|
29
|
+
description:
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Claws
|
|
2
|
+
module Rule
|
|
3
|
+
class ImplicitPersistCredentials < BaseRule
|
|
4
|
+
description <<~DESC
|
|
5
|
+
By default, actions/checkout will store generated credentials to disk so that
|
|
6
|
+
subsequent git operations will not require reauthentication. These credentials
|
|
7
|
+
will be available to subsequent steps and jobs. This may be undesirable and
|
|
8
|
+
potentially unsafe in scenarios where these credentials may be accessible to
|
|
9
|
+
untrusted code. In these cases, if these credentials are stolen they can be used
|
|
10
|
+
externally by an attacker to clone repositories that would otherwise have been
|
|
11
|
+
inaccessible.
|
|
12
|
+
|
|
13
|
+
If you know you will not need to access this repository for the rest of your
|
|
14
|
+
workflow, consider setting `persist-credentials` to false. Conversely,
|
|
15
|
+
explicitly set it to true if you know you will need these credentials.
|
|
16
|
+
|
|
17
|
+
For more information:
|
|
18
|
+
https://github.com/betterment/claws/blob/main/README.md#implicitpersistcredentials
|
|
19
|
+
DESC
|
|
20
|
+
|
|
21
|
+
on_step %(
|
|
22
|
+
$step.meta.action.name == "actions/checkout" &&
|
|
23
|
+
!contains([true, false], dig($step, "with.persist-credentials"))
|
|
24
|
+
), highlight: "uses"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Claws
|
|
2
|
+
module Rule
|
|
3
|
+
class StaticAwsCredentials < BaseRule
|
|
4
|
+
description <<~DESC
|
|
5
|
+
Avoid using long-lived AWS secret access keys in Github workflows. Static
|
|
6
|
+
credentials can be tricky to audit and rotate, making them risky to hold
|
|
7
|
+
onto, especially in the event of an incident where they may be leaked.
|
|
8
|
+
|
|
9
|
+
Use GitHub's OIDC provider and authenticate with `role-to-assume` instead.
|
|
10
|
+
|
|
11
|
+
For more information:
|
|
12
|
+
https://github.com/betterment/claws/blob/main/README.md#staticawscredentials
|
|
13
|
+
DESC
|
|
14
|
+
|
|
15
|
+
on_workflow %(
|
|
16
|
+
get_key($workflow.env, "AWS_SECRET_ACCESS_KEY") =~ "{{.*secrets\..*" ||
|
|
17
|
+
get_key($workflow.env, "AWS_SECRET_ACCESS_KEY") =~ "{{.*env\..*" ||
|
|
18
|
+
get_key($workflow.env, "AWS_SECRET_ACCESS_KEY") =~ "{{.*vars\..*" ||
|
|
19
|
+
get_key($workflow.env, "AWS_SECRET_ACCESS_KEY") =~ "^[A-Za-z0-9/+=]+$"
|
|
20
|
+
), highlight: "env.AWS_SECRET_ACCESS_KEY"
|
|
21
|
+
|
|
22
|
+
on_job %(
|
|
23
|
+
get_key($job.env, "AWS_SECRET_ACCESS_KEY") =~ "{{.*secrets\..*" ||
|
|
24
|
+
get_key($job.env, "AWS_SECRET_ACCESS_KEY") =~ "{{.*env\..*" ||
|
|
25
|
+
get_key($job.env, "AWS_SECRET_ACCESS_KEY") =~ "{{.*vars\..*" ||
|
|
26
|
+
get_key($job.env, "AWS_SECRET_ACCESS_KEY") =~ "^[A-Za-z0-9/+=]+$"
|
|
27
|
+
), highlight: "env.AWS_SECRET_ACCESS_KEY"
|
|
28
|
+
|
|
29
|
+
on_step %(
|
|
30
|
+
$step.meta.action.name == "aws-actions/configure-aws-credentials" &&
|
|
31
|
+
(
|
|
32
|
+
get_key($step.with, "aws-secret-access-key") =~ "{{.*secrets\..*" ||
|
|
33
|
+
get_key($step.with, "aws-secret-access-key") =~ "{{.*env\..*" ||
|
|
34
|
+
get_key($step.with, "aws-secret-access-key") =~ "{{.*vars\..*" ||
|
|
35
|
+
get_key($step.with, "aws-secret-access-key") =~ "^[A-Za-z0-9/+=]+$"
|
|
36
|
+
)
|
|
37
|
+
), highlight: "with.aws-secret-access-key"
|
|
38
|
+
|
|
39
|
+
on_step %(
|
|
40
|
+
get_key($step.env, "AWS_SECRET_ACCESS_KEY") =~ "{{.*secrets\..*" ||
|
|
41
|
+
get_key($step.env, "AWS_SECRET_ACCESS_KEY") =~ "{{.*env\..*" ||
|
|
42
|
+
get_key($step.env, "AWS_SECRET_ACCESS_KEY") =~ "{{.*vars\..*" ||
|
|
43
|
+
get_key($step.env, "AWS_SECRET_ACCESS_KEY") =~ "^[A-Za-z0-9/+=]+$"
|
|
44
|
+
), highlight: "env.AWS_SECRET_ACCESS_KEY"
|
|
45
|
+
|
|
46
|
+
on_step %(
|
|
47
|
+
$step.run =~ "AWS_SECRET_ACCESS_KEY=['\\x22]?\\$\\{\\{\\s*(secrets\\.|vars\\.)" ||
|
|
48
|
+
$step.run =~ "aws configure set aws_secret_access_key\\s+\\$\\{\\{\\s*(secrets\\.|vars\\.)" ||
|
|
49
|
+
$step.run =~ "AWS_SECRET_ACCESS_KEY=['\\x22]?[A-Za-z0-9/+=]+" ||
|
|
50
|
+
$step.run =~ "aws configure set aws_secret_access_key\\s+[A-Za-z0-9/+=]+"
|
|
51
|
+
), highlight: "run"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/claws/rule.rb
CHANGED
|
@@ -11,3 +11,7 @@ require "claws/rule/inherited_secrets"
|
|
|
11
11
|
require "claws/rule/command_injection"
|
|
12
12
|
require "claws/rule/bulk_permissions"
|
|
13
13
|
require "claws/rule/shellcheck"
|
|
14
|
+
require "claws/rule/checkout_with_static_credentials"
|
|
15
|
+
require "claws/rule/static_aws_credentials"
|
|
16
|
+
require "claws/rule/implicit_persist_credentials"
|
|
17
|
+
require "claws/rule/global_permissions_block"
|
data/lib/claws/version.rb
CHANGED
data/lib/claws/workflow.rb
CHANGED
|
@@ -4,7 +4,7 @@ require "claws/cli/yaml_with_lines"
|
|
|
4
4
|
class Workflow
|
|
5
5
|
extend Forwardable
|
|
6
6
|
|
|
7
|
-
attr_accessor :data, :on, :jobs, :name, :meta, :permissions
|
|
7
|
+
attr_accessor :data, :on, :jobs, :name, :meta, :permissions, :env
|
|
8
8
|
|
|
9
9
|
def_delegators :@workflow, :get_line, :include?, :keys
|
|
10
10
|
|
|
@@ -19,6 +19,7 @@ class Workflow
|
|
|
19
19
|
extract_normalized_jobs(@workflow)
|
|
20
20
|
extract_normalized_name(@workflow)
|
|
21
21
|
extract_permissions(@workflow)
|
|
22
|
+
extract_env(@workflow)
|
|
22
23
|
|
|
23
24
|
@raw_yaml = raw_yaml
|
|
24
25
|
end
|
|
@@ -35,6 +36,7 @@ class Workflow
|
|
|
35
36
|
return @on if key.to_s == "on"
|
|
36
37
|
return @jobs if key.to_s == "jobs"
|
|
37
38
|
return @name if key.to_s == "name"
|
|
39
|
+
return @env if key.to_s == "env"
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
def get_snippet(line, context: 3)
|
|
@@ -92,6 +94,10 @@ class Workflow
|
|
|
92
94
|
@meta["permissions"] = normalize_permissions(input["permissions"])
|
|
93
95
|
end
|
|
94
96
|
|
|
97
|
+
def extract_env(input)
|
|
98
|
+
@env = input["env"]
|
|
99
|
+
end
|
|
100
|
+
|
|
95
101
|
def normalize_permissions(input) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
96
102
|
permissions = {
|
|
97
103
|
read: [],
|
|
@@ -173,7 +179,7 @@ class Workflow
|
|
|
173
179
|
name, version = action.split("@", 2)
|
|
174
180
|
author = name.split("/", 2)[0]
|
|
175
181
|
local = author == "."
|
|
176
|
-
{ type: "action", name
|
|
182
|
+
{ type: "action", name:, author:, version:, local: }
|
|
177
183
|
end
|
|
178
184
|
|
|
179
185
|
def extract_container_info_from_job(job)
|
|
@@ -195,8 +201,8 @@ class Workflow
|
|
|
195
201
|
|
|
196
202
|
{
|
|
197
203
|
type: "container",
|
|
198
|
-
image
|
|
199
|
-
version
|
|
204
|
+
image:,
|
|
205
|
+
version:,
|
|
200
206
|
full: "#{image}:#{version}"
|
|
201
207
|
}
|
|
202
208
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: claws-scan
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Omar
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-08-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: equation
|
|
@@ -74,7 +74,6 @@ executables:
|
|
|
74
74
|
extensions: []
|
|
75
75
|
extra_rdoc_files: []
|
|
76
76
|
files:
|
|
77
|
-
- ".policy.yml"
|
|
78
77
|
- ".rspec"
|
|
79
78
|
- ".rubocop.yml"
|
|
80
79
|
- ".ruby-version"
|
|
@@ -85,6 +84,7 @@ files:
|
|
|
85
84
|
- Rakefile
|
|
86
85
|
- bin/analyze
|
|
87
86
|
- config.yml
|
|
87
|
+
- example-config.yml
|
|
88
88
|
- lib/claws.rb
|
|
89
89
|
- lib/claws/application.rb
|
|
90
90
|
- lib/claws/base_rule.rb
|
|
@@ -97,13 +97,17 @@ files:
|
|
|
97
97
|
- lib/claws/rule.rb
|
|
98
98
|
- lib/claws/rule/automatic_merge.rb
|
|
99
99
|
- lib/claws/rule/bulk_permissions.rb
|
|
100
|
+
- lib/claws/rule/checkout_with_static_credentials.rb
|
|
100
101
|
- lib/claws/rule/command_injection.rb
|
|
101
102
|
- lib/claws/rule/empty_name.rb
|
|
103
|
+
- lib/claws/rule/global_permissions_block.rb
|
|
104
|
+
- lib/claws/rule/implicit_persist_credentials.rb
|
|
102
105
|
- lib/claws/rule/inherited_secrets.rb
|
|
103
106
|
- lib/claws/rule/no_containers.rb
|
|
104
107
|
- lib/claws/rule/risky_triggers.rb
|
|
105
108
|
- lib/claws/rule/shellcheck.rb
|
|
106
109
|
- lib/claws/rule/special_permissions.rb
|
|
110
|
+
- lib/claws/rule/static_aws_credentials.rb
|
|
107
111
|
- lib/claws/rule/unapproved_runners.rb
|
|
108
112
|
- lib/claws/rule/unpinned_action.rb
|
|
109
113
|
- lib/claws/rule/unsafe_checkout.rb
|
|
@@ -123,7 +127,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
123
127
|
requirements:
|
|
124
128
|
- - ">="
|
|
125
129
|
- !ruby/object:Gem::Version
|
|
126
|
-
version: '3.
|
|
130
|
+
version: '3.2'
|
|
127
131
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
128
132
|
requirements:
|
|
129
133
|
- - ">="
|
data/.policy.yml
DELETED