{"record":{"id":"597367981b32e8af","repo":"usebruno/bruno","slug":"security-error-opencollection-yml-cannot-be-a-sym","errorCode":null,"errorMessage":"Security error: opencollection.yml cannot be a symbolic link","messagePattern":"Security error: opencollection\\.yml cannot be a symbolic link","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/bruno-electron/src/ipc/collection.js","lineNumber":2731,"sourceCode":"          const singleItemStat = fs.lstatSync(singleItem);\n          if (singleItemStat.isDirectory() && !singleItemStat.isSymbolicLink()) {\n            collectionDir = singleItem;\n          }\n        }\n\n        const brunoJsonPath = path.join(collectionDir, 'bruno.json');\n        const openCollectionYmlPath = path.join(collectionDir, 'opencollection.yml');\n\n        if (!fs.existsSync(brunoJsonPath) && !fs.existsSync(openCollectionYmlPath)) {\n          throw new Error('Invalid collection: Neither bruno.json nor opencollection.yml found in the ZIP file');\n        }\n\n        // Ensure config files are not symlinks\n        if (fs.existsSync(brunoJsonPath) && fs.lstatSync(brunoJsonPath).isSymbolicLink()) {\n          throw new Error('Security error: bruno.json cannot be a symbolic link');\n        }\n        if (fs.existsSync(openCollectionYmlPath) && fs.lstatSync(openCollectionYmlPath).isSymbolicLink()) {\n          throw new Error('Security error: opencollection.yml cannot be a symbolic link');\n        }\n\n        let collectionName = 'Imported Collection';\n        let brunoConfig = { name: collectionName, version: '1', type: 'collection', ignore: ['node_modules', '.git'] };\n        if (fs.existsSync(openCollectionYmlPath)) {\n          try {\n            const content = fs.readFileSync(openCollectionYmlPath, 'utf8');\n            const parsed = parseCollection(content, { format: 'yml' });\n            brunoConfig = parsed.brunoConfig || brunoConfig;\n            collectionName = brunoConfig.name || collectionName;\n          } catch (e) {\n            console.error(`Error parsing opencollection.yml at ${openCollectionYmlPath}:`, e);\n          }\n        } else if (fs.existsSync(brunoJsonPath)) {\n          try {\n            brunoConfig = JSON.parse(fs.readFileSync(brunoJsonPath, 'utf8'));\n            collectionName = brunoConfig.name || collectionName;\n          } catch (e) {","sourceCodeStart":2713,"sourceCodeEnd":2749,"githubUrl":"https://github.com/usebruno/bruno/blob/9bdd81c7bdc57006e5f5ebffb79321a8d979f712/packages/bruno-electron/src/ipc/collection.js#L2713-L2749","documentation":"Same security guard as the bruno.json check, applied to `opencollection.yml`. After the file is found, `fs.lstatSync(...).isSymbolicLink()` rejects a symlinked config to prevent traversal outside the extraction directory during ZIP import.","triggerScenarios":"A ZIP whose `opencollection.yml` entry is a symlink to an absolute or `../`-traversing target; a collection directory where opencollection.yml was symlinked and then archived.","commonSituations":"Untrusted collection archive crafted to escape the temp dir; a shared config symlinked across multiple collections and zipped.","solutions":["Replace the `opencollection.yml` symlink with a real file copy inside the archive.","Re-export via Bruno rather than zipping a directory containing symlinks.","Treat archives from untrusted sources as hostile and inspect entries first."],"exampleFix":"// before\nln -s /shared/opencollection.yml my-collection/opencollection.yml\n// after\ncp /shared/opencollection.yml my-collection/opencollection.yml","handlingStrategy":"validation","validationCode":"const assertRealConfigFile = (p) => {\n  const fs = require('fs');\n  if (fs.existsSync(p) && fs.lstatSync(p).isSymbolicLink()) {\n    throw new Error(`Refusing symlink config: ${p}`);\n  }\n};","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never archive directories containing symlinks to config files.","Inspect opencollection.yml entries in untrusted archives before importing.","Re-export via Bruno instead of zipping symlinked configs."],"tags":["security","symlink","collection","import"],"backgroundTag":null,"analyzedSha":"9bdd81c7bdc57006e5f5ebffb79321a8d979f712","analyzedAt":"2026-08-13T04:09:25.751Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}