{"record":{"id":"797b1fada68412a8","repo":"apache/beam","slug":"refusing-to-serve-filepath-as-it-is-not-under-rootdir","errorCode":null,"errorMessage":"Refusing to serve \" + filePath + \" as it is not under \" + rootDir","messagePattern":"Refusing to serve \" \\+ filePath \\+ \" as it is not under \" \\+ rootDir","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"sdks/typescript/src/apache_beam/runners/artifacts.ts","lineNumber":145,"sourceCode":"          isLast: false,\n          response: {\n            oneofKind: \"resolveArtifactResponse\",\n            resolveArtifactResponse: {\n              replacements: msg.request.resolveArtifact.artifacts,\n            },\n          },\n        });\n        break;\n\n      case \"getArtifact\":\n        switch (msg.request.getArtifact.artifact!.typeUrn) {\n          case \"beam:artifact:type:file:v1\":\n            const payload = runnerApi.ArtifactFilePayload.fromBinary(\n              msg.request.getArtifact.artifact!.typePayload,\n            );\n            const filePath = path.normalize(payload.path);\n            if (!filePath.startsWith(rootDir)) {\n              throw new Error(\n                \"Refusing to serve \" +\n                  filePath +\n                  \" as it is not under \" +\n                  rootDir,\n              );\n            }\n            const handle = fs.createReadStream(filePath);\n            for await (const chunk of handle) {\n              call.requests.send({\n                stagingToken: stagingToken,\n                isLast: false,\n                response: {\n                  oneofKind: \"getArtifactResponse\",\n                  getArtifactResponse: { data: chunk },\n                },\n              });\n            }\n            call.requests.send({","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/typescript/src/apache_beam/runners/artifacts.ts#L127-L163","documentation":"During artifact offering, the ArtifactsServiceHandler serving file artifacts normalizes the requested artifact path and refuses to serve any file that resolves outside the configured rootDir. This is a deliberate security guard against path traversal: the runner should only ever serve artifacts staged under its own artifact root.","triggerScenarios":"The runner proxy receives a beam:artifact:type:file:v1 request whose ArtifactFilePayload.path normalizes to a path that does not start with rootDir (e.g. '/etc/passwd' or '../secrets' or a path staged elsewhere on disk).","commonSituations":"Artifact staging directory misconfigured (rootDir doesn't match where the job service actually staged files); artifacts built in a different temp directory than the one handed to the handler; a malicious or buggy job submitting absolute paths.","solutions":["Ensure the artifact staging root passed as rootDir matches the directory where artifacts were actually staged.","Fix the artifact payload paths so they are relative to (or inside) rootDir before offering artifacts.","Check environment/config that determines the staging directory (temp dir, --artifacts_dir equivalent) so both sides agree.","If intentionally serving from elsewhere, reconfigure the handler's rootDir to include that location (after verifying it is safe)."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// before offering\nconst normalized = path.normalize(artifactPath);\nif (!normalized.startsWith(rootDir)) {\n  console.error('Artifact outside staging root:', normalized);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await handler.offerArtifacts(...);\n} catch (e) {\n  if ((e as Error).message.includes('Refusing to serve')) {\n    // re-stage artifacts under the expected root, then retry once\n  } else throw e;\n}","preventionTips":["Always stage artifacts inside the directory passed as rootDir.","Use relative paths in artifact payloads where possible.","Keep the staging root consistent across job service and runner config.","Never accept user-supplied absolute artifact paths."],"tags":["path-traversal","security","artifacts","runner"],"backgroundTag":"path-traversal-blocked","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-20T03:17:13.778Z"}