zengate 1.0.1 → 1.0.2

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/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  [![CI](https://github.com/developingchet/zengate/actions/workflows/ci.yml/badge.svg)](https://github.com/developingchet/zengate/actions/workflows/ci.yml)
4
4
  [![npm](https://img.shields.io/npm/v/zengate)](https://www.npmjs.com/package/zengate)
5
5
  [![Docker Hub](https://img.shields.io/docker/v/developingchet/zengate?label=docker&sort=semver)](https://hub.docker.com/r/developingchet/zengate)
6
+ [![Socket](https://socket.dev/api/badge/npm/package/zengate)](https://socket.dev/npm/package/zengate)
6
7
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
8
 
8
9
  An **OpenAI-compatible API for OpenCode's free Zen models: no Zen account or upstream API key needed.**
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "zengate",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Keyless OpenAI-compatible API (Chat Completions + Responses) for OpenCode's free Zen models, served through the real OpenCode CLI.",
5
+ "author": {
6
+ "name": "developingchet",
7
+ "url": "https://github.com/developingchet"
8
+ },
5
9
  "homepage": "https://github.com/developingchet/zengate#readme",
6
10
  "repository": {
7
11
  "type": "git",
@@ -46,12 +50,16 @@
46
50
  "self-hosted"
47
51
  ],
48
52
  "license": "MIT",
49
- "engines": {
50
- "node": ">=24.0.0"
51
- },
52
53
  "dependencies": {
53
- "cors": "^2.8.5",
54
54
  "express": "^5.1.0",
55
55
  "opencode-ai": "^1.18.32"
56
+ },
57
+ "overrides": {
58
+ "es-define-property": "npm:@socketregistry/es-define-property@^1",
59
+ "safer-buffer": "npm:@socketregistry/safer-buffer@^1",
60
+ "side-channel": "npm:@socketregistry/side-channel@^1"
61
+ },
62
+ "engines": {
63
+ "node": ">=24.0.0"
56
64
  }
57
65
  }
package/src/server/app.js CHANGED
@@ -1,8 +1,8 @@
1
- import cors from 'cors';
2
1
  import express from 'express';
3
2
  import { chatCompletionsHandler } from '../openai/chat.js';
4
3
  import { modelsHandlers } from '../openai/models.js';
5
4
  import { responsesHandlers } from '../openai/responses.js';
5
+ import { corsMiddleware } from './cors.js';
6
6
  import { ApiError, sendError, toApiError } from './errors.js';
7
7
  import { createLimiter } from './limiter.js';
8
8
  import { createMetrics } from './metrics.js';
@@ -68,7 +68,7 @@ export function createApp({ config, logger, backend, hub, catalog, runner, store
68
68
  app.set('trust proxy', config.TRUST_PROXY || false);
69
69
  app.use(requestId, securityHeaders, metrics.middleware, requestLogger(logger));
70
70
  if (config.CORS_ORIGINS.length) {
71
- app.use(cors({ origin: config.CORS_ORIGINS, exposedHeaders: EXPOSED_HEADERS, maxAge: 600 }));
71
+ app.use(corsMiddleware({ origins: config.CORS_ORIGINS, exposedHeaders: EXPOSED_HEADERS, maxAge: 600 }));
72
72
  }
73
73
 
74
74
  app.get('/health', (req, res) => res.json({ status: 'ok' }));
@@ -0,0 +1,33 @@
1
+ const ALLOWED_METHODS = 'GET,HEAD,POST,DELETE,OPTIONS';
2
+ // A comma-separated list of HTTP header names (RFC 9110 tokens).
3
+ const HEADER_LIST = /^[!#$%&'*+.^`|~\w-]+(?:\s*,\s*[!#$%&'*+.^`|~\w-]+)*$/;
4
+
5
+ /**
6
+ * CORS for an explicit list of browser origins. Origins are matched exactly,
7
+ * credentials are never allowed, and preflights are answered directly.
8
+ * @param {{ origins: string[], exposedHeaders: string[], maxAge: number }} options
9
+ */
10
+ export function corsMiddleware({ origins, exposedHeaders, maxAge }) {
11
+ const allowed = new Set(origins);
12
+ const exposed = exposedHeaders.join(',');
13
+
14
+ return (req, res, next) => {
15
+ const origin = req.headers.origin;
16
+ if (!origin) return next();
17
+ res.vary('Origin');
18
+ const granted = allowed.has(origin);
19
+ const isPreflight = req.method === 'OPTIONS' && Boolean(req.headers['access-control-request-method']);
20
+
21
+ if (!isPreflight) {
22
+ if (granted) res.set({ 'Access-Control-Allow-Origin': origin, 'Access-Control-Expose-Headers': exposed });
23
+ return next();
24
+ }
25
+ if (granted) {
26
+ const requested = String(req.headers['access-control-request-headers'] || '').trim();
27
+ res.set({ 'Access-Control-Allow-Origin': origin, 'Access-Control-Allow-Methods': ALLOWED_METHODS, 'Access-Control-Max-Age': String(maxAge) });
28
+ if (requested && HEADER_LIST.test(requested)) res.set('Access-Control-Allow-Headers', requested);
29
+ res.vary('Access-Control-Request-Headers');
30
+ }
31
+ return res.status(204).end();
32
+ };
33
+ }