Skip to content

fix(gateway): escape path params and validate before vault access - #44

Merged
shreyanshjain7174 merged 2 commits into
mainfrom
fix/escape-path-params
Oct 17, 2026
Merged

shreyanshjain7174 merged 2 commits into
mainfrom
fix/escape-path-params

Conversation

@shreyanshjain7174

Copy link
Copy Markdown
Contributor

Plan

Security fix found while reviewing #43. buildURL put agent-controlled params into path templates without escaping.

Impact before this fix:

  • An agent could send {"repo": "../../admin/users"} or values with ? or #. That reached other endpoints on the SaaS host with the vault-injected credential attached.
  • A value containing another placeholder, such as {owner}, could be substituted again, depending on map iteration order.
  • Bad params were only checked after the vault fetch and token refresh.

What changed

  • buildURL scans the template once, left to right. Each placeholder is replaced once, never re-scanned.

  • Values are escaped with url.PathEscape as single path segments.

  • Rejected with 400 bad_request and a receipt:

    • missing params and empty values;
    • . and ..;
    • objects, arrays, null and non-integer numbers.

    Large integers keep their exact text.

  • After building, the final URL must keep the base URL's scheme and host.

  • Templates with a placeholder after ? fail at registry load, and buildURL rejects them too. PathEscape is wrong for query values.

  • URL building now runs right after the action lookup, before any vault access. Bad params never trigger an OAuth refresh. An invalid base_url is an operator error: 500 internal, with a receipt.

  • README and docs/registry.md say path params are escaped as single segments.

Validation

  • go build ./...: passed
  • go vet ./...: passed
  • go test -race ./...: passed
  • semgrep --config auto --error --quiet internal/gateway internal/registry: passed, no findings

Tests cover:

  • traversal, slash, query and fragment characters;
  • %2e%2e (sent as %252e%252e), checked on the raw request line at the upstream;
  • placeholder-in-value, run 100 times;
  • missing params, wrong types, large ints and unicode;
  • query placeholders rejected at load;
  • zero vault and refresh calls for bad params;
  • all 39 shipped connector templates.

Review

Reviewed by a second model (Claude Opus 5.5). Approved. Its 2 minors (query placeholders, check order) are fixed here. The third, the same old code in unused internal/proxy, goes away in the dead-code cleanup PR.

Compatibility

A custom connector that passed slashes inside one param must move the separators into its template. No shipped connector does this.

shreyanshjain7174 and others added 2 commits October 18, 2026 04:10
Escape each path value once to prevent credentialed endpoint traversal.
Reject missing or invalid segments and record denied attempts.

Signed-off-by: Shreyansh Sancheti <43677304+shreyanshjain7174@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reject query placeholders before loading registry actions.
Validate URLs before vault access to avoid refresh side effects.
Keep invalid base URLs separate from agent input errors.

Signed-off-by: Shreyansh Sancheti <43677304+shreyanshjain7174@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@shreyanshjain7174
shreyanshjain7174 merged commit 6138192 into main Oct 17, 2026
1 check passed
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