Skip to content

fix(kap-server): close auth bypass via percent-encoded API paths - #1753

Merged
sailist merged 3 commits into
MoonshotAI:mainfrom
sailist:fix-kap-server-auth-encoded-path
Jul 15, 2026
Merged

fix(kap-server): close auth bypass via percent-encoded API paths#1753
sailist merged 3 commits into
MoonshotAI:mainfrom
sailist:fix-kap-server-auth-encoded-path

Conversation

@sailist

@sailist sailist commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Related Issue

No linked issue — the problem is explained below.

Problem

The kap-server auth hook decided whether a request could skip bearer-token authentication by inspecting req.url — the raw, still percent-encoded URL. The router (find-my-way), however, matches routes against the decoded path. A request to /%61pi/v1/sessions (percent-encoded /api/v1/sessions) therefore failed the hook's /api/ prefix check, skipped authentication, yet still matched the /api/v1/sessions route — giving unauthenticated clients access to every API route.

What changed

  • packages/kap-server/src/middleware/auth.ts: decode the request path (query string stripped) with decodeURIComponent before applying the bypass policy, so the auth decision sees the same path the router matches against. If the path cannot be decoded, fail closed — the request is never treated as bypassed.
  • packages/kap-server/test/rateLimit.test.ts: regression tests covering percent-encoded /api/ paths (401 without a token, 200 with a valid token), a percent-encoded meta document, the percent-encoded healthz probe (still bypassed), and non-API paths (still bypassed).
  • Added a patch changeset for @moonshot-ai/kimi-code.

Keeping the decode at the single point where the bypass policy is evaluated aligns the auth decision with routing semantics, instead of special-casing encoded variants in every bypass rule.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

The auth hook checked the raw, still percent-encoded request URL against
its bypass policy, while find-my-way matches routes against the decoded
path. A raw /%61pi/v1/... URL therefore skipped the bearer-token check
yet still reached the /api/... handlers, allowing unauthenticated access
to every API route.

Decode the request path the same way the router does before matching,
and fail closed when the path cannot be decoded. Add tests covering
encoded /api/ paths, encoded meta documents, the encoded healthz probe,
and unaffected non-API bypasses.
@changeset-bot

changeset-bot Bot commented Jul 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7477fde

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jul 15, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@moonshot-ai/kimi-code@7477fde
npx https://pkg.pr.new/@moonshot-ai/kimi-code@7477fde

commit: 7477fde

sailist added 2 commits July 15, 2026 21:18
SessionFsService confined paths only lexically, so a symlink planted
inside the workspace could steer read, list, stat, mkdir, resolvePath
and resolveDownload to host files outside the session directory.

- add IHostFileSystem.realpath (Node fs.realpath semantics) and
  implement it in the node-local HostFileSystem
- resolveWithin now re-verifies each candidate through realpath of the
  longest existing prefix (so not-yet-created paths still work) and
  rejects paths escaping the canonicalized workspace roots, which are
  resolved once and cached
- cover symlink escapes for every fs entry point plus kap-server
  read/download e2e; in-workspace symlink targets remain allowed
WorkspaceRegistryService.createOrTouch probes the root with the
lstat-based IHostFileSystem.stat, so a root given as a symlink to a
directory reported isDirectory=false and session creation was rejected
with fs.path_not_found ("not a directory").

Re-check a non-directory root through IHostFileSystem.realpath before
failing, while keeping the workspace identity lexical (v1 deliberately
never realpaths the root either). Covered by a unit test for a
symlink-form root and a kap-server e2e that creates a session with a
symlinked cwd and reads a file through it.
@sailist
sailist merged commit d8ddabb into MoonshotAI:main Jul 15, 2026
14 checks passed
@github-actions github-actions Bot mentioned this pull request Jul 15, 2026
7723qqq pushed a commit to 7723qqq/kimi-code that referenced this pull request Jul 15, 2026
…nshotAI#1753)

* fix(kap-server): fail closed on percent-encoded API paths in auth bypass

The auth hook checked the raw, still percent-encoded request URL against
its bypass policy, while find-my-way matches routes against the decoded
path. A raw /%61pi/v1/... URL therefore skipped the bearer-token check
yet still reached the /api/... handlers, allowing unauthenticated access
to every API route.

Decode the request path the same way the router does before matching,
and fail closed when the path cannot be decoded. Add tests covering
encoded /api/ paths, encoded meta documents, the encoded healthz probe,
and unaffected non-API bypasses.

* fix(agent-core-v2): make session fs path confinement symlink-aware

SessionFsService confined paths only lexically, so a symlink planted
inside the workspace could steer read, list, stat, mkdir, resolvePath
and resolveDownload to host files outside the session directory.

- add IHostFileSystem.realpath (Node fs.realpath semantics) and
  implement it in the node-local HostFileSystem
- resolveWithin now re-verifies each candidate through realpath of the
  longest existing prefix (so not-yet-created paths still work) and
  rejects paths escaping the canonicalized workspace roots, which are
  resolved once and cached
- cover symlink escapes for every fs entry point plus kap-server
  read/download e2e; in-workspace symlink targets remain allowed

* fix(agent-core-v2): accept workspace roots given through a symlink

WorkspaceRegistryService.createOrTouch probes the root with the
lstat-based IHostFileSystem.stat, so a root given as a symlink to a
directory reported isDirectory=false and session creation was rejected
with fs.path_not_found ("not a directory").

Re-check a non-directory root through IHostFileSystem.realpath before
failing, while keeping the workspace identity lexical (v1 deliberately
never realpaths the root either). Covered by a unit test for a
symlink-form root and a kap-server e2e that creates a session with a
symlinked cwd and reads a file through it.
7723qqq added a commit to 7723qqq/kimi-code that referenced this pull request Jul 15, 2026
…oonshotAI#1676 MoonshotAI#1625 MoonshotAI#1746 MoonshotAI#1753 MoonshotAI#1757)

Critical fixes cherry-picked from upstream v0.24.x:
- fix(agent-core-v2): align rate-limit retries with v1 (MoonshotAI#1598)
- fix: preserve empty reasoning across providers (MoonshotAI#1676)
- fix: resolve and synchronize thinking effort (MoonshotAI#1625)
- fix: align Anthropic-compatible model capabilities (MoonshotAI#1746)
- fix(kap-server): close auth bypass via percent-encoded API paths (MoonshotAI#1753)
- fix: preserve the crash error in diagnostic logs on unexpected exit (MoonshotAI#1757)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant