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} 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): 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 ────────────────────────────────────────────────