nyte 1.2.2 → 1.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nyte",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "Nyte.js is a high-level framework for building web applications with ease and speed. It provides a robust set of tools and features to streamline development and enhance productivity.",
5
5
  "types": "dist/index.d.ts",
6
6
  "author": "itsmuzin",
@@ -29,6 +29,11 @@
29
29
  "import": "./dist/index.js",
30
30
  "require": "./dist/index.js"
31
31
  },
32
+ "./console": {
33
+ "types": "./dist/api/console.d.ts",
34
+ "import": "./dist/api/console.js",
35
+ "require": "./dist/api/console.js"
36
+ },
32
37
  "./react": {
33
38
  "types": "./dist/client/client.d.ts",
34
39
  "import": "./dist/client/client.js",
@@ -1 +0,0 @@
1
- export {};
@@ -1,170 +0,0 @@
1
- "use strict";
2
- /*
3
- * Lightweight security self-tests (no external test runner required).
4
- *
5
- * These tests validate that:
6
- * - Static serving does NOT allow path traversal outside its base dirs.
7
- * - HTTP->HTTPS redirect does NOT trust hostile Host headers.
8
- *
9
- * Run (from repo root):
10
- * node packages/nytejs/dist/security.selftest.js
11
- */
12
- var __importDefault = (this && this.__importDefault) || function (mod) {
13
- return (mod && mod.__esModule) ? mod : { "default": mod };
14
- };
15
- Object.defineProperty(exports, "__esModule", { value: true });
16
- const fs_1 = __importDefault(require("fs"));
17
- const path_1 = __importDefault(require("path"));
18
- const http_1 = __importDefault(require("http"));
19
- const helpers_1 = require("./helpers");
20
- function assert(condition, message) {
21
- if (!condition)
22
- throw new Error(message);
23
- }
24
- async function httpRequest(opts) {
25
- return new Promise((resolve, reject) => {
26
- const req = http_1.default.request(opts, (res) => {
27
- let data = '';
28
- res.setEncoding('utf8');
29
- res.on('data', (c) => (data += c));
30
- res.on('end', () => resolve({ status: res.statusCode || 0, headers: res.headers, body: data }));
31
- });
32
- req.on('error', reject);
33
- if (opts.body)
34
- req.write(opts.body);
35
- req.end();
36
- });
37
- }
38
- function randomPort() {
39
- // Avoid privileged ports. Not cryptographic; just for tests.
40
- return 20000 + Math.floor(Math.random() * 20000);
41
- }
42
- // --- Redirect host poisoning: test a pure builder (no TLS required) ---
43
- function buildHttpsRedirectLocation(params) {
44
- const rawHost = String(params.requestHostHeader || '').trim();
45
- let hostToUse = String(params.configuredHostname || params.fallbackHostname || '').trim();
46
- if (!hostToUse) {
47
- const hostWithoutPort = rawHost.split(':')[0];
48
- const isValidHost = /^[a-zA-Z0-9.-]+$/.test(hostWithoutPort) && !hostWithoutPort.includes('..');
49
- hostToUse = isValidHost ? hostWithoutPort : 'localhost';
50
- }
51
- let redirectUrl = `https://${hostToUse}`;
52
- if (params.httpsPort !== 443)
53
- redirectUrl += `:${params.httpsPort}`;
54
- redirectUrl += params.requestUrl || '/';
55
- return redirectUrl;
56
- }
57
- async function withTempProject(fn) {
58
- const base = path_1.default.join(process.cwd(), '.nyte-security-selftest');
59
- fs_1.default.mkdirSync(base, { recursive: true });
60
- const projectDir = fs_1.default.mkdtempSync(path_1.default.join(base, 'proj-'));
61
- // minimal folder structure
62
- fs_1.default.mkdirSync(path_1.default.join(projectDir, 'src', 'web', 'routes'), { recursive: true });
63
- fs_1.default.mkdirSync(path_1.default.join(projectDir, 'src', 'backend', 'routes'), { recursive: true });
64
- fs_1.default.mkdirSync(path_1.default.join(projectDir, 'public'), { recursive: true });
65
- // Basic route so app.prepare() succeeds (route loader expects files)
66
- const routeFile = path_1.default.join(projectDir, 'src', 'web', 'routes', 'index.tsx');
67
- fs_1.default.writeFileSync(routeFile, `export default function Index(){ return null; }\nexport const component = Index;\n`, 'utf8');
68
- // A secret file OUTSIDE public/.nyte to test traversal attempts
69
- fs_1.default.writeFileSync(path_1.default.join(projectDir, 'SECRET.txt'), 'TOP_SECRET', 'utf8');
70
- try {
71
- return await fn(projectDir);
72
- }
73
- finally {
74
- try {
75
- fs_1.default.rmSync(projectDir, { recursive: true, force: true });
76
- }
77
- catch {
78
- // ignore cleanup issues
79
- }
80
- }
81
- }
82
- async function testStaticPathTraversal() {
83
- await withTempProject(async (projectDir) => {
84
- const port = randomPort();
85
- const server = await (0, helpers_1.app)({ dir: projectDir, dev: true, port, hostname: '127.0.0.1' }).init();
86
- try {
87
- // Try to escape public dir
88
- const res1 = await httpRequest({
89
- hostname: '127.0.0.1',
90
- port,
91
- method: 'GET',
92
- path: '/../SECRET.txt'
93
- });
94
- assert(res1.status !== 200, `Expected traversal from public to be blocked, got 200 with body: ${res1.body}`);
95
- assert(!res1.body.includes('TOP_SECRET'), 'Traversal leaked secret content');
96
- // Try to escape .nyte via /_nyte/
97
- const res2 = await httpRequest({
98
- hostname: '127.0.0.1',
99
- port,
100
- method: 'GET',
101
- path: '/_nyte/../SECRET.txt'
102
- });
103
- assert(res2.status !== 200, `Expected traversal from /_nyte to be blocked, got 200 with body: ${res2.body}`);
104
- assert(!res2.body.includes('TOP_SECRET'), 'Traversal via /_nyte leaked secret content');
105
- // Encoded traversal
106
- const res3 = await httpRequest({
107
- hostname: '127.0.0.1',
108
- port,
109
- method: 'GET',
110
- path: '/_nyte/%2e%2e/SECRET.txt'
111
- });
112
- assert(res3.status !== 200, `Expected encoded traversal to be blocked, got 200 with body: ${res3.body}`);
113
- assert(!res3.body.includes('TOP_SECRET'), 'Encoded traversal leaked secret content');
114
- }
115
- finally {
116
- await new Promise((resolve) => server.close(() => resolve()));
117
- }
118
- });
119
- }
120
- async function testHttpsRedirectHostPoisoning() {
121
- // configuredHostname should win over hostile Host header
122
- const loc1 = buildHttpsRedirectLocation({
123
- configuredHostname: 'example.com',
124
- fallbackHostname: '0.0.0.0',
125
- httpsPort: 8443,
126
- requestHostHeader: 'evil.com',
127
- requestUrl: '/login?x=1'
128
- });
129
- assert(loc1.startsWith('https://example.com:8443/'), `Expected configured hostname, got ${loc1}`);
130
- assert(!loc1.includes('evil.com'), `Redirect trusted hostile Host header. Location=${loc1}`);
131
- // without configured host, a valid host header is accepted
132
- const loc2 = buildHttpsRedirectLocation({
133
- httpsPort: 443,
134
- requestHostHeader: 'good.example',
135
- requestUrl: '/'
136
- });
137
- assert(loc2 === 'https://good.example/', `Expected host header to be used, got ${loc2}`);
138
- // invalid host header should be rejected
139
- const loc3 = buildHttpsRedirectLocation({
140
- httpsPort: 443,
141
- requestHostHeader: 'evil.com\r\nX-Evil: 1',
142
- requestUrl: '/'
143
- });
144
- assert(loc3 === 'https://localhost/', `Expected invalid host to fallback to localhost, got ${loc3}`);
145
- }
146
- async function main() {
147
- const results = [];
148
- async function run(name, fn) {
149
- try {
150
- await fn();
151
- results.push({ name, ok: true });
152
- }
153
- catch (e) {
154
- results.push({ name, ok: false, error: e?.message || String(e) });
155
- }
156
- }
157
- await run('static path traversal', testStaticPathTraversal);
158
- await run('https redirect host poisoning', testHttpsRedirectHostPoisoning);
159
- const failed = results.filter(r => !r.ok);
160
- for (const r of results) {
161
- // eslint-disable-next-line no-console
162
- console.log(`${r.ok ? 'PASS' : 'FAIL'} - ${r.name}${r.ok ? '' : `: ${r.error}`}`);
163
- }
164
- if (failed.length) {
165
- process.exitCode = 1;
166
- }
167
- }
168
- // Only run when executed directly
169
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
170
- main();
@@ -1,192 +0,0 @@
1
- /*
2
- * Lightweight security self-tests (no external test runner required).
3
- *
4
- * These tests validate that:
5
- * - Static serving does NOT allow path traversal outside its base dirs.
6
- * - HTTP->HTTPS redirect does NOT trust hostile Host headers.
7
- *
8
- * Run (from repo root):
9
- * node packages/nytejs/dist/security.selftest.js
10
- */
11
-
12
- import fs from 'fs';
13
- import path from 'path';
14
- import http from 'http';
15
- import { app } from './helpers';
16
-
17
- function assert(condition: any, message: string): void {
18
- if (!condition) throw new Error(message);
19
- }
20
-
21
- async function httpRequest(opts: http.RequestOptions & { body?: string }): Promise<{ status: number; headers: http.IncomingHttpHeaders; body: string }> {
22
- return new Promise((resolve, reject) => {
23
- const req = http.request(opts, (res) => {
24
- let data = '';
25
- res.setEncoding('utf8');
26
- res.on('data', (c) => (data += c));
27
- res.on('end', () => resolve({ status: res.statusCode || 0, headers: res.headers, body: data }));
28
- });
29
- req.on('error', reject);
30
- if (opts.body) req.write(opts.body);
31
- req.end();
32
- });
33
- }
34
-
35
- function randomPort(): number {
36
- // Avoid privileged ports. Not cryptographic; just for tests.
37
- return 20000 + Math.floor(Math.random() * 20000);
38
- }
39
-
40
- // --- Redirect host poisoning: test a pure builder (no TLS required) ---
41
- function buildHttpsRedirectLocation(params: {
42
- configuredHostname?: string;
43
- fallbackHostname?: string;
44
- httpsPort: number;
45
- requestHostHeader?: string;
46
- requestUrl?: string;
47
- }): string {
48
- const rawHost = String(params.requestHostHeader || '').trim();
49
- let hostToUse = String(params.configuredHostname || params.fallbackHostname || '').trim();
50
-
51
- if (!hostToUse) {
52
- const hostWithoutPort = rawHost.split(':')[0];
53
- const isValidHost = /^[a-zA-Z0-9.-]+$/.test(hostWithoutPort) && !hostWithoutPort.includes('..');
54
- hostToUse = isValidHost ? hostWithoutPort : 'localhost';
55
- }
56
-
57
- let redirectUrl = `https://${hostToUse}`;
58
- if (params.httpsPort !== 443) redirectUrl += `:${params.httpsPort}`;
59
- redirectUrl += params.requestUrl || '/';
60
- return redirectUrl;
61
- }
62
-
63
- async function withTempProject<T>(fn: (projectDir: string) => Promise<T>): Promise<T> {
64
- const base = path.join(process.cwd(), '.nyte-security-selftest');
65
- fs.mkdirSync(base, { recursive: true });
66
- const projectDir = fs.mkdtempSync(path.join(base, 'proj-'));
67
-
68
- // minimal folder structure
69
- fs.mkdirSync(path.join(projectDir, 'src', 'web', 'routes'), { recursive: true });
70
- fs.mkdirSync(path.join(projectDir, 'src', 'backend', 'routes'), { recursive: true });
71
- fs.mkdirSync(path.join(projectDir, 'public'), { recursive: true });
72
-
73
- // Basic route so app.prepare() succeeds (route loader expects files)
74
- const routeFile = path.join(projectDir, 'src', 'web', 'routes', 'index.tsx');
75
- fs.writeFileSync(
76
- routeFile,
77
- `export default function Index(){ return null; }\nexport const component = Index;\n`,
78
- 'utf8'
79
- );
80
-
81
- // A secret file OUTSIDE public/.nyte to test traversal attempts
82
- fs.writeFileSync(path.join(projectDir, 'SECRET.txt'), 'TOP_SECRET', 'utf8');
83
-
84
- try {
85
- return await fn(projectDir);
86
- } finally {
87
- try {
88
- fs.rmSync(projectDir, { recursive: true, force: true });
89
- } catch {
90
- // ignore cleanup issues
91
- }
92
- }
93
- }
94
-
95
- async function testStaticPathTraversal(): Promise<void> {
96
- await withTempProject(async (projectDir) => {
97
- const port = randomPort();
98
- const server = await app({ dir: projectDir, dev: true, port, hostname: '127.0.0.1' }).init();
99
- try {
100
- // Try to escape public dir
101
- const res1 = await httpRequest({
102
- hostname: '127.0.0.1',
103
- port,
104
- method: 'GET',
105
- path: '/../SECRET.txt'
106
- });
107
- assert(res1.status !== 200, `Expected traversal from public to be blocked, got 200 with body: ${res1.body}`);
108
- assert(!res1.body.includes('TOP_SECRET'), 'Traversal leaked secret content');
109
-
110
- // Try to escape .nyte via /_nyte/
111
- const res2 = await httpRequest({
112
- hostname: '127.0.0.1',
113
- port,
114
- method: 'GET',
115
- path: '/_nyte/../SECRET.txt'
116
- });
117
- assert(res2.status !== 200, `Expected traversal from /_nyte to be blocked, got 200 with body: ${res2.body}`);
118
- assert(!res2.body.includes('TOP_SECRET'), 'Traversal via /_nyte leaked secret content');
119
-
120
- // Encoded traversal
121
- const res3 = await httpRequest({
122
- hostname: '127.0.0.1',
123
- port,
124
- method: 'GET',
125
- path: '/_nyte/%2e%2e/SECRET.txt'
126
- });
127
- assert(res3.status !== 200, `Expected encoded traversal to be blocked, got 200 with body: ${res3.body}`);
128
- assert(!res3.body.includes('TOP_SECRET'), 'Encoded traversal leaked secret content');
129
- } finally {
130
- await new Promise<void>((resolve) => (server as any).close(() => resolve()));
131
- }
132
- });
133
- }
134
-
135
- async function testHttpsRedirectHostPoisoning(): Promise<void> {
136
- // configuredHostname should win over hostile Host header
137
- const loc1 = buildHttpsRedirectLocation({
138
- configuredHostname: 'example.com',
139
- fallbackHostname: '0.0.0.0',
140
- httpsPort: 8443,
141
- requestHostHeader: 'evil.com',
142
- requestUrl: '/login?x=1'
143
- });
144
- assert(loc1.startsWith('https://example.com:8443/'), `Expected configured hostname, got ${loc1}`);
145
- assert(!loc1.includes('evil.com'), `Redirect trusted hostile Host header. Location=${loc1}`);
146
-
147
- // without configured host, a valid host header is accepted
148
- const loc2 = buildHttpsRedirectLocation({
149
- httpsPort: 443,
150
- requestHostHeader: 'good.example',
151
- requestUrl: '/'
152
- });
153
- assert(loc2 === 'https://good.example/', `Expected host header to be used, got ${loc2}`);
154
-
155
- // invalid host header should be rejected
156
- const loc3 = buildHttpsRedirectLocation({
157
- httpsPort: 443,
158
- requestHostHeader: 'evil.com\r\nX-Evil: 1',
159
- requestUrl: '/'
160
- });
161
- assert(loc3 === 'https://localhost/', `Expected invalid host to fallback to localhost, got ${loc3}`);
162
- }
163
-
164
- async function main(): Promise<void> {
165
- const results: Array<{ name: string; ok: boolean; error?: string }> = [];
166
-
167
- async function run(name: string, fn: () => Promise<void>) {
168
- try {
169
- await fn();
170
- results.push({ name, ok: true });
171
- } catch (e: any) {
172
- results.push({ name, ok: false, error: e?.message || String(e) });
173
- }
174
- }
175
-
176
- await run('static path traversal', testStaticPathTraversal);
177
- await run('https redirect host poisoning', testHttpsRedirectHostPoisoning);
178
-
179
- const failed = results.filter(r => !r.ok);
180
- for (const r of results) {
181
- // eslint-disable-next-line no-console
182
- console.log(`${r.ok ? 'PASS' : 'FAIL'} - ${r.name}${r.ok ? '' : `: ${r.error}`}`);
183
- }
184
-
185
- if (failed.length) {
186
- process.exitCode = 1;
187
- }
188
- }
189
-
190
- // Only run when executed directly
191
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
192
- main();