{"record":{"id":"8df4598af09389ff","repo":"ruvnet/ruflo","slug":"unknown-strategy-name-available-roster-ma","errorCode":null,"errorMessage":"unknown strategy \"${name}\". Available: ${roster.map((r) => r.name).join(', ')}","messagePattern":"unknown strategy \"(.+?)\"\\. Available: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugins/ruflo-arena/src/domain/strategies.ts","lineNumber":130,"sourceCode":"      antiCopy(a, 0, 'suspicious-anti-tft'),\n      alternate(a, 'alternate'),\n      random(a, 'random'),\n    ];\n  }\n  return [\n    constant(a, 0, `always-${a[0]}`),\n    constant(a, 1, `always-${a[1]}`),\n    copyOpponent(a, 0, 'copy-opponent'),\n    antiCopy(a, 0, 'anti-copy'),\n    alternate(a, 'alternate'),\n    random(a, 'random'),\n  ];\n}\n\nexport function findStrategy(game: GameSpec, name: string): Strategy {\n  const roster = classicRoster(game);\n  const s = roster.find((r) => r.name === name || r.name.startsWith(name));\n  if (!s) throw new Error(`unknown strategy \"${name}\". Available: ${roster.map((r) => r.name).join(', ')}`);\n  return s;\n}\n\n// --- Evolvable FSMs — random genomes + mutation operators (ADR-148) ------------------------\n\nexport function randomFSM(game: GameSpec, rng: () => number, nStates = 2, name = 'evolved'): FsmStrategy {\n  const a = game.actions;\n  const states = [];\n  for (let i = 0; i < nStates; i++) {\n    states.push({\n      action: choice(rng, a),\n      next: Object.fromEntries(a.map((x) => [x, randInt(rng, nStates)])),\n    });\n  }\n  return { kind: 'fsm', name, nStates, start: randInt(rng, nStates), states };\n}\n\nfunction cloneFSM(fsm: FsmStrategy): FsmStrategy {","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/plugins/ruflo-arena/src/domain/strategies.ts#L112-L148","documentation":"validateConfigPath() calls normalize() on the input and then rejects any result containing the substring '..'. The intent is to block classic path traversal (../../etc/passwd.json) so a config path can never climb above the server's working directory. Note the check is substring-based, so even a harmless filename containing two dots such as \"my..config.json\" is rejected, not just real traversal segments.","triggerScenarios":"path=\"../../../etc/app/config.json\"; path=\"configs/../claude-flow.config.json\" (normalize() keeps the '..' because it would climb, so it survives); a benign filename with a double dot like \"v2..release.config.json\" which triggers a false positive on the includes('..') test.","commonSituations":"Reusing CLI flags or templates that include ../ to point at a parent-directory config; generated filenames containing '..' (version strings like '1.0..json'); user input sanitized for traversal elsewhere but not for dot-runs.","solutions":["Remove all '..' segments: pass the direct relative path, or pre-compute path.relative(process.cwd(), targetPath) which yields a clean traversal-free path","If the target genuinely sits above the server's cwd, restart the MCP server from that parent directory so a flat relative path works","Rename files whose names contain '..' (e.g. my..config.json -> my.config.json) since the substring check cannot distinguish them from traversal","Double-check the path string for accidental '..' introduced by template literals or string concatenation"],"exampleFix":"// before\nawait client.callTool('config_save', {\n  path: '../shared/claude-flow.config.json', // throws [1121]\n  config: cfg,\n});\n\n// after (run server from repo root, or flatten the path)\nawait client.callTool('config_save', {\n  path: 'shared/claude-flow.config.json',\n  config: cfg,\n});","handlingStrategy":"validation","validationCode":"import { relative, isAbsolute, sep } from 'path';\n\nfunction safeRelativeConfigPath(raw: string): string | null {\n  const rel = isAbsolute(raw) ? relative(process.cwd(), raw) : raw;\n  const norm = rel.replace(/\\\\/g, '/');\n  if (norm.split('/').some(seg => seg === '..' || seg.includes('..'))) return null;\n  return norm;\n}","typeGuard":null,"tryCatchPattern":"try { await client.callTool('config_load', { path }); }\ncatch (e) {\n  if (e instanceof Error && e.message.includes('Path traversal')) {\n    throw new Error(`Config path ${path} escapes the server cwd; move the file or change server cwd`);\n  }\n  throw e;\n}","preventionTips":["Never build config paths with '../' — compute path.relative(cwd, target) once and cache it","Add a lint rule or unit test asserting your config path constants contain no '..' substring","Keep shared configs inside the server's working tree instead of reaching up to parent directories"],"tags":["mcp","config","path-traversal","security"],"backgroundTag":"path-traversal-blocked","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}