@piifirewall/proxy
v1.0.0
Published
PIIFirewall API proxy — Express server with multi-provider AI routing (npm B2 standalone bundle)
Readme
@piifirewall/proxy
The Express proxy server for PII Firewall.
It uses @piifirewall/core to mask PII and streams requests to OpenAI / Anthropic / Google Gemini.
Endpoints
| Method | Path | Description |
| ------ | ------------------- | ---------------------------------------------- |
| POST | /chat | Mask PII, then stream to the AI provider (SSE) |
| POST | /chat-multi | Send to multiple AIs at once (comparison mode) |
| POST | /upload | File upload + PII masking |
| POST | /mask | Mask the PII in a text and return it |
| POST | /detect | Detect PII candidates only (no masking) |
| POST | /restore | Restore the original value by token ID |
| POST | /restore-text | Restore every token found in the text |
| POST | /restore-all | Bulk-scan and restore all tokens in the text |
| POST | /detect-injection | Detect prompt injection |
| POST | /detect-sql | Detect SQL injection |
| POST | /remove-injection | Remove injection patterns |
| POST | /test-api-key | Test the API key connection |
| GET | /health | Server status and provider connectivity check |
| GET | /providers | List of available AI providers |
| GET | /logs | Recent event logs |
| GET | /stats | Statistics |
| GET | /store-status | State of the secret-sharing store |
Two more read an account rather than a text, so they answer only a caller who
can prove whose account it is — the console session token, in
Authorization: Bearer. A pf_live_ API key is not one: the resolver rejects it
by prefix, so it answers 401 exactly as a missing header does. A self-hosted
deployment has no account for them to read.
| Method | Path | Description |
| ------ | ----------------------- | ----------------------------------- |
| GET | /credits | Credit balance, plan and seat count |
| GET | /credits/transactions | This account's purchase history |
Setup
cd proxy
cp .env.example .env
# Edit .env to set your API keys
npm install
npm startBy default it starts at http://localhost:3001.
Environment variables
| Variable | Required | Description |
| ------------------- | ------------ | ---------------------------------------------------- |
| OPENAI_API_KEY | one of these | OpenAI API key |
| ANTHROPIC_API_KEY | one of these | Anthropic API key |
| GOOGLE_API_KEY | one of these | Google Gemini API key |
| PORT | optional | Port number (default: 3001) |
| UI_ORIGIN | optional | Allowed CORS origin (default: http://localhost:5173) |
Licensing and the monthly report (self-hosted)
A self-hosted deployment authenticates from the license it was started with and reports its usage once a month. That report is also how entitlement changes reach it: the response carries the current plan, expiry and seat count, and the deployment applies them.
Two consequences worth knowing before relying on it:
- Cancellation takes effect on the next report — up to about a month later. Enforcement here is deliberately soft (a local deployment cannot be gated absolutely), so a canceled subscription keeps serving its previous plan until the next monthly round trip. Restarting the deployment applies it sooner.
- Degrading never stops protection. Losing entitlement drops the deployment to the Free plan — masking, detection and injection checks all keep running; only plan-scaled allowances (such as the custom dictionary) shrink.
A payment failure is not a downgrade: the issued key outlives the payment retry window, so a subscription in dunning keeps its plan while the charge is retried.
Airgap deployments (PIIFW_LICENSE_FILE) send no report and take no entitlement
from the network. Their .lic file is replaced by hand.
/chat request format
POST /chat
Headers:
Content-Type: application/json
Authorization: Bearer pf_live_... # Cloud API key. Self-hosted sends none.
Body:
{
"provider": "anthropic",
"messages": [
{ "role": "user", "content": "My email [email protected] is..." }
],
"extraTypes": ["name"]
}SSE response format
data: {"masked_input": "My email [SECURED:type=email,id=abc123] is...", "detections": [...]}
data: {"content": "AI response text"}
data: {"done": true}File layout
proxy/
├── index.js # Server startup, middleware, and where routes/ is mounted
├── routes/ # The endpoints themselves, one file per group
│ # core-pii.js /mask /detect /restore /restore-text
│ # injection.js /detect-injection /detect-sql
│ # redaction.js /redact-document /restore-document
│ # integrations.js /shadow-ai (Enterprise)
├── lib/ # Shared helpers the routes are built from
├── router.js # OpenAI / Anthropic / Google streaming relay
├── license-auth.js # Licence verification and the monthly report
├── logger.js # Local event logging
├── file-extractor.js # PDF / Word / Excel text extraction
├── providers.js # AI provider config / UI metadata
├── package.json
├── .env.example
├── Dockerfile
└── docker-compose.ymlDocker
docker compose updocker-compose.yml loads the .env inside proxy/.
