@nac3/forge-cli 1.9.1 → 1.9.3

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.
@@ -0,0 +1,142 @@
1
+ /**
2
+ * public_origin -- el origen PUBLICO autorizado de un panel servido detras de
3
+ * un reverse proxy (Pablo 2026-10-10, desplegando el embed de ADIP-Kikan).
4
+ *
5
+ * El panel liga 127.0.0.1 y su guard de requests mutantes exige que el `Host`
6
+ * y el `Origin` sean loopback: es la defensa contra DNS-rebinding y CSRF, y
7
+ * para el modo normal (tunel SSH a 127.0.0.1) es exactamente lo que queremos.
8
+ *
9
+ * Pero el embed residente se publica por un vhost con `auth_request`
10
+ * (forge.<host-de-la-app>): nginx reenvia `Host: forge...` y el navegador
11
+ * manda ese `Origin`, asi que TODO POST/PUT/PATCH/DELETE caia con 403 --
12
+ * validar la API key, mandar un mensaje, aprobar un modal. El panel cargaba
13
+ * (los GET pasan) y parecia vivo a medias.
14
+ *
15
+ * La solucion NO es relajar el guard: es declarar, por despliegue, el origen
16
+ * publico legitimo. Va por env var (no por el perfil commiteado) porque es
17
+ * propio de la VM -- el mismo repo se despliega detras de un sslip.io, de un
18
+ * dominio real o de ningun proxy:
19
+ *
20
+ * Environment=YF_PUBLIC_ORIGIN=https://forge.35-199-81-8.sslip.io
21
+ *
22
+ * Acepta varios separados por coma. Todo lo que no sea loopback ni uno de
23
+ * estos sigue rechazado.
24
+ *
25
+ * ASCII-only.
26
+ */
27
+ export const LOOPBACK_HOSTNAMES = new Set([
28
+ 'localhost', '127.0.0.1', '::1', '[::1]', '0.0.0.0',
29
+ ]);
30
+ /**
31
+ * Parsea YF_PUBLIC_ORIGIN. Acepta "https://forge.ejemplo.com",
32
+ * "forge.ejemplo.com" y varias entradas separadas por coma o espacio.
33
+ * Defensivo: una entrada malformada se ignora en vez de tirar el arranque.
34
+ */
35
+ export function parsePublicOrigins(raw) {
36
+ const out = [];
37
+ const seen = new Set();
38
+ for (const piece of String(raw || '').split(/[,\s]+/)) {
39
+ const s = piece.trim();
40
+ if (!s)
41
+ continue;
42
+ let hostname = '', origin = '';
43
+ if (s.indexOf('://') > 0) {
44
+ try {
45
+ const u = new URL(s);
46
+ hostname = u.hostname.toLowerCase();
47
+ origin = u.origin.toLowerCase();
48
+ }
49
+ catch {
50
+ continue;
51
+ }
52
+ }
53
+ else {
54
+ /* solo el host: se acepta cualquier scheme contra ese host. Se exige al
55
+ * menos un punto para no tragarse palabras sueltas de un valor mal
56
+ * escrito ("no una url" no son tres hosts). */
57
+ hostname = s.toLowerCase().replace(/\/+$/, '');
58
+ if (!/^[a-z0-9-]+(\.[a-z0-9-]+)+(:\d+)?$/.test(hostname))
59
+ continue;
60
+ hostname = hostname.split(':')[0];
61
+ }
62
+ if (!hostname)
63
+ continue;
64
+ const key = hostname + '|' + origin;
65
+ if (seen.has(key))
66
+ continue;
67
+ seen.add(key);
68
+ out.push({ hostname, origin });
69
+ }
70
+ return out;
71
+ }
72
+ /** El hostname que declara un header Host (sin puerto, en minuscula). */
73
+ export function hostnameFromHostHeader(hostHeader) {
74
+ const h = String(hostHeader || '').trim().toLowerCase();
75
+ if (!h)
76
+ return '';
77
+ if (h.charAt(0) === '[') { /* [::1]:4847 */
78
+ const close = h.indexOf(']');
79
+ return close > 0 ? h.slice(0, close + 1) : h;
80
+ }
81
+ return h.split(':')[0];
82
+ }
83
+ /**
84
+ * Decide si un request mutante puede pasar, dado su Host, su Origin y los
85
+ * origenes publicos declarados. Pura: la testea `public-origin` y la usa el
86
+ * guard del chat server.
87
+ */
88
+ export function checkMutatingRequest(hostHeader, originHeader, allowed) {
89
+ const host = hostnameFromHostHeader(hostHeader);
90
+ const hostOk = !host
91
+ || LOOPBACK_HOSTNAMES.has(host)
92
+ || allowed.some((a) => a.hostname === host);
93
+ if (!hostOk) {
94
+ return {
95
+ ok: false,
96
+ reason: 'Host no-loopback rechazado (posible DNS-rebinding): ' + host.slice(0, 60)
97
+ + (allowed.length
98
+ ? ''
99
+ : '. Si este panel se sirve detras de un reverse proxy, declara su origen publico'
100
+ + ' en la env var YF_PUBLIC_ORIGIN del servicio.'),
101
+ };
102
+ }
103
+ const origin = String(originHeader || '').trim();
104
+ if (origin && origin.toLowerCase() !== 'null') {
105
+ let oh = '', oorigin = '';
106
+ try {
107
+ const u = new URL(origin);
108
+ oh = u.hostname.toLowerCase();
109
+ oorigin = u.origin.toLowerCase();
110
+ }
111
+ catch {
112
+ return { ok: false, reason: 'Origin malformado' };
113
+ }
114
+ const originOk = LOOPBACK_HOSTNAMES.has(oh)
115
+ || LOOPBACK_HOSTNAMES.has('[' + oh + ']')
116
+ /* con origen declarado se exige el origen COMPLETO (scheme + puerto)
117
+ * cuando se declaro con scheme; si solo se declaro el host, basta el host */
118
+ || allowed.some((a) => (a.origin ? a.origin === oorigin : a.hostname === oh));
119
+ if (!originOk) {
120
+ return {
121
+ ok: false,
122
+ reason: 'Origin cross-site rechazado (CSRF): ' + origin.slice(0, 80)
123
+ + (allowed.length
124
+ ? ''
125
+ : '. Si este panel se sirve detras de un reverse proxy, declara su origen publico'
126
+ + ' en la env var YF_PUBLIC_ORIGIN del servicio.'),
127
+ };
128
+ }
129
+ }
130
+ return { ok: true };
131
+ }
132
+ let CACHE = null;
133
+ /** Los origenes publicos declarados para este proceso (cacheado por valor). */
134
+ export function publicOrigins() {
135
+ const raw = String(process.env.YF_PUBLIC_ORIGIN || '');
136
+ if (!CACHE || CACHE.raw !== raw)
137
+ CACHE = { raw, parsed: parsePublicOrigins(raw) };
138
+ return CACHE.parsed;
139
+ }
140
+ /** Para tests. */
141
+ export function _resetPublicOriginsCache() { CACHE = null; }
142
+ //# sourceMappingURL=public_origin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public_origin.js","sourceRoot":"","sources":["../../src/core/public_origin.ts"],"names":[],"mappings":"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"}
package/dist/version.d.ts CHANGED
@@ -2,5 +2,5 @@
2
2
  * Yujin Forge CLI version. Kept in sync with package.json by the npm
3
3
  * "version" lifecycle (scripts/sync_version.mjs) so the two never drift.
4
4
  */
5
- export declare const VERSION = "1.9.1";
5
+ export declare const VERSION = "1.9.3";
6
6
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -2,5 +2,5 @@
2
2
  * Yujin Forge CLI version. Kept in sync with package.json by the npm
3
3
  * "version" lifecycle (scripts/sync_version.mjs) so the two never drift.
4
4
  */
5
- export const VERSION = '1.9.1';
5
+ export const VERSION = '1.9.3';
6
6
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nac3/forge-cli",
3
- "version": "1.9.1",
3
+ "version": "1.9.3",
4
4
  "description": "Yujin Forge -- voice-first NAC-3 React development framework. CLI + chat panel + spec ingest + 10-format document reader + voice loop.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Pablo Kuschnirof <pablo@rpaforce.com>",