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:
Dylan M. Taylor
2026-10-24 22:44:09 -04:00
parent 13491f8120
commit 58e2894843
3 changed files with 17 additions and 3 deletions
+2 -2
View File
@@ -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"
+12
View File
@@ -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"])):
+3 -1
View File
@@ -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)