Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/generate-patterns.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
14 changes: 12 additions & 2 deletions quickpat/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand Down
10 changes: 10 additions & 0 deletions quickpat/compose/spec_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
47 changes: 47 additions & 0 deletions tests/test_spec_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ────────────────────────────────────────────────

Expand Down
Loading