gitlab-compliance 2.4.0__tar.gz → 2.6.0__tar.gz
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.
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/PKG-INFO +2 -2
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/pyproject.toml +4 -2
- gitlab_compliance-2.6.0/src/__init__.py +1 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/behave_support/steps/script_steps.py +113 -18
- gitlab_compliance-2.6.0/src/compliance/builtin_policies/baseline-jobs.feature +45 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/builtin_policies/component-inputs.feature +18 -1
- gitlab_compliance-2.6.0/src/compliance/builtin_policies/include-pinning.feature +46 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/builtin_policies/shell/shell-ci-conventions.feature +22 -4
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/builtin_policies/shell/shell-command-substitution.feature +15 -3
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/builtin_policies/shell/shell-conditionals.feature +15 -3
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/builtin_policies/shell/shell-error-handling.feature +21 -4
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/builtin_policies/shell/shell-file-operations.feature +20 -4
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/builtin_policies/shell/shell-pinning.feature +77 -11
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/builtin_policies/shell/shell-pipelines.feature +15 -3
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/builtin_policies/shell/shell-portability.feature +23 -4
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/builtin_policies/shell/shell-quoting.feature +26 -5
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/builtin_policies/shell/shell-references.feature +12 -2
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/builtin_policies/shell/shell-security.feature +39 -6
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/builtin_policies/supply-chain/image-pinning.feature +36 -5
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/builtin_policies/supply-chain/include-versions.feature +58 -7
- gitlab_compliance-2.6.0/src/compliance/builtin_policies/supply-chain/service-pinning.feature +30 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/builtin_policies/variable-allowlist.feature +13 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/console.py +25 -5
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/metadata.py +12 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/models.py +2 -0
- gitlab_compliance-2.6.0/src/compliance/policy_doc.py +341 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/policy_fix.py +12 -12
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/render.py +106 -26
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/runner.py +814 -623
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/script_analysis.py +384 -49
- gitlab_compliance-2.6.0/src/compliance/shell_lex.py +777 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/shell_render.py +76 -23
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/gitlab_compliance.py +39 -5
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/modules/gitstrings.py +7 -4
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/modules/swagger_markdown.py +3 -1
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/properties/table_render.py +2 -1
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/properties/yaml_paths.py +2 -1
- gitlab_compliance-2.4.0/src/__init__.py +0 -1
- gitlab_compliance-2.4.0/src/compliance/builtin_policies/baseline-jobs.feature +0 -17
- gitlab_compliance-2.4.0/src/compliance/builtin_policies/include-pinning.feature +0 -15
- gitlab_compliance-2.4.0/src/compliance/builtin_policies/supply-chain/service-pinning.feature +0 -7
- gitlab_compliance-2.4.0/src/compliance/policy_doc.py +0 -183
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/LICENSE +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/docs/index.md +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/__init__.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/api_config.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/api_enrichment.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/behave_support/environment.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/behave_support/steps/__init__.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/behave_support/steps/given_steps.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/behave_support/steps/then_steps.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/behave_support/steps/when_steps.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/builtin_policies/__init__.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/container_fix.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/gitlab_api.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/image_versions.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/include_fetch.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/include_fix.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/include_resolution.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/include_versions.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/merge_requests.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/model.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/oci_registry.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/release_cache.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/secret_redact.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/stash.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/compliance/supply_chain_fix.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/gitlab_docs.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/modules/__init__.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/modules/command_reference.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/modules/common.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/modules/constants.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/modules/doc_controller.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/modules/gitlab_reference.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/modules/job_composition.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/modules/logging.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/modules/output_filters.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/modules/pipeline_data.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/modules/release.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/modules/swagger_html.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/modules/yaml_lines.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/modules/yaml_md_table.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/properties/__init__.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/properties/extract_job_attribute.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/properties/includes.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/properties/inputs.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/properties/jobs.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/properties/variables.py +0 -0
- {gitlab_compliance-2.4.0 → gitlab_compliance-2.6.0}/src/properties/workflows.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitlab-compliance
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.0
|
|
4
4
|
Summary: BDD compliance testing for GitLab CI/CD pipelines and project settings — by MaturityBuilder
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -34,7 +34,7 @@ Requires-Dist: requests (==2.34.2)
|
|
|
34
34
|
Requires-Dist: rich (==15.0.0)
|
|
35
35
|
Requires-Dist: semver (==3.0.4)
|
|
36
36
|
Requires-Dist: typer (==0.27.0)
|
|
37
|
-
Requires-Dist: urllib3 (==2.
|
|
37
|
+
Requires-Dist: urllib3 (==2.8.0)
|
|
38
38
|
Project-URL: Documentation, https://maturitybuilder.github.io/gitlab-compliance/
|
|
39
39
|
Project-URL: Home Page, https://maturitybuilder.github.io/gitlab-compliance/
|
|
40
40
|
Project-URL: Repository, https://github.com/MaturityBuilder/gitlab-compliance
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "gitlab-compliance"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.6.0"
|
|
4
4
|
dynamic = [ "readme" ]
|
|
5
5
|
license = "Apache-2.0"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -42,7 +42,7 @@ pytablewriter = "1.2.1"
|
|
|
42
42
|
oyaml = "*"
|
|
43
43
|
typer = "0.27.0"
|
|
44
44
|
markdown-analysis = "0.1.5"
|
|
45
|
-
urllib3 = "2.
|
|
45
|
+
urllib3 = "2.8.0"
|
|
46
46
|
jq = "1.12.0"
|
|
47
47
|
click = "^8.1.8"
|
|
48
48
|
semver = "3.0.4"
|
|
@@ -62,6 +62,8 @@ gitlab-compliance = 'src.gitlab_compliance:gitlab_compliance'
|
|
|
62
62
|
[tool.poetry.group.dev.dependencies]
|
|
63
63
|
setuptools = "83.0.0"
|
|
64
64
|
pytest = "^9.0.3"
|
|
65
|
+
pytest-xdist = "^3.8.0"
|
|
66
|
+
pytest-cov = "^6.2.1"
|
|
65
67
|
pre-commit = "^4.6.1"
|
|
66
68
|
pyyaml = "^6.0.3"
|
|
67
69
|
coverage = "^7.9.2"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.4.0" # x-release-please-version
|
|
@@ -6,6 +6,33 @@ from behave import given, then, when
|
|
|
6
6
|
|
|
7
7
|
from src.compliance.behave_support.environment import _skip_remaining_steps
|
|
8
8
|
from src.compliance.script_analysis import (
|
|
9
|
+
evidence_backticks,
|
|
10
|
+
evidence_bashism_in_posix_test,
|
|
11
|
+
evidence_bashisms_without_bash,
|
|
12
|
+
evidence_chmod_777,
|
|
13
|
+
evidence_curl_missing_fail,
|
|
14
|
+
evidence_dangerous_rm,
|
|
15
|
+
evidence_deprecated_ci_build,
|
|
16
|
+
evidence_download_without_checksum,
|
|
17
|
+
evidence_eval,
|
|
18
|
+
evidence_hardcoded_secrets,
|
|
19
|
+
evidence_insecure_temp_files,
|
|
20
|
+
evidence_invalid_shebang,
|
|
21
|
+
evidence_masked_failure,
|
|
22
|
+
evidence_missing_strict_mode,
|
|
23
|
+
evidence_nested_backticks,
|
|
24
|
+
evidence_pipeline_without_pipefail,
|
|
25
|
+
evidence_posix_bashisms,
|
|
26
|
+
evidence_remote_pipe,
|
|
27
|
+
evidence_unpinned_docker,
|
|
28
|
+
evidence_unpinned_manager,
|
|
29
|
+
evidence_unquoted_command_substitution,
|
|
30
|
+
evidence_unquoted_path_variables,
|
|
31
|
+
evidence_unquoted_test_variables,
|
|
32
|
+
evidence_unquoted_variables,
|
|
33
|
+
evidence_unresolved_references,
|
|
34
|
+
evidence_unsafe_array_expansion,
|
|
35
|
+
evidence_unverified_git_clone,
|
|
9
36
|
format_script_violation,
|
|
10
37
|
job_has_effective_script,
|
|
11
38
|
job_has_script_field,
|
|
@@ -49,6 +76,19 @@ from src.compliance.script_analysis import (
|
|
|
49
76
|
from src.compliance.stash import filter_entities, name_starts_with
|
|
50
77
|
|
|
51
78
|
|
|
79
|
+
def _shell_check_verbose(context) -> bool:
|
|
80
|
+
config = getattr(context, "config", None)
|
|
81
|
+
userdata = getattr(config, "userdata", None) if config is not None else None
|
|
82
|
+
if not isinstance(userdata, dict):
|
|
83
|
+
return False
|
|
84
|
+
return str(userdata.get("shell_check_verbose", "false")).lower() in {
|
|
85
|
+
"true",
|
|
86
|
+
"1",
|
|
87
|
+
"yes",
|
|
88
|
+
"on",
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
52
92
|
def _set_stash(context, entities: list[dict]):
|
|
53
93
|
context.stash = list(entities)
|
|
54
94
|
context.step_mode = "given"
|
|
@@ -72,32 +112,36 @@ def _apply_filter(context, entities: list[dict], reason: str):
|
|
|
72
112
|
context.stash = entities
|
|
73
113
|
|
|
74
114
|
|
|
75
|
-
def _assert_none(context, predicate, message: str):
|
|
115
|
+
def _assert_none(context, predicate, message: str, evidence=None):
|
|
76
116
|
if context.scenario_skipped:
|
|
77
117
|
return
|
|
78
118
|
context.step_mode = "then"
|
|
79
119
|
if not context.stash:
|
|
80
120
|
raise AssertionError("No entities in stash to assert against.")
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if predicate(entity)
|
|
85
|
-
|
|
121
|
+
verbose = _shell_check_verbose(context)
|
|
122
|
+
failures = []
|
|
123
|
+
for entity in context.stash:
|
|
124
|
+
if not predicate(entity):
|
|
125
|
+
continue
|
|
126
|
+
found = evidence(entity) if verbose and evidence is not None else None
|
|
127
|
+
failures.append(format_script_violation(entity, message, found=found))
|
|
86
128
|
if failures:
|
|
87
129
|
raise AssertionError("; ".join(failures))
|
|
88
130
|
|
|
89
131
|
|
|
90
|
-
def _assert_all_true(context, predicate, message: str):
|
|
132
|
+
def _assert_all_true(context, predicate, message: str, evidence=None):
|
|
91
133
|
if context.scenario_skipped:
|
|
92
134
|
return
|
|
93
135
|
context.step_mode = "then"
|
|
94
136
|
if not context.stash:
|
|
95
137
|
raise AssertionError("No entities in stash to assert against.")
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if
|
|
100
|
-
|
|
138
|
+
verbose = _shell_check_verbose(context)
|
|
139
|
+
failures = []
|
|
140
|
+
for entity in context.stash:
|
|
141
|
+
if predicate(entity):
|
|
142
|
+
continue
|
|
143
|
+
found = evidence(entity) if verbose and evidence is not None else None
|
|
144
|
+
failures.append(format_script_violation(entity, message, found=found))
|
|
101
145
|
if failures:
|
|
102
146
|
raise AssertionError("; ".join(failures))
|
|
103
147
|
|
|
@@ -230,6 +274,7 @@ def then_variables_quoted(context):
|
|
|
230
274
|
context,
|
|
231
275
|
script_has_unquoted_variables,
|
|
232
276
|
"Unquoted variable expansions found",
|
|
277
|
+
evidence=evidence_unquoted_variables,
|
|
233
278
|
)
|
|
234
279
|
|
|
235
280
|
|
|
@@ -239,6 +284,7 @@ def then_path_variables_quoted(context):
|
|
|
239
284
|
context,
|
|
240
285
|
script_has_unquoted_path_variables,
|
|
241
286
|
"Unquoted variables used in file paths",
|
|
287
|
+
evidence=evidence_unquoted_path_variables,
|
|
242
288
|
)
|
|
243
289
|
|
|
244
290
|
|
|
@@ -248,6 +294,7 @@ def then_cmd_sub_quoted(context):
|
|
|
248
294
|
context,
|
|
249
295
|
script_has_unquoted_command_substitution,
|
|
250
296
|
"Unquoted command substitution found",
|
|
297
|
+
evidence=evidence_unquoted_command_substitution,
|
|
251
298
|
)
|
|
252
299
|
|
|
253
300
|
|
|
@@ -257,6 +304,7 @@ def then_array_at_syntax(context):
|
|
|
257
304
|
context,
|
|
258
305
|
script_has_unsafe_array_expansion,
|
|
259
306
|
"Unsafe array expansion found",
|
|
307
|
+
evidence=evidence_unsafe_array_expansion,
|
|
260
308
|
)
|
|
261
309
|
|
|
262
310
|
|
|
@@ -266,6 +314,7 @@ def then_strict_mode_table(context):
|
|
|
266
314
|
context,
|
|
267
315
|
script_enables_strict_mode,
|
|
268
316
|
"Multi-line script missing set -e/-u/pipefail",
|
|
317
|
+
evidence=evidence_missing_strict_mode,
|
|
269
318
|
)
|
|
270
319
|
|
|
271
320
|
|
|
@@ -277,7 +326,12 @@ def then_failure_handled(context):
|
|
|
277
326
|
return True
|
|
278
327
|
return not script_masks_failures(entity)
|
|
279
328
|
|
|
280
|
-
_assert_all_true(
|
|
329
|
+
_assert_all_true(
|
|
330
|
+
context,
|
|
331
|
+
ok,
|
|
332
|
+
"Command failures are not handled explicitly",
|
|
333
|
+
evidence=evidence_masked_failure,
|
|
334
|
+
)
|
|
281
335
|
|
|
282
336
|
|
|
283
337
|
@then("functions must propagate failures")
|
|
@@ -291,6 +345,7 @@ def then_functions_propagate(context):
|
|
|
291
345
|
context,
|
|
292
346
|
script_functions_mask_failures,
|
|
293
347
|
"Functions mask failures with || true",
|
|
348
|
+
evidence=evidence_masked_failure,
|
|
294
349
|
)
|
|
295
350
|
|
|
296
351
|
|
|
@@ -300,6 +355,7 @@ def then_file_paths_quoted(context):
|
|
|
300
355
|
context,
|
|
301
356
|
script_has_unquoted_path_variables,
|
|
302
357
|
"Unquoted file paths found",
|
|
358
|
+
evidence=evidence_unquoted_path_variables,
|
|
303
359
|
)
|
|
304
360
|
|
|
305
361
|
|
|
@@ -309,6 +365,7 @@ def then_mktemp_used(context):
|
|
|
309
365
|
context,
|
|
310
366
|
script_uses_insecure_temp_files,
|
|
311
367
|
"Insecure temporary file creation without mktemp",
|
|
368
|
+
evidence=evidence_insecure_temp_files,
|
|
312
369
|
)
|
|
313
370
|
|
|
314
371
|
|
|
@@ -318,12 +375,18 @@ def then_rm_protected(context):
|
|
|
318
375
|
context,
|
|
319
376
|
script_has_dangerous_rm,
|
|
320
377
|
"Dangerous recursive rm without path validation",
|
|
378
|
+
evidence=evidence_dangerous_rm,
|
|
321
379
|
)
|
|
322
380
|
|
|
323
381
|
|
|
324
382
|
@then("command substitution must use dollar parentheses")
|
|
325
383
|
def then_no_backticks(context):
|
|
326
|
-
_assert_none(
|
|
384
|
+
_assert_none(
|
|
385
|
+
context,
|
|
386
|
+
script_uses_backticks,
|
|
387
|
+
"Legacy backtick substitution found",
|
|
388
|
+
evidence=evidence_backticks,
|
|
389
|
+
)
|
|
327
390
|
|
|
328
391
|
|
|
329
392
|
@then("nested command substitution must use dollar parentheses")
|
|
@@ -332,6 +395,7 @@ def then_nested_dollar(context):
|
|
|
332
395
|
context,
|
|
333
396
|
script_has_nested_backticks,
|
|
334
397
|
"Nested backtick substitution found",
|
|
398
|
+
evidence=evidence_nested_backticks,
|
|
335
399
|
)
|
|
336
400
|
|
|
337
401
|
|
|
@@ -341,6 +405,7 @@ def then_posix_operators(context):
|
|
|
341
405
|
context,
|
|
342
406
|
script_has_bashism_in_posix_test,
|
|
343
407
|
"Non-portable operators in [ tests",
|
|
408
|
+
evidence=evidence_bashism_in_posix_test,
|
|
344
409
|
)
|
|
345
410
|
|
|
346
411
|
|
|
@@ -350,6 +415,7 @@ def then_test_vars_quoted(context):
|
|
|
350
415
|
context,
|
|
351
416
|
script_has_unquoted_test_variables,
|
|
352
417
|
"Unquoted variables in test expressions",
|
|
418
|
+
evidence=evidence_unquoted_test_variables,
|
|
353
419
|
)
|
|
354
420
|
|
|
355
421
|
|
|
@@ -359,12 +425,15 @@ def then_pipefail(context):
|
|
|
359
425
|
context,
|
|
360
426
|
lambda e: (not script_has_pipeline(e)) or script_has_pipefail(e),
|
|
361
427
|
"Pipeline used without pipefail",
|
|
428
|
+
evidence=evidence_pipeline_without_pipefail,
|
|
362
429
|
)
|
|
363
430
|
|
|
364
431
|
|
|
365
432
|
@then("eval must not be used")
|
|
366
433
|
def then_no_eval(context):
|
|
367
|
-
_assert_none(
|
|
434
|
+
_assert_none(
|
|
435
|
+
context, script_uses_eval, "eval is used in script", evidence=evidence_eval
|
|
436
|
+
)
|
|
368
437
|
|
|
369
438
|
|
|
370
439
|
@then("untrusted remote scripts must not be executed")
|
|
@@ -373,6 +442,7 @@ def then_no_remote_pipe(context):
|
|
|
373
442
|
context,
|
|
374
443
|
script_has_remote_pipe_to_shell,
|
|
375
444
|
"Remote pipe to shell (curl|sh) detected",
|
|
445
|
+
evidence=evidence_remote_pipe,
|
|
376
446
|
)
|
|
377
447
|
|
|
378
448
|
|
|
@@ -382,6 +452,7 @@ def then_user_input_safe(context):
|
|
|
382
452
|
context,
|
|
383
453
|
script_has_unquoted_variables,
|
|
384
454
|
"User/CI variables used without quoting",
|
|
455
|
+
evidence=evidence_unquoted_variables,
|
|
385
456
|
)
|
|
386
457
|
|
|
387
458
|
|
|
@@ -391,6 +462,7 @@ def then_no_secrets(context):
|
|
|
391
462
|
context,
|
|
392
463
|
script_has_hardcoded_secrets,
|
|
393
464
|
"Hardcoded secret pattern detected",
|
|
465
|
+
evidence=evidence_hardcoded_secrets,
|
|
394
466
|
)
|
|
395
467
|
|
|
396
468
|
|
|
@@ -400,6 +472,7 @@ def then_downloads_checksum(context):
|
|
|
400
472
|
context,
|
|
401
473
|
script_downloads_without_checksum,
|
|
402
474
|
"Download without nearby checksum verification",
|
|
475
|
+
evidence=evidence_download_without_checksum,
|
|
403
476
|
)
|
|
404
477
|
|
|
405
478
|
|
|
@@ -416,7 +489,12 @@ def then_packages_pinned(context, manager):
|
|
|
416
489
|
predicate = predicates.get(manager)
|
|
417
490
|
if predicate is None:
|
|
418
491
|
raise AssertionError(f"Unknown package manager '{manager}'")
|
|
419
|
-
_assert_none(
|
|
492
|
+
_assert_none(
|
|
493
|
+
context,
|
|
494
|
+
predicate,
|
|
495
|
+
f"Unpinned {manager} package install",
|
|
496
|
+
evidence=lambda entity: evidence_unpinned_manager(entity, manager),
|
|
497
|
+
)
|
|
420
498
|
|
|
421
499
|
|
|
422
500
|
@then("docker commands must pin container images to a tag or sha256 digest")
|
|
@@ -425,6 +503,7 @@ def then_docker_images_pinned(context):
|
|
|
425
503
|
context,
|
|
426
504
|
script_has_unpinned_docker_image,
|
|
427
505
|
"Docker command references an unpinned container image",
|
|
506
|
+
evidence=evidence_unpinned_docker,
|
|
428
507
|
)
|
|
429
508
|
|
|
430
509
|
|
|
@@ -434,12 +513,18 @@ def then_git_clone_verified(context):
|
|
|
434
513
|
context,
|
|
435
514
|
script_has_unverified_git_clone,
|
|
436
515
|
"git clone without commit/tag verification",
|
|
516
|
+
evidence=evidence_unverified_git_clone,
|
|
437
517
|
)
|
|
438
518
|
|
|
439
519
|
|
|
440
520
|
@then("curl must use fail flag")
|
|
441
521
|
def then_curl_fail(context):
|
|
442
|
-
_assert_none(
|
|
522
|
+
_assert_none(
|
|
523
|
+
context,
|
|
524
|
+
script_curl_missing_fail,
|
|
525
|
+
"curl without --fail/-f",
|
|
526
|
+
evidence=evidence_curl_missing_fail,
|
|
527
|
+
)
|
|
443
528
|
|
|
444
529
|
|
|
445
530
|
@then("deprecated CI_BUILD variables must not be used")
|
|
@@ -450,6 +535,7 @@ def then_no_ci_build(context):
|
|
|
450
535
|
context,
|
|
451
536
|
script_uses_deprecated_ci_build_vars,
|
|
452
537
|
"Deprecated $CI_BUILD_* variable used",
|
|
538
|
+
evidence=evidence_deprecated_ci_build,
|
|
453
539
|
)
|
|
454
540
|
|
|
455
541
|
|
|
@@ -459,6 +545,7 @@ def then_no_unresolved_refs(context):
|
|
|
459
545
|
context,
|
|
460
546
|
script_has_unresolved_references,
|
|
461
547
|
"Unresolved !reference in script blocks",
|
|
548
|
+
evidence=evidence_unresolved_references,
|
|
462
549
|
)
|
|
463
550
|
|
|
464
551
|
|
|
@@ -468,6 +555,7 @@ def then_shebang_valid(context):
|
|
|
468
555
|
context,
|
|
469
556
|
lambda e: (not script_has_shebang(e)) or script_shebang_is_valid(e),
|
|
470
557
|
"Invalid shebang in script block",
|
|
558
|
+
evidence=evidence_invalid_shebang,
|
|
471
559
|
)
|
|
472
560
|
|
|
473
561
|
|
|
@@ -477,6 +565,7 @@ def then_bash_declared(context):
|
|
|
477
565
|
context,
|
|
478
566
|
script_bashisms_without_bash_shebang,
|
|
479
567
|
"Bash-specific syntax without bash shebang",
|
|
568
|
+
evidence=evidence_bashisms_without_bash,
|
|
480
569
|
)
|
|
481
570
|
|
|
482
571
|
|
|
@@ -486,9 +575,15 @@ def then_posix_no_bashisms(context):
|
|
|
486
575
|
context,
|
|
487
576
|
script_posix_shebang_with_bashisms,
|
|
488
577
|
"Bashisms used with sh shebang",
|
|
578
|
+
evidence=evidence_posix_bashisms,
|
|
489
579
|
)
|
|
490
580
|
|
|
491
581
|
|
|
492
582
|
@then("chmod 777 must not be used")
|
|
493
583
|
def then_no_chmod_777(context):
|
|
494
|
-
_assert_none(
|
|
584
|
+
_assert_none(
|
|
585
|
+
context,
|
|
586
|
+
script_has_chmod_777,
|
|
587
|
+
"chmod 777 is used",
|
|
588
|
+
evidence=evidence_chmod_777,
|
|
589
|
+
)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# METADATA
|
|
2
|
+
# title: Baseline job controls
|
|
3
|
+
# description: Core job image and execution rules bundled with gitlab-compliance.
|
|
4
|
+
# custom:
|
|
5
|
+
# id: GLCI-BUILTIN-BASELINE
|
|
6
|
+
# severity: HIGH
|
|
7
|
+
# owasp_cicd:
|
|
8
|
+
# - CICD-SEC-3
|
|
9
|
+
# - CICD-SEC-7
|
|
10
|
+
# iso27001:
|
|
11
|
+
# - A.8.25
|
|
12
|
+
# - A.8.9
|
|
13
|
+
Feature: Baseline job controls
|
|
14
|
+
|
|
15
|
+
# METADATA
|
|
16
|
+
# title: Job images must not use the latest tag
|
|
17
|
+
# custom:
|
|
18
|
+
# id: GLCI-BUILTIN-BASELINE-01
|
|
19
|
+
# severity: HIGH
|
|
20
|
+
# owasp_cicd:
|
|
21
|
+
# - CICD-SEC-3
|
|
22
|
+
# - CICD-SEC-9
|
|
23
|
+
# iso27001:
|
|
24
|
+
# - A.8.25
|
|
25
|
+
# - A.8.9
|
|
26
|
+
Scenario: Job images must not use the latest tag
|
|
27
|
+
Given I have any job defined
|
|
28
|
+
When it has image
|
|
29
|
+
Then its image must not match ":latest$"
|
|
30
|
+
|
|
31
|
+
# METADATA
|
|
32
|
+
# title: Non-template jobs must define rules
|
|
33
|
+
# custom:
|
|
34
|
+
# id: GLCI-BUILTIN-BASELINE-02
|
|
35
|
+
# severity: HIGH
|
|
36
|
+
# owasp_cicd:
|
|
37
|
+
# - CICD-SEC-1
|
|
38
|
+
# - CICD-SEC-7
|
|
39
|
+
# iso27001:
|
|
40
|
+
# - A.8.9
|
|
41
|
+
# - A.8.32
|
|
42
|
+
Scenario: Non-template jobs must define rules
|
|
43
|
+
Given I have any job defined
|
|
44
|
+
When its name does not start with "."
|
|
45
|
+
Then it must contain rules
|
|
@@ -2,10 +2,27 @@
|
|
|
2
2
|
# title: Component input constraints
|
|
3
3
|
# description: Advanced Scenario Outline for component inputs and conditional fields.
|
|
4
4
|
# custom:
|
|
5
|
-
# id: GLCI-BUILTIN-COMPONENT
|
|
5
|
+
# id: GLCI-BUILTIN-COMPONENT
|
|
6
6
|
# severity: MEDIUM
|
|
7
|
+
# owasp_cicd:
|
|
8
|
+
# - CICD-SEC-7
|
|
9
|
+
# - CICD-SEC-8
|
|
10
|
+
# iso27001:
|
|
11
|
+
# - A.8.9
|
|
12
|
+
# - A.8.25
|
|
7
13
|
Feature: Component input constraints
|
|
8
14
|
|
|
15
|
+
# METADATA
|
|
16
|
+
# title: Component input mode constrains dependent fields
|
|
17
|
+
# custom:
|
|
18
|
+
# id: GLCI-BUILTIN-COMPONENT-01
|
|
19
|
+
# severity: MEDIUM
|
|
20
|
+
# owasp_cicd:
|
|
21
|
+
# - CICD-SEC-7
|
|
22
|
+
# - CICD-SEC-8
|
|
23
|
+
# iso27001:
|
|
24
|
+
# - A.8.9
|
|
25
|
+
# - A.8.25
|
|
9
26
|
Scenario Outline: Component input mode constrains dependent fields
|
|
10
27
|
Given I have include type "component" defined
|
|
11
28
|
When the entity input <input> equals <value>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# METADATA
|
|
2
|
+
# title: Include pinning essentials
|
|
3
|
+
# description: Baseline checks for component and fragment version pinning.
|
|
4
|
+
# custom:
|
|
5
|
+
# id: GLCI-BUILTIN-INCPIN
|
|
6
|
+
# severity: HIGH
|
|
7
|
+
# owasp_cicd:
|
|
8
|
+
# - CICD-SEC-3
|
|
9
|
+
# - CICD-SEC-9
|
|
10
|
+
# iso27001:
|
|
11
|
+
# - A.8.25
|
|
12
|
+
# - A.8.9
|
|
13
|
+
# - A.5.19
|
|
14
|
+
Feature: Include pinning essentials
|
|
15
|
+
|
|
16
|
+
# METADATA
|
|
17
|
+
# title: Component includes must not track a branch
|
|
18
|
+
# custom:
|
|
19
|
+
# id: GLCI-BUILTIN-INCPIN-01
|
|
20
|
+
# severity: HIGH
|
|
21
|
+
# owasp_cicd:
|
|
22
|
+
# - CICD-SEC-3
|
|
23
|
+
# - CICD-SEC-9
|
|
24
|
+
# iso27001:
|
|
25
|
+
# - A.8.25
|
|
26
|
+
# - A.8.9
|
|
27
|
+
# - A.5.19
|
|
28
|
+
Scenario: Component includes must not track a branch
|
|
29
|
+
Given I have include type "component" defined
|
|
30
|
+
Then its version must not match "^(main|master|develop)$"
|
|
31
|
+
|
|
32
|
+
# METADATA
|
|
33
|
+
# title: Component includes must use semver
|
|
34
|
+
# custom:
|
|
35
|
+
# id: GLCI-BUILTIN-INCPIN-02
|
|
36
|
+
# severity: HIGH
|
|
37
|
+
# owasp_cicd:
|
|
38
|
+
# - CICD-SEC-3
|
|
39
|
+
# - CICD-SEC-9
|
|
40
|
+
# iso27001:
|
|
41
|
+
# - A.8.25
|
|
42
|
+
# - A.8.9
|
|
43
|
+
# - A.5.19
|
|
44
|
+
Scenario: Component includes must use semver
|
|
45
|
+
Given I have include type "component" defined
|
|
46
|
+
Then its version must match "^\\d+\\.\\d+\\.\\d+(-[\\w.]+)?$"
|
|
@@ -2,15 +2,24 @@
|
|
|
2
2
|
# title: GitLab CI script conventions
|
|
3
3
|
# description: CI-specific hygiene for curl and deprecated variables.
|
|
4
4
|
# custom:
|
|
5
|
-
# id: GLCI-SHELL-CI
|
|
5
|
+
# id: GLCI-BUILTIN-SHELL-CI
|
|
6
6
|
# severity: MEDIUM
|
|
7
|
+
# owasp_cicd:
|
|
8
|
+
# - CICD-SEC-7
|
|
9
|
+
# iso27001:
|
|
10
|
+
# - A.8.25
|
|
11
|
+
# - A.8.28
|
|
7
12
|
Feature: GitLab CI script conventions
|
|
8
13
|
|
|
9
14
|
# METADATA
|
|
10
15
|
# title: curl uses fail flag
|
|
11
16
|
# custom:
|
|
12
|
-
# id: GLCI-SHELL-CI-
|
|
17
|
+
# id: GLCI-BUILTIN-SHELL-CI-01
|
|
13
18
|
# severity: MEDIUM
|
|
19
|
+
# owasp_cicd:
|
|
20
|
+
# - CICD-SEC-7
|
|
21
|
+
# iso27001:
|
|
22
|
+
# - A.8.28
|
|
14
23
|
Scenario: curl must use fail flag
|
|
15
24
|
Given I have any job with effective script defined
|
|
16
25
|
Then curl must use fail flag
|
|
@@ -18,8 +27,13 @@ Feature: GitLab CI script conventions
|
|
|
18
27
|
# METADATA
|
|
19
28
|
# title: Deprecated CI_BUILD variables are not used
|
|
20
29
|
# custom:
|
|
21
|
-
# id: GLCI-SHELL-CI-
|
|
30
|
+
# id: GLCI-BUILTIN-SHELL-CI-02
|
|
22
31
|
# severity: MEDIUM
|
|
32
|
+
# owasp_cicd:
|
|
33
|
+
# - CICD-SEC-7
|
|
34
|
+
# iso27001:
|
|
35
|
+
# - A.8.9
|
|
36
|
+
# - A.8.25
|
|
23
37
|
Scenario: Deprecated CI_BUILD variables must not be used
|
|
24
38
|
Given I have any job with effective script defined
|
|
25
39
|
Then deprecated CI_BUILD variables must not be used
|
|
@@ -27,8 +41,12 @@ Feature: GitLab CI script conventions
|
|
|
27
41
|
# METADATA
|
|
28
42
|
# title: CI variables should be quoted
|
|
29
43
|
# custom:
|
|
30
|
-
# id: GLCI-SHELL-CI-
|
|
44
|
+
# id: GLCI-BUILTIN-SHELL-CI-03
|
|
31
45
|
# severity: MEDIUM
|
|
46
|
+
# owasp_cicd:
|
|
47
|
+
# - CICD-SEC-7
|
|
48
|
+
# iso27001:
|
|
49
|
+
# - A.8.28
|
|
32
50
|
Scenario: CI variables should be quoted
|
|
33
51
|
Given I have any job with effective script defined
|
|
34
52
|
Then user supplied variables must be quoted or validated
|
|
@@ -2,15 +2,23 @@
|
|
|
2
2
|
# title: Command substitution standards
|
|
3
3
|
# description: Prefer modern dollar-parentheses substitution over backticks.
|
|
4
4
|
# custom:
|
|
5
|
-
# id: GLCI-SHELL-SUB
|
|
5
|
+
# id: GLCI-BUILTIN-SHELL-SUB
|
|
6
6
|
# severity: MEDIUM
|
|
7
|
+
# owasp_cicd:
|
|
8
|
+
# - CICD-SEC-7
|
|
9
|
+
# iso27001:
|
|
10
|
+
# - A.8.28
|
|
7
11
|
Feature: Command substitution
|
|
8
12
|
|
|
9
13
|
# METADATA
|
|
10
14
|
# title: Legacy backticks are not used
|
|
11
15
|
# custom:
|
|
12
|
-
# id: GLCI-SHELL-SUB-
|
|
16
|
+
# id: GLCI-BUILTIN-SHELL-SUB-01
|
|
13
17
|
# severity: MEDIUM
|
|
18
|
+
# owasp_cicd:
|
|
19
|
+
# - CICD-SEC-7
|
|
20
|
+
# iso27001:
|
|
21
|
+
# - A.8.28
|
|
14
22
|
Scenario: Legacy backticks are not used
|
|
15
23
|
Given I have any job with effective script defined
|
|
16
24
|
Then command substitution must use dollar parentheses
|
|
@@ -18,8 +26,12 @@ Feature: Command substitution
|
|
|
18
26
|
# METADATA
|
|
19
27
|
# title: Nested command substitution is readable
|
|
20
28
|
# custom:
|
|
21
|
-
# id: GLCI-SHELL-SUB-
|
|
29
|
+
# id: GLCI-BUILTIN-SHELL-SUB-02
|
|
22
30
|
# severity: MEDIUM
|
|
31
|
+
# owasp_cicd:
|
|
32
|
+
# - CICD-SEC-7
|
|
33
|
+
# iso27001:
|
|
34
|
+
# - A.8.28
|
|
23
35
|
Scenario: Nested command substitution is readable
|
|
24
36
|
Given I have any job with effective script defined
|
|
25
37
|
Then nested command substitution must use dollar parentheses
|
|
@@ -2,15 +2,23 @@
|
|
|
2
2
|
# title: Conditional expression standards
|
|
3
3
|
# description: Portable and safely quoted test expressions.
|
|
4
4
|
# custom:
|
|
5
|
-
# id: GLCI-SHELL-TEST
|
|
5
|
+
# id: GLCI-BUILTIN-SHELL-TEST
|
|
6
6
|
# severity: MEDIUM
|
|
7
|
+
# owasp_cicd:
|
|
8
|
+
# - CICD-SEC-7
|
|
9
|
+
# iso27001:
|
|
10
|
+
# - A.8.28
|
|
7
11
|
Feature: Conditional expressions
|
|
8
12
|
|
|
9
13
|
# METADATA
|
|
10
14
|
# title: Test operators are portable
|
|
11
15
|
# custom:
|
|
12
|
-
# id: GLCI-SHELL-TEST-
|
|
16
|
+
# id: GLCI-BUILTIN-SHELL-TEST-01
|
|
13
17
|
# severity: MEDIUM
|
|
18
|
+
# owasp_cicd:
|
|
19
|
+
# - CICD-SEC-7
|
|
20
|
+
# iso27001:
|
|
21
|
+
# - A.8.28
|
|
14
22
|
Scenario: Test operators are portable
|
|
15
23
|
Given I have any job with effective script defined
|
|
16
24
|
Then POSIX compliant operators must be used
|
|
@@ -18,8 +26,12 @@ Feature: Conditional expressions
|
|
|
18
26
|
# METADATA
|
|
19
27
|
# title: Variables are quoted in test statements
|
|
20
28
|
# custom:
|
|
21
|
-
# id: GLCI-SHELL-TEST-
|
|
29
|
+
# id: GLCI-BUILTIN-SHELL-TEST-02
|
|
22
30
|
# severity: HIGH
|
|
31
|
+
# owasp_cicd:
|
|
32
|
+
# - CICD-SEC-7
|
|
33
|
+
# iso27001:
|
|
34
|
+
# - A.8.28
|
|
23
35
|
Scenario: Variables are quoted in test statements
|
|
24
36
|
Given I have any job with effective script defined
|
|
25
37
|
Then variables in test expressions must be quoted
|