Skip to content

About

Prompt Gate — privacy-first, on-device DLP for the AI era. Blocks unauthorized AI tools at the DNS layer and inspects content sent to approved tools for secrets/PII, entirely on-device.

Topics

Resources

Contributing

Security policy

Accessibility

Stars

49 stars

Watchers

0 watching

Forks

Latest commit

 

History

105 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Prompt Gate

CI Go Report Card Coverage: agent/internal/dlp ≥ 80% Go Reference Docs

CodeQL OpenSSF Scorecard SLSA Provenance Signed with Sigstore Reproducible build Tests Vulnerabilities DLP precision Whitepaper OpenSSF Best Practices

Latest release Downloads Stars License: MIT Platform: macOS | Windows | Linux Go Privacy: zero data persisted

Prompt Gate demo

Open-source, privacy-first AI Data Leakage Prevention for desktop.

Most secret scanners stop at "does this look like an AWS key." Prompt Gate starts there and keeps going — it normalises away the tricks a human or LLM uses to sneak a secret past a regex, scores matches against where they're going, and lets the user say "never block this specific value again" without sending anything off-device.

Try it in 30 seconds:

go install github.com/ShieldNet-360/prompt-gate/agent/cmd/prompt-gate@latest
echo "leaked: AKIA9P2QRMZNL5CVXBT4" | prompt-gate scan
{
  "blocked": true,
  "pattern_name": "AWS Access Key",
  "score": 2
}

Now the same value with a single Cyrillic А swapped in (looks identical to your eye, invisible to a naive regex):

printf 'leaked: \xd0\x90KIA9P2QRMZNL5CVXBT4\n' | prompt-gate scan
{
  "blocked": true,
  "pattern_name": "AWS Access Key",
  "score": 2
}

Still caught. The normaliser folds ~50 Cyrillic and Greek homoglyphs to Latin ASCII, strips zero-width characters, decodes inline base64, and runs NFKC — all before the pattern matcher sees the input.

Bench (real source code)

On 1,000,000 lines of source code sampled from a real ~/Developer directory (5,676 files across Go / Python / TS / Java / YAML / Terraform / JSON / Markdown), the layered scoring delivers measurable FP reduction over a regex-only baseline. We report two numbers, because the source-context code_host: −2 bias depends on where the content is going:

Generation Total blocks Δ vs prev p50 latency
regex baseline 211 — 23 µs
+ accuracy layers 206 — 23 µs
+ source-context bias, realistic destination mix 161 −22 % 37 µs
+ source-context bias, worst-case (every line code_host) 41 −80 % 36 µs

The realistic-mix number is what production actually feels: destinations sampled 40 % ai_chat, 30 % code_host, 20 % ai_code, 10 % programmatic exfil. The worst-case number is the ceiling under the assumption that every paste goes to GitHub. About 75 % of the worst-case 80 % was bench artifact — the source-context bias only fires when the paste target is a code host, and in real production that's one destination among several.

Reproduce with agent/cmd/dlp-bench --destination-mix=realistic; full snapshot + per-pattern breakdown + methodology lives in BENCHMARKS.md.

The product

Prompt Gate is more than the CLI above. It's a cross-platform desktop agent (Windows, macOS, Linux) that blocks unauthorised AI tools at the DNS level and inspects content sent to approved AI tools via the same layered DLP pipeline. Content reaches the pipeline through a Chrome/Firefox/Safari browser extension or, for non-browser traffic, an optional local MITM proxy that decrypts only Tier-2 domains and tunnels everything else opaquely. It runs as a minimal system-tray app, consumes negligible CPU and memory, and logs nothing about user access — only running aggregate counters.

The DLP pipeline pairs an Aho-Corasick + regex deterministic core with four accuracy layers — adversary-resistant normalisation, a per-tab multi-piece correlator, a public-example bloom, and a placeholder-shape router — and source-context bias layers (destination bias, path bias, and an opt-in feedback allowlist). The launch-day benchmark over the in-repo FP corpus is precision 100 % / recall 73 % / FP rate 0 % at under 1 ms per scan with no ML dependency.

Privacy First

The agent stores only:

  • Policy configuration — which categories are allowed, inspected, or blocked
  • Anonymous aggregate counters — dns_queries_total, dns_blocks_total, dlp_scans_total, dlp_blocks_total (integers only, no per-event timestamps)
  • Rule files — domain lists and DLP patterns

No domain names, URLs, IP addresses, user identifiers, or per-event timestamps are ever written to disk. DLP block notifications are shown in real time and discarded. The store/privacy_test.go test sweeps every text column in the SQLite database and asserts none of those values reach disk.

Policy Tiers

Tier Action Mechanism
1 Allow Pass-through, no inspection
2 Allow + DLP Forwarded, inspected by the layered DLP pipeline
3 Block (AI) DNS resolver returns NXDOMAIN
4 Block (Other) DNS resolver returns NXDOMAIN

Quick Start

git clone http://github.com/ShieldNet-360/prompt-gate.git
cd prompt-gate

# Build and run the Go agent (DNS listener + local API on 127.0.0.1:9191).
cd agent
make build
./prompt-gate-agent --config ../config.yaml      # or omit --config for defaults

# In a second shell, install and run the Electron tray app.
cd electron
npm install
npm run build
npm start

The default dns_listen port is 127.0.0.1:15353 — no sudo required.

Build Prerequisites

Dependency Version Required for
Go ≥ 1.22 Agent binary
Node.js ≥ 22 Electron app, browser extension
Python ≤ 3.13 macOS DMG packaging (electron-builder uses dmgbuild which is incompatible with Python 3.14+)

macOS note: macOS no longer ships python. Install a compatible version and ensure it is available as python (not just python3) in your PATH, because electron-builder spawns a non-interactive shell where aliases are not loaded:

brew install python@3.12
sudo ln -sf /opt/homebrew/opt/python@3.12/bin/python3.12 /opt/homebrew/bin/python

Makefile Targets

make macos         # macOS .dmg (drag-to-Applications)
make windows       # Windows NSIS installer (.exe) + portable ZIP
make portable-win  # Windows portable ZIP only (no installer)
make linux         # Linux .deb or .tar.gz
make agent         # Go agent binary (current OS)
make electron      # Electron app only
make extension     # Browser extension only
make test          # Run all tests
make clean         # Remove all build artifacts

Example config.yaml:

upstream_dns: "8.8.8.8:53"
dns_listen: "127.0.0.1:15353"
api_listen: "127.0.0.1:9191"
db_path: "prompt-gate.db"
stats_flush_interval: 60s
rule_paths:
  - rules/ai_chat_blocked.txt
  - rules/ai_code_blocked.txt
  - rules/ai_allowed.txt
  - rules/ai_chat_dlp.txt
  - rules/phishing.txt
  - rules/social.txt
  - rules/news.txt
dlp_patterns: rules/dlp_patterns.json     # optional — enables /api/dlp/*
dlp_exclusions: rules/dlp_exclusions.json # optional

# Rule updater. Leaving rule_update_url blank disables the
# updater; everything else (DNS, policies, DLP) keeps working.
rule_update_url: ""                       # e.g. https://example.com/manifest.json
rule_update_interval: 6h                  # cadence; default 6h
rules_dir: rules                          # output dir for downloaded rule files

# Local MITM proxy. proxy_enabled=false (default) leaves the
# listener stopped — /api/proxy/enable can start it at runtime.
proxy_listen: "127.0.0.1:8443"
proxy_enabled: false
ca_cert_path: ""                          # default ~/.prompt-gate/ca.crt
ca_key_path: ""                           # default ~/.prompt-gate/ca.key
proxy_pinning_bypass: []                  # hostnames to tunnel even if Tier-2
                                          # (e.g. apps that pin a specific CA)

# DLP engine tuning. Omitting a field keeps the built-in default.
# large_content_threshold: 51200   # bytes; skip low/medium patterns above this
# dlp_cache_ttl_seconds: 5         # 0 disables the scan-result cache
# dlp_cache_capacity: 256          # max cache entries
# dlp_rate_limit_per_sec: 100      # 0 disables the /api/dlp/scan rate limiter
# dlp_disabled_categories: []      # e.g. ["PII"] to disable a category

# Agent self-update. Both fields required to enable.
# agent_update_manifest_url: ""    # e.g. http://github.com/.../manifest.json
# agent_update_public_key: ""      # hex-encoded Ed25519 public key

Leaving dlp_patterns blank disables the DLP pipeline and returns 503 from the /api/dlp/* endpoints; everything else (DNS, policies, stats) keeps working. Likewise, leaving rule_update_url blank returns 503 from /api/rules/*.

Project Structure

prompt-gate/
├── README.md            ARCHITECTURE.md  CHANGELOG.md
├── CONTRIBUTING.md      SECURITY.md  SECURITY_RULES.md  LICENSE
├── ROADMAP.md           GOVERNANCE.md  SUPPORT.md
├── agent/                            # Go backend (single static binary)
│   ├── cmd/agent/main.go
│   ├── internal/
│   │   ├── api/                      # HTTP API server + handlers + ratelimit.go (token bucket)
│   │   ├── config/                   # YAML configuration loader
│   │   ├── dlp/                      # Layered DLP pipeline + cache.go (LRU)
│   │   │                             #   + normalize.go / correlator.go
│   │   │                             #   / bloom.go / codecontext.go
│   │   │                             #   + testdata/fp_corpus/ + fp_corpus_test.go
│   │   ├── dns/                      # Embedded DNS resolver (miekg/dns)
│   │   ├── heartbeat/                # Optional outbound heartbeat
│   │   ├── policy/                   # Policy engine
│   │   ├── profile/                  # Enterprise profile loader + lock
│   │   ├── proxy/                    # Selective MITM proxy
│   │   ├── rules/                    # Rule-file parser + lookup + updater + admin override
│   │   ├── stats/                    # Anonymous aggregate counters
│   │   ├── store/                    # SQLite (modernc.org/sqlite, WAL)
│   │   ├── tamper/                   # OS DNS/proxy tamper detector
│   │   └── updater/                  # Agent self-update
│   ├── scripts/{post,pre}*.sh
│   ├── go.mod / go.sum
│   └── Makefile                      # build / test / lint targets
├── electron/                         # System-tray app (Electron + React)
│   ├── main.ts                       # Tray icon, health polling, BrowserWindow
│   ├── preload.ts                    # Secure contextBridge
│   ├── src/
│   │   ├── pages/{ProxySettings,Rules,Setup}.tsx
│   │   ├── components/{SetupIcon,Toast}.tsx
│   │   └── api/agent.ts              # HTTP client for the Go agent
│   ├── package.json
│   └── electron-builder.yml
├── extension/                        # Chrome / Firefox / Safari Manifest V3 companion
│   ├── manifest.json                 # Chrome MV3
│   ├── manifest.firefox.json         # Firefox MV3 (browser_specific_settings)
│   ├── manifest.safari.json          # Safari Web Extension (wrapped via xcrun)
│   ├── native-messaging/             # Native Messaging host manifest + installers
│   ├── src/
│   │   ├── background/               # service-worker.ts, native-messaging.ts, dynamic-hosts.ts
│   │   ├── content/                  # paste-, form-, network-, drag-, clipboard-* interceptors + scan-client.ts + toast.ts
│   │   ├── options/                  # Extension options page
│   │   └── popup/                    # Toolbar popup status UI
│   ├── tests/integration/            # Playwright smoke tests
│   ├── scripts/{build-firefox,build-safari}.mjs
│   ├── package.json
│   └── tsconfig.json
├── rules/                            # Bundled domain lists + DLP rules
│   ├── ai_chat_blocked.txt   ai_chat_dlp.txt   ai_code_blocked.txt
│   ├── ai_allowed.txt        phishing.txt      social.txt
│   ├── news.txt              manifest.json
│   ├── dlp_patterns.json
│   └── dlp_exclusions.json
├── docs/                             # Operator + contributor documentation
│   ├── admin-guide.md
│   ├── user-guide.md
│   ├── rule-contribution-guide.md
│   ├── dlp-pattern-authoring-guide.md
│   └── accessibility.md
├── scripts/                          # Platform install / DNS / proxy scripts
│   ├── macos/                        # postinstall.sh, uninstall.sh,
│   │                                 # configure-dns.sh, install-ca.sh,
│   │                                 # configure-proxy.sh,
│   │                                 # com.shieldnet360.promptgate.plist
│   ├── windows/                      # postinstall.ps1, uninstall.ps1,
│   │                                 # configure-dns.ps1, register-service.ps1,
│   │                                 # install-ca.ps1, configure-proxy.ps1
│   └── linux/                        # postinstall.sh,
│                                     # preremove.sh, uninstall.sh,
│                                     # configure-dns.sh, install-ca.sh,
│                                     # configure-proxy.sh, prompt-gate.service
└── .github/
    ├── ISSUE_TEMPLATE/               # bug_report.md + feature_request.md
    ├── PULL_REQUEST_TEMPLATE.md
    └── workflows/
        ├── ci.yml                    # Go + Electron + extension typecheck + tests
        └── release.yml               # multi-arch builds + GitHub Release on tags

API

Local HTTP API on 127.0.0.1:9191 (configurable):

Method Path Description
GET /api/status Agent status, uptime, version, Go runtime stats, DLP pattern count, rule file mtimes
GET /api/policies List [category, action] rows
PUT /api/policies/:category Update an action; triggers policy reload
GET /api/stats Aggregate counters (integers only)
POST /api/stats/reset Reset all counters to zero
POST /api/dlp/scan Scan {content, session_id?} through the DLP pipeline; returns {blocked, pattern_name, score}. When session_id is supplied (the browser extension generates a per-tab opaque UUID), the correlator reassembles secrets split across consecutive pastes in the same tab. Content is processed in memory and never persisted.
GET /api/dlp/config Current DLP scoring weights and per-severity thresholds
PUT /api/dlp/config Update DLP scoring weights and thresholds
POST /api/rules/update Trigger an immediate rule-manifest check; returns {updated, version, files_downloaded}
GET /api/rules/status Current rule version + last/next check time + manifest URL
POST /api/proxy/enable Generate the per-device CA if missing and start the local MITM proxy; returns {ca_cert_path} for OS trust install
POST /api/proxy/disable Stop the local MITM proxy; pass {"remove_ca": true} to also delete the CA files
GET /api/proxy/status {running, ca_installed, listen_addr, dlp_scans_total, dlp_blocks_total}
GET /api/profile Current enterprise profile, or 404 if none is loaded
POST /api/profile/import Import a profile from {url} or {profile} body and apply it; locks local policy edits when managed=true
GET /api/tamper/status {dns_ok, proxy_ok, last_check, detections_total} from the tamper detector
GET /api/stats/export Downloadable JSON envelope {agent_version, os_type, os_arch, exported_at, stats}
GET /api/rules/override List the admin allow/block override sets
POST /api/rules/override Add {domain, list:"allow"|"block"} to the override store; moves between lists if needed
DELETE /api/rules/override/:domain Remove an override regardless of list
GET /api/agent/update-check Check whether a newer agent release is published on the configured manifest channel. Returns 503 when no updater is wired.
POST /api/agent/update Download the latest agent release, verify its SHA-256 + Ed25519 signature, and stage it for restart.

action is one of allow, allow_with_dlp, deny.

The DLP endpoints return 503 Service Unavailable when the agent is started without a dlp_patterns config entry (DNS-only deployments). The /api/rules/* endpoints return 503 when rule_update_url is blank. The /api/proxy/* endpoints return 503 when the proxy controller has not been configured (e.g. agents built without proxy_listen).

The extension prefers to reach the agent through Chrome Native Messaging (no CORS, survives air-gapped networks) and falls back to direct HTTP to 127.0.0.1:9191 when the native host is unavailable. Install the host manifest with extension/native-messaging/install.sh (macOS/Linux) or install.ps1 (Windows). Safari Web Extensions have no Native Messaging, so the Safari port uses the HTTP fallback exclusively; the agent's CORS allowlist accepts chrome-extension://, moz-extension://, and safari-web-extension:// origins.

Enterprise Features

Optional features for managed deployments — every one of them honours the same privacy invariant as the base agent.

  • Configuration profiles. Set profile_path or profile_url in config.yaml; the JSON profile (name, version, managed, categories, dlp) is applied on startup. When managed=true, PUT /api/policies/:category and PUT /api/dlp/config return 403 Forbidden and the Electron settings UI disables every input.
  • Tamper detection. Background goroutine (default 60s) checks that OS DNS still points at the agent and, when the local MITM proxy is enabled, that the system proxy still points at 127.0.0.1:8443. Transitions bump tamper_detections_total in the existing aggregate_stats row; the tray surfaces an ephemeral balloon — no per-event log on disk.
  • Optional heartbeat. Set heartbeat_url to enable. Payload is exactly {agent_version, os_type, os_arch, aggregate_counters} — no URL, domain, IP, or DLP-match data is ever serialised. Tests in agent/internal/heartbeat/heartbeat_test.go assert this on the JSON wire format.
  • Admin overrides. Drop files into rules/local/ (allow.txt, block.txt, dlp_patterns_override.json, dlp_exclusions_override.json) to add company-specific rules without touching bundled files. The Electron Settings page has an allow/block UI that writes through POST /api/rules/override and DLP threshold sliders that hit PUT /api/dlp/config.
  • Stats export. GET /api/stats/export returns the counter snapshot with a Content-Disposition: attachment envelope — counters only, no access data.

Testing

cd agent
make test                 # runs `go test -race ./...`, includes DLP + proxy unit + integration tests
make lint                 # runs `go vet ./...`
make dlp-bench            # runs TestFPCorpus and prints precision / recall / F1 / FP rate
                          # against agent/internal/dlp/testdata/fp_corpus/

cd ../electron
npm run typecheck         # TypeScript strict mode against renderer + main

cd ../extension
npm install && npm run typecheck   # browser-extension Manifest V3 typecheck
npm test                            # node --test on content + background scripts
npm run build:firefox               # Firefox bundle in dist-firefox/
npm run build:safari                # Safari Web Extension (macOS-only; uses xcrun)

DLP coverage includes one *_test.go per pipeline component (classifier, ahocorasick, regex, hotword, entropy, exclusion, scorer, threshold) plus a pipeline_test.go integration test exercising real AWS keys with hotword context (block), the AWS docs example key AKIAIOSFODNN7EXAMPLE (exclude), benign prose (allow), empty content, and large content embedding a real-looking key.

The enterprise-feature tests live alongside their packages: profile/, tamper/, heartbeat/, and rules/override_test.go / dlp/override_test.go verify the loader-lock interaction, platform-isolated tamper probes (via build tags), the heartbeat payload shape (assertion: no access / domain / IP / DLP-match fields ever leak), and admin override merging without corrupting bundled rules. Performance benchmarks for the DLP pipeline, DNS resolver, and stats counter live in *_bench_test.go files — see BENCHMARKS.md.

The accuracy layers add bloom_test.go, codecontext_test.go, normalize_test.go, correlator_test.go, and fp_corpus_test.go (precision / recall / F1 / FP rate against the labelled corpus under agent/internal/dlp/testdata/fp_corpus/). The FP-corpus test gates CI at precision ≥ 90 % and recall ≥ 60 %; launch-day measurement is precision 100 %, recall 73 %, FP rate 0 %.

DLP Coverage

Prompt Gate ships 165 real-world detection patterns across 14 JSON categories: cloud providers (AWS, Azure, GCP, Google Services), cloud infrastructure (Cloudflare, DigitalOcean, Vercel, Netlify, Supabase, Pulumi, Helm, Terraform, Docker, K8s), version control (GitHub, GitLab, Bitbucket), AI/ML platforms (OpenAI, Anthropic, HuggingFace, Cohere, Replicate, Pinecone, Mistral, W&B, LangSmith, Together, Groq), payment processors (Stripe, PayPal, Square, Braintree, Adyen, Plaid, Coinbase), CI/CD (CircleCI, Travis, Jenkins), messaging (Slack, Discord, Telegram, Twilio, SendGrid, Vonage, Mailchimp), auth/identity (Auth0, Okta, OneLogin, Keycloak, Firebase Admin, Supabase JWT, Clerk), language ecosystems (Java, Rust, JS/TS, Swift, Kotlin, Dart, Go, Python), mobile (iOS APNs, Android signing, Flutter / React Native), databases (Postgres, MySQL, MongoDB, Redis, MSSQL, SQLite, Cassandra, Elasticsearch), PEM/private keys, JWTs, generic password-in-code, and PII (SSN, credit cards, emails, phones).

See SECURITY_RULES.md for the complete per-pattern table (name, severity, prefix, hotword requirement).

Documentation

Architecture & reference

Contributing

Contributions are welcome under the MIT license. See CONTRIBUTING.md for development setup, the PR process, coding standards, and test requirements.

Good first contributions:

  • Rule lists — add domains (one per line, leading . for "include subdomains") to rules/*.txt.
  • DLP patterns / exclusions — rules/dlp_patterns.json, rules/dlp_exclusions.json.
  • Bug reports — use the GitHub Issues template at .github/ISSUE_TEMPLATE/bug_report.md.

Report security vulnerabilities via the process in SECURITY.md — please do not file public issues for security reports.

License

MIT

About

Prompt Gate — privacy-first, on-device DLP for the AI era. Blocks unauthorized AI tools at the DNS layer and inspects content sent to approved tools for secrets/PII, entirely on-device.

Topics

Resources

Contributing

Security policy

Accessibility

Stars

49 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages