-
Notifications
You must be signed in to change notification settings - Fork 1
fix(MESHCENT-003): CU-86akhf8u4 7 review findings across 3 files #187
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -132,7 +132,8 @@ function loadConfig(configfile) { | |
| var raw = fs.readFileSync(configfile, 'utf8'); | ||
| // Strip any ${VAR} placeholders that weren't substituted β fall back to env | ||
| raw = raw.replace(/\$\{([A-Z_][A-Z0-9_]*)\}/g, function (m, name) { | ||
| return process.env[name] || ''; | ||
| var val = process.env[name] || ''; | ||
| return JSON.stringify(val).slice(1, -1); | ||
| }); | ||
| return JSON.parse(raw); | ||
| } | ||
|
Comment on lines
132
to
139
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π loadConfig performs unsafe regex-based env var substitution then JSON.parse without escaping injected values In π€ Prompt for AI agentsfix confidence: π’ 90 high β react π/π to teach the reviewer |
||
|
|
@@ -254,6 +255,7 @@ function ensureDeviceGroup(db, domain, userid, cb) { | |
| db.Get(userid, function (getErr, userDocs) { | ||
| if (getErr || !userDocs || userDocs.length !== 1) { | ||
| // Not fatal β mesh is created, just couldn't update user links | ||
| err('Could not update user links for ' + userid + ' after creating mesh ' + meshid + (getErr ? (': ' + getErr) : '')); | ||
| return cb(null, meshid); | ||
| } | ||
| var user = userDocs[0]; | ||
|
Comment on lines
255
to
261
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π ensureDeviceGroup swallows failed user-link update without surfacing a warning In π€ Prompt for AI agentsfix confidence: π’ 90 high β react π/π to teach the reviewer |
||
|
|
@@ -275,13 +277,22 @@ function writeMeshIdFiles(meshid, serverIdHex) { | |
| var parts = meshid.split('/'); | ||
| var base64Hash = parts[parts.length - 1]; | ||
|
|
||
| if (base64Hash.indexOf('..') !== -1 || base64Hash.indexOf('/') !== -1 || base64Hash.indexOf('\\') !== -1) { | ||
| throw new Error('Refusing to write mesh id files: derived base64Hash contains unsafe path characters: ' + base64Hash); | ||
| } | ||
|
|
||
| fs.writeFileSync(path.join(MESH_DIR, 'mesh_device_group_id'), base64Hash); | ||
| log('Wrote mesh_device_group_id: ' + base64Hash); | ||
|
|
||
| // Convert base64 (with MC's @$ escaping) β hex with 0x prefix | ||
| var standardBase64 = base64Hash.replace(/@/g, '+').replace(/\$/g, '/'); | ||
| var hex = Buffer.from(standardBase64, 'base64').toString('hex').toUpperCase(); | ||
| var meshIdHex = '0x' + hex; | ||
|
|
||
| if (meshIdHex.indexOf('..') !== -1 || meshIdHex.indexOf('/') !== -1 || meshIdHex.indexOf('\\') !== -1) { | ||
| throw new Error('Refusing to write mesh id files: derived meshIdHex contains unsafe path characters: ' + meshIdHex); | ||
| } | ||
|
|
||
| fs.writeFileSync(path.join(MESH_DIR, 'mesh_id'), meshIdHex); | ||
| log('Wrote mesh_id: ' + meshIdHex); | ||
|
|
||
|
Comment on lines
277
to
298
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π΄ writeMeshIdFiles / generateMshFile write into MESH_DIR without validating derived tenant domain or meshid content for path traversal In π€ Prompt for AI agentsfix confidence: π‘ 85 medium β react π/π to teach the reviewer |
||
|
|
@@ -294,6 +305,10 @@ function writeMeshIdFiles(meshid, serverIdHex) { | |
| // --- Step 7: Generate meshagent.msh file --- | ||
|
|
||
| function generateMshFile(meshIdHex, serverIdHex) { | ||
| if (meshIdHex.indexOf('..') !== -1 || meshIdHex.indexOf('/') !== -1 || meshIdHex.indexOf('\\') !== -1) { | ||
| throw new Error('Refusing to generate msh file: meshIdHex contains unsafe path characters: ' + meshIdHex); | ||
| } | ||
|
|
||
| var meshServerUrl; | ||
| if (OPENFRAME_MODE === 'true' && OPENFRAME_GATEWAY_URL) { | ||
| log('OpenFrame mode enabled β using gateway URL'); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,11 @@ const path = require('path'); | |
| const MESH_DIR = process.env.MESH_DIR || '/opt/mesh'; | ||
| const MESH_DEVICE_GROUP = process.env.MESH_DEVICE_GROUP || ''; | ||
|
|
||
| // RFC 1123 hostname (with optional port), or a bare IPv4 address (with optional port). | ||
| // This deliberately rejects control characters, whitespace, newlines and other | ||
| // characters that could inject additional MSH directives into the generated file. | ||
| const HOST_PATTERN = /^[A-Za-z0-9]([A-Za-z0-9-]{0,62}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,62}[A-Za-z0-9])?)*(:[0-9]{1,5})?$/; | ||
|
|
||
| // --- Helpers --- | ||
|
|
||
| function corsHeaders(res) { | ||
|
Comment on lines
6
to
16
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π deriveTenantDomain duplicated inline in plugins/openframe.js instead of shared with db.js Did not extract π€ Prompt for AI agentsfix confidence: π΄ 30 low β review closely β react π/π to teach the reviewer |
||
|
|
@@ -59,6 +64,12 @@ module.exports.openframe = function (pluginHandler) { | |
| app.get('/generate-msh', function (req, res) { | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π΄ User-supplied host parameter used to build MSH file without path/content validation before embedding into downloadable file In π€ Prompt for AI agentsfix confidence: π΄ 55 low β review closely β react π/π to teach the reviewer |
||
| corsHeaders(res); | ||
|
|
||
| // Require an authenticated MeshCentral session, consistent with other endpoints (e.g. | ||
| // amt-ider.js), since this generates a downloadable agent config tied to server identity. | ||
| if (req.session == null || !req.session.userid) { | ||
| return sendError(res, 401, 'Authentication required'); | ||
| } | ||
|
|
||
| var host = req.query.host; | ||
| if (!host) return sendError(res, 400, 'Missing required parameter: host'); | ||
|
|
||
|
|
@@ -74,6 +85,13 @@ module.exports.openframe = function (pluginHandler) { | |
|
|
||
| var protocol = host.startsWith('http://') ? 'ws' : 'wss'; | ||
| var cleanHost = host.replace(/^https?:\/\//, '').replace(/^wss?:\/\//, ''); | ||
|
|
||
| // Validate the stripped host: must be a plain hostname/IPv4 with optional port and no | ||
| // newlines, whitespace or other characters that could inject extra MSH directives. | ||
| if (!HOST_PATTERN.test(cleanHost)) { | ||
| return sendError(res, 400, 'Invalid host parameter'); | ||
| } | ||
|
|
||
| var meshServerUrl = protocol + '://' + cleanHost + '/ws/tools/agent/meshcentral-server/agent.ashx'; | ||
|
|
||
| var mshContent = [ | ||
|
|
@@ -113,6 +131,10 @@ module.exports.openframe = function (pluginHandler) { | |
|
|
||
| // 1. Verify device exists in DB | ||
| db.Get(nodeId, function (err, docs) { | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π db.Get callback in /api/deviceStatus ignores err parameter entirely In the π€ Prompt for AI agentsfix confidence: π‘ 85 medium β react π/π to teach the reviewer |
||
| if (err) { | ||
| log('db.Get error for ' + nodeId + ': ' + err); | ||
| return sendError(res, 500, 'Database error'); | ||
| } | ||
| if (docs == null || docs.length !== 1) return sendError(res, 404, 'Device not found'); | ||
|
|
||
| // 2. Live connectivity state from MeshCentral in-memory store | ||
|
|
@@ -121,6 +143,10 @@ module.exports.openframe = function (pluginHandler) { | |
|
|
||
| // 3. Last connection record from DB | ||
| db.Get('lc' + nodeId, function (err, docs) { | ||
| if (err) { | ||
| log('db.Get error for lc' + nodeId + ': ' + err); | ||
| return sendError(res, 500, 'Database error'); | ||
| } | ||
| var lc = (docs != null && docs.length === 1) ? docs[0] : null; | ||
|
|
||
| res.json({ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
𦩠π΄ agents/meshinstall-linux.js writes user-supplied --installPath value into shell command without '..' path traversal check
In the CLI arg parsing loop (the
if (!skip) { if (process.argv.includes('-install') || ...) { ... } }block near line 225), added an explicit check that rejects any--installPath=value containing..before it is pushed onto theparray passed to_install(). If..is found, the script logs an error and callsprocess.exit()instead of proceeding, preventing the tainted path from ever reaching filesystem operations performed by the spawned install process. This directly implements the required "check for '..' before use" per MESHCENT-003/MESHCENT-004-2. Residual risk: this only blocks the literal substring..; it does not canonicalize/resolve the path (e.g. via realpath) to catch more exotic traversal encodings, and the downstream_installre-spawn (in the full-install child process, not shown/available in this file) is not modified β a complete fix might also validate the path is absolute/within an allowed root at the point it's actually used for filesystem writes.π€ Prompt for AI agents
fix confidence: π‘ 75 medium β react π/π to teach the reviewer