catalyst-core 0.3.0-beta.2 → 0.3.0-beta.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/dist/native/androidSetup.js +8 -6
- package/dist/offline/catalyst-sw.js +1 -0
- package/dist/scripts/build.js +15 -11
- package/dist/scripts/generateOfflineManifest.js +1 -0
- package/dist/scripts/scriptUtils.js +1 -0
- package/dist/scripts/serve.js +24 -22
- package/dist/scripts/start.js +1 -3
- package/dist/server/expressServer.js +1 -0
- package/dist/server/renderer/document/Head.jsx +2 -0
- package/dist/server/renderer/extract.js +1 -0
- package/dist/server/renderer/handler.jsx +1 -0
- package/dist/vite/inject-cache-key-plugin.js +7 -0
- package/dist/vite/loadCustomViteConfig.js +1 -0
- package/dist/vite/manifest-categorization-plugin.js +2 -0
- package/dist/vite/node-loader.mjs +6 -0
- package/dist/vite/vite.config.js +6 -0
- package/mcp_v2/tools/github.js +11 -0
- package/package.json +2 -2
|
@@ -209,13 +209,15 @@ async function checkEmulator(ADB_PATH) {
|
|
|
209
209
|
}
|
|
210
210
|
async function startEmulator(EMULATOR_PATH, androidConfig) {
|
|
211
211
|
progress.log(`Starting emulator: ${androidConfig.emulatorName}...`);
|
|
212
|
-
// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process -
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
212
|
+
// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - spawn with an argv array (no shell), so androidConfig.emulatorName can't be interpreted as shell syntax even though it comes from a locally-editable config file.
|
|
213
|
+
const child = (0, _child_process.spawn)(EMULATOR_PATH, ["-avd", androidConfig.emulatorName, "-read-only"], {
|
|
214
|
+
detached: true,
|
|
215
|
+
stdio: "ignore"
|
|
216
|
+
});
|
|
217
|
+
child.on("error", error => {
|
|
218
|
+
progress.log(`Error starting emulator: ${error}`, "error");
|
|
218
219
|
});
|
|
220
|
+
child.unref();
|
|
219
221
|
}
|
|
220
222
|
async function updateLocalProperties(sdkPath) {
|
|
221
223
|
progress.start("properties");
|
|
@@ -99,6 +99,7 @@ function isOfflineRoute(url, manifest) {
|
|
|
99
99
|
const { pathname } = new URL(url)
|
|
100
100
|
return (manifest.routes || []).some((route) => {
|
|
101
101
|
try {
|
|
102
|
+
// nosemgrep: javascript.lang.security.audit.detect-non-literal-regexp.detect-non-literal-regexp - route.regex is generated at build time by generateOfflineManifest.js's routeRegex(), which escapes every regex metacharacter in literal path segments and only ever emits flat (non-nested, non-repeating) patterns for :param and * segments — it cannot produce a catastrophic-backtracking shape. The manifest itself is same-origin build output, not user-submitted at runtime.
|
|
102
103
|
return new RegExp(route.regex).test(pathname)
|
|
103
104
|
} catch (_) {
|
|
104
105
|
return false
|
package/dist/scripts/build.js
CHANGED
|
@@ -4,6 +4,7 @@ import { arrayToObject } from "./scriptUtils.js"
|
|
|
4
4
|
import { fileURLToPath } from "url"
|
|
5
5
|
import { dirname } from "path"
|
|
6
6
|
import { readFileSync, existsSync, rmSync } from "fs"
|
|
7
|
+
import { createRequire } from "module"
|
|
7
8
|
|
|
8
9
|
const __filename = fileURLToPath(import.meta.url)
|
|
9
10
|
const __dirname = dirname(__filename)
|
|
@@ -11,16 +12,19 @@ const loaderPath = path.resolve(__dirname, "../../dist/vite/node-loader.mjs")
|
|
|
11
12
|
const configPath = path.join(process.env.PWD, "config/config.json")
|
|
12
13
|
const configJSON = JSON.parse(readFileSync(configPath), "utf-8")
|
|
13
14
|
|
|
15
|
+
// Resolve vite's actual JS entry point rather than spawning the "vite" command name,
|
|
16
|
+
// which on Windows only resolves via the npm-installed .cmd shim (i.e. requires a shell).
|
|
17
|
+
// Invoking process.execPath + this path directly works cross-platform with no shell.
|
|
18
|
+
const require = createRequire(import.meta.url)
|
|
19
|
+
const viteBinPath = path.join(path.dirname(require.resolve("vite/package.json")), "bin", "vite.js")
|
|
20
|
+
|
|
14
21
|
/**
|
|
15
|
-
* @param {string}
|
|
22
|
+
* @param {string[]} args
|
|
16
23
|
* @param {import('child_process').SpawnOptions} options
|
|
17
24
|
*/
|
|
18
|
-
function runBuildStep(
|
|
25
|
+
function runBuildStep(args, options) {
|
|
19
26
|
return new Promise((resolve, reject) => {
|
|
20
|
-
const child = spawn(
|
|
21
|
-
...options,
|
|
22
|
-
shell: true,
|
|
23
|
-
})
|
|
27
|
+
const child = spawn(process.execPath, args, options)
|
|
24
28
|
child.on("close", (code) => {
|
|
25
29
|
if (code === 0) {
|
|
26
30
|
resolve()
|
|
@@ -69,8 +73,8 @@ async function build() {
|
|
|
69
73
|
]),
|
|
70
74
|
}
|
|
71
75
|
|
|
72
|
-
const
|
|
73
|
-
const
|
|
76
|
+
const serverBuildArgs = [viteBinPath, "build", "--config", "./dist/vite/vite.config.server.js", "--ssr"]
|
|
77
|
+
const clientBuildArgs = [viteBinPath, "build", "--config", "./dist/vite/vite.config.client.js"]
|
|
74
78
|
const spawnBase = {
|
|
75
79
|
cwd: dirname,
|
|
76
80
|
stdio: "inherit",
|
|
@@ -80,11 +84,11 @@ async function build() {
|
|
|
80
84
|
|
|
81
85
|
try {
|
|
82
86
|
await Promise.all([
|
|
83
|
-
runBuildStep(
|
|
87
|
+
runBuildStep(serverBuildArgs, {
|
|
84
88
|
...spawnBase,
|
|
85
89
|
env: { ...baseEnv, CATALYST_VITE_CACHE_ID: "ssr" },
|
|
86
90
|
}),
|
|
87
|
-
runBuildStep(
|
|
91
|
+
runBuildStep(clientBuildArgs, {
|
|
88
92
|
...spawnBase,
|
|
89
93
|
env: { ...baseEnv, CATALYST_VITE_CACHE_ID: "client" },
|
|
90
94
|
}),
|
|
@@ -96,7 +100,7 @@ async function build() {
|
|
|
96
100
|
|
|
97
101
|
console.log("✅ Server and client builds completed!")
|
|
98
102
|
|
|
99
|
-
await runBuildStep("
|
|
103
|
+
await runBuildStep(["./dist/scripts/generateOfflineManifest.js"], {
|
|
100
104
|
...spawnBase,
|
|
101
105
|
env: baseEnv,
|
|
102
106
|
})
|
|
@@ -68,6 +68,7 @@ async function loadAppRoutes(appRoot) {
|
|
|
68
68
|
})
|
|
69
69
|
|
|
70
70
|
try {
|
|
71
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - appRoot is the app's own build root and the remainder is a hardcoded literal path, not request input.
|
|
71
72
|
const routesModule = await vite.ssrLoadModule(path.join(appRoot, "src/js/routes/utils.js"))
|
|
72
73
|
return typeof routesModule.getRoutes === "function" ? routesModule.getRoutes() : []
|
|
73
74
|
} finally {
|
|
@@ -23,6 +23,7 @@ export const printBundleInformation = () => {
|
|
|
23
23
|
const files = fs.readdirSync(directoryPath)
|
|
24
24
|
files.forEach((file) => {
|
|
25
25
|
if (!file.includes("txt") && !file.includes("json")) {
|
|
26
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - file comes from fs.readdirSync(directoryPath), i.e. actual filenames already on disk in the build output, not request input.
|
|
26
27
|
const filePath = path.join(directoryPath, file)
|
|
27
28
|
const fileSize = getFileSizeSync(filePath)
|
|
28
29
|
if (fileSize !== null) {
|
package/dist/scripts/serve.js
CHANGED
|
@@ -24,28 +24,30 @@ function startProd() {
|
|
|
24
24
|
|
|
25
25
|
console.log("🚀 Starting production server...")
|
|
26
26
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
27
|
+
const inspectArgs = commandLineArguments.includes("--inspect") ? ["--inspect"] : []
|
|
28
|
+
spawnSync(
|
|
29
|
+
"node",
|
|
30
|
+
[...inspectArgs, "--import", preInitPath, "--loader", loaderPath, "./dist/server/expressServer.js"],
|
|
31
|
+
{
|
|
32
|
+
cwd: dirname,
|
|
33
|
+
stdio: "inherit",
|
|
34
|
+
env: {
|
|
35
|
+
...process.env,
|
|
36
|
+
src_path: process.env.PWD,
|
|
37
|
+
NODE_ENV: "production",
|
|
38
|
+
IS_DEV_COMMAND: false,
|
|
39
|
+
APPLICATION: name || "catalyst_app",
|
|
40
|
+
...argumentsObject,
|
|
41
|
+
filterKeys: JSON.stringify([
|
|
42
|
+
"src_path",
|
|
43
|
+
"NODE_ENV",
|
|
44
|
+
"IS_DEV_COMMAND",
|
|
45
|
+
"APPLICATION",
|
|
46
|
+
...Object.keys(argumentsObject),
|
|
47
|
+
]),
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
)
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
startProd()
|
package/dist/scripts/start.js
CHANGED
|
@@ -22,11 +22,9 @@ function start() {
|
|
|
22
22
|
const packageJson = JSON.parse(readFileSync(path.join(process.env.PWD, "package.json"), "utf-8"))
|
|
23
23
|
const { name } = packageJson
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
spawnSync(command, [], {
|
|
25
|
+
spawnSync("node", ["--import", preInitPath, "--loader", loaderPath, "./dist/server/expressServer.js"], {
|
|
27
26
|
cwd: dirname,
|
|
28
27
|
stdio: "inherit",
|
|
29
|
-
shell: true,
|
|
30
28
|
env: {
|
|
31
29
|
...process.env,
|
|
32
30
|
src_path: process.env.PWD,
|
|
@@ -126,6 +126,7 @@ const isProduction = process.env.NODE_ENV === "production"
|
|
|
126
126
|
|
|
127
127
|
function serveBuildFile(app, buildPath, urlPath, fileName, headers = {}) {
|
|
128
128
|
app.get(urlPath, (_req, res, next) => {
|
|
129
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - fileName is always a hardcoded literal passed at each serveBuildFile call site, never derived from the request.
|
|
129
130
|
const filePath = path.join(buildPath, fileName)
|
|
130
131
|
if (!fs.existsSync(filePath)) return next()
|
|
131
132
|
res.set(headers)
|
|
@@ -34,9 +34,11 @@ export function Head(props) {
|
|
|
34
34
|
{metaTags && metaTags}
|
|
35
35
|
|
|
36
36
|
{/* Inline critical CSS — prevents FOUC/CLS */}
|
|
37
|
+
{/* nosemgrep: typescript.react.security.audit.react-dangerouslysetinnerhtml.react-dangerouslysetinnerhtml - inlineCss is compiled CSS read from disk (readCssFromDisk), sourced from the app's own build output, never from request/user input. */}
|
|
37
38
|
{inlineCss && <style dangerouslySetInnerHTML={{ __html: inlineCss }} />}
|
|
38
39
|
|
|
39
40
|
{/* Deferred CSS from prior SSRs on this route — avoids late layout from post-body <style> */}
|
|
41
|
+
{/* nosemgrep: typescript.react.security.audit.react-dangerouslysetinnerhtml.react-dangerouslysetinnerhtml - deferredRouteInlineCss is compiled CSS read from disk (readCssFromDisk), sourced from the app's own build output, never from request/user input. */}
|
|
40
42
|
{deferredRouteInlineCss && <style dangerouslySetInnerHTML={{ __html: deferredRouteInlineCss }} />}
|
|
41
43
|
|
|
42
44
|
{/* JS modules */}
|
|
@@ -103,6 +103,7 @@ export const readCssFromDisk = (cssPaths = [], basePath) => {
|
|
|
103
103
|
seen.add(asset)
|
|
104
104
|
if (asset.startsWith("http")) continue
|
|
105
105
|
|
|
106
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - asset paths come from the Vite build manifest generated at build time, never from a request, so there is no user-controlled input here.
|
|
106
107
|
const filePath = path.isAbsolute(asset) ? asset : path.join(basePath, asset.replace(/^\/+/, ""))
|
|
107
108
|
|
|
108
109
|
try {
|
|
@@ -265,6 +265,7 @@ const _renderMarkUp = async (
|
|
|
265
265
|
if (chunkExtractor) {
|
|
266
266
|
const renderedKeys = chunkExtractor.getRenderedComponentKeys()
|
|
267
267
|
this.push(
|
|
268
|
+
// nosemgrep: javascript.lang.security.audit.unknown-value-with-script-tag.unknown-value-with-script-tag - renderedKeys are internal bundler component-module keys tracked by ChunkExtractor, never request/user input, and are JSON.stringify-escaped before embedding.
|
|
268
269
|
`<script>window.__SSR_RENDERED_COMPONENTS__=new Set(${JSON.stringify(renderedKeys)})</script>`
|
|
269
270
|
)
|
|
270
271
|
}
|
|
@@ -45,9 +45,11 @@ function loadAliases() {
|
|
|
45
45
|
for (const [alias, aliasPath] of Object.entries(allAliases)) {
|
|
46
46
|
if (aliasPath && typeof aliasPath === "string") {
|
|
47
47
|
try {
|
|
48
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - appRoot is the app's own build root and aliasPath comes from the developer's package.json config, not request/user input.
|
|
48
49
|
const resolvedPath = path.resolve(appRoot, ...aliasPath.split("/"))
|
|
49
50
|
aliasCache[alias] = resolvedPath
|
|
50
51
|
} catch (error) {
|
|
52
|
+
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring - alias is a key from the developer's own package.json config, not attacker-controlled; console.warn here does no printf-style substitution.
|
|
51
53
|
console.warn(`Failed to resolve alias ${alias}:`, error.message)
|
|
52
54
|
}
|
|
53
55
|
}
|
|
@@ -87,6 +89,7 @@ function resolveWithExtensions(basePath) {
|
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
for (const ext of extensions) {
|
|
92
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - basePath is derived from module import specifiers resolved by this plugin's own alias/relative-import logic, never from a request.
|
|
90
93
|
const candidate = path.join(basePath, `index${ext}`)
|
|
91
94
|
if (existsSync(candidate)) {
|
|
92
95
|
return candidate
|
|
@@ -107,9 +110,11 @@ function resolveToAbsolutePath(importPath, importerId) {
|
|
|
107
110
|
for (const [alias, aliasPath] of Object.entries(aliases)) {
|
|
108
111
|
if (importPath === alias || importPath.startsWith(alias + "/")) {
|
|
109
112
|
const remainingPath = importPath === alias ? "" : importPath.slice(alias.length + 1)
|
|
113
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - importPath comes from the app's own module import graph and aliasPath from the developer's package.json config, never from a request.
|
|
110
114
|
const rawResolved = path.join(aliasPath, remainingPath)
|
|
111
115
|
const finalResolved = resolveWithExtensions(rawResolved)
|
|
112
116
|
if (finalResolved) {
|
|
117
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - finalResolved is derived from the same alias/import resolution above, not request input.
|
|
113
118
|
return path.resolve(finalResolved)
|
|
114
119
|
}
|
|
115
120
|
}
|
|
@@ -118,6 +123,7 @@ function resolveToAbsolutePath(importPath, importerId) {
|
|
|
118
123
|
// 2. Relative imports — resolve from the importer's directory
|
|
119
124
|
if (importPath.startsWith("./") || importPath.startsWith("../")) {
|
|
120
125
|
const importerDir = path.dirname(importerId)
|
|
126
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - importPath comes from the app's own module import graph and importerId from Vite's module resolution, never from a request.
|
|
121
127
|
const absolutePath = path.resolve(importerDir, importPath)
|
|
122
128
|
const resolved = resolveWithExtensions(absolutePath)
|
|
123
129
|
if (resolved) {
|
|
@@ -149,6 +155,7 @@ function resolveImportPath(importPath, importerId) {
|
|
|
149
155
|
// Fallback: strip leading ./ and return as-is
|
|
150
156
|
return importPath.replace(/^\.\//, "")
|
|
151
157
|
} catch (error) {
|
|
158
|
+
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring - importPath comes from the app's own module import graph resolved by Vite at build time, not attacker-controlled; console.warn here does no printf-style substitution.
|
|
152
159
|
console.warn(`Error resolving import path ${importPath}:`, error.message)
|
|
153
160
|
return importPath.replace(/^\.\//, "")
|
|
154
161
|
}
|
|
@@ -10,6 +10,7 @@ const emptyConfig = {
|
|
|
10
10
|
export async function loadCustomViteConfig() {
|
|
11
11
|
const configNames = ["buildConfig.js", "webpackConfig.js"]
|
|
12
12
|
const configPath = configNames
|
|
13
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - configName is one of two hardcoded literals above, and process.env.src_path is the app root set by the framework's own build tooling, not request/user input.
|
|
13
14
|
.map((configName) => path.join(process.env.src_path, configName))
|
|
14
15
|
.find((candidatePath) => existsSync(candidatePath))
|
|
15
16
|
|
|
@@ -293,6 +293,7 @@ export function manifestCategorizationPlugin(options = {}) {
|
|
|
293
293
|
// branch of the import graph, the affected CSS would vanish from server responses
|
|
294
294
|
// with no runtime error — this check surfaces that as a build-time warning.
|
|
295
295
|
function validateCssCoverage(categorizedManifest, buildDir) {
|
|
296
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - buildDir is the build tool's own output directory and the remaining segments are hardcoded literals, not request input.
|
|
296
297
|
const cssDir = path.join(buildDir, "client", "assets", "css")
|
|
297
298
|
if (!fs.existsSync(cssDir)) return
|
|
298
299
|
|
|
@@ -403,6 +404,7 @@ export function manifestCategorizationPlugin(options = {}) {
|
|
|
403
404
|
})
|
|
404
405
|
}
|
|
405
406
|
} catch (err) {
|
|
407
|
+
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring - importPath comes from the app's own module import graph resolved by Vite at build time, not attacker-controlled; console.log here does no printf-style substitution.
|
|
406
408
|
console.log(`❌ Error resolving ${importPath}:`, err.message)
|
|
407
409
|
}
|
|
408
410
|
})
|
|
@@ -56,6 +56,7 @@ function loadAliases() {
|
|
|
56
56
|
const resolvedPath = resolvePath(appRoot, ...aliasPath.split("/"))
|
|
57
57
|
aliasCache[alias] = resolvedPath
|
|
58
58
|
} catch (error) {
|
|
59
|
+
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring - alias is a key from the developer's own package.json _moduleAliases config, not attacker-controlled; console.warn here does no printf-style substitution.
|
|
59
60
|
console.warn(`Failed to resolve alias ${alias}:`, error.message)
|
|
60
61
|
}
|
|
61
62
|
}
|
|
@@ -100,6 +101,7 @@ function resolveWithExtensions(basePath) {
|
|
|
100
101
|
|
|
101
102
|
// Try index files inside a directory
|
|
102
103
|
for (const ext of extensions) {
|
|
104
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - basePath is derived from module import specifiers resolved by the Node loader itself, never from a request.
|
|
103
105
|
const candidate = join(basePath, `index${ext}`)
|
|
104
106
|
if (existsSync(candidate)) {
|
|
105
107
|
return candidate
|
|
@@ -119,10 +121,12 @@ function resolveAlias(specifier, parentURL) {
|
|
|
119
121
|
for (const [alias, aliasPath] of Object.entries(aliases)) {
|
|
120
122
|
if (specifier.startsWith(alias + "/") || specifier === alias) {
|
|
121
123
|
const remainingPath = specifier === alias ? "" : specifier.slice(alias.length + 1)
|
|
124
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - specifier comes from an ES module import statement in the app's own source code, and aliasPath from the developer's package.json config, never from a request.
|
|
122
125
|
const rawResolvedPath = join(aliasPath, remainingPath)
|
|
123
126
|
const finalResolvedPath = resolveWithExtensions(rawResolvedPath)
|
|
124
127
|
|
|
125
128
|
if (finalResolvedPath) {
|
|
129
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - finalResolvedPath is derived from the same alias/import resolution above, not request input.
|
|
126
130
|
return resolvePath(finalResolvedPath)
|
|
127
131
|
}
|
|
128
132
|
}
|
|
@@ -140,6 +144,7 @@ function resolveRelative(specifier, parentURL) {
|
|
|
140
144
|
try {
|
|
141
145
|
const parentPath = fileURLToPath(parentURL)
|
|
142
146
|
const parentDir = dirname(parentPath)
|
|
147
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - specifier comes from an ES module import statement in the app's own source code, parentURL from the module loader itself, never from a request.
|
|
143
148
|
const absolutePath = resolvePath(parentDir, specifier)
|
|
144
149
|
return resolveWithExtensions(absolutePath)
|
|
145
150
|
} catch {
|
|
@@ -225,6 +230,7 @@ export async function resolve(specifier, context, defaultResolve) {
|
|
|
225
230
|
*/
|
|
226
231
|
function findNearestPackageType(dir) {
|
|
227
232
|
while (dir !== dirname(dir)) {
|
|
233
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - dir is walked up from the module's own filesystem location via dirname(), never from a request; "package.json" is a hardcoded literal.
|
|
228
234
|
const pkgPath = join(dir, "package.json")
|
|
229
235
|
if (existsSync(pkgPath)) {
|
|
230
236
|
try {
|
package/dist/vite/vite.config.js
CHANGED
|
@@ -113,6 +113,7 @@ try {
|
|
|
113
113
|
const packageJson = JSON.parse(packageJsonContent)
|
|
114
114
|
_moduleAliases = packageJson._moduleAliases || {}
|
|
115
115
|
} catch (error) {
|
|
116
|
+
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring - packageJsonConfig is a build-time-resolved local path, not attacker-controlled; console.warn here does no printf-style substitution.
|
|
116
117
|
console.warn(`Failed to read or parse package.json from ${packageJsonConfig}:`, error.message)
|
|
117
118
|
}
|
|
118
119
|
|
|
@@ -122,6 +123,7 @@ try {
|
|
|
122
123
|
catalyst_moduleAliases = catalystPackageJson._moduleAliases || {}
|
|
123
124
|
} catch (error) {
|
|
124
125
|
console.warn(
|
|
126
|
+
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring - catalystPackageJsonConfig is a build-time-resolved local path, not attacker-controlled; console.warn here does no printf-style substitution.
|
|
125
127
|
`Failed to read or parse catalyst package.json from ${catalystPackageJsonConfig}:`,
|
|
126
128
|
error.message
|
|
127
129
|
)
|
|
@@ -146,6 +148,7 @@ import { imageUrl, fontUrl } from "./scssParams.js"
|
|
|
146
148
|
// options — so anything else is left alone and Vite falls back to its normal
|
|
147
149
|
// auto-loaded postcss.config.js resolution.
|
|
148
150
|
const resolvePostcssPlugins = () => {
|
|
151
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - process.env.src_path is the app root set by the framework's own build tooling, not request/user input.
|
|
149
152
|
const configPath = path.join(process.env.src_path, "postcss.config.js")
|
|
150
153
|
if (!fs.existsSync(configPath)) return null
|
|
151
154
|
|
|
@@ -168,6 +171,7 @@ const resolvePostcssPlugins = () => {
|
|
|
168
171
|
return plugin(finalOptions)
|
|
169
172
|
})
|
|
170
173
|
} catch (error) {
|
|
174
|
+
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring - configPath is a build-time-resolved local path, not attacker-controlled; console.warn here does no printf-style substitution.
|
|
171
175
|
console.warn(`Failed to patch postcss config at ${configPath}:`, error.message)
|
|
172
176
|
return null
|
|
173
177
|
}
|
|
@@ -183,9 +187,11 @@ const alias = () => {
|
|
|
183
187
|
return Object.keys(allAliases).reduce((moduleEnvMap, alias) => {
|
|
184
188
|
if (allAliases[alias] && typeof allAliases[alias] === "string") {
|
|
185
189
|
try {
|
|
190
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - process.env.src_path is the app root and allAliases comes from the developer's own package.json _moduleAliases config, not request/user input.
|
|
186
191
|
const aliasPath = path.join(process.env.src_path, ...allAliases[alias].split("/"))
|
|
187
192
|
moduleEnvMap[alias] = aliasPath
|
|
188
193
|
} catch (error) {
|
|
194
|
+
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring - alias is a key from the developer's own package.json _moduleAliases config, not attacker-controlled; console.warn here does no printf-style substitution.
|
|
189
195
|
console.warn(`Failed to configure alias ${alias}:`, error.message)
|
|
190
196
|
}
|
|
191
197
|
}
|
package/mcp_v2/tools/github.js
CHANGED
|
@@ -161,6 +161,7 @@ function safeReadText(filePath, maxBytes = 8000) {
|
|
|
161
161
|
|
|
162
162
|
function runGit(root, command) {
|
|
163
163
|
try {
|
|
164
|
+
// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - command is always one of the fixed git/node/npm strings hardcoded at call sites, never external input.
|
|
164
165
|
return execSync(command, {
|
|
165
166
|
cwd: root,
|
|
166
167
|
encoding: "utf8",
|
|
@@ -183,6 +184,7 @@ function slugify(input) {
|
|
|
183
184
|
}
|
|
184
185
|
|
|
185
186
|
function ensureFallbackDir(root) {
|
|
187
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - root is the project_path MCP tool argument, an intentional/documented parameter letting a local developer's coding agent point this tool at any project directory it already has filesystem access to (see mcp.js inputSchema). Not a network-facing input; the caller already has direct fs access via other tools. FALLBACK_DIR is a hardcoded literal.
|
|
186
188
|
const dir = path.join(root, FALLBACK_DIR)
|
|
187
189
|
fs.mkdirSync(dir, { recursive: true })
|
|
188
190
|
return dir
|
|
@@ -406,7 +408,9 @@ function appendSection(sections, heading, content) {
|
|
|
406
408
|
}
|
|
407
409
|
|
|
408
410
|
function isPathInside(root, candidate) {
|
|
411
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - this is the containment-check helper itself (used by callers below to reject paths outside root); root/candidate here are the values being validated, not yet trusted for file access.
|
|
409
412
|
const resolvedRoot = path.resolve(root)
|
|
413
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - this is the containment-check helper itself; candidate is the value being validated, not yet trusted for file access.
|
|
410
414
|
const resolvedCandidate = path.resolve(candidate)
|
|
411
415
|
const relative = path.relative(resolvedRoot, resolvedCandidate)
|
|
412
416
|
return relative === "" || (relative && !relative.startsWith("..") && !path.isAbsolute(relative))
|
|
@@ -543,6 +547,7 @@ function normalizeImages(images, root) {
|
|
|
543
547
|
continue
|
|
544
548
|
}
|
|
545
549
|
|
|
550
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - raw is an MCP tool argument (attachment path) and root the project_path argument; the isPathInside() check on the next line rejects anything that resolves outside root before it is ever read.
|
|
546
551
|
const absolutePath = path.isAbsolute(raw) ? path.resolve(raw) : path.resolve(root, raw)
|
|
547
552
|
if (!isPathInside(root, absolutePath)) {
|
|
548
553
|
localFiles.push({ path: raw, exists: false, rejected: true, reason: "outside_project_root" })
|
|
@@ -789,6 +794,7 @@ function collectRelatedFiles(root, query, explicitFiles) {
|
|
|
789
794
|
const candidates = Array.isArray(explicitFiles) ? explicitFiles : []
|
|
790
795
|
for (const file of candidates) {
|
|
791
796
|
if (typeof file !== "string") continue
|
|
797
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - file is an MCP tool argument (explicit related-files list) and root the project_path argument; the isPathInside() check on the next line rejects anything that resolves outside root before it is ever read.
|
|
792
798
|
const absolute = path.isAbsolute(file) ? path.resolve(file) : path.resolve(root, file)
|
|
793
799
|
if (!isPathInside(root, absolute)) continue
|
|
794
800
|
const content = safeReadText(absolute, 12000)
|
|
@@ -802,6 +808,7 @@ function collectRelatedFiles(root, query, explicitFiles) {
|
|
|
802
808
|
const words = normalizeSearchText(query).split(/\s+/).filter(Boolean).slice(0, 5)
|
|
803
809
|
if (words.length === 0) return files
|
|
804
810
|
|
|
811
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - root is the project_path MCP tool argument, an intentional/documented parameter (see mcp.js inputSchema); "src" is a hardcoded literal.
|
|
805
812
|
const srcDir = path.join(root, "src")
|
|
806
813
|
const matches = []
|
|
807
814
|
function walk(dir) {
|
|
@@ -813,6 +820,7 @@ function collectRelatedFiles(root, query, explicitFiles) {
|
|
|
813
820
|
}
|
|
814
821
|
for (const entry of entries) {
|
|
815
822
|
if (entry.name === "node_modules" || entry.name === ".git") continue
|
|
823
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - entry.name comes from fs.readdirSync(dir) above, i.e. actual filenames already on disk under srcDir, not request input.
|
|
816
824
|
const full = path.join(dir, entry.name)
|
|
817
825
|
if (entry.isDirectory()) {
|
|
818
826
|
walk(full)
|
|
@@ -831,7 +839,9 @@ function collectRelatedFiles(root, query, explicitFiles) {
|
|
|
831
839
|
|
|
832
840
|
function gatherGithubIssueContext(args = {}) {
|
|
833
841
|
const root = getProjectRoot(args)
|
|
842
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - root is the project_path MCP tool argument, an intentional/documented parameter (see mcp.js inputSchema); "package.json" is a hardcoded literal.
|
|
834
843
|
const pkg = safeReadJson(path.join(root, "package.json"))
|
|
844
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - root is the project_path MCP tool argument, an intentional/documented parameter (see mcp.js inputSchema); "config/config.json" is a hardcoded literal.
|
|
835
845
|
const configJson = safeReadJson(path.join(root, "config", "config.json"))
|
|
836
846
|
const branch = runGit(root, "git rev-parse --abbrev-ref HEAD")
|
|
837
847
|
const commit = runGit(root, "git rev-parse --short HEAD")
|
|
@@ -874,6 +884,7 @@ function generateIssueMarkdown(args = {}) {
|
|
|
874
884
|
const built = buildIssueBody(args, { enrich: true })
|
|
875
885
|
const labels = resolveIssueLabels(args, built.body)
|
|
876
886
|
const dir = ensureFallbackDir(root)
|
|
887
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - dir is the FALLBACK_DIR under the project_path MCP argument; the filename is a generated ISO timestamp plus slugify(title), which strips to [a-z0-9-] only, so it cannot contain path separators.
|
|
877
888
|
const filePath = path.join(dir, `${new Date().toISOString().replace(/[:.]/g, "-")}-${slugify(title)}.md`)
|
|
878
889
|
|
|
879
890
|
const markdown = [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "catalyst-core",
|
|
3
|
-
"version": "0.3.0-beta.
|
|
3
|
+
"version": "0.3.0-beta.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.jsx",
|
|
6
6
|
"description": "Web framework that provides great performance out of the box",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"react-refresh": "^0.14.0",
|
|
52
52
|
"react-router-dom": "^6.16.0",
|
|
53
53
|
"redux": "^4.2.1",
|
|
54
|
-
"sanitize-html": "^2.
|
|
54
|
+
"sanitize-html": "^2.17.6",
|
|
55
55
|
"sass": "^1.89.2",
|
|
56
56
|
"ua-parser-js": "^1.0.37",
|
|
57
57
|
"vite": "^6.4.3",
|