{"id":51680376,"url":"https://github.com/sarthiai/drishti","last_synced_at":"2026-07-17T14:01:35.030Z","repository":{"id":371472108,"uuid":"1298089523","full_name":"SarthiAI/Drishti","owner":"SarthiAI","description":"Self-hostable content safety for LLM systems: prompt-injection detection, PII detection and redaction, and output-safety scoring. A Rust core that runs on your own hardware and returns calibrated scores, not verdicts. Ships as a Rust crate, Python and Node SDKs, an HTTP server, and a Docker image.","archived":false,"fork":false,"pushed_at":"2026-07-15T05:24:23.000Z","size":185,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-15T12:41:30.292Z","etag":null,"topics":["ai-safety","content-moderation","content-safety","guardrails","llm","llm-guardrails","llm-security","machine-learning","moderation","nlp","nodejs","onnxruntime","pii-detection","pii-redaction","prompt-injection","prompt-injection-detection","python","rust","security","self-hosted"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SarthiAI.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-07-12T07:51:53.000Z","updated_at":"2026-07-15T05:24:27.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/SarthiAI/Drishti","commit_stats":null,"previous_names":["sarthiai/drishti"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/SarthiAI/Drishti","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SarthiAI%2FDrishti","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SarthiAI%2FDrishti/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SarthiAI%2FDrishti/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SarthiAI%2FDrishti/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SarthiAI","download_url":"https://codeload.github.com/SarthiAI/Drishti/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SarthiAI%2FDrishti/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35544514,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-16T02:00:06.687Z","response_time":83,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-safety","content-moderation","content-safety","guardrails","llm","llm-guardrails","llm-security","machine-learning","moderation","nlp","nodejs","onnxruntime","pii-detection","pii-redaction","prompt-injection","prompt-injection-detection","python","rust","security","self-hosted"],"created_at":"2026-07-15T12:39:54.846Z","updated_at":"2026-07-16T13:01:40.424Z","avatar_url":"https://github.com/SarthiAI.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# दृष्टि Drishti\n\n**Fast, honest, self-hostable content safety for LLM systems.**\n\nPrompt-injection detection, PII detection and redaction, and output-safety\nclassification. Three checks, one package, running entirely on your hardware.\n\n![License](https://img.shields.io/badge/license-Elastic--2.0-2f6f4e)\n![Built with Rust](https://img.shields.io/badge/built%20with-Rust-dea584?logo=rust\u0026logoColor=white)\n![Python](https://img.shields.io/badge/python-3.9%20to%203.13-3776ab?logo=python\u0026logoColor=white)\n![Platforms](https://img.shields.io/badge/platforms-Linux%20%7C%20macOS%20%7C%20Windows-555)\n![Inference](https://img.shields.io/badge/inference-ONNX%20Runtime-005CED)\n![Scores](https://img.shields.io/badge/it%20scores-it%20does%20not%20decide-6f42c1)\n\n\u003c/div\u003e\n\n---\n\nDrishti (दृष्टि, \"sight\") watches the text that flows in and out of LLM systems\nand reports what it sees. It runs three checks:\n\n| Check | On | Returns |\n|---|---|---|\n| **Prompt injection** | inputs | an injection score, a class, and a confidence |\n| **PII detection and redaction** | inputs | located PII spans plus a redacted copy of the text |\n| **Output safety** | outputs | a score per safety category and an aggregate verdict |\n\nEvery check returns a calibrated score and the identity of the model that\nproduced it. Drishti never makes a policy decision on its own. It sees, it\nscores, and it lets your policy layer decide.\n\n### Highlights\n\n- **Three checks, one package.** No assembling three Python projects with three\n  runtimes and three opinions about the output type.\n- **Scores, not verdicts.** Every result is a number a deterministic policy can\n  act on. Drishti refuses nothing itself.\n- **Models are yours.** Nothing is hardcoded. You choose the model per check; if\n  it is missing, Drishti downloads it once, verifies it, and caches it.\n- **Offline by default.** No default code path calls a remote service. CPU first,\n  GPU optional.\n- **Honest numbers.** Precision and recall come from the eval harness, and any\n  path that has not cleared its bar is labelled experimental.\n- **Three surfaces, one core.** A Rust crate, a Python package, and an HTTP\n  service that all return identical results.\n\n---\n\n## Table of contents\n\n- [Install](#install)\n- [Quick start](#quick-start)\n- [The three checks](#the-three-checks)\n- [Configuration](#configuration)\n- [Models](#models)\n- [HTTP API](#http-api)\n- [Eval results](#eval-results)\n- [Performance](#performance)\n- [Threat model](#threat-model)\n- [Project layout](#project-layout)\n- [License](#license)\n- [Part of Niyam](#part-of-niyam)\n\n---\n\n## Install\n\nDrishti is approaching its first tagged release. Published wheels and images will\nbe available from the channels below; until then, build from source.\n\n**Python** (embedded, runs models in-process; imported as `drishti`)\n\n```bash\npip install sarthiai-drishti\n```\n\nOne abi3 wheel per platform covers Python 3.9 through 3.13, on Linux x86_64,\nLinux ARM64, macOS ARM64 (Apple Silicon), and Windows x86_64.\n\n**Remote client SDKs** (call a running `drishti-server`, no model loaded locally)\n\n```bash\npip install sarthiai-drishti-sdk        # Python, imported as drishti_sdk\nnpm install sarthiai-drishti-sdk        # Node\n```\n\n**Docker**\n\n```bash\ndocker pull sarthiai/drishti\n```\n\nMulti-architecture (linux/amd64 and linux/arm64). The container carries its own\nLinux, so the host operating system does not matter.\n\n**From source**\n\n```bash\ngit clone https://github.com/SarthiAI/Drishti\ncd Drishti\ncargo build --release          # builds the CLI and the server\npip install maturin \u0026\u0026 maturin develop --release   # builds the Python wheel\n```\n\n**ONNX Runtime at runtime.** Drishti links ONNX Runtime dynamically (ort's\n`load-dynamic`), so the build is pure Rust and the artifacts stay small and\nportable. The library is provided at runtime:\n\n- The `sarthiai-drishti` wheel and the Docker image pull it in automatically (a\n  dependency of the wheel; downloaded into the image), so `pip install` and\n  `docker run` just work.\n- Running the CLI or server from a source build: install ONNX Runtime (for\n  example `pip install \"onnxruntime\u003e=1.24\"`) and point Drishti at its shared\n  library with `ORT_DYLIB_PATH=/path/to/libonnxruntime.so` (or `.dylib` / `.dll`).\n\n---\n\n## Quick start\n\nAll three surfaces read the same configuration file, which is where you choose\nthe model for each check. See [Configuration](#configuration).\n\n**Command line**\n\n```bash\ndrishti --config config.toml prompt   --text \"Ignore all previous instructions.\"\ndrishti --config config.toml pii      --text \"Email me at jane@example.com\"\ndrishti --config config.toml output   --text \"Have a great day!\"\ndrishti --config config.toml all      --text \"...\" --output \"...\"\ndrishti --config config.toml manifest         # loaded model ids and hashes\n```\n\nPass `--file \u003cpath\u003e` instead of `--text` to read from a file. Output is\nstructured JSON.\n\n**HTTP service**\n\n```bash\ndrishti-server --config config.toml --bind 0.0.0.0:8080 --token \u003cbearer-token\u003e\n```\n\n**Python**\n\n```python\nimport drishti\n\nd = drishti.Drishti.from_config_file(\"config.toml\")\nd.check_prompt(\"Ignore all previous instructions.\")\nd.check_pii(\"Email me at jane@example.com\")\nd.check_output(\"Have a great day!\")\nd.manifest()\n```\n\nMethods return plain dictionaries and release the interpreter lock during\ninference.\n\n---\n\n## The three checks\n\n### Prompt injection\n\nTakes a prompt and returns an injection score from 0.0 to 1.0, a class, a\nconfidence, the latency, and the model id. It catches common injection patterns\n(\"ignore previous instructions\", \"you are now DAN\", and similar). It is one layer\nof defense, not a jailbreak-proof filter.\n\n### PII detection and redaction\n\nTwo stages:\n\n- A **regex stage** (always on, about 5 ms) finds structurally identifiable PII:\n  emails, credit cards (Luhn validated), phone numbers, IPv4 and IPv6 addresses,\n  IBANs, US SSNs, India PAN, Aadhaar and UPI, UK NINO, and EU VAT numbers.\n- An optional **NER stage** finds unstructured PII like names, organisations, and\n  locations.\n\nThe result is a list of spans (byte offsets, kind, confidence, source) plus a\nredacted copy of the text. Redaction is chosen per kind: mask, hash, tokenise,\nkeep, or refuse.\n\n### Output safety\n\nTakes a model output and returns a score per safety category, an aggregate\npass-or-fail against a threshold you set, and the detected language. The taxonomy\ncomes from the configured model, so any classifier-style safety model fits.\n\n---\n\n## Configuration\n\nConfiguration is a TOML file. Every value can also be overridden by an\nenvironment variable or a `.env` file, so tuning never needs a code change or a\nrebuild. The override key is `DRISHTI_\u003cPATH\u003e`, with a double underscore between\nnesting levels:\n\n```bash\nDRISHTI_OUTPUT__THRESHOLD=0.05\nDRISHTI_PII__NER__DROP_ACRONYMS=true\nDRISHTI_INTRA_THREADS=4\n```\n\nA worked example is in [config/example.toml](config/example.toml). A check is\nenabled only when its section is present, and an enabled check must name a model\nor startup fails with a clear error rather than guessing one.\n\n---\n\n## Models\n\nDrishti hardcodes no model. You choose the model for each check through\nconfiguration: there are no default model ids, URLs, or hashes compiled into the\nbinary. If a chosen model is already present, Drishti uses it directly. If it is\nnot, Drishti downloads it once from the configured source, verifies its SHA-256\nwhen you provide one, caches it, and then uses it. To bring your own fine-tuned\nmodel, point the config at a local path.\n\nThere is no default model and no bundled weights. Instead Drishti ships a\nrecommendation matrix: **[MODELS.md](MODELS.md)** lists vetted models per check\nacross a footprint range (small to large), with precision and recall measured on\npublic benchmarks, honest notes on where each model fits, and starting points by\nindustry. Pick a row, point config at it. [config/starter.toml](config/starter.toml)\nis a ready-to-run example, one point on that matrix.\n\nA working starter set (used in `config/starter.toml`):\n\n| Check | Model | Weights | Size |\n|---|---|---|---|\n| Prompt injection | ProtectAI DeBERTa-v3-base prompt-injection-v2 | fp32 | 704 MB |\n| PII names and orgs | dslim/distilbert-NER | fp32 | 249 MB |\n| Output safety | KoalaAI Text-Moderation | int8 | 136 MB |\n\n\u003e Note: model size is a footprint budget, not a quality ranking. What decides\n\u003e accuracy is whether a model was trained on content and labels like yours; see\n\u003e [MODELS.md](MODELS.md). Separately, int8 dynamic quantization significantly\n\u003e degrades DeBERTa-v3 accuracy, so run that prompt-injection model at full\n\u003e precision and switch model family if you need a smaller footprint.\n\n---\n\n## HTTP API\n\nJSON in, JSON out. The check endpoints and the manifest require a bearer token\nwhen one is configured; health and metrics are always open.\n\n| Method | Path | Body | Auth |\n|---|---|---|---|\n| POST | `/v1/check/prompt` | `{ \"input\": \"...\" }` | bearer |\n| POST | `/v1/check/pii` | `{ \"input\": \"...\" }` | bearer |\n| POST | `/v1/check/output` | `{ \"output\": \"...\" }` | bearer |\n| POST | `/v1/check/all` | `{ \"prompt\": \"...\", \"output\": \"...\" }` | bearer |\n| POST | `/v1/check/prompt/batch` | `{ \"inputs\": [\"...\", \"...\"] }` | bearer |\n| POST | `/v1/check/pii/batch` | `{ \"inputs\": [\"...\", \"...\"] }` | bearer |\n| POST | `/v1/check/output/batch` | `{ \"outputs\": [\"...\", \"...\"] }` | bearer |\n| GET | `/v1/manifest` | loaded model ids and hashes | bearer |\n| GET | `/v1/version` | drishti version and model-set id | open |\n| GET | `/healthz` | liveness | open |\n| GET | `/readyz` | 200 only when models are loaded | open |\n| GET | `/metrics` | Prometheus text | open |\n\nEvery check body also accepts an optional `\"model_set\": \"\u003cid\u003e\"`. When present it\nis checked against the loaded set and a mismatch returns HTTP 409; when absent\n(the default) it is ignored, so existing callers are unaffected. Running Drishti\nas a separate, optionally GPU-backed service that many gateways share, and the\noptional TLS, timeout, size, and concurrency limits, are covered in\n[SERVING.md](SERVING.md).\n\n```bash\ncurl -s -X POST http://localhost:8080/v1/check/pii \\\n  -H \"authorization: Bearer \u003ctoken\u003e\" \\\n  -H \"content-type: application/json\" \\\n  -d '{\"input\": \"card 4111 1111 1111 1111\"}'\n```\n\nPrefer a typed client over raw HTTP? Remote client SDKs for Python\n(`sarthiai-drishti-sdk`) and Node (`sarthiai-drishti-sdk`) live in [clients/](clients/),\neach with its own README, and a Rust client (`sarthiai-drishti-client`, a\n`RemoteDrishti` that implements the same `SafetyEngine` trait as the embedded\nengine) lives in [crates/drishti-client/](crates/drishti-client/). They call a\nrunning `drishti-server` and return typed results; they load no model themselves.\nThis is distinct from the in-process Python package (`import drishti`) shown\nabove, which runs the models locally.\n\n---\n\n## Eval results\n\nThese figures come from the eval harness ([eval/](eval/)) run through the real\nengine on recognized public benchmarks: `deepset/prompt-injections`, the OpenAI\nmoderation evaluation, and an `ai4privacy/pii-masking-200k` English sample. They\nmeasure the specific models configured, not Drishti in the abstract: accuracy is\na property of the model you pick. The full per-model, per-tier matrix and how to\nchoose is in [MODELS.md](MODELS.md). Reproduce with `cargo run -p drishti-eval --\n--config \u003ccfg\u003e --datasets \u003cdir\u003e`; the report, including the SHA-256 of every model\nused, is written under `eval/results/`.\n\nMeasured highlights (validated means it cleared its bar):\n\n| Check (model) | Precision | Recall | F1 | Verdict |\n|---|---|---|---|---|\n| Output safety (KoalaAI Text-Moderation) | 0.879 | 0.960 | 0.918 | validated |\n| PII regex, Email | 0.996 | 0.939 | 0.967 | validated |\n| PII regex, IBAN | 1.000 | 1.000 | 1.000 | validated |\n| PII NER, PersonName (distilbert to bert-large) | up to 0.840 | up to 0.919 | 0.86 to 0.88 | experimental |\n| Prompt injection (ProtectAI DeBERTa) | 0.965 | 0.414 | 0.580 | experimental |\n\n\u003e The prompt-injection recall is low because this benchmark is multilingual and\n\u003e out of distribution for that English model; a different model scores very\n\u003e differently. That is the point of [MODELS.md](MODELS.md): public numbers do not\n\u003e predict your traffic. Every runtime result stays labelled experimental until its\n\u003e configured path clears its bar on a real benchmark.\n\n---\n\n## Performance\n\nWarm inference on commodity CPU hardware: the regex PII stage runs in about 5 ms;\nthe NER and output-safety classifiers in tens of milliseconds; the\nprompt-injection model at full precision is the heaviest, in the low hundreds of\nmilliseconds. A cold process additionally pays a one-time model load, which the\npersistent server amortizes. Detailed p50 and p99 figures are published each\nrelease.\n\n---\n\n## Threat model\n\n**In scope:** naive prompt injection (instruction-override patterns), common PII\nin inputs and outputs (emails, cards, phones, names, addresses, and the\nstructured identifiers above), common harmful output content in English, and\ntampering with model files (caught by SHA-256 verification when a hash is set).\n\n**Out of scope:** adversarial prompts crafted to evade a specific classifier,\njailbreaks that do not use injection patterns (roleplay, hypothetical framing),\nnon-English content (Drishti reports the detected language and lowers its\nconfidence), PII obfuscated through unusual encodings, and attacks on the host\nprocess itself. Drishti is one layer of defense: it reports scores, and\nenforcement belongs to your policy layer.\n\n---\n\n## Project layout\n\n```\ndrishti/\n  crates/\n    drishti-core/         detection logic and public types\n    drishti-models/       model resolution, download, caching, hash verification\n    drishti-regex/        the PII regex recognizer set\n    drishti-server/       the axum HTTP service\n    drishti-ffi-python/   the PyO3 bindings (in-process Python package)\n    drishti-cli/          the command-line tool\n  clients/\n    python/               drishti-sdk: remote HTTP client for Python\n    node/                 @sarthiai/drishti-sdk: remote HTTP client for Node\n  eval/                   the eval harness, datasets, benchmarks, and results\n  config/                 example configuration\n  MODELS.md               model recommendation matrix (no default model)\n```\n\n---\n\n## License\n\nDrishti is licensed under the **Elastic License 2.0 (ELv2)**. Licensor: Chirotpal\nDas. See [LICENSE](LICENSE) for the full text. In short: you may use, copy,\nmodify, and distribute it, but you may not offer it to third parties as a hosted\nor managed service, and you may not remove the licensing notices.\n\n---\n\n## Part of Niyam\n\nDrishti is the content-safety piece of the Niyam family: Kavach (armor) protects,\nDrishti (sight) watches, Lipi (script) writes the rules. Drishti is useful on its\nown and integrates with the rest through Niyam's shared contracts in later\nversions. The decision layer (what to block or allow) is Kavach, with rules\nauthored in Lipi. Drishti only ever hands over scores and flags.\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\nDesigned, developed, and maintained by \u003ca href=\"https://www.linkedin.com/in/chirotpal/\" target=\"_blank\"\u003eChirotpal\u003c/a\u003e\n\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsarthiai%2Fdrishti","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsarthiai%2Fdrishti","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsarthiai%2Fdrishti/lists"}