{"record":{"id":"c8fecbdd928e11eb","repo":"different-ai/openwork","slug":"invalid-return-path","errorCode":"invalid_return_path","errorMessage":"GitHub install return path must be a safe relative path.","messagePattern":"GitHub install return path must be a safe relative path\\.","errorType":"http","errorClass":"PluginArchRouteFailure","httpStatus":400,"severity":"warning","filePath":"ee/apps/den-api/src/routes/org/plugin-system/store.ts","lineNumber":6871,"sourceCode":"\n  await db.update(ConnectorAccountTable).set({\n    displayName: installation.displayName,\n    externalAccountRef: installation.accountLogin,\n    metadataJson: {\n      ...(existingRows[0].metadataJson ?? {}),\n      ...metadata,\n    },\n    status: \"active\",\n    updatedAt: new Date(),\n  }).where(eq(ConnectorAccountTable.id, existingRows[0].id))\n\n  return getConnectorAccountDetail(input.context, existingRows[0].id)\n}\n\nexport async function startGithubConnectorInstall(input: { context: PluginArchActorContext; returnPath: string }) {\n  const returnPath = input.returnPath.trim()\n  if (!returnPath.startsWith(\"/\") || returnPath.startsWith(\"//\")) {\n    throw new PluginArchRouteFailure(400, \"invalid_return_path\", \"GitHub install return path must be a safe relative path.\")\n  }\n\n  let app: Awaited<ReturnType<typeof getGithubAppSummary>>\n  try {\n    app = await getGithubAppSummary({ config: githubConnectorAppConfig() })\n  } catch (error) {\n    wrapGithubConnectorError(error)\n  }\n  const state = createGithubInstallStateToken({\n    orgId: input.context.organizationContext.organization.id,\n    returnPath,\n    secret: env.betterAuthSecret,\n    userId: input.context.organizationContext.currentMember.userId,\n  })\n\n  return {\n    redirectUrl: buildGithubAppInstallUrl({ app, state }),\n    state,","sourceCodeStart":6853,"sourceCodeEnd":6889,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-api/src/routes/org/plugin-system/store.ts#L6853-L6889","documentation":"Thrown (400) by startGithubConnectorInstall when `returnPath` does not start with `/` or starts with `//`. The return path is used to redirect the user back after the GitHub OAuth/app-install round trip, so it must be a safe relative in-app path — absolute URLs or protocol-relative paths would enable open-redirect.","triggerScenarios":"Calling startGithubConnectorInstall with returnPath like \"https://evil.com\", \"//evil.com\", \"\" (empty after trim), or a path missing the leading slash (\"dashboard/connectors\").","commonSituations":"Frontend builds the return path by string concatenation and drops the leading slash; passing a full URL from window.location instead of pathname; SSRF/open-redirect guards in other tools sending absolute URLs.","solutions":["Pass a root-relative path that begins with a single `/`, e.g. `/org/settings/connectors`.","On the client, derive the value from `new URL(window.location.href).pathname + search` rather than the full href.","Sanitize any stored/deep-link return path before starting the install (strip scheme/host, ensure leading single slash)."],"exampleFix":"// before\nawait startGithubConnectorInstall({ returnPath: window.location.href }) // 'https://host/org/...' -> invalid_return_path\n// after\nconst u = new URL(window.location.href)\nawait startGithubConnectorInstall({ returnPath: u.pathname + u.search }) // '/org/settings/connectors?x=1'","handlingStrategy":"validation","validationCode":"function safeReturnPath(p: string): string {\n  const t = p.trim()\n  if (!t.startsWith('/') || t.startsWith('//')) throw new Error(`returnPath must be a relative path starting with a single '/', got: ${p}`)\n  return t\n}","typeGuard":"function isSafeRelativePath(p: string): boolean { return p.startsWith('/') && !p.startsWith('//') }","tryCatchPattern":"try {\n  await startGithubConnectorInstall({ returnPath })\n} catch (e) {\n  if (e instanceof PluginArchRouteFailure && e.code === 'invalid_return_path') {\n    await startGithubConnectorInstall({ returnPath: '/org/settings/connectors' })\n  } else throw e\n}","preventionTips":["Derive return paths from location.pathname, never location.href","Centralize return-path construction in one sanitized helper","Add a unit test asserting no scheme or protocol-relative forms reach the API"],"tags":["security","open-redirect","github","validation"],"backgroundTag":"open-redirect-prevention","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}