npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@honlnk/zcode-prompt-sanitizer

v0.1.0

Published

Local reverse proxy that rewrites sensitive prompt fragments injected by ZCode before they reach third-party API providers, preventing WAF false positives.

Readme

zcode-prompt-sanitizer

A local reverse proxy that sanitizes ZCode-injected prompt fragments before they reach third-party API providers, preventing WAF false positives that block legitimate coding requests.

Tests

The problem

When you use a third-party API provider (e.g. Tencent Copilot / WorkBuddy) in ZCode, every request sent from inside a git repository gets blocked by the provider's WAF content filter, returning a message like:

系统检测到您当前输入的信息存在敏感内容

This is a WAF false positive, not a real safety issue. The root cause: ZCode automatically injects git context into the system prompt — specifically the line:

Main branch (you will usually use this for PRs): master

The provider's WAF pattern-matches "you will usually use this for PRs" as a prompt-injection / jailbreak attempt and rejects the whole request before it ever reaches the model (you can confirm this: blocked requests report tokens = 0).

The solution

zcode-prompt-sanitizer runs as a local reverse proxy between ZCode and your provider. It:

  1. Intercepts outgoing chat-completion requests.
  2. Rewrites the injected fragments that trigger WAF (configurable, substring-based, applied to system-role content by default).
  3. Forwards the cleaned request to the real upstream.
  4. Streams the response back verbatim — SSE chunk-by-chunk, no buffering.

For the known trigger above, the built-in default rule rewrites it to:

Default git branch: master

The model still sees the branch name; the WAF no longer sees the trigger phrase.


Quick start

# Install globally (or use npx)
npm install -g @honlnk/zcode-prompt-sanitizer

# Start the proxy (uses built-in defaults that cover the known trigger)
zps

You'll see:

  🛡  zcode-prompt-sanitizer v0.1.0
     Proxy      →  http://127.0.0.1:18790
     Dashboard  →  http://127.0.0.1:18790/__zps__
     Rules      →  2 active / 2 total
     Upstreams  →  none configured (passthrough by Host header)

…or run with Docker

# Build and run with docker compose (recommended)
docker compose up -d

Put your config at ./config.yaml next to docker-compose.yml (see examples/docker-compose.example.yml). The proxy is then reachable at http://127.0.0.1:18790, dashboard at http://127.0.0.1:18790/__zps__.

# Build
docker build -t zcode-prompt-sanitizer .

# Run — mount your config, map the port
docker run -d --name zps \
  -p 18790:18790 \
  -v "$PWD/config.yaml:/data/config.yaml" \
  zcode-prompt-sanitizer

# Or run with zero config (built-in defaults)
docker run -d --name zps -p 18790:18790 zcode-prompt-sanitizer \
  node dist/cli.js --no-dashboard

Note: Inside a container the proxy binds 0.0.0.0 (set via ZPS_HOST in the Dockerfile), because 127.0.0.1 in a container refers to its own loopback and is unreachable from the host. Port mapping (-p 18790:18790) makes it accessible from your machine as usual.

Point ZCode at the proxy

Configure your provider in ZCode to use the proxy address. Exactly how depends on how your provider is set up:

Option A — Provider with a fixed base URL

Set the provider's base URL to the proxy, and configure an upstream in your sanitizer config so the proxy knows where to forward:

# ~/.zcode-prompt-sanitizer/config.yaml
upstreams:
  workbuddy:
    target: https://copilot.tencent.com
    headers:
      # Optional: inject the real Authorization if ZCode only knows the proxy
      # authorization: Bearer sk-xxxx

In ZCode, set the provider base URL to http://127.0.0.1:18790 and send the header x-zps-provider: workbuddy (or name the provider so it's matched against the Authorization value / Host).

Option B — Host-header passthrough (zero config)

If your provider setup lets you control the Host header, point it at 127.0.0.1:18790 with the real upstream as Host. The proxy forwards based on the Host header with no upstream config needed.


Configuration

The proxy loads config from the first of these it finds:

  1. --config <path> CLI flag
  2. ZPS_CONFIG env var
  3. ~/.zcode-prompt-sanitizer/config.yaml

If none exists, built-in defaults are used. Dashboard edits are persisted to the same path (~/.zcode-prompt-sanitizer/config.yaml by default, or /data/config.yaml in Docker).

Full example

# ~/.zcode-prompt-sanitizer/config.yaml
port: 18790
host: 127.0.0.1            # always bind locally
verbose: false
maxBodyBytes: 8388608      # 8 MiB request body cap

dashboard:
  enabled: true
  port: 0                  # 0 = serve on the proxy port under /__zps__

upstreams:
  workbuddy:
    target: https://copilot.tencent.com
    changeHost: true
    headers:
      x-custom-header: value

rules:
  - id: zcode-git-pr-hint
    description: Neutralize the git hint that triggers WAF
    enabled: true
    scopes: [system]
    match: "Main branch (you will usually use this for PRs):"
    replacement: "Default git branch:"
  - id: redact-internal-token
    enabled: true
    scopes: [system, user]
    match: "INTERNAL-TOKEN-"
    replacement: ""        # empty = delete the match

Rule semantics

| Field | Type | Notes | | ------------- | -------- | ----------------------------------------------------------------- | | id | string | Unique. Used in stats/logs. | | enabled | boolean | Skip when false. Defaults to true. | | scopes | string[] | One or more of system, user, assistant, tool. Default ["system"]. | | match | string | Literal substring — no regex. Matched with indexOf. | | replacement | string | Literal replacement. Empty string deletes the match. | | description | string | Optional, shown in the dashboard. |

Rules apply in order; replacements can chain (rule A's output is visible to rule B in the same pass). All occurrences of a match are replaced.

CLI flags & env vars

| Flag | Env var | Default | Description | | ---------------- | --------------- | -------- | --------------------------------- | | --config <path>| ZPS_CONFIG | — | Config file path | | --port <n> | ZPS_PORT | 18790 | Listen port | | --host <addr> | ZPS_HOST | 127.0.0.1 | Bind address | | --verbose | ZPS_VERBOSE=1 | off | Log every proxied request | | --no-dashboard | ZPS_DASHBOARD=0 | on | Disable the management dashboard | | -v, --version | — | — | Print version | | -h, --help | — | — | Show help |


Dashboard

Open http://127.0.0.1:18790/__zps__ in a browser to:

  • View live stats (uptime, rule match counts, enabled/total).
  • Toggle, add, edit, and delete rules — changes apply immediately and persist to your config file.
  • Inspect the active ruleset as JSON.

The dashboard binds to 127.0.0.1 only. As a purely local tool, it intentionally does not implement authentication.


How it works

ZCode  ──►  [zcode-prompt-sanitizer :18790]  ──►  Provider (e.g. copilot.tencent.com)
                 │
                 ├─ parse JSON body
                 ├─ apply rewrite rules (system-role content)
                 ├─ rebuild body
                 └─ forward request ──────────────────────┐
                                                            │
            ◄──── stream response verbatim (SSE-aware) ─────┘

Key design decisions:

  • Request bodies only are inspected/rewritten. Responses pass through untouched.
  • SSE is streamed chunk-by-chunk (upstreamRes.pipe(clientRes)), never buffered — so streaming model output stays real-time.
  • Substring matching, no regex — predictable and safe against metacharacter surprises.
  • Zero hard-coded provider logic — rules and upstreams are fully configurable; the built-in defaults just happen to cover the known Tencent WAF trigger.

Programmatic API

import { Sanitizer } from '@honlnk/zcode-prompt-sanitizer';

const sanitizer = new Sanitizer([
  {
    id: 'pr-hint',
    enabled: true,
    scopes: ['system'],
    match: 'Main branch (you will usually use this for PRs):',
    replacement: 'Default git branch:',
  },
]);

const result = sanitizer.rewrite({
  messages: [
    { role: 'system', content: 'Main branch (you will usually use this for PRs): master' },
    { role: 'user', content: '你好' },
  ],
});

console.log(result.changed);        // true
console.log(result.firedRuleIds);   // ['pr-hint']

See src/index.ts for the full export surface.


Development

npm install
npm run build         # compile TypeScript
npm test              # run the 36-test suite
npm run typecheck     # type-check without emitting
npm run dev           # watch mode via tsx

Project layout

src/
  types.ts              # shared types
  sanitizer.ts          # the rewrite engine (pure, testable)
  proxy.ts              # reverse proxy + SSE passthrough (Node http)
  server.ts             # wires proxy + dashboard together
  cli.ts                # CLI entrypoint (bin)
  index.ts              # library exports
  config/
    defaults.ts         # built-in rules + default config
    loader.ts           # YAML/JSON load + validation
    persist.ts          # write config back to disk (dashboard edits)
  dashboard/
    api.ts              # management REST API under /__zps__/api
    html.ts             # self-contained dashboard HTML
test/
  sanitizer.test.ts     # rewrite engine unit tests
  config.test.ts        # config loading/validation tests
  proxy.test.ts         # end-to-end proxy + SSE tests
  dashboard.test.ts     # management API tests

Testing

The test suite (36 tests) covers:

  • Rewrite engine — exact substring matching, multi-occurrence, empty-replacement deletion, literal (non-regex) matching, scope filtering, vision-style array content, rule chaining, stats.
  • Config — YAML/JSON loading, default merging, validation errors (duplicate ids, empty matches, invalid scopes, malformed YAML).
  • Proxy end-to-end — trigger phrase is rewritten before reaching upstream, user-role content is preserved, SSE streaming verified (chunks arrive incrementally, not buffered), 502 on unreachable upstream, Host-header passthrough fallback.
  • Dashboard API — status, list/add/update/delete/replace rules, validation rejection, HTML serving.

Run with:

npm test

License

MIT © honlnk