claws-scan 0.9.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/Gemfile.lock +17 -11
- data/README.md +121 -0
- data/example-config.yml +1 -0
- data/lib/claws/base_rule.rb +24 -3
- 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 +3 -0
- data/lib/claws/version.rb +1 -1
- data/lib/claws/workflow.rb +7 -1
- metadata +5 -3
- 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/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
claws-scan (0.9.
|
|
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
|
@@ -292,6 +292,12 @@ If you need a Github Token to perform some authenticated action where the defaul
|
|
|
292
292
|
|
|
293
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
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
|
+
|
|
295
301
|
### UnapprovedRunners
|
|
296
302
|
|
|
297
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.
|
|
@@ -395,6 +401,121 @@ This rule only looks for user supplied branches being checked out for `pull_requ
|
|
|
395
401
|
|-------------|------------------------|---------------------------------------------------------------|
|
|
396
402
|
| `risky_events` | ["pull_request_target", "workflow_dispatch"] | An array of Github events you consider risky. |
|
|
397
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
|
+
|
|
398
519
|
## Walkthrough
|
|
399
520
|
|
|
400
521
|
Let's start with a minimal configuration file that enables some basic Rules.
|
data/example-config.yml
CHANGED
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: {
|
|
@@ -15,7 +15,28 @@ class BaseRule
|
|
|
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 }
|
|
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
|
+
}
|
|
19
40
|
}
|
|
20
41
|
)
|
|
21
42
|
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
|
@@ -12,3 +12,6 @@ require "claws/rule/command_injection"
|
|
|
12
12
|
require "claws/rule/bulk_permissions"
|
|
13
13
|
require "claws/rule/shellcheck"
|
|
14
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: [],
|
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.9.
|
|
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"
|
|
@@ -101,11 +100,14 @@ files:
|
|
|
101
100
|
- lib/claws/rule/checkout_with_static_credentials.rb
|
|
102
101
|
- lib/claws/rule/command_injection.rb
|
|
103
102
|
- lib/claws/rule/empty_name.rb
|
|
103
|
+
- lib/claws/rule/global_permissions_block.rb
|
|
104
|
+
- lib/claws/rule/implicit_persist_credentials.rb
|
|
104
105
|
- lib/claws/rule/inherited_secrets.rb
|
|
105
106
|
- lib/claws/rule/no_containers.rb
|
|
106
107
|
- lib/claws/rule/risky_triggers.rb
|
|
107
108
|
- lib/claws/rule/shellcheck.rb
|
|
108
109
|
- lib/claws/rule/special_permissions.rb
|
|
110
|
+
- lib/claws/rule/static_aws_credentials.rb
|
|
109
111
|
- lib/claws/rule/unapproved_runners.rb
|
|
110
112
|
- lib/claws/rule/unpinned_action.rb
|
|
111
113
|
- lib/claws/rule/unsafe_checkout.rb
|
data/.policy.yml
DELETED