# Semgrep DPDPA Compliance Rules
# Version: 1.0
# Last Updated: 2026-03-29
# Legal Reference: DPDPA 2023, DPDP Rules 2025
#
# Purpose: Detect privacy violations and DPDPA compliance gaps at the CI/CD stage
# Usage: semgrep --config semgrep-dpdpa-rules.yaml src/
#
# Categories:
# - PII Exposure (logging, printing, hardcoded data)
# - Encryption Violations (unencrypted storage, weak crypto)
# - Data Minimization (over-collection, unnecessary fields)
# - Purpose Binding (purpose drift, unauthorized processing)
# - Retention (hardcoded retention periods, missing TTL)
# - Indian PII Patterns (Aadhaar, PAN, Passport, Voter ID, Driving License)

rules:
  # ============================================================================
  # Category: PII in Logs (Rule 6(1)(a) masking/obfuscation - logs must not expose PII)
  # ============================================================================

  - id: dpdpa-pii-in-logs-python
    message: >
      Logging personal data undermines the DPDPA Section 8(5) duty to take
      reasonable security safeguards. Rule 6(1)(a) lists "encryption, obfuscation,
      masking or the use of virtual tokens"; mask or redact PII fields before logging.
      Note: Rule 6(1)(c) asks for "visibility on the accessing of such personal
      data, through appropriate logs" - log who accessed the data, not the data itself.
    severity: ERROR
    metadata:
      category: privacy
      subcategory: pii-exposure
      dpdpa_section: S.8(5), Rule 6(1)(a)
      cwe: CWE-532
      owasp: A09:2021-Security Logging and Monitoring Failures
      confidence: HIGH
    languages: [python]
    patterns:
      - pattern-either:
          - pattern: logging.$LEVEL(..., $EXPR, ...)
          - pattern: logger.$LEVEL(..., $EXPR, ...)
          - pattern: log.$LEVEL(..., $EXPR, ...)
      - metavariable-regex:
          metavariable: $EXPR
          regex: .*(email|phone|mobile|aadhaar|aadhar|pan_number|pan|passport|address|dob|date_of_birth|voter_id|dl_number|driving_license).*

  - id: dpdpa-pii-in-logs-javascript
    message: >
      Logging personal data undermines DPDPA S.8(5) safeguards (Rule 6(1)(a): mask or obfuscate). Do not log PII fields.
      Use structured logging with field-level redaction or sanitize before logging.
    severity: ERROR
    metadata:
      category: privacy
      subcategory: pii-exposure
      dpdpa_section: S.8(5), Rule 6(1)(a)
      confidence: HIGH
    languages: [javascript, typescript]
    patterns:
      - pattern-either:
          - pattern: console.$METHOD(..., $EXPR, ...)
          - pattern: logger.$METHOD(..., $EXPR, ...)
          - pattern: log.$METHOD(..., $EXPR, ...)
      - metavariable-regex:
          metavariable: $EXPR
          regex: .*(email|phone|mobile|aadhaar|pan|passport|address|dateOfBirth|voterId|dlNumber).*

  - id: dpdpa-pii-in-print-statements
    message: >
      Print statements containing PII can leak to stdout/stderr logs.
      Remove print statements with personal data or use redacted logging.
      Ref: S.8(5), Rule 6(1)(a) (masking).
    severity: WARNING
    metadata:
      category: privacy
      subcategory: pii-exposure
      dpdpa_section: Rule 6(1)(a)
      confidence: MEDIUM
    languages: [python]
    patterns:
      - pattern-either:
          - pattern: print(..., $EXPR, ...)
      - metavariable-regex:
          metavariable: $EXPR
          regex: .*(email|phone|aadhaar|pan|passport|address|user\.email|user\.phone|data_principal).*

  # ============================================================================
  # Category: Hardcoded PII Patterns (Aadhaar, PAN, etc.)
  # ============================================================================

  - id: dpdpa-hardcoded-aadhaar-pattern
    message: >
      Hardcoded Aadhaar number pattern detected (XXXX-XXXX-XXXX or 12 digits).
      Real Aadhaar numbers must never be in source code or config files.
      Use synthetic test data (e.g., "XXXX-XXXX-0000") per DPDPA Section 6(1)
      data minimization. If this is test data, replace with clearly fake values.
    severity: WARNING
    metadata:
      category: privacy
      subcategory: hardcoded-pii
      dpdpa_section: S.6(1)
      confidence: MEDIUM
    languages: [python, javascript, typescript, java, go, yaml, json]
    pattern-regex: '[0-9]{4}[\s\-][0-9]{4}[\s\-][0-9]{4}|[0-9]{12}'

  - id: dpdpa-hardcoded-pan-pattern
    message: >
      Hardcoded PAN number pattern detected (AAAAA0000A format).
      Real PAN numbers must not be in source code. Use synthetic test data
      (e.g., "AAAAA0000X") that clearly cannot be real PAN numbers.
    severity: WARNING
    metadata:
      category: privacy
      subcategory: hardcoded-pii
      dpdpa_section: S.6(1)
      confidence: MEDIUM
    languages: [python, javascript, typescript, java, go, yaml, json]
    pattern-regex: '[A-Z]{5}[0-9]{4}[A-Z]{1}'

  - id: dpdpa-hardcoded-email-pattern
    message: >
      Hardcoded email address detected. Verify this is not real PII.
      Use clearly fake domains (e.g., example.com, test.local) for test data.
    severity: INFO
    metadata:
      category: privacy
      subcategory: hardcoded-pii
      dpdpa_section: S.6(1)
      confidence: LOW
    languages: [python, javascript, typescript, java, go, yaml, json]
    pattern-regex: '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'
    paths:
      exclude:
        - '*.test.*'
        - '*.spec.*'
        - 'tests/'
        - '__tests__/'

  - id: dpdpa-hardcoded-phone-pattern-india
    message: >
      Hardcoded Indian phone number pattern detected (+91-XXXXXXXXXX or 10 digits).
      Verify this is not real PII. Use clearly fake numbers (e.g., +91-9999900000)
      for test fixtures per Section 6(1) data minimization.
    severity: WARNING
    metadata:
      category: privacy
      subcategory: hardcoded-pii
      dpdpa_section: S.6(1)
      confidence: MEDIUM
    languages: [python, javascript, typescript, java, go, yaml, json]
    pattern-regex: '(\+91[\s\-]?)?[6-9][0-9]{9}'

  # ============================================================================
  # Category: Encryption Violations (Rule 6(1)(a))
  # ============================================================================

  - id: dpdpa-unencrypted-pii-storage-django
    message: >
      Database field storing personal data without encryption config.
      Rule 6(1)(a) lists "securing of personal data through encryption, obfuscation,
      masking or the use of virtual tokens mapped to that personal data."
      Use field-level encryption (e.g., django-encrypted-model-fields) or ensure
      database has encryption at rest enabled with key management per Rule 6(1)(a).
    severity: WARNING
    metadata:
      category: privacy
      subcategory: encryption
      dpdpa_section: Rule 6(1)(a)
      cwe: CWE-311
      owasp: A02:2021-Cryptographic Failures
      confidence: MEDIUM
    languages: [python]
    patterns:
      - pattern: |
          class $MODEL(...):
              ...
              $FIELD = models.$FIELDTYPE(...)
              ...
      - metavariable-regex:
          metavariable: $FIELD
          regex: (aadhaar|pan|pan_number|email|phone|mobile|address|dob|date_of_birth|passport|voter_id|dl_number).*
      - pattern-not: $FIELD = models.CharField(..., encrypted=True, ...)
      - pattern-not: $FIELD = EncryptedField(...)

  - id: dpdpa-weak-encryption-algorithm
    message: >
      Weak or deprecated encryption algorithm detected. Rule 6(1)(a) lists
      encryption among its data security measures. Use industry-standard
      strong encryption: AES-256-GCM, ChaCha20-Poly1305, or RSA-2048+.
      Avoid MD5, SHA1, DES, 3DES, RC4 for cryptographic purposes.
    severity: ERROR
    metadata:
      category: privacy
      subcategory: encryption
      dpdpa_section: Rule 6(1)(a)
      cwe: CWE-327
      owasp: A02:2021-Cryptographic Failures
      confidence: HIGH
    languages: [python, javascript, typescript]
    pattern-either:
      - pattern: hashlib.md5(...)
      - pattern: hashlib.sha1(...)
      - pattern: Crypto.Cipher.DES.new(...)
      - pattern: Crypto.Cipher.ARC4.new(...)
      - pattern: crypto.createCipher('des', ...)
      - pattern: crypto.createCipher('rc4', ...)
      - pattern: crypto.createHash('md5')
      - pattern: crypto.createHash('sha1')

  - id: dpdpa-missing-tls-config
    message: >
      HTTP client/server configuration without explicit TLS enforcement.
      Rule 6(1)(a) lists encryption as a data security measure; apply it in transit. Ensure TLS 1.3 or TLS 1.2
      is enforced with strong cipher suites. Set verify=True for requests,
      require HTTPS endpoints, and reject cleartext HTTP for personal data.
    severity: WARNING
    metadata:
      category: privacy
      subcategory: encryption
      dpdpa_section: Rule 6(1)(a)
      cwe: CWE-319
      owasp: A02:2021-Cryptographic Failures
      confidence: MEDIUM
    languages: [python]
    pattern-either:
      - pattern: requests.$METHOD(..., verify=False, ...)
      - pattern: requests.$METHOD("http://...", ...)
      - pattern: urllib.request.urlopen("http://...", ...)

  # ============================================================================
  # Category: Data Minimization (Section 6(1))
  # ============================================================================

  - id: dpdpa-excessive-data-collection-serializer
    message: >
      API serializer using fields='__all__' may collect more data than necessary.
      DPDPA Section 6(1) requires "limited to such personal data as is necessary
      for such specified purpose." Explicitly list only required fields to comply
      with data minimization principle. Review fields against purpose specification.
    severity: WARNING
    metadata:
      category: privacy
      subcategory: data-minimization
      dpdpa_section: S.6(1)
      owasp: A01:2021-Broken Access Control
      confidence: MEDIUM
    languages: [python]
    patterns:
      - pattern: |
          class $SERIALIZER(...):
              ...
              class Meta:
                  ...
                  fields = '__all__'
                  ...

  - id: dpdpa-select-star-query
    message: >
      SELECT * query may retrieve more personal data than necessary for the
      processing purpose. Section 6(1) data minimization requires limiting
      collection to necessary fields. Use explicit column selection instead.
    severity: INFO
    metadata:
      category: privacy
      subcategory: data-minimization
      dpdpa_section: S.6(1)
      confidence: LOW
    languages: [python, javascript, typescript]
    pattern-regex: '(SELECT\s+\*\s+FROM|select\s+\*\s+from)'

  # ============================================================================
  # Category: Purpose Binding (Section 6(1) - specified purpose)
  # ============================================================================

  - id: dpdpa-purpose-drift-missing-purpose-param
    message: >
      Personal data query without purpose parameter/filter. Section 6(1) limits
      consent to "the processing of her personal data for the specified purpose".
      Ensure queries
      for personal data validate purpose and consent scope before retrieval.
    severity: WARNING
    metadata:
      category: privacy
      subcategory: purpose-binding
      dpdpa_section: S.6(1)
      confidence: MEDIUM
    languages: [python]
    patterns:
      - pattern: |
          $MODEL.objects.filter(...).filter($PII_FIELD=...)
      - pattern-not: $MODEL.objects.filter(..., purpose=..., ...)
      - metavariable-regex:
          metavariable: $PII_FIELD
          regex: (email|phone|aadhaar|pan|user_id|data_principal_id)

  - id: dpdpa-missing-consent-check-before-processing
    message: >
      Processing personal data without explicit consent verification.
      Section 6(1) requires consent to be "free, specific, informed, unconditional
      and unambiguous with a clear affirmative action". Check it before processing. Verify consent status and purpose scope before
      any personal data operation. Consider consent middleware or decorator.
    severity: WARNING
    metadata:
      category: privacy
      subcategory: consent
      dpdpa_section: S.6(1), S.6(10)
      confidence: MEDIUM
    languages: [python]
    patterns:
      - pattern: |
          def $FUNC(..., $USER, ...):
              ...
              $USER.$PII_FIELD
              ...
      - pattern-not: |
          def $FUNC(..., $USER, ...):
              ...
              if verify_consent(...):
                  ...
              ...
      - metavariable-regex:
          metavariable: $PII_FIELD
          regex: (email|phone|aadhaar|pan|personal_data)

  # ============================================================================
  # Category: Retention Periods (Section 8(7), Rule 8(1))
  # ============================================================================

  - id: dpdpa-hardcoded-retention-period
    message: >
      Hardcoded retention period detected. Retention policies should be configurable
      and based on Section 8(7) (erase once the purpose is served), the Rule 8(3)
      one-year minimum and, for the platform classes listed in the Third Schedule,
      the Rule 8(1) inactivity period. Use config file or database-driven retention
      policy instead of hardcoded values to ensure compliance with Section 8(7)
      erasure obligations.
    severity: WARNING
    metadata:
      category: privacy
      subcategory: retention
      dpdpa_section: S.8(7), Rule 8(1)
      confidence: MEDIUM
    languages: [python, javascript, typescript]
    pattern-regex: '(retention_days|ttl|expiry_days|delete_after)\s*=\s*[0-9]+'

  - id: dpdpa-missing-ttl-on-personal-data-cache
    message: >
      Cached personal data without TTL/expiration. Section 8(7) requires erasure
      upon purpose completion. Cached PII must have explicit TTL to comply with
      retention limits. Set cache expiration based on processing purpose
      (Section 8(7)) and, where it applies, the Rule 8(1) Third Schedule period.
    severity: WARNING
    metadata:
      category: privacy
      subcategory: retention
      dpdpa_section: S.8(7)
      confidence: MEDIUM
    languages: [python]
    patterns:
      - pattern-either:
          - pattern: cache.set($KEY, $VALUE)
          - pattern: redis_client.set($KEY, $VALUE)
      - pattern-not: cache.set($KEY, $VALUE, timeout=...)
      - pattern-not: redis_client.setex($KEY, $TTL, $VALUE)
      - metavariable-regex:
          metavariable: $KEY
          regex: .*(user|email|phone|pii|personal_data|data_principal).*

  # ============================================================================
  # Category: Access Controls (Rule 6(1)(b))
  # ============================================================================

  - id: dpdpa-missing-auth-check-pii-endpoint
    message: >
      API endpoint accessing personal data without authentication check.
      Rule 6(1)(b) requires "appropriate measures to control access to the
      computer resources." Ensure all PII endpoints have authentication and
      authorization checks. Use @login_required, @permission_required, or
      equivalent middleware.
    severity: ERROR
    metadata:
      category: privacy
      subcategory: access-control
      dpdpa_section: Rule 6(1)(b)
      cwe: CWE-306
      owasp: A01:2021-Broken Access Control
      confidence: HIGH
    languages: [python]
    patterns:
      - pattern: |
          def $FUNC(request, ...):
              ...
              $MODEL.objects.filter(..., $PII_FIELD=...)
              ...
      - pattern-not: |
          @login_required
          def $FUNC(request, ...):
              ...
      - pattern-not: |
          @permission_required(...)
          def $FUNC(request, ...):
              ...
      - metavariable-regex:
          metavariable: $PII_FIELD
          regex: (email|phone|aadhaar|pan|user|data_principal)

  # ============================================================================
  # Category: Cross-Border Transfer (Section 16)
  # ============================================================================

  - id: dpdpa-cross-border-transfer-check
    message: >
      Potential cross-border data transfer without jurisdiction validation.
      Section 16(1) empowers Central Government to restrict transfers to specific
      countries. Ensure destination country validation against MeitY notification
      list before transferring personal data outside India. Implement transfer
      validation middleware or infrastructure policy enforcement.
    severity: WARNING
    metadata:
      category: privacy
      subcategory: cross-border-transfer
      dpdpa_section: S.16(1)
      confidence: LOW
    languages: [python, javascript, typescript]
    pattern-either:
      - pattern: requests.$METHOD(..., url="https://$DOMAIN/...", ...)
      - pattern: axios.$METHOD("https://$DOMAIN/...", ...)
      - pattern: fetch("https://$DOMAIN/...", ...)

  # ============================================================================
  # Category: Breach Detection (Section 8(6), Rule 7)
  # ============================================================================

  - id: dpdpa-missing-audit-logging-pii-access
    message: >
      Personal data access without audit logging. Rule 6(1)(c) requires
      "visibility on the accessing of such personal data, through appropriate
      logs, monitoring and review." Rule 6(1)(e) requires retaining logs for
      minimum one year. Implement audit trail for all PII access events to
      enable breach detection per Section 8(6) and Rule 7 obligations.
    severity: WARNING
    metadata:
      category: privacy
      subcategory: audit-logging
      dpdpa_section: Rule 6(1)(c), Rule 6(1)(e)
      cwe: CWE-778
      confidence: MEDIUM
    languages: [python]
    patterns:
      - pattern: |
          def $FUNC(...):
              ...
              $USER.$PII_FIELD
              ...
      - pattern-not: |
          def $FUNC(...):
              ...
              audit_log.record(...)
              ...
              $USER.$PII_FIELD
              ...
      - metavariable-regex:
          metavariable: $PII_FIELD
          regex: (email|phone|aadhaar|pan|personal_data)

  # ============================================================================
  # Category: Data Processor Compliance (Section 8(1)-(2))
  # ============================================================================

  - id: dpdpa-third-party-api-without-dpa
    message: >
      Third-party API call that may process personal data. Section 8(2) allows
      a Data Processor to be engaged "only under a valid contract"; Rule 6(1)(f)
      requires that contract to provide for reasonable security safeguards, and
      Section 8(7)(b) requires you to cause the processor to erase data. Breach
      notice, subprocessor and audit-right clauses are good practice, not statutory text.
    severity: INFO
    metadata:
      category: privacy
      subcategory: data-processor
      dpdpa_section: S.8(1), S.8(2), Rule 6(1)(f)
      confidence: LOW
    languages: [python, javascript, typescript]
    pattern-either:
      - pattern: 'requests.$METHOD("https://$EXTERNAL_DOMAIN/...", data=..., ...)'
      - pattern: 'axios.$METHOD("https://$EXTERNAL_DOMAIN/...", ..., data=...)'
      - pattern: 'fetch("https://$EXTERNAL_DOMAIN/...", { method: ..., body: ... })'

  # ============================================================================
  # Category: Children's Data (Section 9)
  # ============================================================================

  - id: dpdpa-children-data-without-parental-consent
    message: >
      Processing data of users under 18 without parental consent check.
      Section 9(1) requires "verifiable consent of the parent" (manner: Rule 10).
      Section 9(3) prohibits tracking, behavioural monitoring, or targeted
      advertising of children. Implement age verification and parental consent
      workflow before processing data of minors.
    severity: ERROR
    metadata:
      category: privacy
      subcategory: children-data
      dpdpa_section: S.9(1), S.9(3), Rule 10
      confidence: MEDIUM
    languages: [python]
    patterns:
      - pattern: |
          def $FUNC(..., $USER, ...):
              ...
              if $USER.age < 18:
                  ...
                  $PROCESS_DATA
              ...
      - pattern-not: |
          def $FUNC(..., $USER, ...):
              ...
              if $USER.age < 18:
                  if verify_parental_consent(...):
                      ...
              ...

# ==============================================================================
# End of DPDPA Semgrep Rules
# ==============================================================================

# Usage Examples:
#
# 1. Scan all source files:
#    semgrep --config semgrep-dpdpa-rules.yaml src/
#
# 2. Scan with specific severity:
#    semgrep --config semgrep-dpdpa-rules.yaml --severity ERROR src/
#
# 3. Integrate with CI/CD (GitHub Actions):
#    - uses: returntocorp/semgrep-action@v1
#      with:
#        config: .semgrep/semgrep-dpdpa-rules.yaml
#
# 4. Generate JSON report:
#    semgrep --config semgrep-dpdpa-rules.yaml --json --output report.json src/
#
# 5. Exclude test files:
#    semgrep --config semgrep-dpdpa-rules.yaml --exclude 'tests/' src/
#
# For more information on Semgrep rule syntax:
# https://semgrep.dev/docs/writing-rules/rule-syntax/
#
# For DPDPA compliance resources:
# https://statechecksecurity.com/dpdpa-compliance
