veryfront 0.1.241 → 0.1.243

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.
Files changed (56) hide show
  1. package/esm/cli/templates/manifest.js +77 -77
  2. package/esm/deno.js +1 -1
  3. package/esm/src/agent/conversation-root-run-context.d.ts.map +1 -1
  4. package/esm/src/agent/conversation-root-run-context.js +2 -0
  5. package/esm/src/agent/conversation-run-context.d.ts +2 -0
  6. package/esm/src/agent/conversation-run-context.d.ts.map +1 -1
  7. package/esm/src/agent/durable.d.ts +23 -0
  8. package/esm/src/agent/durable.d.ts.map +1 -1
  9. package/esm/src/agent/durable.js +39 -0
  10. package/esm/src/agent/index.d.ts +1 -1
  11. package/esm/src/agent/index.d.ts.map +1 -1
  12. package/esm/src/agent/index.js +1 -1
  13. package/esm/src/oauth/handlers/callback-handler.d.ts +2 -2
  14. package/esm/src/oauth/handlers/callback-handler.d.ts.map +1 -1
  15. package/esm/src/oauth/handlers/callback-handler.js +17 -5
  16. package/esm/src/oauth/handlers/init-handler.d.ts +24 -4
  17. package/esm/src/oauth/handlers/init-handler.d.ts.map +1 -1
  18. package/esm/src/oauth/handlers/init-handler.js +47 -10
  19. package/esm/src/oauth/providers/base.d.ts +9 -2
  20. package/esm/src/oauth/providers/base.d.ts.map +1 -1
  21. package/esm/src/oauth/providers/base.js +12 -5
  22. package/esm/src/oauth/token-store/index.d.ts +1 -1
  23. package/esm/src/oauth/token-store/index.d.ts.map +1 -1
  24. package/esm/src/oauth/token-store/memory.d.ts +21 -9
  25. package/esm/src/oauth/token-store/memory.d.ts.map +1 -1
  26. package/esm/src/oauth/token-store/memory.js +42 -28
  27. package/esm/src/oauth/types.d.ts +33 -7
  28. package/esm/src/oauth/types.d.ts.map +1 -1
  29. package/esm/src/platform/compat/framework-source-resolver.d.ts.map +1 -1
  30. package/esm/src/platform/compat/framework-source-resolver.js +34 -0
  31. package/esm/src/routing/api/module-loader/loader.d.ts +11 -0
  32. package/esm/src/routing/api/module-loader/loader.d.ts.map +1 -1
  33. package/esm/src/routing/api/module-loader/loader.js +18 -2
  34. package/esm/src/server/handlers/dev/dashboard/api.d.ts.map +1 -1
  35. package/esm/src/server/handlers/dev/dashboard/api.js +34 -13
  36. package/esm/src/server/handlers/dev/files/esbuild-plugins.d.ts.map +1 -1
  37. package/esm/src/server/handlers/dev/files/esbuild-plugins.js +45 -4
  38. package/esm/src/utils/version-constant.d.ts +1 -1
  39. package/esm/src/utils/version-constant.js +1 -1
  40. package/package.json +1 -1
  41. package/src/cli/templates/manifest.js +77 -77
  42. package/src/deno.js +1 -1
  43. package/src/src/agent/conversation-root-run-context.ts +2 -0
  44. package/src/src/agent/durable.ts +60 -0
  45. package/src/src/agent/index.ts +3 -0
  46. package/src/src/oauth/handlers/callback-handler.ts +25 -8
  47. package/src/src/oauth/handlers/init-handler.ts +83 -15
  48. package/src/src/oauth/providers/base.ts +12 -5
  49. package/src/src/oauth/token-store/index.ts +1 -1
  50. package/src/src/oauth/token-store/memory.ts +48 -35
  51. package/src/src/oauth/types.ts +34 -7
  52. package/src/src/platform/compat/framework-source-resolver.ts +32 -0
  53. package/src/src/routing/api/module-loader/loader.ts +18 -2
  54. package/src/src/server/handlers/dev/dashboard/api.ts +32 -10
  55. package/src/src/server/handlers/dev/files/esbuild-plugins.ts +54 -5
  56. package/src/src/utils/version-constant.ts +1 -1
@@ -91,7 +91,7 @@ async function readProjectDependencies(
91
91
  * `new Function` to evaluate them in a proper CJS wrapper with require,
92
92
  * exports, module, __filename, and __dirname bindings.
93
93
  */
94
- function generateCompiledBinaryRequireShim(projectDir: string): string {
94
+ export function generateCompiledBinaryRequireShim(projectDir: string): string {
95
95
  const builtinSet = JSON.stringify(NODE_BUILTINS);
96
96
  const safeProjectDir = JSON.stringify(projectDir + "/package.json");
97
97
  const safeProjectRoot = JSON.stringify(pathHelper.resolve(projectDir));
@@ -102,6 +102,12 @@ import { dirname as __vf_dirname, resolve as __vf_resolve } from "node:path";
102
102
  var __vf_builtinRequire = __vf_createRequire(${safeProjectDir});
103
103
  var __vf_builtinSet = new Set(${builtinSet});
104
104
  var __vf_projectRoot = ${safeProjectRoot};
105
+ // VULN-FS-5: Canonicalize the project root so containment checks using
106
+ // Deno.realPathSync(resolved) compare canonical-vs-canonical. Without this,
107
+ // when the project itself is opened via a symlink, the realpath'd resolved
108
+ // module path has a different prefix than the non-canonical projectRoot and
109
+ // legitimate dependencies would be rejected.
110
+ try { __vf_projectRoot = Deno.realPathSync(__vf_projectRoot); } catch (_) { /* expected: projectRoot may not exist at shim init in some environments */ }
105
111
  var __vf_cache = Object.create(null);
106
112
  function __vf_assertContained(resolved) {
107
113
  var norm = __vf_resolve(resolved).replace(/\\\\/g, "/");
@@ -124,10 +130,20 @@ function __vf_loadCjs(id, parentDir) {
124
130
  try { Deno.statSync(resolved + exts[i]); resolved += exts[i]; break; } catch (_) { /* expected: probing file extensions */ }
125
131
  }
126
132
  }
127
- __vf_assertContained(resolved);
128
133
  } else {
129
134
  resolved = __vf_builtinRequire.resolve(id);
130
135
  }
136
+ // VULN-FS-5: Always assert containment after resolution (both branches),
137
+ // then re-canonicalize via realPathSync to resist symlinked node_modules
138
+ // entries that could point outside the project root.
139
+ __vf_assertContained(resolved);
140
+ try {
141
+ var real = Deno.realPathSync(resolved);
142
+ __vf_assertContained(real);
143
+ resolved = real;
144
+ } catch (_) {
145
+ /* expected: realPathSync fails for non-existent paths — assertContained above already held */
146
+ }
131
147
  if (resolved in __vf_cache) return __vf_cache[resolved];
132
148
  var code = Deno.readTextFileSync(resolved);
133
149
  if (resolved.endsWith(".json")) {
@@ -24,17 +24,31 @@ import { isRSCEnabled } from "../../../../utils/feature-flags.js";
24
24
  import { getEnvironmentConfig } from "../../../../config/environment-config.js";
25
25
  import { getErrorCollector } from "../../../../observability/error-collector.js";
26
26
  import { getLogBuffer } from "../../../../observability/log-buffer.js";
27
+ import { validatePathSync } from "../../../../security/index.js";
27
28
  import { ReloadNotifier } from "../../../reload-notifier.js";
28
29
  import type { HandlerContext } from "../../types.js";
29
30
 
30
31
  const WORKFLOW_EXECUTION_TIMEOUT_MS = 30_000;
31
32
 
32
- /** Validate a relative path for directory traversal and null bytes.
33
- * Note: searchParams.get() already percent-decodes, so no extra decoding needed. */
34
- function isValidRelativePath(path: string): boolean {
35
- if (path.includes("\0")) return false;
36
- if (path.includes("..")) return false;
37
- return true;
33
+ /**
34
+ * Validate a relative path against the project directory.
35
+ *
36
+ * Uses `validatePathSync` in strict mode (rejects absolute paths, null bytes,
37
+ * `..` traversal, and any resolved path that escapes `baseDir`).
38
+ *
39
+ * Note: `searchParams.get()` already percent-decodes; no extra decoding needed
40
+ * (double-decoding would itself be a vulnerability).
41
+ *
42
+ * Returns the canonicalized absolute path on success, or `null` when invalid.
43
+ */
44
+ function validateRelativePath(path: string, projectDir: string): string | null {
45
+ const result = validatePathSync(path, {
46
+ baseDir: projectDir,
47
+ allowAbsolute: false,
48
+ level: "strict",
49
+ });
50
+ if (!result.valid || !result.canonicalPath) return null;
51
+ return result.canonicalPath;
38
52
  }
39
53
 
40
54
  const JSON_HEADERS = {
@@ -397,9 +411,15 @@ async function handleListFiles(req: Request, ctx: HandlerContext): Promise<Respo
397
411
  if (!projectDir) return errorResponse("No project directory configured", 500);
398
412
 
399
413
  const relativePath = new URL(req.url).searchParams.get("path") ?? "";
400
- if (!isValidRelativePath(relativePath)) return errorResponse("Invalid path", 400);
401
414
 
402
- const fullPath = relativePath ? `${projectDir}/${relativePath}` : projectDir;
415
+ let fullPath: string;
416
+ if (relativePath === "") {
417
+ fullPath = projectDir;
418
+ } else {
419
+ const canonical = validateRelativePath(relativePath, projectDir);
420
+ if (canonical === null) return errorResponse("Invalid path", 400);
421
+ fullPath = canonical;
422
+ }
403
423
 
404
424
  try {
405
425
  const files: Array<{ name: string; type: "file" | "directory"; path: string }> = [];
@@ -433,10 +453,12 @@ async function handleReadFileContent(req: Request, ctx: HandlerContext): Promise
433
453
 
434
454
  const relativePath = new URL(req.url).searchParams.get("path") ?? "";
435
455
  if (!relativePath) return errorResponse("path parameter is required", 400);
436
- if (!isValidRelativePath(relativePath)) return errorResponse("Invalid path", 400);
456
+
457
+ const canonical = validateRelativePath(relativePath, projectDir);
458
+ if (canonical === null) return errorResponse("Invalid path", 400);
437
459
 
438
460
  try {
439
- const content = await adapter.fs.readFile(`${projectDir}/${relativePath}`);
461
+ const content = await adapter.fs.readFile(canonical);
440
462
  const extension = relativePath.split(".").pop() ?? "";
441
463
 
442
464
  if (!TEXT_EXTENSIONS.has(extension.toLowerCase())) {
@@ -2,7 +2,12 @@ import type { OnLoadArgs, OnResolveArgs, Plugin, PluginBuild } from "esbuild";
2
2
  import { NETWORK_ERROR } from "../../../../errors/index.js";
3
3
  // Direct import from base.ts to avoid circular dependency through barrel
4
4
  import type { RuntimeAdapter } from "../../../../platform/adapters/base.js";
5
- import { getDirectory, joinPath } from "../../../../utils/path-utils.js";
5
+ import {
6
+ getDirectory,
7
+ isWithinDirectory,
8
+ joinPath,
9
+ normalizePath,
10
+ } from "../../../../utils/path-utils.js";
6
11
 
7
12
  import {
8
13
  computeIntegrity,
@@ -34,16 +39,43 @@ export function createRelativeFsPlugin(projectDir: string, adapter: RuntimeAdapt
34
39
  const exts = [".tsx", ".ts", ".jsx", ".js", ".mjs"];
35
40
 
36
41
  build.onResolve({ filter: /^(\.?\.?\/|\/)\/*/ }, async (args: OnResolveArgs) => {
37
- const basedir = args.importer ? getDirectory(args.importer) : projectDir;
38
- const candidate = args.path.startsWith("/")
39
- ? joinPath(projectDir, args.path)
40
- : joinPath(basedir, args.path);
42
+ // VULN-FS-6: NUL bytes are never legitimate in module paths.
43
+ if (args.path.includes("\0")) {
44
+ return {
45
+ errors: [{ text: `Import path contains NUL byte: ${args.path}`, location: null }],
46
+ };
47
+ }
48
+
49
+ const basedir = args.resolveDir ||
50
+ (args.importer ? getDirectory(args.importer) : projectDir);
51
+ // normalizePath collapses `./` and `foo/../` segments produced by
52
+ // `joinPath` so downstream `adapter.fs.stat` lookups match the file
53
+ // system's canonical key. Still inside the containment check below.
54
+ const candidate = normalizePath(
55
+ args.path.startsWith("/")
56
+ ? joinPath(projectDir, args.path)
57
+ : joinPath(basedir, args.path),
58
+ );
59
+
60
+ // VULN-FS-6: refuse anything that, after joining, escapes the project
61
+ // root. esbuild plugins fire per-import; an entry file with
62
+ // `import "../../../../etc/hostname"` would otherwise embed the file.
63
+ if (!isWithinDirectory(projectDir, candidate)) {
64
+ return {
65
+ errors: [{
66
+ text: `Import escapes project directory: ${args.path}`,
67
+ location: null,
68
+ }],
69
+ };
70
+ }
41
71
 
42
72
  const candidates: string[] = [candidate];
43
73
  for (const ext of exts) candidates.push(candidate + ext);
44
74
  for (const ext of exts) candidates.push(joinPath(candidate, `index${ext}`));
45
75
 
46
76
  for (const f of candidates) {
77
+ // Defence in depth: each extension probe must also stay inside.
78
+ if (!isWithinDirectory(projectDir, f)) continue;
47
79
  try {
48
80
  const st = await adapter.fs.stat(f);
49
81
  if (st.isFile) return { path: f };
@@ -56,6 +88,23 @@ export function createRelativeFsPlugin(projectDir: string, adapter: RuntimeAdapt
56
88
  });
57
89
 
58
90
  build.onLoad({ filter: /\.(tsx?|jsx?|mjs)$/ }, async (args: OnLoadArgs) => {
91
+ // VULN-FS-6: belt-and-braces — reject any onLoad call whose path
92
+ // escapes the project root or carries a NUL byte. onResolve already
93
+ // gates this, but esbuild can call onLoad with paths produced by
94
+ // other plugins or namespaces.
95
+ if (args.path.includes("\0")) {
96
+ return {
97
+ errors: [{ text: `Load path contains NUL byte: ${args.path}`, location: null }],
98
+ };
99
+ }
100
+ if (!isWithinDirectory(projectDir, args.path)) {
101
+ return {
102
+ errors: [{
103
+ text: `Load path escapes project directory: ${args.path}`,
104
+ location: null,
105
+ }],
106
+ };
107
+ }
59
108
  try {
60
109
  const contents = await adapter.fs.readFile(args.path);
61
110
  return { contents, loader: getLoaderForPath(args.path) };
@@ -1,3 +1,3 @@
1
1
  // Keep in sync with deno.json version.
2
2
  // scripts/release.ts updates this constant during releases.
3
- export const VERSION = "0.1.241";
3
+ export const VERSION = "0.1.243";