Repository navigation
fix(ci): vendor secret scanner for allowlist, repair lockfile and workflow contracts #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3106fca
60e9736
58f62b7
7719fed
5aba1a4
99442b7
cfb0f55
49939bb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # SPDX-License-Identifier: MPL-2.0 | ||
| # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json | ||
| # CodeRabbit is advisory in this repository. The estate signing policy forbids | ||
| # bot pushes to PR branches; every landed commit must be GitHub-verified. | ||
| # Disable CodeRabbit's code-changing finishing touches. This does not reduce | ||
| # the GitHub App's permissions or disable a separately enabled Autopilot task. | ||
| reviews: | ||
| finishing_touches: | ||
| docstrings: | ||
| enabled: false | ||
| unit_tests: | ||
| enabled: false | ||
| autofix: | ||
| enabled: false | ||
| fix_ci: | ||
| enabled: false | ||
| resolve_merge_conflict: | ||
| enabled: false |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| #!/usr/bin/env bash | ||
| # SPDX-License-Identifier: MPL-2.0 | ||
| # Wrapper for the YAML-aware workflow contract checker. | ||
| set -euo pipefail | ||
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| if ! command -v python3 >/dev/null 2>&1; then | ||
| echo "ERROR: python3 is required to inspect workflow contracts" >&2 | ||
| exit 2 | ||
| fi | ||
| exec python3 "$SCRIPT_DIR/check_workflow_contracts.py" "$@" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,246 @@ | ||
| #!/usr/bin/env python3 | ||
| # SPDX-License-Identifier: MPL-2.0 | ||
| """Narrow, fail-closed workflow contracts for hyperpolymath/.github. | ||
|
|
||
| This is not a replacement for `gh actions-lock --verify`. It catches the | ||
| estate-specific regressions that a syntactically valid lock can still hide: | ||
| moved reusable owners, non-immutable reusable pins, missing SPDX headers, | ||
| synthetic lock edges for remote reusable workflows, and inline notification | ||
| action SHAs that disagree with actions.lock. | ||
|
|
||
| Exit codes: | ||
| 0 every inspected contract passed | ||
| 1 at least one contract violation | ||
| 2 not inspected (missing input, unreadable YAML, missing PyYAML) | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import pathlib | ||
| import re | ||
| import sys | ||
|
|
||
| try: | ||
| import yaml | ||
| except ImportError: # pragma: no cover - environment failure, not a pass | ||
| print("ERROR: PyYAML is required to inspect workflow contracts", file=sys.stderr) | ||
| sys.exit(2) | ||
|
|
||
|
|
||
| class UniqueKeyLoader(yaml.SafeLoader): | ||
| """SafeLoader that rejects duplicate mapping keys instead of hiding them.""" | ||
|
|
||
|
|
||
| def _unique_mapping(loader: UniqueKeyLoader, node: yaml.MappingNode, deep: bool = False) -> dict: | ||
| loader.flatten_mapping(node) | ||
| result: dict = {} | ||
| for key_node, value_node in node.value: | ||
| key = loader.construct_object(key_node, deep=deep) | ||
| if key in result: | ||
| raise ValueError(f"duplicate YAML key: {key!r}") | ||
| result[key] = loader.construct_object(value_node, deep=deep) | ||
| return result | ||
|
|
||
|
|
||
| UniqueKeyLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, _unique_mapping) | ||
|
|
||
| REMOTE_REUSABLE = re.compile(r"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+/\.github/workflows/[^@]+@(.+)$") | ||
| SHA40 = re.compile(r"^[0-9a-f]{40}$") | ||
| SMTP_REF = re.compile(r"^hyperpolymath/smtp-notify-action@(v[0-9]+\.[0-9]+\.[0-9]+)$") | ||
| LOCAL_SCANNER = "./.github/workflows/secret-scanner-reusable.yml" | ||
| VENDORED_SCANNER_PIN = "8d1a2aab4211493cf42f147f2080f024605797dc" | ||
| INLINE_SHA = re.compile(r"sha1-([0-9a-f]{40})") | ||
|
|
||
| failures: list[str] = [] | ||
|
|
||
|
|
||
| def fail(message: str) -> None: | ||
| """Report a contract violation and count it for the final exit status.""" | ||
| failures.append(message) | ||
|
|
||
|
|
||
| def load_yaml(path: pathlib.Path): | ||
| try: | ||
| text = path.read_text(encoding="utf-8") | ||
| return yaml.load(text, Loader=UniqueKeyLoader), text | ||
| except Exception as exc: | ||
| print(f"ERROR: cannot inspect {path}: {exc}", file=sys.stderr) | ||
| sys.exit(2) | ||
|
|
||
|
|
||
| def iter_uses(node, path=()): | ||
| """Yield (value, zero-based line, path) for every scalar `uses:` key.""" | ||
| if isinstance(node, yaml.MappingNode): | ||
| for key_node, value_node in node.value: | ||
| if not isinstance(key_node, yaml.ScalarNode): | ||
| continue | ||
| key = key_node.value | ||
| if key == "uses" and isinstance(value_node, yaml.ScalarNode): | ||
| yield value_node.value, value_node.start_mark.line, path | ||
| yield from iter_uses(value_node, path + (key,)) | ||
| elif isinstance(node, yaml.SequenceNode): | ||
| for index, item in enumerate(node.value): | ||
| yield from iter_uses(item, path + (str(index),)) | ||
|
|
||
|
|
||
| def main() -> int: | ||
| root = pathlib.Path(sys.argv[1]) if len(sys.argv) > 1 else pathlib.Path.cwd() | ||
| wf_dir = root / ".github" / "workflows" | ||
| lock_path = wf_dir / "actions.lock" | ||
|
|
||
| if not wf_dir.is_dir(): | ||
| print(f"ERROR: no workflow directory: {wf_dir}", file=sys.stderr) | ||
| return 2 | ||
| if not lock_path.is_file(): | ||
| print(f"ERROR: no actions.lock: {lock_path}", file=sys.stderr) | ||
| return 2 | ||
|
|
||
| workflows = sorted(list(wf_dir.glob("*.yml")) + list(wf_dir.glob("*.yaml"))) | ||
| if not workflows: | ||
| print("ERROR: no workflow files", file=sys.stderr) | ||
| return 2 | ||
|
|
||
| lock, _ = load_yaml(lock_path) | ||
| if not isinstance(lock, dict): | ||
| print("ERROR: actions.lock is not a mapping", file=sys.stderr) | ||
| return 2 | ||
| lock_workflows = lock.get("workflows") | ||
| dependencies = lock.get("dependencies") | ||
| if not isinstance(lock_workflows, dict) or not isinstance(dependencies, dict): | ||
| print("ERROR: actions.lock workflows/dependencies are not mappings", file=sys.stderr) | ||
| return 2 | ||
|
|
||
| for wf in workflows: | ||
| rel = f".github/workflows/{wf.name}" | ||
| try: | ||
| first_line = wf.read_text(encoding="utf-8").splitlines()[0] | ||
| except Exception as exc: | ||
| print(f"ERROR: cannot read {rel}: {exc}", file=sys.stderr) | ||
| return 2 | ||
| if first_line != "# SPDX-License-Identifier: MPL-2.0": | ||
| fail(f"{rel}: SPDX-License-Identifier must be the first line") | ||
|
|
||
| if rel not in lock_workflows: | ||
| fail(f"{rel}: missing actions.lock workflows entry") | ||
| entry = [] | ||
| else: | ||
| entry = lock_workflows[rel] | ||
| if not isinstance(entry, list): | ||
| fail(f"{rel}: actions.lock workflow entry is not a list") | ||
| entry = [] | ||
|
|
||
| data, text = load_yaml(wf) | ||
| try: | ||
| composed = yaml.compose(text, Loader=yaml.SafeLoader) | ||
| except Exception as exc: | ||
| print(f"ERROR: cannot compose {rel}: {exc}", file=sys.stderr) | ||
| return 2 | ||
| lines = text.splitlines() | ||
| uses = list(iter_uses(composed)) if composed is not None else [] | ||
|
|
||
| has_step_use = False | ||
| has_remote_reusable = False | ||
| for value, line_no, path in uses: | ||
| if not isinstance(value, str): | ||
| fail(f"{rel}:{line_no + 1}: uses value is not a string") | ||
| continue | ||
| if "steps" in path: | ||
| has_step_use = True | ||
|
|
||
| if value.startswith("hyperpolymath/standards/"): | ||
| fail(f"{rel}:{line_no + 1}: pre-transfer hyperpolymath/standards coordinate: {value}") | ||
|
|
||
| if value.startswith("./.github/workflows/"): | ||
| local_target = root / value[2:] | ||
| if not local_target.is_file(): | ||
| fail(f"{rel}:{line_no + 1}: local reusable workflow does not exist: {value}") | ||
|
|
||
| if "/.github/workflows/" in value and not value.startswith(("./", "$/")): | ||
| has_remote_reusable = True | ||
| match = REMOTE_REUSABLE.match(value) | ||
| if not match: | ||
| fail(f"{rel}:{line_no + 1}: remote reusable must name an exact ref: {value}") | ||
| elif not SHA40.match(match.group(1)): | ||
| fail(f"{rel}:{line_no + 1}: remote reusable must be pinned to a 40-hex commit: {value}") | ||
|
|
||
| # gh-actions-lock v0.4.0 does not follow remote job-level reusable | ||
| # workflows. A synthetic through-ref under a reusable-only caller is | ||
| # an unused pin and fails --verify. Keep the reviewed closure TSV as | ||
| # the trust record, but do not put that edge in the caller lock entry. | ||
| if has_remote_reusable and not has_step_use and entry: | ||
| fail(f"{rel}: reusable-only workflow must have an empty actions.lock entry") | ||
|
|
||
| for pin in entry: | ||
| if not isinstance(pin, str): | ||
| fail(f"{rel}: non-string actions.lock pin: {pin!r}") | ||
| elif pin not in dependencies: | ||
| fail(f"{rel}: actions.lock pin has no dependency record: {pin}") | ||
|
|
||
| if wf.name == "secret-scanner.yml": | ||
| scanner_uses = [v for v, _line, path in uses if isinstance(v, str) and "jobs" in path and "steps" not in path] | ||
| if scanner_uses != [LOCAL_SCANNER]: | ||
| fail(f"{rel}: must call the local vendored scanner {LOCAL_SCANNER} (Actions allowlist blocks the remote standards reusable)") | ||
| vendored = wf_dir / "secret-scanner-reusable.yml" | ||
| if not vendored.is_file(): | ||
| fail(f"{rel}: vendored scanner missing: {vendored}") | ||
| else: | ||
| vtext = vendored.read_text(encoding="utf-8") | ||
| if f"at {VENDORED_SCANNER_PIN}." not in vtext: | ||
| fail(f"{rel}: vendored scanner missing provenance pin {VENDORED_SCANNER_PIN}") | ||
|
Comment on lines
+187
to
+189
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The provenance check only searches for the pinned SHA in a comment, so any modification to the vendored executable workflow passes as long as line 4 remains unchanged. That means the contract cannot detect the silent drift it is intended to prevent or establish that the local copy actually matches Useful? React with 👍 / 👎. |
||
| if "ref: ${{ job.workflow_sha }}" in vtext: | ||
|
Comment on lines
+187
to
+190
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate the baseline checkout refs, not the provenance comment. If a standards checkout changes its Based on learnings, a security-invariant check must assert the exact required value rather than a loose substring.
|
||
| fail(f"{rel}: vendored scanner must pin standards baseline explicitly, not job.workflow_sha") | ||
|
|
||
| if wf.name == "push-email-notify.yml": | ||
| smtp_count = 0 | ||
| for value, line_no, _path in uses: | ||
| if not isinstance(value, str): | ||
| continue | ||
| if value.startswith("hyperpolymath/smtp-notify-action@"): | ||
| smtp_count += 1 | ||
| match = SMTP_REF.match(value) | ||
| if not match: | ||
| fail(f"{rel}:{line_no + 1}: smtp-notify-action must use an exact semver tag: {value}") | ||
| continue | ||
| else: | ||
| continue | ||
| tag = match.group(1) | ||
| raw_line = lines[line_no] if line_no < len(lines) else "" | ||
| inline = INLINE_SHA.search(raw_line) | ||
| if not inline: | ||
| fail(f"{rel}:{line_no + 1}: missing inline sha1 pin comment") | ||
| continue | ||
| sha = inline.group(1) | ||
| key = f"hyperpolymath/smtp-notify-action@{tag}" | ||
| dep = dependencies.get(key) | ||
| if not isinstance(dep, dict): | ||
| fail(f"actions.lock: missing dependency record for {key}") | ||
| elif dep.get("commit") != f"sha1-{sha}": | ||
| fail(f"actions.lock: {key} commit does not match inline sha1-{sha}") | ||
| if value not in entry: | ||
| fail(f"actions.lock: {rel} does not reference {value}") | ||
| if smtp_count == 0: | ||
| fail(f"{rel}: missing smtp-notify-action uses line") | ||
|
|
||
| for key, dep in dependencies.items(): | ||
| if not isinstance(dep, dict): | ||
| fail(f"actions.lock: dependency {key} is not a mapping") | ||
| continue | ||
| commit = dep.get("commit") | ||
| if not isinstance(commit, str) or not re.match(r"^sha1-[0-9a-f]{40}$", commit): | ||
| fail(f"actions.lock: dependency {key} has invalid commit: {commit!r}") | ||
| ref = dep.get("ref") | ||
| key_sha = key.rsplit("@", 1)[-1] | ||
| if SHA40.match(key_sha) and isinstance(ref, str) and ref == key_sha and "uses" not in dep: | ||
| fail(f"actions.lock: dependency {key} repeats its own SHA in ref") | ||
|
|
||
| if failures: | ||
| for message in failures: | ||
| print(f"ERROR: {message}", file=sys.stderr) | ||
| print(f"{len(failures)} workflow contract failure(s)", file=sys.stderr) | ||
| return 1 | ||
| print("workflow contracts: OK") | ||
| return 0 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| sys.exit(main()) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| # SPDX-License-Identifier: MPL-2.0 | ||
| # Contract-checker dependency. Hashes cover every PyPI artifact for this release. | ||
| PyYAML==6.0.3 \ | ||
| --hash=sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b \ | ||
| --hash=sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956 \ | ||
| --hash=sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8 \ | ||
| --hash=sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198 \ | ||
| --hash=sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b \ | ||
| --hash=sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0 \ | ||
| --hash=sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69 \ | ||
| --hash=sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e \ | ||
| --hash=sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c \ | ||
| --hash=sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e \ | ||
| --hash=sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824 \ | ||
| --hash=sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c \ | ||
| --hash=sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00 \ | ||
| --hash=sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d \ | ||
| --hash=sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a \ | ||
| --hash=sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4 \ | ||
| --hash=sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b \ | ||
| --hash=sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf \ | ||
| --hash=sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196 \ | ||
| --hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0 \ | ||
| --hash=sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28 \ | ||
| --hash=sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c \ | ||
| --hash=sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc \ | ||
| --hash=sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e \ | ||
| --hash=sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea \ | ||
| --hash=sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5 \ | ||
| --hash=sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b \ | ||
| --hash=sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd \ | ||
| --hash=sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8 \ | ||
| --hash=sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1 \ | ||
| --hash=sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c \ | ||
| --hash=sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5 \ | ||
| --hash=sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 \ | ||
| --hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 \ | ||
| --hash=sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be \ | ||
| --hash=sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26 \ | ||
| --hash=sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c \ | ||
| --hash=sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb \ | ||
| --hash=sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac \ | ||
| --hash=sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310 \ | ||
| --hash=sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7 \ | ||
| --hash=sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788 \ | ||
| --hash=sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5 \ | ||
| --hash=sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764 \ | ||
| --hash=sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35 \ | ||
| --hash=sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3 \ | ||
| --hash=sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba \ | ||
| --hash=sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c \ | ||
| --hash=sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702 \ | ||
| --hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \ | ||
| --hash=sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065 \ | ||
| --hash=sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65 \ | ||
| --hash=sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9 \ | ||
| --hash=sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b \ | ||
| --hash=sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac \ | ||
| --hash=sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3 \ | ||
| --hash=sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f \ | ||
| --hash=sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4 \ | ||
| --hash=sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3 \ | ||
| --hash=sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6 \ | ||
| --hash=sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369 \ | ||
| --hash=sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295 \ | ||
| --hash=sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b \ | ||
| --hash=sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da \ | ||
| --hash=sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917 \ | ||
| --hash=sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9 \ | ||
| --hash=sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5 \ | ||
| --hash=sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a \ | ||
| --hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 \ | ||
| --hash=sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7 \ | ||
| --hash=sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0 \ | ||
| --hash=sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007 \ | ||
| --hash=sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Require the vendored scanner to perform a Gitleaks scan.
This check validates the caller’s
usesvalue but does not validate the called workflow’s scan steps. Thevalidfixture has agitleaksjob with only a checkout step, yet it expects the checker to pass. A vendored workflow in that shape can report a successfulscan / gitleakscheck without scanning secrets. Check the required scan commands in the vendored workflow, or compare the vendored file with a reviewed content digest. (raw.githubusercontent.com)