From f76bb2263fe14d3e228a39cbf09fdd9b6e1a92e0 Mon Sep 17 00:00:00 2001 From: Aaron Smith <5852322+atyronesmith@users.noreply.github.com> Date: Fri, 21 Aug 2026 10:55:54 -0400 Subject: [PATCH 1/3] Potential fix for code scanning alert no. 14: Clear-text logging of sensitive information Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- quickpat/cli.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/quickpat/cli.py b/quickpat/cli.py index bf21799..8deb176 100644 --- a/quickpat/cli.py +++ b/quickpat/cli.py @@ -1338,6 +1338,16 @@ def cmd_validate(args): sys.exit(0 if result.valid else 1) +def _sanitize_warning_for_display(warning): + """Redact potentially sensitive warning details before printing.""" + text = str(warning) + lowered = text.lower() + sensitive_markers = ("secret", "token", "password", "apikey", "api_key", "key") + if any(marker in lowered for marker in sensitive_markers): + return "[REDACTED] Warning contains potentially sensitive details." + return text + + def _print_transform_result(result: TransformResult): if result.success: output_dir = result.pattern_dir @@ -1360,11 +1370,11 @@ def _print_transform_result(result: TransformResult): if result.warnings: print("\nWarnings:") for w in result.warnings: - print(f" {w}") + print(f" {_sanitize_warning_for_display(w)}") else: print("Transform failed:") for w in result.warnings: - print(f" {w}") + print(f" {_sanitize_warning_for_display(w)}") def ask(prompt, default=None): From 77677a69e5134620cb9b5bdb40c269938a3e9466 Mon Sep 17 00:00:00 2001 From: Aaron Smith Date: Fri, 21 Aug 2026 14:44:58 -0400 Subject: [PATCH 2/3] more security edits regarding secret printing add a test for the new handler --- quickpat/compose/spec_validator.py | 10 +++++++ tests/test_spec_validator.py | 47 ++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/quickpat/compose/spec_validator.py b/quickpat/compose/spec_validator.py index a6900b6..d25eb1d 100644 --- a/quickpat/compose/spec_validator.py +++ b/quickpat/compose/spec_validator.py @@ -251,6 +251,16 @@ def _check_secrets(spec: ApplicationSpec) -> list: message=f"{loc}: field '{field.name}' sets both 'value' and 'path' — " f"'path' takes precedence and 'value' will be ignored", )) + elif field.value is not None: + issues.append(Issue( + file='spec.yaml', + severity='warning', + message=f"{loc}: field '{field.name}' sets a literal 'value' — this gets " + f"written in cleartext into generated output (scripts/create-secrets.sh " + f"and templates/secrets/*.yaml), which is typically committed to git. " + f"Only use 'value' for non-sensitive defaults; for real credentials use " + f"'path' (local file, not embedded) or omit the field to prompt/generate.", + )) # SV-7: vault_path convention if secret.vault_path: diff --git a/tests/test_spec_validator.py b/tests/test_spec_validator.py index 793d576..5f42792 100644 --- a/tests/test_spec_validator.py +++ b/tests/test_spec_validator.py @@ -455,6 +455,53 @@ def test_secret_with_fields_no_warning(self, tmp_path): _, result, _ = _validate(spec_yaml, tmp_path) assert not any('fields' in w for w in _warnings(result)) + def test_secret_field_with_literal_value_is_warning(self, tmp_path): + spec_yaml = """\ + apiVersion: supplychain/v1alpha1 + kind: ApplicationSpec + metadata: + name: test + tier: sandbox + upstream: {} + blocks: {} + wiring: [] + custom: {} + vault: + enabled: true + secrets: + - name: my-key + vault_path: test/my-key + fields: + - name: api_key + value: sk-not-a-real-secret + """ + _, result, _ = _validate(spec_yaml, tmp_path) + assert result.valid # warning only + assert any('literal' in w and 'api_key' in w for w in _warnings(result)) + + def test_secret_field_with_path_no_literal_value_warning(self, tmp_path): + spec_yaml = """\ + apiVersion: supplychain/v1alpha1 + kind: ApplicationSpec + metadata: + name: test + tier: sandbox + upstream: {} + blocks: {} + wiring: [] + custom: {} + vault: + enabled: true + secrets: + - name: my-key + vault_path: test/my-key + fields: + - name: api_key + path: /run/secrets/api_key + """ + _, result, _ = _validate(spec_yaml, tmp_path) + assert not any('literal' in w for w in _warnings(result)) + # ── SV-7: vault_path convention ──────────────────────────────────────────────── From 4bc1f9648f50c7056880f809a1dd74c2c68ba40e Mon Sep 17 00:00:00 2001 From: Aaron Smith Date: Fri, 21 Aug 2026 14:50:39 -0400 Subject: [PATCH 3/3] json comments returns each comment's .id as a GraphQL node ID (e.g. IC_kwDO...), not the numeric REST database ID. --- .github/workflows/generate-patterns.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/generate-patterns.yml b/.github/workflows/generate-patterns.yml index 0fc27d0..a8aea1b 100644 --- a/.github/workflows/generate-patterns.yml +++ b/.github/workflows/generate-patterns.yml @@ -258,9 +258,10 @@ jobs: REPO="${{ github.repository }}" PR="${{ github.event.pull_request.number }}" - # Check for existing comment to update - EXISTING=$(gh pr view "$PR" --repo "$REPO" \ - --json comments --jq '.comments[] | select(.body | contains("quickpat-validation-summary")) | .id' \ + # Check for existing comment to update (numeric REST id, not the + # GraphQL node id `gh pr view --json comments` would return) + EXISTING=$(gh api "repos/${REPO}/issues/${PR}/comments" \ + --jq '.[] | select(.body | contains("quickpat-validation-summary")) | .id' \ 2>/dev/null | head -1) BODY="${MARKER}