veryfront 0.1.1284 → 0.1.1285
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/docs/README.md +1 -1
- package/docs/config.json +1 -1
- package/docs/manifest.json +5 -5
- package/esm/deno.js +3 -3
- package/esm/extensions/ext-schema-zod/src/adapter.d.ts.map +1 -1
- package/esm/extensions/ext-schema-zod/src/adapter.js +16 -4
- package/esm/extensions/ext-schema-zod/src/json-schema.d.ts +2 -0
- package/esm/extensions/ext-schema-zod/src/json-schema.d.ts.map +1 -1
- package/esm/extensions/ext-schema-zod/src/json-schema.js +18 -2
- package/esm/extensions/ext-schema-zod/src/object-data-properties.d.ts +4 -0
- package/esm/extensions/ext-schema-zod/src/object-data-properties.d.ts.map +1 -0
- package/esm/extensions/ext-schema-zod/src/object-data-properties.js +98 -0
- package/esm/extensions/ext-schema-zod/src/union-diagnostics.d.ts +4 -0
- package/esm/extensions/ext-schema-zod/src/union-diagnostics.d.ts.map +1 -0
- package/esm/extensions/ext-schema-zod/src/union-diagnostics.js +63 -0
- package/esm/src/agent/service/application-request-credentials.d.ts +3 -0
- package/esm/src/agent/service/application-request-credentials.d.ts.map +1 -0
- package/esm/src/agent/service/application-request-credentials.js +15 -0
- package/esm/src/agent/service/broker-ingress.d.ts.map +1 -1
- package/esm/src/agent/service/broker-ingress.js +2 -0
- package/esm/src/agent/service/routes.d.ts.map +1 -1
- package/esm/src/agent/service/routes.js +2 -13
- package/esm/src/discovery/embedded-npm-packages.generated.js +7 -7
- package/esm/src/html/hydration-script-builder/hydration-runtime.generated.js +1 -1
- package/esm/src/platform/compat/http/pinned-fetch.d.ts +1 -1
- package/esm/src/routing/api/module-loader/external-import-rewriter.d.ts.map +1 -1
- package/esm/src/routing/api/module-loader/external-import-rewriter.js +10 -7
- package/esm/src/routing/api/module-loader/loader-helpers.d.ts.map +1 -1
- package/esm/src/routing/api/module-loader/loader-helpers.js +2 -1
- package/esm/src/routing/api/module-loader/loader.d.ts.map +1 -1
- package/esm/src/routing/api/module-loader/loader.js +47 -80
- package/esm/src/routing/api/module-loader/path-redaction.d.ts +31 -0
- package/esm/src/routing/api/module-loader/path-redaction.d.ts.map +1 -0
- package/esm/src/routing/api/module-loader/path-redaction.js +149 -0
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +10 -10
|
@@ -2,6 +2,7 @@ import * as dntShim from "../../../../_dnt.shims.js";
|
|
|
2
2
|
import { computeCodeHash, computeHash, isCompiledBinary, serverLogger } from "../../../utils/index.js";
|
|
3
3
|
import { readTypeScriptDecoratorOptions } from "../../../extensions/bundler/index.js";
|
|
4
4
|
import { createHTTPPlugin } from "./esbuild-plugin.js";
|
|
5
|
+
import { isSeparator, redactFreeText, remoteOrigin, replaceMatches, structuralPathLabel, } from "./path-redaction.js";
|
|
5
6
|
import { rewriteImportMetaLocations } from "./source-capability-analyzer.js";
|
|
6
7
|
import { restrictedRuntimeModuleReason, validateHTTPImports, validateModuleSpecifierHosts, } from "./http-validator.js";
|
|
7
8
|
import { loadSecurityConfig } from "./security-config.js";
|
|
@@ -34,6 +35,9 @@ const IntrinsicReflectOwnKeys = Reflect.ownKeys;
|
|
|
34
35
|
const MapPrototypeDelete = Map.prototype.delete;
|
|
35
36
|
const MapPrototypeGet = Map.prototype.get;
|
|
36
37
|
const MapPrototypeSet = Map.prototype.set;
|
|
38
|
+
// Redaction runs after project code has evaluated in this realm, so it must
|
|
39
|
+
// not dispatch through prototypes that code could have replaced.
|
|
40
|
+
const RegExpPrototypeExec = RegExp.prototype.exec;
|
|
37
41
|
const trustedRequestContextAccessor = currentRequestContext;
|
|
38
42
|
/**
|
|
39
43
|
* A specifier the HTTP plugin fetches rather than a path. URL schemes are
|
|
@@ -67,7 +71,7 @@ export function loadHandlerModule(options) {
|
|
|
67
71
|
return extractAPIRouteHandlers(module);
|
|
68
72
|
}
|
|
69
73
|
catch (error) {
|
|
70
|
-
const errorMsg =
|
|
74
|
+
const errorMsg = redactFreeText(error instanceof Error ? error.message : String(error));
|
|
71
75
|
// The reason, not the error: its stack names host-absolute paths.
|
|
72
76
|
logger.error(`Failed to load API handler ${projectModuleLabel(modulePath, projectDir)}:`, { reason: errorMsg });
|
|
73
77
|
throw toError(createError({
|
|
@@ -105,7 +109,7 @@ export function prepareHandlerModule(options) {
|
|
|
105
109
|
});
|
|
106
110
|
}
|
|
107
111
|
catch (error) {
|
|
108
|
-
const errorMsg =
|
|
112
|
+
const errorMsg = redactFreeText(error instanceof Error ? error.message : String(error));
|
|
109
113
|
logger.error(`Failed to prepare isolated API handler ${projectModuleLabel(modulePath, projectDir)}:`, { reason: errorMsg });
|
|
110
114
|
throw toError(createError({
|
|
111
115
|
type: "api",
|
|
@@ -161,13 +165,13 @@ async function loadModule(args) {
|
|
|
161
165
|
if (!isSpecifierResolutionError(error))
|
|
162
166
|
throw error;
|
|
163
167
|
logger.debug("Direct import could not resolve a specifier, bundling instead", {
|
|
164
|
-
modulePath,
|
|
165
|
-
error: error instanceof Error ? error.message : String(error),
|
|
168
|
+
modulePath: projectModuleLabel(modulePath, projectDir),
|
|
169
|
+
error: redactFreeText(error instanceof Error ? error.message : String(error)),
|
|
166
170
|
});
|
|
167
171
|
return loadAndTranspileModule(modulePath, projectDir, adapter, fs, config, decoratorOptions, allowHostTypeScriptConfigReads);
|
|
168
172
|
}
|
|
169
173
|
}
|
|
170
|
-
logger.debug(`File not local, using adapter-based loading: ${modulePath}`);
|
|
174
|
+
logger.debug(`File not local, using adapter-based loading: ${projectModuleLabel(modulePath, projectDir)}`);
|
|
171
175
|
return loadAndTranspileModule(modulePath, projectDir, adapter, fs, config, undefined, allowHostTypeScriptConfigReads);
|
|
172
176
|
}
|
|
173
177
|
/** @internal Exported for the runtime-selection regression test. */
|
|
@@ -305,7 +309,7 @@ function loadTSModuleDirect(modulePath, revision) {
|
|
|
305
309
|
const url = modulePath.startsWith("file://")
|
|
306
310
|
? `${modulePath}${cacheBuster}`
|
|
307
311
|
: `file://${modulePath}${cacheBuster}`;
|
|
308
|
-
logger.debug(`Direct import (Deno): ${
|
|
312
|
+
logger.debug(`Direct import (Deno): ${pathHelper.basename(modulePath)}`);
|
|
309
313
|
return import(url);
|
|
310
314
|
}
|
|
311
315
|
/** Directly import a JavaScript route, keyed by revision so an edit is not served from the module cache. */
|
|
@@ -409,11 +413,11 @@ function markDirectGraphVisit(visited, filePath) {
|
|
|
409
413
|
* absolute one, which would expose the machine's directory layout.
|
|
410
414
|
*/
|
|
411
415
|
function projectModuleLabel(filePath, projectDir) {
|
|
412
|
-
|
|
416
|
+
const absolute = isSeparator(filePath[0]) ||
|
|
417
|
+
(filePath[1] === ":" && isSeparator(filePath[2]));
|
|
418
|
+
if (!absolute)
|
|
413
419
|
return filePath;
|
|
414
|
-
return
|
|
415
|
-
? pathHelper.relative(projectDir, filePath)
|
|
416
|
-
: pathHelper.basename(filePath);
|
|
420
|
+
return structuralPathLabel(filePath, pathHelper.resolve(projectDir));
|
|
417
421
|
}
|
|
418
422
|
async function inspectDirectGraphModule(options) {
|
|
419
423
|
const { filePath, routeModulePath, projectRoot, fs, allowedHosts, importMap, pending } = options;
|
|
@@ -974,7 +978,7 @@ function createImportMapPlugin(projectDir, sourceSnapshot, routeModulePath, allo
|
|
|
974
978
|
if (!resolvedPath && args.namespace === "import-map" && args.path.startsWith(".")) {
|
|
975
979
|
const modulePath = modulePathOfSpecifier(args.path);
|
|
976
980
|
const absolutePath = resolveContainedLocalModule(projectDir, args.importer, modulePath);
|
|
977
|
-
logger.debug(`[API] Import map relative resolve: ${args.path} (from ${args.importer}) -> ${absolutePath}`);
|
|
981
|
+
logger.debug(`[API] Import map relative resolve: ${args.path} (from ${projectModuleLabel(args.importer, projectDir)}) -> ${projectModuleLabel(absolutePath, projectDir)}`);
|
|
978
982
|
return {
|
|
979
983
|
path: absolutePath,
|
|
980
984
|
namespace: "import-map",
|
|
@@ -1014,10 +1018,10 @@ function createImportMapPlugin(projectDir, sourceSnapshot, routeModulePath, allo
|
|
|
1014
1018
|
? resolveContainedLocalModule(projectDir, args.importer || projectDir, resolvedPath)
|
|
1015
1019
|
: pathHelper.resolve(projectDir, mappedModulePath);
|
|
1016
1020
|
if (!isWithinDirectory(pathHelper.resolve(projectDir), absolutePath)) {
|
|
1017
|
-
logger.error(`[API] Import map entry escapes project directory: ${args.path} -> ${absolutePath}`);
|
|
1021
|
+
logger.error(`[API] Import map entry escapes project directory: ${args.path} -> ${projectModuleLabel(absolutePath, projectDir)}`);
|
|
1018
1022
|
return { errors: [{ text: `Import map path escapes project: ${args.path}` }] };
|
|
1019
1023
|
}
|
|
1020
|
-
logger.debug(`Import map resolved: ${args.path} -> ${absolutePath}`);
|
|
1024
|
+
logger.debug(`Import map resolved: ${args.path} -> ${projectModuleLabel(absolutePath, projectDir)}`);
|
|
1021
1025
|
return {
|
|
1022
1026
|
path: absolutePath,
|
|
1023
1027
|
namespace: "import-map",
|
|
@@ -1068,7 +1072,7 @@ function createNamespaceOnLoadHandler(options) {
|
|
|
1068
1072
|
};
|
|
1069
1073
|
}
|
|
1070
1074
|
catch (error) {
|
|
1071
|
-
const msg =
|
|
1075
|
+
const msg = redactFreeText(error instanceof Error ? error.message : String(error));
|
|
1072
1076
|
logger.error(`Failed to load ${errorLabel}: ${projectModuleLabel(args.path, projectDir)}`, { reason: msg });
|
|
1073
1077
|
return { errors: [{ text: `Failed to load: ${msg}` }] };
|
|
1074
1078
|
}
|
|
@@ -1236,7 +1240,7 @@ function createProjectAliasPlugin(sourceSnapshot, projectDir, routeModulePath, a
|
|
|
1236
1240
|
const aliasedPath = args.path.slice(2);
|
|
1237
1241
|
const absolutePath = pathHelper.resolve(projectRoot, modulePathOfSpecifier(aliasedPath));
|
|
1238
1242
|
if (!isWithinDirectory(projectRoot, absolutePath)) {
|
|
1239
|
-
logger.error(`[API] Project alias escapes project: ${args.path} -> ${absolutePath}`);
|
|
1243
|
+
logger.error(`[API] Project alias escapes project: ${args.path} -> ${projectModuleLabel(absolutePath, projectRoot)}`);
|
|
1240
1244
|
return { errors: [{ text: `Project alias escapes project: ${args.path}` }] };
|
|
1241
1245
|
}
|
|
1242
1246
|
return {
|
|
@@ -1270,12 +1274,12 @@ function createAdapterResolvePlugin(sourceSnapshot, projectDir, routeModulePath,
|
|
|
1270
1274
|
const modulePath = modulePathOfSpecifier(args.path);
|
|
1271
1275
|
const absolutePath = pathHelper.resolve(baseDir, modulePath);
|
|
1272
1276
|
if (!isWithinDirectory(pathHelper.resolve(projectDir), absolutePath)) {
|
|
1273
|
-
logger.error(`[API] Adapter resolve path escapes project: ${args.path} -> ${absolutePath}`);
|
|
1277
|
+
logger.error(`[API] Adapter resolve path escapes project: ${args.path} -> ${projectModuleLabel(absolutePath, projectDir)}`);
|
|
1274
1278
|
return {
|
|
1275
1279
|
errors: [{ text: `Relative import escapes project: ${args.path}` }],
|
|
1276
1280
|
};
|
|
1277
1281
|
}
|
|
1278
|
-
logger.debug(`[API] Adapter resolve: ${args.path} (from ${args.importer
|
|
1282
|
+
logger.debug(`[API] Adapter resolve: ${args.path} (from ${args.importer ? projectModuleLabel(args.importer, projectDir) : "stdin"}) -> ${projectModuleLabel(absolutePath, projectDir)}`);
|
|
1279
1283
|
return {
|
|
1280
1284
|
path: absolutePath,
|
|
1281
1285
|
namespace: "vf-adapter",
|
|
@@ -1311,11 +1315,12 @@ function createAdapterResolvePlugin(sourceSnapshot, projectDir, routeModulePath,
|
|
|
1311
1315
|
* are admitted only through the project's own canonical `node_modules` root;
|
|
1312
1316
|
* an unrelated path is never trusted merely because one segment has that name.
|
|
1313
1317
|
*/
|
|
1314
|
-
function projectBoundaryError(path) {
|
|
1315
|
-
|
|
1318
|
+
function projectBoundaryError(path, projectDir) {
|
|
1319
|
+
const label = projectModuleLabel(path, projectDir);
|
|
1320
|
+
logger.error(`[API] Resolved import escapes project: ${label}`);
|
|
1316
1321
|
return {
|
|
1317
1322
|
errors: [{
|
|
1318
|
-
text: `Import escapes the project directory: ${
|
|
1323
|
+
text: `Import escapes the project directory: ${label}. ` +
|
|
1319
1324
|
`API routes may only import project files and project-owned dependencies.`,
|
|
1320
1325
|
}],
|
|
1321
1326
|
};
|
|
@@ -1354,7 +1359,7 @@ function createProjectBoundaryPlugin(sourceSnapshot, projectDir, routeModulePath
|
|
|
1354
1359
|
}
|
|
1355
1360
|
catch (error) {
|
|
1356
1361
|
if (error instanceof ProjectBoundaryViolationError) {
|
|
1357
|
-
return projectBoundaryError(args.path);
|
|
1362
|
+
return projectBoundaryError(args.path, projectDir);
|
|
1358
1363
|
}
|
|
1359
1364
|
const message = error instanceof Error ? error.message : String(error);
|
|
1360
1365
|
return {
|
|
@@ -1366,61 +1371,19 @@ function createProjectBoundaryPlugin(sourceSnapshot, projectDir, routeModulePath
|
|
|
1366
1371
|
};
|
|
1367
1372
|
}
|
|
1368
1373
|
/**
|
|
1369
|
-
*
|
|
1370
|
-
* project
|
|
1371
|
-
*
|
|
1374
|
+
* A build location by its label. The location is held structurally, so a
|
|
1375
|
+
* project file keeps its project-relative path; a remote module keeps only its
|
|
1376
|
+
* origin.
|
|
1372
1377
|
*/
|
|
1373
|
-
function redactAbsolutePaths(text, projectDir) {
|
|
1374
|
-
// `file:` URLs are paths. Then the project root, so a root containing spaces
|
|
1375
|
-
// is caught whole, and only where a path starts: a web URL may contain it.
|
|
1376
|
-
const root = pathHelper.resolve(projectDir).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1377
|
-
return text
|
|
1378
|
-
.replace(/(?<![\w-])file:[^\s'"`(),;]+/gi, (url) => fileUrlLabel(url, projectDir))
|
|
1379
|
-
.replace(new RegExp(`(?<![\\w.:/\\\\])${root}[\\\\/]`, "g"), "").replace(/(?<![\w.:/\\])(?:[A-Za-z]:[\\/]|\\\\|\/)[^\s'"`(),;]+/g,
|
|
1380
|
-
// A path another platform calls absolute (a drive or UNC path on POSIX)
|
|
1381
|
-
// keeps only its file name.
|
|
1382
|
-
(path) => pathHelper.isAbsolute(path)
|
|
1383
|
-
? projectModuleLabel(path, projectDir)
|
|
1384
|
-
: path.split(/[\\/]/).pop() ?? "");
|
|
1385
|
-
}
|
|
1386
|
-
/**
|
|
1387
|
-
* A `file:` URL by its project label, whatever its authority: a UNC path
|
|
1388
|
-
* converts to `file://server/share/...`, which must not leak either.
|
|
1389
|
-
*/
|
|
1390
|
-
function fileUrlLabel(url, projectDir) {
|
|
1391
|
-
let parsed;
|
|
1392
|
-
try {
|
|
1393
|
-
parsed = new URL(url);
|
|
1394
|
-
}
|
|
1395
|
-
catch {
|
|
1396
|
-
return "file";
|
|
1397
|
-
}
|
|
1398
|
-
// Extra safety: a query or fragment can carry a credential. fromFileUrl and
|
|
1399
|
-
// the pathname fallback already ignore both.
|
|
1400
|
-
parsed.search = "";
|
|
1401
|
-
parsed.hash = "";
|
|
1402
|
-
try {
|
|
1403
|
-
const path = pathHelper.fromFileUrl(parsed);
|
|
1404
|
-
if (pathHelper.isAbsolute(path))
|
|
1405
|
-
return projectModuleLabel(path, projectDir);
|
|
1406
|
-
}
|
|
1407
|
-
catch {
|
|
1408
|
-
// Not a path this platform can convert; fall back to the file name.
|
|
1409
|
-
}
|
|
1410
|
-
return parsed.pathname.split("/").pop() ?? "";
|
|
1411
|
-
}
|
|
1412
|
-
/** A remote module by its origin and path, without credentials or query. */
|
|
1413
1378
|
function buildLocationLabel(file, projectDir, workingDir) {
|
|
1414
|
-
if (/^https?:\/\//i
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
}
|
|
1423
|
-
return projectModuleLabel(pathHelper.resolve(workingDir, file), projectDir);
|
|
1379
|
+
if (testPattern(/^https?:\/\//i, file))
|
|
1380
|
+
return remoteOrigin(file);
|
|
1381
|
+
const absolute = isSeparator(file[0]) || (file[1] === ":" && isSeparator(file[2]));
|
|
1382
|
+
return structuralPathLabel(absolute ? file : pathHelper.resolve(workingDir, file), projectDir);
|
|
1383
|
+
}
|
|
1384
|
+
function testPattern(pattern, text) {
|
|
1385
|
+
pattern.lastIndex = 0;
|
|
1386
|
+
return IntrinsicReflectApply(RegExpPrototypeExec, pattern, [text]) !== null;
|
|
1424
1387
|
}
|
|
1425
1388
|
/**
|
|
1426
1389
|
* A failed build, reported by its first error with a project-relative
|
|
@@ -1433,15 +1396,18 @@ function buildLocationLabel(file, projectDir, workingDir) {
|
|
|
1433
1396
|
export function buildFailure(errors, projectDir, workingDir) {
|
|
1434
1397
|
const first = errors[0];
|
|
1435
1398
|
// Drop an esbuild namespace such as `http-url:`, but not a URL's own scheme.
|
|
1436
|
-
const
|
|
1399
|
+
const location = first?.location?.file;
|
|
1400
|
+
const file = location === undefined
|
|
1401
|
+
? undefined
|
|
1402
|
+
: replaceMatches(location, /^[a-z][a-z-]+:(?!\/\/)/g, () => "");
|
|
1437
1403
|
const where = file
|
|
1438
|
-
? `${buildLocationLabel(file, projectDir, workingDir)}:` +
|
|
1404
|
+
? `${buildLocationLabel(file, pathHelper.resolve(projectDir), workingDir)}:` +
|
|
1439
1405
|
// esbuild columns are zero-based; editors count from one.
|
|
1440
1406
|
`${first?.location?.line}:${(first?.location?.column ?? 0) + 1}: `
|
|
1441
1407
|
: "";
|
|
1442
1408
|
return toError(createError({
|
|
1443
1409
|
type: "api",
|
|
1444
|
-
message: `[API] handler build failed: ${where}${
|
|
1410
|
+
message: `[API] handler build failed: ${where}${redactFreeText(first?.text || "unknown error")}`,
|
|
1445
1411
|
}));
|
|
1446
1412
|
}
|
|
1447
1413
|
async function loadAndTranspileModule(modulePath, projectDir, adapter, fs, config, decoratorOptions, allowHostTypeScriptConfigReads = false) {
|
|
@@ -1599,7 +1565,7 @@ function buildTranspiledModuleSource(modulePath, projectDir, adapter, config, re
|
|
|
1599
1565
|
});
|
|
1600
1566
|
if (result.errors?.length)
|
|
1601
1567
|
throw buildFailure(result.errors, projectDir, workingDir);
|
|
1602
|
-
logger.debug(`built handler ${resolvedPath}`);
|
|
1568
|
+
logger.debug(`built handler ${projectModuleLabel(resolvedPath, projectDir)}`);
|
|
1603
1569
|
const js = result.outputFiles?.[0]?.text ?? "export {}";
|
|
1604
1570
|
logger.debug(`transpiled size ${js.length} bytes`);
|
|
1605
1571
|
return await rewriteExternalImports(js, projectDir, sourceSnapshot, userDeps);
|
|
@@ -1746,8 +1712,9 @@ async function importModuleFromCode(code, fs) {
|
|
|
1746
1712
|
return await import(`file://${tempFile}?v=${Date.now()}`);
|
|
1747
1713
|
}
|
|
1748
1714
|
catch (e) {
|
|
1749
|
-
|
|
1750
|
-
|
|
1715
|
+
// The message, not the stack: its frames name host-absolute paths.
|
|
1716
|
+
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
1717
|
+
logger.error(`dynamic import failed: ${redactFreeText(errorMessage)}`);
|
|
1751
1718
|
throw e;
|
|
1752
1719
|
}
|
|
1753
1720
|
finally {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keep host paths out of API module loader errors and logs.
|
|
3
|
+
*
|
|
4
|
+
* Redaction can run after project code has evaluated in this realm, so it uses
|
|
5
|
+
* intrinsics captured at module load, never the live string, RegExp or URL
|
|
6
|
+
* prototypes that code could have replaced.
|
|
7
|
+
*
|
|
8
|
+
* @module routing/api/module-loader/path-redaction
|
|
9
|
+
*/
|
|
10
|
+
/** Every match of global `pattern` in `text` replaced, in one linear pass. */
|
|
11
|
+
export declare function replaceMatches(text: string, pattern: RegExp, replace: (match: RegExpExecArray) => string): string;
|
|
12
|
+
export declare function isSeparator(char: string | undefined): boolean;
|
|
13
|
+
/** The text after the last `/` or `\`. */
|
|
14
|
+
export declare function lastPathSegment(path: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* A path the code holds structurally (a module or location path, not text
|
|
17
|
+
* scanned out of a message): project-relative inside the resolved
|
|
18
|
+
* `projectDir`, else its file name. A `..` segment is never trusted inside.
|
|
19
|
+
*/
|
|
20
|
+
export declare function structuralPathLabel(path: string, projectDir: string): string;
|
|
21
|
+
/** An `http(s)` URL by scheme and host only: a path can carry a token. */
|
|
22
|
+
export declare function remoteOrigin(url: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* `text` with every absolute path and `file:` URL cut to its file name and
|
|
25
|
+
* every `http(s)` URL cut to its origin. A path is taken through to the next
|
|
26
|
+
* whitespace or quote, so a directory name is never left behind; an outside
|
|
27
|
+
* path that itself contains whitespace is the accepted exception, since text
|
|
28
|
+
* gives no way to tell where it ends. Never throws.
|
|
29
|
+
*/
|
|
30
|
+
export declare function redactFreeText(text: string): string;
|
|
31
|
+
//# sourceMappingURL=path-redaction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-redaction.d.ts","sourceRoot":"","sources":["../../../../../src/src/routing/api/module-loader/path-redaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAaH,8EAA8E;AAC9E,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,MAAM,GAC1C,MAAM,CAgBR;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAE7D;AAED,0CAA0C;AAC1C,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMpD;AAiCD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAI5E;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAYhD;AAmCD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAUnD"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keep host paths out of API module loader errors and logs.
|
|
3
|
+
*
|
|
4
|
+
* Redaction can run after project code has evaluated in this realm, so it uses
|
|
5
|
+
* intrinsics captured at module load, never the live string, RegExp or URL
|
|
6
|
+
* prototypes that code could have replaced.
|
|
7
|
+
*
|
|
8
|
+
* @module routing/api/module-loader/path-redaction
|
|
9
|
+
*/
|
|
10
|
+
const IntrinsicReflectApply = Reflect.apply;
|
|
11
|
+
const IntrinsicDecodeURIComponent = decodeURIComponent;
|
|
12
|
+
const RegExpPrototypeExec = RegExp.prototype.exec;
|
|
13
|
+
const StringPrototypeSlice = String.prototype.slice;
|
|
14
|
+
const REDACTION_FAILED = "<redacted path>";
|
|
15
|
+
function slice(text, start, end = text.length) {
|
|
16
|
+
return IntrinsicReflectApply(StringPrototypeSlice, text, [start, end]);
|
|
17
|
+
}
|
|
18
|
+
/** Every match of global `pattern` in `text` replaced, in one linear pass. */
|
|
19
|
+
export function replaceMatches(text, pattern, replace) {
|
|
20
|
+
let result = "";
|
|
21
|
+
let last = 0;
|
|
22
|
+
pattern.lastIndex = 0;
|
|
23
|
+
for (let match = IntrinsicReflectApply(RegExpPrototypeExec, pattern, [text]); match !== null; match = IntrinsicReflectApply(RegExpPrototypeExec, pattern, [text])) {
|
|
24
|
+
result += slice(text, last, match.index) + replace(match);
|
|
25
|
+
last = match.index + match[0].length;
|
|
26
|
+
if (match[0].length === 0)
|
|
27
|
+
pattern.lastIndex++;
|
|
28
|
+
}
|
|
29
|
+
return result + slice(text, last);
|
|
30
|
+
}
|
|
31
|
+
export function isSeparator(char) {
|
|
32
|
+
return char === "/" || char === "\\";
|
|
33
|
+
}
|
|
34
|
+
/** The text after the last `/` or `\`. */
|
|
35
|
+
export function lastPathSegment(path) {
|
|
36
|
+
let start = 0;
|
|
37
|
+
for (let index = 0; index < path.length; index++) {
|
|
38
|
+
if (isSeparator(path[index]))
|
|
39
|
+
start = index + 1;
|
|
40
|
+
}
|
|
41
|
+
return slice(path, start);
|
|
42
|
+
}
|
|
43
|
+
/** `projectDir` without trailing separators, so `/app/` and `/app` agree. */
|
|
44
|
+
function trimmedRoot(projectDir) {
|
|
45
|
+
let end = projectDir.length;
|
|
46
|
+
while (end > 1 && isSeparator(projectDir[end - 1]))
|
|
47
|
+
end--;
|
|
48
|
+
return slice(projectDir, 0, end);
|
|
49
|
+
}
|
|
50
|
+
/** Whether `path` starts with `root` followed by a separator, either kind. */
|
|
51
|
+
function startsWithRoot(path, root) {
|
|
52
|
+
for (let index = 0; index < root.length; index++) {
|
|
53
|
+
const same = path[index] === root[index] ||
|
|
54
|
+
(isSeparator(path[index]) && isSeparator(root[index]));
|
|
55
|
+
if (!same)
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return isSeparator(path[root.length]);
|
|
59
|
+
}
|
|
60
|
+
/** Whether `path` climbs with a `..` segment, so its prefix proves nothing. */
|
|
61
|
+
function hasParentSegment(path) {
|
|
62
|
+
let segmentStart = 0;
|
|
63
|
+
for (let index = 0; index <= path.length; index++) {
|
|
64
|
+
if (index === path.length || isSeparator(path[index])) {
|
|
65
|
+
if (index - segmentStart === 2 && path[segmentStart] === "." && path[segmentStart + 1] === ".")
|
|
66
|
+
return true;
|
|
67
|
+
segmentStart = index + 1;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* A path the code holds structurally (a module or location path, not text
|
|
74
|
+
* scanned out of a message): project-relative inside the resolved
|
|
75
|
+
* `projectDir`, else its file name. A `..` segment is never trusted inside.
|
|
76
|
+
*/
|
|
77
|
+
export function structuralPathLabel(path, projectDir) {
|
|
78
|
+
const root = trimmedRoot(projectDir);
|
|
79
|
+
if (!hasParentSegment(path) && startsWithRoot(path, root))
|
|
80
|
+
return slice(path, root.length + 1);
|
|
81
|
+
return lastPathSegment(path);
|
|
82
|
+
}
|
|
83
|
+
/** An `http(s)` URL by scheme and host only: a path can carry a token. */
|
|
84
|
+
export function remoteOrigin(url) {
|
|
85
|
+
const schemeEnd = url[4] === ":" ? 7 : 8;
|
|
86
|
+
let hostEnd = schemeEnd;
|
|
87
|
+
while (hostEnd < url.length && !isSeparator(url[hostEnd]) && url[hostEnd] !== "?" &&
|
|
88
|
+
url[hostEnd] !== "#")
|
|
89
|
+
hostEnd++;
|
|
90
|
+
let hostStart = schemeEnd;
|
|
91
|
+
for (let index = schemeEnd; index < hostEnd; index++) {
|
|
92
|
+
if (url[index] === "@")
|
|
93
|
+
hostStart = index + 1;
|
|
94
|
+
}
|
|
95
|
+
return (schemeEnd === 7 ? "http://" : "https://") + slice(url, hostStart, hostEnd);
|
|
96
|
+
}
|
|
97
|
+
/** A `file:` URL by its file name, without its query or fragment. */
|
|
98
|
+
function fileUrlName(url) {
|
|
99
|
+
let end = url.length;
|
|
100
|
+
for (let index = 0; index < url.length; index++) {
|
|
101
|
+
if (url[index] === "?" || url[index] === "#") {
|
|
102
|
+
end = index;
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
let path = slice(url, 5, end);
|
|
107
|
+
if (path[0] === "/" && path[1] === "/") {
|
|
108
|
+
// The authority is a host, never a file name.
|
|
109
|
+
let start = 2;
|
|
110
|
+
while (start < path.length && path[start] !== "/")
|
|
111
|
+
start++;
|
|
112
|
+
path = slice(path, start);
|
|
113
|
+
}
|
|
114
|
+
let name = lastPathSegment(path);
|
|
115
|
+
try {
|
|
116
|
+
name = IntrinsicDecodeURIComponent(name);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// A malformed escape stays encoded.
|
|
120
|
+
}
|
|
121
|
+
// A decoded `%2F` or `%5C` is a separator too.
|
|
122
|
+
return lastPathSegment(name);
|
|
123
|
+
}
|
|
124
|
+
// One pass, three kinds of token, each running to the next whitespace or quote:
|
|
125
|
+
// a `file:` URL, an `http(s)` URL, and an absolute path. A path may follow `:`
|
|
126
|
+
// or a sentence's `.` (`error:/home/a.ts`, `end./home/a.ts`), but not a word,
|
|
127
|
+
// another path character, or a relative `./` or `../`.
|
|
128
|
+
const FREE_TEXT_PATTERN = /(?<![\w-])(file:[^\s'"`]+)|(https?:\/\/[^\s'"`]+)|(?<![\w/\\])(?<!(?<![\w])\.)(?:[A-Za-z]:[\\/]|\\\\|\/)[^\s'"`]+/gi;
|
|
129
|
+
/**
|
|
130
|
+
* `text` with every absolute path and `file:` URL cut to its file name and
|
|
131
|
+
* every `http(s)` URL cut to its origin. A path is taken through to the next
|
|
132
|
+
* whitespace or quote, so a directory name is never left behind; an outside
|
|
133
|
+
* path that itself contains whitespace is the accepted exception, since text
|
|
134
|
+
* gives no way to tell where it ends. Never throws.
|
|
135
|
+
*/
|
|
136
|
+
export function redactFreeText(text) {
|
|
137
|
+
try {
|
|
138
|
+
return replaceMatches(text, FREE_TEXT_PATTERN, (match) => {
|
|
139
|
+
if (match[1] !== undefined)
|
|
140
|
+
return fileUrlName(match[1]);
|
|
141
|
+
if (match[2] !== undefined)
|
|
142
|
+
return remoteOrigin(match[2]);
|
|
143
|
+
return lastPathSegment(match[0]);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return REDACTION_FAILED;
|
|
148
|
+
}
|
|
149
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "veryfront",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1285",
|
|
4
4
|
"description": "The simplest way to build AI-powered apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -504,17 +504,17 @@
|
|
|
504
504
|
"dependencies": {
|
|
505
505
|
"ajv": "8.18.0",
|
|
506
506
|
"ajv-formats": "3.0.1",
|
|
507
|
-
"zod": "4.
|
|
507
|
+
"zod": "4.6.7",
|
|
508
508
|
"@deno/shim-deno": "0.18.0",
|
|
509
509
|
"@deno/shim-crypto": "0.3.1",
|
|
510
510
|
"@types/react": "19.2.14",
|
|
511
511
|
"@types/react-dom": "19.2.3",
|
|
512
|
-
"@veryfront/ext-bundler-esbuild": "0.1.
|
|
513
|
-
"@veryfront/ext-css-tailwind": "0.1.
|
|
514
|
-
"@veryfront/ext-dev-ui-react": "0.1.
|
|
515
|
-
"@veryfront/ext-node-websocket-ws": "0.1.
|
|
516
|
-
"@veryfront/ext-parser-babel": "0.1.
|
|
517
|
-
"@veryfront/ext-yaml": "0.1.
|
|
512
|
+
"@veryfront/ext-bundler-esbuild": "0.1.1285",
|
|
513
|
+
"@veryfront/ext-css-tailwind": "0.1.1285",
|
|
514
|
+
"@veryfront/ext-dev-ui-react": "0.1.1285",
|
|
515
|
+
"@veryfront/ext-node-websocket-ws": "0.1.1285",
|
|
516
|
+
"@veryfront/ext-parser-babel": "0.1.1285",
|
|
517
|
+
"@veryfront/ext-yaml": "0.1.1285"
|
|
518
518
|
},
|
|
519
519
|
"devDependencies": {
|
|
520
520
|
"@types/node": "20.9.0"
|
|
@@ -527,7 +527,7 @@
|
|
|
527
527
|
"@huggingface/transformers": "^4.3.0",
|
|
528
528
|
"react": "^19.2.4",
|
|
529
529
|
"react-dom": "^19.2.4",
|
|
530
|
-
"@veryfront/ext-content-mdx": "0.1.
|
|
530
|
+
"@veryfront/ext-content-mdx": "0.1.1285"
|
|
531
531
|
},
|
|
532
532
|
"peerDependenciesMeta": {
|
|
533
533
|
"@huggingface/transformers": {
|
|
@@ -556,5 +556,5 @@
|
|
|
556
556
|
"NOTICE",
|
|
557
557
|
"README.md"
|
|
558
558
|
],
|
|
559
|
-
"gitHead": "
|
|
559
|
+
"gitHead": "2bac963100515224f14213021beb42e128b201ea"
|
|
560
560
|
}
|