01 / Sent directly
Without Tamga
POST https://api.openai.com/v1/chat/completions {"content":"Refund Jane Doe to TR33 0006 1005 1978 6457 8413 26"}The IBAN reaches the provider.
Sent straight to the provider, the IBAN goes with the request.
Tamga sits in front of the model APIs your apps, agents and browsers call. It reads each request and, under your policy, blocks or masks personal data, secrets and prompt injection. It runs on your own servers.
Request, simulated
POST /v1/chat/completions
This is what the provider receives: the two values are masked.
No. 002Three ways in
Tamga only sees what is sent to it. Applications and SDKs send it their requests, agents send it their whole session, and a browser extension asks it about what people paste into three chat sites.
Request path, simulated
Model providergets the IBAN masked
Modeltool result replaced, session goes on
Chat site: stopped heresend held, nothing sent
Each path asks the same policy. The extension does not forward anything: it asks Tamga for a verdict and the browser acts on it before the site gets the text.
Coding assistants in editorsNo. 003Installation
The proxy alone needs Docker and nothing else: no database, no keys. Then change the base URL your SDK calls; the rest of your code stays as it is.
Install · proxy only
terminal
git clone https://github.com/yatuk/tamga.git && cd tamgacp .env.example .env # no provider keys: TAMGA_MOCK_UPSTREAM=truedocker compose --env-file .env -f deploy/docker-compose.yml --profile minimal up -dcurl -s -o /dev/null -w "%{http_code}\n" -X POST http://localhost:8443/v1/chat/completions -H "Content-Type: application/json" -d '{"model":"gpt-6-luna","messages":[{"role":"user","content":"Card 4532 0151 1283 0366, Jane Doe"}]}'403 blocked before it reaches any providerEndpoint change
The same SDK, through Tamga
OpenAI SDK · 7 lines
app.py
import osfrom openai import OpenAI client = OpenAI( base_url="http://localhost:8443/v1", # was https://api.openai.com/v1 api_key=os.environ["OPENAI_API_KEY"],)No. 004Request comparison
Sent straight to a provider and sent through Tamga. Under the shipped policy an IBAN is masked and the request goes on; a card number or a national ID number would be blocked.
01 / Sent directly
POST https://api.openai.com/v1/chat/completions {"content":"Refund Jane Doe to TR33 0006 1005 1978 6457 8413 26"}The IBAN reaches the provider.
Sent straight to the provider, the IBAN goes with the request.
02 / Masked request
POST http://localhost:8443/v1/chat/completions {"content":"Refund Jane Doe to [iban_REDACTED]"}Masked before it is forwarded.
The provider receives a marker in place of the IBAN. The rest of the text is unchanged.
No. 005Try it here
Write rules, paste a text, and see what the policy decides. Both tools run in this page and send nothing anywhere.
Payload inspector
Checked in your browser as you type, against the shipped policy's rules for these kinds. Nothing you enter is sent anywhere. It is a few regular expressions with the scanner's checksums, not the scanner.
What the scan saw
Please refund Jane Doe (jane.doe@example.com, +44 20 7946 0958) to IBAN TR33 0006 1005 1978 6457 8413 26.
The provider receives
Please refund Jane Doe ([email_REDACTED], +44 20 7946 0958) to IBAN [iban_REDACTED].
Forwarded with the values masked.
Policy authoring
Write the rules in YAML and see which action applies to a sample text. It runs in this page and sends nothing anywhere.
Policy editor
Each finding takes the action of the rule that lists its type; when several apply, the strongest wins.
The proxy refuses a policy it cannot read: an unknown key or an unknown action is a load error. This editor checks only the action names.
Evaluation record
5 rules / 2 findings
Final action
| Type | Category | Rule | Action |
|---|---|---|---|
| pii | rules.pii_detection | REDACT | |
| pii | iban | rules.pii_detection | REDACT |
No. 006New in v0.9.0
v0.9.0 is not released yet; all of this is on the main branch. Each row says what it does, how well it was measured, and where to check.
Policy · language
List the languages allowed; a user or tool message mostly in another one is blocked, stripped or warned about. Code, logs, URLs and identifiers are not counted. Off in the shipped policy.
Check language.allow: [en, de]Policy · topics
Subjects written in plain words are matched by meaning with an optional local model. It is weak: at the 0.8 threshold it found 11 of 30 requests with English descriptions and 2 of 30 with Turkish ones. Start it on WARN.
Check precision 0.85 · recall 0.37 at 0.8Policy · ROUTE
A request that holds something sensitive is answered by a model you run, unmasked, and never reaches the provider. If that model is down, the request is refused, not sent out.
Check 503 tamga_route_unavailableDetection · identifiers
Card numbers and IBANs with their checksums, national ID numbers, tax IDs, company registry numbers, passport numbers, IMEIs and vehicle plates, among them the Turkish formats, with four starting-point policy templates.
Check proxy/policies/templates/Detection · quoted attacks
An attack phrase that only appears in a quotation, a code block or an example, in a text that discusses attacks, is now a warning. Benign passages blocked went from 58 of 104 to 3; all 38 attacks in the same dress are still blocked.
Check injection:quoted_injection → WARNReleases
The release workflow signs every artefact with Sigstore cosign (keyless), attaches SLSA provenance and SPDX and CycloneDX SBOMs. It has not run yet: no release exists.
Check cosign verify-blob · gh attestation verifyNo. 007Evidence
The share of attacks the deterministic rules catch on each test set, under the shipped policy. Most sets were written by the same person who wrote the rules, which flatters them. The two written by someone else show where the rules stop.
Main corpus, mostly English
prompts.csv · same author as the rules
220/2430/172 benign flagged
Held-out set, same author
holdout.csv · same author as the rules
78/780/91 benign flagged
Turkish, tuning part
tr_tuning.csv · same author as the rules
142/1852/186 benign flagged
Turkish, locked part
tr_locked.csv · same author as the rules
104/1423/125 benign flagged
Turkish, written by someone else (train split)
trd_tuning.csv · written by someone else
27/2030/342 benign flagged
Turkish, written by someone else (test split)
trd_locked.csv · written by someone else
2/590/44 benign flagged
Where the rules stop
Turkish attacks without an override phrase (a new task slipped after a question, a persona) mostly pass the rules. That is what the optional inline classifier is for, and on that Turkish set at its shipped threshold it catches little either.
0.14 → 0.19classifier recall at 0.98 · trd_locked.csv
16-core laptop CPU, Docker limited to 12 GB, mocked upstream · 2026-10-03
All sets, the classifier and the methodNo. 008Control coverage
Where each of the ten 2025 categories stands, from the project's own mapping, including the ones Tamga does not cover.
Covered
4/ 10
Partial
4/ 10
Planned
1/ 10
Out of scope
1/ 10
No. 009Alerts
A blocked, stripped or warned request is posted to the destinations you connect, each in its own format. The message names the request, the action and the kinds of finding. It carries no text from the request and no matched value.
Tamga
A blocked, stripped or warned request is posted to the destinations you connect, each in its own format. The message names the request, the action and the kinds of finding. It carries no text from the request and no matched value.
Splunk HEC
SIEMCEF
Microsoft Sentinel
SIEMCEF
IBM QRadar
SIEMLEEF
Datadog
SIEMevent
Slack
Chattext
Microsoft Teams
Chatcard
PagerDuty
Pagingevent
Opsgenie
Pagingalert
ServiceNow
Ticketsincident
Jira
Ticketsissue
Generic webhook
CustomJSON
Each format is tested against a local stand-in. None has been tried against the real service, and Splunk, Datadog, ServiceNow and Jira need their authentication header added by hand.
Alert settings in the operations guideNo. 010Limits
A proxy sees the requests that pass through it and nothing else. These are the gaps the README lists first.
01
Text typed into a chat site goes from the browser to that site. The extension covers three sites in Chromium browsers, as an MVP, and not file uploads or the sites' apps.
02
An agent talks to its MCP servers directly. What a tool returned reaches Tamga only when the agent puts it in its next model request, as a tool result.
03
An application that still calls the provider's own URL is not scanned. Tamga does not discover or stop such calls.
04
They are forwarded without being read. An attached text file is decoded and scanned.
No. 011Source
Tamga is AGPL-3.0. That is the only licence and the repository is the only edition: nothing is held back for a paid one. It is maintained by one person and is pre-1.0.
Start here
git clone https://github.com/yatuk/tamga.gitThen start the proxy alone with Docker Compose. There is no hosted Tamga: it runs on your machines, so your requests never pass through ours.
AGPL-3.0 for everything above. What is not there yet is listed in the README under Project status: no SSO beyond GitHub sign-in, no custom roles, no multi-region replication.
No. 012Next step
Start the proxy alone with Docker Compose, point one SDK at it, and read what it finds on your own traffic before you rely on it. Expect less than the published recall: the test sets were mostly written by the author of the rules.