mirror of
https://github.com/storytold/craft-common-actions.git
synced 2026-11-02 18:06:16 +00:00
security(workflows): strengthen SonarCloud project-key and FreeBSD architecture input validation (F-005, F-006, F-007, F-009, F-010)
- Restrict SonarCloud project-key grammar to start with an alphanumeric/underscore, reject leading hyphens/flags, and enforce 400 character length limit (F-006) - Verify Nix extra-args argument parsing without eval (F-005) - Verify Windows ARM64 PowerShell environment variable passing and path component sanitization (F-007) - Add unit test verifying FreeBSD workflow architecture pre-validation rejects invalid architectures and injection payloads prior to VM execution (F-009) - Verify FreeBSD custom-test-command environment data passing (F-010)
This commit is contained in:
@@ -51,8 +51,8 @@ jobs:
|
||||
LC_ALL: C
|
||||
run: |
|
||||
# The scan action splits args; reject characters that could introduce properties.
|
||||
if [[ ! "$PROJECT_KEY" =~ ^[a-zA-Z0-9_.:-]+$ ]]; then
|
||||
echo "::error::Invalid SonarCloud project key: use only letters, digits, dash, underscore, period or colon."
|
||||
if [[ ! "$PROJECT_KEY" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_.:-]*$ || ${#PROJECT_KEY} -gt 400 ]]; then
|
||||
echo "::error::Invalid SonarCloud project key: must start with an alphanumeric character or underscore and contain only letters, digits, dash, underscore, period or colon (max 400 characters)."
|
||||
exit 1
|
||||
fi
|
||||
printf 'key=%s\n' "$PROJECT_KEY" >>"$GITHUB_OUTPUT"
|
||||
|
||||
@@ -398,6 +398,18 @@ fi
|
||||
text=True, capture_output=True, timeout=15)
|
||||
self.assertEqual(result.returncode, code, result)
|
||||
|
||||
def test_freebsd_workflow_rejects_unsupported_architectures_before_the_vm(self):
|
||||
workflow = yaml.load((ROOT / ".github/workflows/freebsd.yml").read_text(), Loader=yaml.BaseLoader)
|
||||
steps = workflow["jobs"]["freebsd"]["steps"]
|
||||
check = next(step for step in steps if step.get("name") == "Validate the architecture")
|
||||
self.assertEqual(check["env"]["ARCH"], "${{ matrix.arch }}")
|
||||
for arch, code in (("x86_64", 0), ("aarch64", 0), ("riscv64", 1), ("amd64", 1), ("", 1),
|
||||
("x86_64; id", 1), ("`touch injected`", 1)):
|
||||
with self.subTest(arch=arch):
|
||||
result = subprocess.run(["bash", "-e", "-c", check["run"]], env={**os.environ, "ARCH": arch},
|
||||
text=True, capture_output=True, timeout=15)
|
||||
self.assertEqual(result.returncode, code, result)
|
||||
|
||||
def vm_steps(self):
|
||||
release = yaml.load((ROOT / ".github/workflows/release.yml").read_text(), Loader=yaml.BaseLoader)
|
||||
for name, job in (("freebsd.yml", DATA["jobs"]["freebsd"]), ("release.yml", release["jobs"]["freebsd"])):
|
||||
|
||||
@@ -44,12 +44,14 @@ class SonarCloudTests(unittest.TestCase):
|
||||
|
||||
def test_invalid_keys_cannot_publish_scanner_arguments(self):
|
||||
for key in (
|
||||
'', 'key -Dsonar.host.url=https://attacker.invalid',
|
||||
'', '-Dsonar.host.url=https://attacker.invalid',
|
||||
'-leading-dash', 'key -Dsonar.host.url=https://attacker.invalid',
|
||||
'key\t-Dsonar.host.url=https://attacker.invalid',
|
||||
'key\n-Dsonar.host.url=https://attacker.invalid',
|
||||
'key\r\nkey=other', 'key" -Dsonar.host.url="https://attacker.invalid',
|
||||
"key' -Dsonar.host.url='https://attacker.invalid",
|
||||
'key\\ value', '$(touch injected)', '`touch injected`', 'key/other',
|
||||
'a' * 401,
|
||||
):
|
||||
with self.subTest(key=key):
|
||||
result, output = self.run_step(self.validation, PROJECT_KEY=key)
|
||||
|
||||
Reference in New Issue
Block a user