rules:
  # ── Go: HTTP handler context hygiene ────────────────────────────────────────

  - id: veylant-context-background-in-handler
    languages: [go]
    severity: WARNING
    message: >
      HTTP handler uses context.Background() instead of r.Context().
      This bypasses request cancellation, tracing, and tenant context propagation.
      Use r.Context() to inherit the request lifetime.
    patterns:
      - pattern: |
          func $HANDLER($W http.ResponseWriter, $R *http.Request) {
            ...
            context.Background()
            ...
          }
    paths:
      include:
        - "internal/**/*.go"
        - "cmd/**/*.go"

  # ── Go: SQL injection risk ──────────────────────────────────────────────────

  - id: veylant-sql-string-concatenation
    languages: [go]
    severity: ERROR
    message: >
      SQL query built using string concatenation or fmt.Sprintf.
      This is a potential SQL injection vulnerability.
      Use parameterised queries ($1, $2, ...) or named placeholders instead.
    patterns:
      - pattern: db.QueryContext($CTX, $QUERY + $VAR, ...)
      - pattern: db.QueryRowContext($CTX, $QUERY + $VAR, ...)
      - pattern: db.ExecContext($CTX, $QUERY + $VAR, ...)
      - pattern: db.QueryContext($CTX, fmt.Sprintf(...), ...)
      - pattern: db.QueryRowContext($CTX, fmt.Sprintf(...), ...)
      - pattern: db.ExecContext($CTX, fmt.Sprintf(...), ...)
    paths:
      include:
        - "internal/**/*.go"

  # ── Go: Sensitive data in structured logs ───────────────────────────────────

  - id: veylant-sensitive-field-in-log
    languages: [go]
    severity: WARNING
    message: >
      Potentially sensitive field name logged. Ensure this does not contain PII,
      API keys, passwords, or tokens. Use redaction helpers for sensitive values.
    patterns:
      - pattern: zap.String("password", ...)
      - pattern: zap.String("api_key", ...)
      - pattern: zap.String("token", ...)
      - pattern: zap.String("secret", ...)
      - pattern: zap.String("Authorization", ...)
      - pattern: zap.String("email", ...)
      - pattern: zap.String("prompt", ...)
    paths:
      include:
        - "internal/**/*.go"
        - "cmd/**/*.go"

  # ── Go: Hardcoded credentials ───────────────────────────────────────────────

  - id: veylant-hardcoded-api-key
    languages: [go]
    severity: ERROR
    message: >
      Hardcoded string that looks like an API key or secret.
      API keys must be loaded from environment variables or Vault — never hardcoded.
    patterns:
      - pattern: |
          $KEY = "sk-..."
      - pattern: |
          APIKey: "sk-..."
    paths:
      include:
        - "internal/**/*.go"
        - "cmd/**/*.go"

  # ── Go: Missing request size limit ─────────────────────────────────────────

  - id: veylant-missing-max-bytes-reader
    languages: [go]
    severity: WARNING
    message: >
      HTTP request body decoded without http.MaxBytesReader().
      A client can send an unbounded body, causing memory exhaustion.
      Wrap r.Body with http.MaxBytesReader(w, r.Body, maxBytes) before decoding.
    patterns:
      - pattern: json.NewDecoder($R.Body).Decode(...)
    paths:
      include:
        - "internal/**/*.go"
    fix: |
      r.Body = http.MaxBytesReader(w, r.Body, 1<<20) // 1 MiB
      json.NewDecoder(r.Body).Decode(...)

  # ── Python: Eval/exec of user input ─────────────────────────────────────────

  - id: veylant-python-eval-user-input
    languages: [python]
    severity: ERROR
    message: >
      eval() or exec() called with a variable — potential code injection.
      Never evaluate user-supplied data.
    patterns:
      - pattern: eval($X)
      - pattern: exec($X)
    paths:
      include:
        - "services/**/*.py"
