Skip to main content

dpdp-scan

Finds Indian personal data in code, schemas, fixtures and logs, and points each finding at the obligation under India's Digital Personal Data Protection Act, 2023 and the DPDP Rules, 2025 that it touches. A CLI and a GitHub Action, built to run in CI.

Status: v0.1.0 (6 November 2026). Value detectors, schema classifier, Semgrep rule pack, SARIF/JSON/Markdown outputs, config, baseline, a GitHub Action and a Docker image. See CHANGELOG.md; the build plan is in .claude/PLAN.md. Try it on DVDPA, a deliberately leaky demo app.

Not legal advice. dpdp-scan reports where personal data appears and which obligation it engages. It gives no compliance verdict. Whether a finding matters depends on your processing, your contracts and how the Data Protection Board reads the Rules.

What it does

Part What Why it matters
Validated detectors Aadhaar (Verhoeff checksum, first digit 2–9), PAN (holder-type character), GSTIN (mod-36 check character, state code, embedded PAN), IFSC, UPI VPA (known PSP handles), Indian mobile numbers, voter EPIC, passport, driving licence, email Checksums are what keep false positives down. A 12-digit order id is not an Aadhaar.
Schema classifier SQL DDL and migrations, Prisma, OpenAPI / JSON Schema; a field-name lexicon that knows aadhar_no, pan_no, mob_no, dob, father_name Most personal data in a repo is declared, not written out. The inventory starts here.
Semgrep rule pack Personal data written to logs; personal data sent to analytics SDKs; age or DOB collected with no age gate nearby (a Section 9 signal) The rules are plain YAML you can read, test and extend.
Outputs SARIF 2.1.0 for GitHub code scanning, a JSON data inventory, a Markdown summary SARIF puts findings in the Security tab; the inventory seeds a data map or RoPA.
CI ergonomics .dpdp-scan.yml, a baseline file to accept existing findings, --fail-on, documented exit codes, a pre-commit hook The gitleaks shape: drop it in, it only fails on what is new.

Masking is a ground rule. Every report, log line and SARIF snippet masks matched values (at most the last four characters). The scanner must never move an Aadhaar number from a repository into a CI log. A test fails the build if any raw value reaches an output.

Not in v0.1: cross-service data-flow mapping, free-text NER (an optional Presidio plug-in later), databases or live systems (it scans repositories only), any compliance score or verdict.

Precision and recall

Measured on a synthetic, labelled corpus that the benchmark generates (no real identifier is stored anywhere). Numbers are k/N with the misses named, so they can be checked and so nobody has to write "detects all Aadhaar numbers".

Precision and recall on the synthetic benchmark (2026-11-06, commit 82ab268). python -m dpdp_scan.bench, seed 20261106, 200 per class.

Detector Recall (found / positives) Precision (true / fired) Misses False hits
email 200/200 (100%) 200/200 (100%) none none
in_aadhaar 200/200 (100%) 200/236 (85%) none order_id_prefixed 21, random_12_digits 15
in_driving_licence 200/200 (100%) 200/200 (100%) none none
in_gstin 200/200 (100%) 200/200 (100%) none none
in_ifsc 200/200 (100%) 200/200 (100%) none none
in_mobile 200/200 (100%) 200/299 (67%) none random_10_digits 99
in_pan 200/200 (100%) 200/200 (100%) none none
in_passport 200/200 (100%) 200/200 (100%) none none
in_upi 200/200 (100%) 200/200 (100%) none none
in_voter_epic 200/200 (100%) 200/200 (100%) none none

Recall counts a positive as found at any confidence. Precision counts every firing on a negative as a false hit, including the ones the detector itself marks LOW. Two floors are structural: about one random 12-digit number in ten passes the Verhoeff checksum, and four random 10-digit numbers in nine start with 6 to 9 and so look like a mobile number. Both are reported at reduced confidence (MEDIUM with an order-id context, LOW for a bare ten digits), and field names are what lift real values above them. The corpus does not yet contain real-world text with Indian names, addresses or Hinglish, so these numbers are an upper bound on synthetic data, not a claim about your repository.

How it compares

Checked on 5 November 2026 against each project's repository.

Project Indian identifiers with checksums Scans code, schemas, logs Readable rules SARIF + Action Licence
dpdp-scan (v0.1 target) yes yes yes (Semgrep YAML) yes Apache-2.0
Privado no code data-flow (Joern) partial no SARIF LGPL-3.0
Bearer CLI no code, 122 data types partial yes Elastic 2.0 (not open source)
Presidio Aadhaar yes; PAN, GSTIN regex only; no UPI, IFSC, mobile free text n/a no MIT
VibeDoctor Aadhaar code, logs, prompts no SARIF, no Action GPL-3.0
Vettam dpdp-compliance-scanner not found JS/TS/Python data map partial Action, no SARIF Apache-2.0
truscanner regex, no checksums code no no MIT
Scrutora dpdp-scan Action unknown (closed engine) code no yes none (engine is not open source)
gitleaks n/a (secrets, not personal data) code yes yes MIT

Presidio is the engine dpdp-scan builds on for recogniser interfaces; gitleaks is the CI experience it copies. The rest are the field.

Install

pipx install dpdp-scan semgrep   # or: pip install dpdp-scan semgrep
dpdp-scan .                      # text summary
dpdp-scan . --format sarif --sarif results.sarif --fail-on high

Semgrep is optional: without it the value detectors and schema classifier still run and the rule pack is skipped with a note. Until the PyPI release lands, install from GitHub: pip install git+https://github.com/statecheck-security/dpdp-scan@v0.1.0.

GitHub Action. Findings go to the Security tab as code scanning alerts:

permissions:
  contents: read
  security-events: write
steps:
  - uses: actions/checkout@v5
  - uses: statecheck-security/dpdp-scan@v0.1.0
    with:
      fail-on: high            # none | low | medium | high
      # baseline: .dpdp-scan-baseline.json

Docker. docker build -t dpdp-scan . then docker run --rm -v "$PWD:/repo:ro" dpdp-scan /repo --format sarif > results.sarif.

Configuration

Put a .dpdp-scan.yml in the repository root (or pass --config FILE):

version: 1
ignore:
  paths: ["fixtures/", "docs/**/*.md"]      # prefixes or globs, repo-relative
detectors:
  disable: ["in_mobile", "rule:dob-without-age-gate-js"]   # ids, or "schema" / "rules"
allow:
  domains: ["example-company.in"]            # your own mailboxes are not findings
  values_sha256: ["<sha256>"]                # from `dpdp-scan --hash-value VALUE`; never the value
fail_on: medium                              # default for --fail-on
baseline: .dpdp-scan-baseline.json

Values are allowlisted by hash so the config never holds the value itself. Unknown keys are errors: a typo must not silently switch a check off.

Baseline. dpdp-scan --write-baseline .dpdp-scan-baseline.json records a fingerprint for every current finding (detector, path, line and masked evidence; no values). With --baseline or the config's baseline, those findings are dropped and CI fails only on what is new.

Exit codes.

Code Meaning
0 Scan completed; no finding at or above --fail-on (default none: never fail)
1 Scan completed; a finding is at or above --fail-on, or in a --fail-on-category
2 Scan did not complete: bad arguments, unreadable path, invalid config or baseline

Semgrep being absent is not an error: the rule pack is skipped, a note goes to stderr, and the SARIF carries a tool notification.

Development

python -m venv .venv && . .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
ruff check . && ruff format --check . && pytest

CI runs the same on Ubuntu and Windows for Python 3.12 and 3.13.

Licence

Apache License 2.0. See LICENSE and NOTICE.

Built by StateCheck Security.

Metadata

Release files for dpdp-scan 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dpdp-scan 0.1.1
File Size Uploaded Provenance Actions
dpdp_scan-0.1.1.tar.gz 75.5 kB With provenance Details for dpdp_scan-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for dpdp-scan 0.1.1
File Interpreter ABI Platform Actions
dpdp_scan-0.1.1-py3-none-any.whl Python 3 none any Details for dpdp_scan-0.1.1-py3-none-any.whl

Total release size: 139.5 kB

Release files / dpdp_scan-0.1.1.tar.gz

Download URL dpdp_scan-0.1.1.tar.gz
Size 75.5 kB
Tags Source
SHA-256 checksum
How to use checksums
46818add5332aa0e6be32c98bec316510fc3388a01958446a63f397381f1cde5
BLAKE2b-256 checksum
How to use checksums
7dba9ad712f9922ca9873ce47d446ff4332ad2b229aa30c506034f4249025511
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

Release files / dpdp_scan-0.1.1-py3-none-any.whl

Download URL dpdp_scan-0.1.1-py3-none-any.whl
Size 63.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1a671bcdd856da5a721559be7c2bc2c12afa4384562a07bc6847dacb1bac52d1
BLAKE2b-256 checksum
How to use checksums
547c6c8a231842b8b21a481e81f1cfc293053c22b5b8135a730cfb27aefdad50
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

Security for dpdp-scan 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for dpdp_scan-0.1.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page