@chatpanel/gateway 0.6.62 → 0.6.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatpanel/gateway",
3
- "version": "0.6.62",
3
+ "version": "0.6.63",
4
4
  "description": "Local privacy gateway — redacts PII out of OpenAI/Anthropic API traffic before it reaches a model, then restores it in the reply. Point opencode, codex, aider, Claude Code, etc. at it.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,7 +27,7 @@
27
27
  "node": ">=18"
28
28
  },
29
29
  "dependencies": {
30
- "@chatpanel/pii": "^0.7.0",
30
+ "@chatpanel/pii": "^0.7.1",
31
31
  "@huggingface/transformers": "^4.2.0",
32
32
  "onnxruntime-web": "1.26.0-dev.20260416-b7804b056c",
33
33
  "phonemizer": "^1.2.1"
package/src/redact.js CHANGED
@@ -20,7 +20,14 @@ import * as engine from './ner-engine.js';
20
20
  // quality — free requests get the full tier (names/orgs via NER) within their
21
21
  // allowance. The custom dictionary is capped for free: gatedDictionary limits it
22
22
  // to FREE_DICT_LIMIT via the shared chatpanel-pii gate.
23
- export async function redactSegments(segments, redactionCfg, { signal, isPro = true, onEgress = null, fetchImpl: fetchOverride = null } = {}) {
23
+ export async function redactSegments(segments, redactionCfg, {
24
+ signal, isPro = true, onEgress = null, fetchImpl: fetchOverride = null,
25
+ // The bundled detector, INJECTED rather than reached for. An ES module namespace cannot
26
+ // be monkey-patched, so with a hard import there is no way to ask "does an entity the
27
+ // engine found actually come out the other side as a token" without downloading a model.
28
+ // That question went unasked for exactly that reason, and the answer was no.
29
+ nerEngine = engine,
30
+ } = {}) {
24
31
  const vault = createVault();
25
32
 
26
33
  // De-steganography FIRST (before detection). Invisible/format Unicode is a triple
@@ -51,7 +58,7 @@ export async function redactSegments(segments, redactionCfg, { signal, isPro = t
51
58
  // @chatpanel/pii's caching / timeout / type-gating — one source of truth.
52
59
  const det = redactionCfg.detection;
53
60
  const useExternal = !!(det && det.backend && det.backend !== 'off');
54
- const useEngine = !useExternal && engine.isReady();
61
+ const useEngine = !useExternal && nerEngine.isReady();
55
62
 
56
63
  let entities = [];
57
64
  if (tier === 'full' && (useExternal || useEngine)) {
@@ -61,12 +68,18 @@ export async function redactSegments(segments, redactionCfg, { signal, isPro = t
61
68
  // under a second, but a cold one must be allowed to finish; on timeout the turn
62
69
  // falls back to dictionary/deterministic-only redaction.
63
70
  const detection = useEngine
64
- ? { backend: 'endpoint', url: 'inproc:ner', timeoutMs: 30000, maxChars: 8000, types: det?.types }
71
+ // `transport: 'in-process'` is load-bearing, not decoration. Without it the sentinel
72
+ // URL below fails @chatpanel/pii's SSRF scheme check, the throw is swallowed by the
73
+ // fail-open path, and the bundled engine contributes NOTHING to any redaction while
74
+ // reporting itself ready — names and organisations reach the model in full under a
75
+ // config that says "full". The flag is only honoured alongside an injected fetch,
76
+ // which is `engine.fetchAdapter` on the next line.
77
+ ? { backend: 'endpoint', url: 'inproc:ner', transport: 'in-process', timeoutMs: 30000, maxChars: 8000, types: det?.types }
65
78
  : { ...det, timeoutMs: Math.max(Number(det.timeoutMs) || 0, 30000) };
66
79
  // The in-process engine is already injected this way; `fetchOverride` is the same seam
67
80
  // for a test, so the detector hop can be exercised without a network. Never used in
68
81
  // production — nothing passes it but tests.
69
- const fetchImpl = useEngine ? engine.fetchAdapter : (fetchOverride || undefined);
82
+ const fetchImpl = useEngine ? nerEngine.fetchAdapter : (fetchOverride || undefined);
70
83
  try {
71
84
  entities = await detectEntities(texts.join('\n\n'), { detection }, {
72
85
  signal,
package/src/server.js CHANGED
@@ -55,7 +55,7 @@ import * as openai from './openai.js';
55
55
  import * as responses from './responses.js';
56
56
  import * as anthropic from './anthropic.js';
57
57
 
58
- export const VERSION = '0.6.62';
58
+ export const VERSION = '0.6.63';
59
59
 
60
60
  // WARM search tier — SQLite + FTS5 record store (falls back to an encrypted-JSON
61
61
  // store if SQLite can't load), fed by the extension's ingest sync + backup-ingest.