@com55/node-red-ai-access 0.1.0 → 0.1.1

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/CHANGELOG.md CHANGED
@@ -6,6 +6,74 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to
7
7
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
8
 
9
+ ## [0.1.1] - 2026-10-17
10
+
11
+ ### Added
12
+
13
+ - `/status` (REST and MCP `get_status`) now includes `openapiJson` in
14
+ `caller.baseUrls` for `rest` tokens, pointing at the served
15
+ `GET /mcp/api/v1/openapi.json`.
16
+ - Optional `aiAccess.corsAllowedOrigins` setting (settings.js): list of browser
17
+ origins allowed to call the AI-facing endpoints cross-origin, with
18
+ `Vary: Origin` and `OPTIONS` preflight support. Origins match
19
+ case-insensitively and the configured canonical origin is echoed; malformed
20
+ origins are rejected. `WWW-Authenticate`, `Retry-After`, and the `RateLimit*`
21
+ headers are exposed to allowed origins.
22
+ - Optional `aiAccess.publicBaseUrl` setting (settings.js) that sets the public
23
+ base URL for absolute links (access-link `url`, proposal `reviewUrl`,
24
+ `accessLinksUrl`), overriding `Host` and `X-Forwarded-*`.
25
+ - The MCP client snippets in the GET-link guide use the public origin
26
+ (`publicBaseUrl`, else the validated request origin) instead of the
27
+ `https://YOUR-NODE-RED` placeholder, which remains only when the origin cannot
28
+ be determined.
29
+
30
+ ### Changed
31
+
32
+ - **Behavior change:** every response on the GET access-link channel
33
+ (`/mcp/a/<token>/*`), including the markdown guide and JSON success and error
34
+ bodies, now defaults to `Content-Type: text/html; charset=utf-8`, because some
35
+ AI web fetchers only read `text/html`. The HTML is a minimal document with the
36
+ same markdown or pretty-printed JSON, fully escaped in one `<pre>`, with safe
37
+ relative `./…` links linkified (no scripts, no external resources). Status
38
+ codes and data are unchanged. The previous representation is returned when
39
+ `Accept` explicitly lists `text/markdown` or `application/json`, or
40
+ `?format=md` / `?format=json` is set; an explicit `Accept: text/plain` returns
41
+ the raw text as `text/plain`. All `/mcp/a/*` responses carry `Vary: Accept`,
42
+ `Cache-Control: no-store`, `Referrer-Policy: no-referrer`,
43
+ `X-Robots-Tag: noindex, nofollow`, and `X-Content-Type-Options: nosniff`; HTML
44
+ adds a restrictive `Content-Security-Policy`. REST and MCP are unchanged.
45
+ - **Behavior change:** the AI-facing endpoints (`/mcp/a/*`, `/mcp/api/*`,
46
+ `/mcp/rpc`) no longer send `Access-Control-Allow-Origin: *` (inherited from
47
+ Node-RED's admin app). No CORS headers are sent unless the request `Origin` is
48
+ listed in `aiAccess.corsAllowedOrigins`. Only browser-based callers are
49
+ affected; server-side clients are not.
50
+ - Attempts to create, list, or revoke access links under a GET access link
51
+ (`/mcp/a/<token>/links`) now return **403** `link_create_not_supported`
52
+ instead of 405. Error registry, `docs/errors.md`, OpenAPI, and get-link docs
53
+ updated.
54
+ - `/status` (GET link, REST, and MCP `get_status`) no longer includes
55
+ `storageDir` for access-link callers; it remains in admin-session responses
56
+ (shown on the `/mcp` page).
57
+
58
+ ### Fixed
59
+
60
+ - Proposal titles no longer repeat the flow name when a long `<title> in <flow>`
61
+ was capped at 80 characters.
62
+ - GET-link HTML no longer linkifies template paths such as `./nodes/<id>`.
63
+
64
+ - **Security:** absolute URLs no longer copy unvalidated `X-Forwarded-Proto` /
65
+ `X-Forwarded-Host` (or `Host`) values. The scheme must be exactly `http` or
66
+ `https` (else the connection scheme is used); hosts must be a DNS name, IPv4,
67
+ or bracketed IPv6 literal with an optional port 1-65535, with no userinfo,
68
+ path, query, fragment, or whitespace. Invalid forwarded hosts fall back to
69
+ `Host`; an invalid `Host` falls back to a relative path.
70
+
71
+ - Absolute URLs (access-link `url`, proposal `reviewUrl`, `accessLinksUrl`) used
72
+ `http://` behind a TLS-terminating proxy. With Express `trust proxy` enabled
73
+ the first `X-Forwarded-Proto` value is now used explicitly; without it, set
74
+ `aiAccess.publicBaseUrl`. Forwarded headers stay ignored when `trust proxy` is
75
+ off.
76
+
9
77
  ## [0.1.0] - 2026-10-16
10
78
 
11
79
  First public release of **Node-RED AI Access**: an admin-gated Node-RED 4 plugin
@@ -316,4 +384,5 @@ Requires Node.js 20+.
316
384
  auth, 413 limits) served at `GET /mcp/api/v1/openapi.yaml`; README install,
317
385
  config, `/mcp` usage, and AI quickstart.
318
386
 
387
+ [0.1.1]: https://github.com/com55/node-red-ai-access/compare/v0.1.0...v0.1.1
319
388
  [0.1.0]: https://github.com/com55/node-red-ai-access/releases/tag/v0.1.0
package/README.md CHANGED
@@ -74,10 +74,59 @@ module.exports = {
74
74
  rateLimitWindowMs: 60000,
75
75
  rateLimitMaxPerIp: 600,
76
76
  rateLimitMaxPerToken: 300,
77
+ // Public base URL for absolute links (access-link url, reviewUrl,
78
+ // accessLinksUrl). Set this behind a TLS-terminating proxy (default: unset)
79
+ // publicBaseUrl: 'https://nodered.example.com',
80
+ // Browser origins allowed to call /mcp/a, /mcp/api, and /mcp/rpc
81
+ // cross-origin (default: none — no CORS headers are sent)
82
+ // corsAllowedOrigins: ['https://app.example.com'],
77
83
  },
78
84
  };
79
85
  ```
80
86
 
87
+ ### Absolute URLs behind a reverse proxy
88
+
89
+ Access-link `url`, proposal `reviewUrl`, and `accessLinksUrl` (in
90
+ `token_expired`) are absolute URLs built by one shared helper:
91
+
92
+ 1. `aiAccess.publicBaseUrl`, when set, is used as-is (scheme, host, and an
93
+ optional path prefix). It is read from `settings.js` only.
94
+ 2. Otherwise, when Express `trust proxy` is enabled (for example
95
+ `httpServerOptions: { 'trust proxy': true }`), the first `X-Forwarded-Proto`
96
+ and `X-Forwarded-Host` values are used.
97
+ 3. Otherwise the request `Host` header and the connection protocol are used;
98
+ `X-Forwarded-*` headers are ignored so clients cannot spoof link origins.
99
+
100
+ Forwarded and `Host` values are validated: the scheme must be `http` or `https`
101
+ (else the connection scheme is used) and the host must be a DNS name, IPv4, or
102
+ bracketed IPv6 literal with an optional port. An invalid forwarded host falls
103
+ back to `Host`; an invalid `Host` yields relative paths. The MCP client snippets
104
+ in the GET-link guide use the same origin, and show a `https://YOUR-NODE-RED`
105
+ placeholder only when no origin can be determined.
106
+
107
+ Behind a TLS-terminating proxy (for example Cloudflare) with neither option set,
108
+ links therefore start with `http://`.
109
+
110
+ ### CORS
111
+
112
+ CORS only affects code running in a browser (a web page calling the plugin from
113
+ another origin). Server-side AI fetchers and agents (for example Gemini,
114
+ ChatGPT, or an MCP client running outside a browser) ignore CORS and are not
115
+ affected by this setting.
116
+
117
+ By default the AI-facing endpoints (`/mcp/a/*`, `/mcp/api/*`, `/mcp/rpc`) send
118
+ **no** CORS headers, overriding Node-RED's built-in
119
+ `Access-Control-Allow-Origin: *` for those paths. To allow a browser app, list
120
+ its origin in `aiAccess.corsAllowedOrigins` (`settings.js` only, restart
121
+ required). For a listed `Origin` the response echoes it in
122
+ `Access-Control-Allow-Origin` with `Vary: Origin`, and `OPTIONS` preflight
123
+ returns `204` with allowed methods (`GET, POST, DELETE, OPTIONS`) and headers
124
+ (`Authorization`, `Content-Type`, `Accept`, `Mcp-Session-Id`,
125
+ `Mcp-Protocol-Version`, `Last-Event-ID`). Origins match case-insensitively and
126
+ the configured origin is echoed; malformed origins are rejected. Exposed headers
127
+ include `Mcp-Session-Id`, `WWW-Authenticate`, `Retry-After`, and the
128
+ `RateLimit*` headers. Other origins get no CORS headers.
129
+
81
130
  ### Settings precedence
82
131
 
83
132
  Effective values are resolved **without restart** in this order (highest wins):
@@ -215,6 +264,21 @@ errors mark it **failed**. Reject never mutates flows.
215
264
  `nodes:[{op:'add'|'remove'|'set',...}]` (or a full `replace_flow` document).
216
265
  Field drafts validate node/field at `draft/new`. Unknown `/mcp/a/*` paths
217
266
  return JSON `not_found` without echoing the token.
267
+
268
+ Responses on GET links default to HTML, because some AI web fetchers only
269
+ read `text/html`. The HTML is a minimal document (no scripts or external
270
+ resources) holding the same markdown or pretty-printed JSON, fully escaped in
271
+ one `<pre>`, with relative `./…` links clickable. Request the real type via
272
+ Accept or `?format=`: `Accept: text/markdown` or `?format=md` for the guide,
273
+ `Accept: application/json` or `?format=json` for JSON. An explicit
274
+ `Accept: text/plain` returns the same raw text as `text/plain`. No Accept,
275
+ `*/*`, `text/html`, or browser-style lists get HTML. Status codes and data
276
+ are unchanged; every response carries `Vary: Accept`,
277
+ `Cache-Control: no-store`, `Referrer-Policy: no-referrer`,
278
+ `X-Robots-Tag: noindex, nofollow`, and `X-Content-Type-Options: nosniff`, and
279
+ HTML adds a restrictive `Content-Security-Policy`. REST and MCP endpoints are
280
+ not affected.
281
+
218
282
  3. Changes become **pending proposals**; a human approves on `/mcp`. JSON
219
283
  responses use `Cache-Control: no-store`.
220
284
 
package/ai-access.html CHANGED
@@ -14,7 +14,7 @@
14
14
  }
15
15
  window.__com55AiAccessSidebarLoading = true;
16
16
 
17
- var EDITOR_SIDEBAR_ASSET_VERSION = '0.1.0';
17
+ var EDITOR_SIDEBAR_ASSET_VERSION = '0.1.1';
18
18
  var root = (RED.settings.httpAdminRoot || '/').replace(/\/$/, '');
19
19
  var src =
20
20
  (root || '') +