different-ai/openwork · warning · PluginArchRouteFailure

invalid_return_path

invalid_return_path

Error message

GitHub install return path must be a safe relative path.

What it means

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.

Source

Thrown at ee/apps/den-api/src/routes/org/plugin-system/store.ts:6871

  await db.update(ConnectorAccountTable).set({
    displayName: installation.displayName,
    externalAccountRef: installation.accountLogin,
    metadataJson: {
      ...(existingRows[0].metadataJson ?? {}),
      ...metadata,
    },
    status: "active",
    updatedAt: new Date(),
  }).where(eq(ConnectorAccountTable.id, existingRows[0].id))

  return getConnectorAccountDetail(input.context, existingRows[0].id)
}

export async function startGithubConnectorInstall(input: { context: PluginArchActorContext; returnPath: string }) {
  const returnPath = input.returnPath.trim()
  if (!returnPath.startsWith("/") || returnPath.startsWith("//")) {
    throw new PluginArchRouteFailure(400, "invalid_return_path", "GitHub install return path must be a safe relative path.")
  }

  let app: Awaited<ReturnType<typeof getGithubAppSummary>>
  try {
    app = await getGithubAppSummary({ config: githubConnectorAppConfig() })
  } catch (error) {
    wrapGithubConnectorError(error)
  }
  const state = createGithubInstallStateToken({
    orgId: input.context.organizationContext.organization.id,
    returnPath,
    secret: env.betterAuthSecret,
    userId: input.context.organizationContext.currentMember.userId,
  })

  return {
    redirectUrl: buildGithubAppInstallUrl({ app, state }),
    state,

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Pass a root-relative path that begins with a single `/`, e.g. `/org/settings/connectors`.
  2. On the client, derive the value from `new URL(window.location.href).pathname + search` rather than the full href.
  3. Sanitize any stored/deep-link return path before starting the install (strip scheme/host, ensure leading single slash).

Example fix

// before
await startGithubConnectorInstall({ returnPath: window.location.href }) // 'https://host/org/...' -> invalid_return_path
// after
const u = new URL(window.location.href)
await startGithubConnectorInstall({ returnPath: u.pathname + u.search }) // '/org/settings/connectors?x=1'
Defensive patterns

Strategy: validation

Validate before calling

function safeReturnPath(p: string): string {
  const t = p.trim()
  if (!t.startsWith('/') || t.startsWith('//')) throw new Error(`returnPath must be a relative path starting with a single '/', got: ${p}`)
  return t
}

Type guard

function isSafeRelativePath(p: string): boolean { return p.startsWith('/') && !p.startsWith('//') }

Try / catch

try {
  await startGithubConnectorInstall({ returnPath })
} catch (e) {
  if (e instanceof PluginArchRouteFailure && e.code === 'invalid_return_path') {
    await startGithubConnectorInstall({ returnPath: '/org/settings/connectors' })
  } else throw e
}

Prevention

When it happens

Trigger: Calling startGithubConnectorInstall with returnPath like "https://evil.com", "//evil.com", "" (empty after trim), or a path missing the leading slash ("dashboard/connectors").

Common situations: 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.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/c8fecbdd928e11eb. Report an issue: GitHub.