Skip to content

fix: handle url encoded file name in url params for single file serving endpoint - #4495

Merged
akshaydeo merged 2 commits into
devfrom
fix/06-17-fix_handle_url_encoded_file_name_in_url_params_for_single_file_serving_endpoint
Jun 19, 2026
Merged

fix: handle url encoded file name in url params for single file serving endpoint#4495
akshaydeo merged 2 commits into
devfrom
fix/06-17-fix_handle_url_encoded_file_name_in_url_params_for_single_file_serving_endpoint

Conversation

@danpiths

Copy link
Copy Markdown
Collaborator

Summary

URL-encoded path parameters (e.g., file paths containing spaces like
nested dir/file with spaces.txt) were not being decoded before use, causing
file serving requests with percent-encoded characters to fail. This PR
introduces a shared helper that centralises path parameter extraction and adds
proper url.PathUnescape decoding.

Changes

  • Introduced decodeStringPathParam helper that extracts a named path parameter
    from a fasthttp.RequestCtx, validates it, and URL-decodes it via
    url.PathUnescape before returning it to the caller.
  • Replaced duplicated inline path-param extraction logic in doServeFileContent
    and lookupSkillByPathParam with calls to the new helper.
  • Added an integration test
    (TestSkillsServingGenericFileDownloadDecodesEncodedPathParams) that creates
    a skill with a file at a path containing spaces, issues a request with a
    percent-encoded URI, and asserts the file content is returned with HTTP 200.

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (React)
  • Docs

How to test

go test ./transports/bifrost-http/handlers/...

The new test TestSkillsServingGenericFileDownloadDecodesEncodedPathParams
exercises the fix end-to-end by serving a file whose path contains spaces via a
percent-encoded URL and asserting the correct body and status code are returned.

Screenshots/Recordings

N/A

Breaking changes

  • Yes
  • No

Related issues

N/A

Security considerations

url.PathUnescape is used rather than url.QueryUnescape to avoid
misinterpreting + as a space in file paths. Decoded paths are validated to be
non-empty before use, preventing empty-string path traversal edge cases.

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

roroghost17 and others added 2 commits June 17, 2026 15:45
## Summary

Fixes a bug where OTEL plugin headers were being overwritten with redacted placeholder values when saving a plugin configuration. After the multi-profile change, header values stored as plain strings inside the `profiles` array were not being restored from the database before saving, causing real credentials to be replaced with masked values like `****`.

## Changes

- Extracted `restoreRedactedValue` as a standalone recursive helper, replacing the inline logic in `restoreRedactedFromExisting`. This allows the restoration logic to descend into both nested maps and slices.
- Added slice traversal support (index-aligned) so that elements within arrays like the OTEL `profiles` array are individually checked and restored.
- Added plain-string redaction detection so that header values stored as raw strings (rather than `EnvVar` objects) are also restored from the existing DB config when they carry a redaction artifact. Empty strings are intentionally left as-is to allow clearing a value.
- Added `TestRestoreRedacted_OTELProfilesHeaders` to cover both failure modes: slice traversal and plain-string secret restoration. Also asserts that genuinely new (non-redacted) values pass through unchanged.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [x] Transports (HTTP)
- [ ] Providers/Integrations
- [x] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./transports/bifrost-http/handlers/...
```

Verify that saving an OTEL plugin configuration with multiple profiles, after a GET that returns redacted header values, does not overwrite the stored credentials in the database. Confirm that providing a genuinely new header value still persists correctly.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

N/A

## Security considerations

This fix ensures that redacted credential placeholders returned to the client are never written back over real secrets stored in the database. The restoration logic only replaces values that are confirmed redaction artifacts; empty strings and non-redacted values are always passed through as-is, preserving the ability to clear a credential intentionally.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
@danpiths
danpiths requested a review from akshaydeo June 17, 2026 12:48
@CLAassistant

CLAassistant commented Jun 17, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ roroghost17
❌ danpiths
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced redacted field restoration in plugin configurations to support nested structures, arrays, and plain-string redacted values
    • Improved URL-encoded character handling and validation for file path parameters with consistent error messaging
  • Tests

    • Added coverage for OTEL credential and header restoration within plugin profile arrays
    • Added coverage for URL-encoded file path parameter decoding

Walkthrough

Two independent handler improvements: (1) restoreRedactedFromExisting in plugins.go is rewritten to recursively restore redacted values across EnvVar objects, nested maps, and index-aligned slices, with a new restoreRedactedValue helper and a matching test for OTEL profile header restoration; (2) a new decodeStringPathParam helper centralizes URL-decoding and validation for route parameters in skills_serving.go, applied to both filepath and skill-name parameters, verified by a new end-to-end test.

Changes

Plugin Redaction Restore

Layer / File(s) Summary
restoreRedactedFromExisting + restoreRedactedValue implementation and test
transports/bifrost-http/handlers/plugins.go, transports/bifrost-http/handlers/plugins_test.go
restoreRedactedFromExisting delegates all per-value logic to a new restoreRedactedValue helper that recursively handles EnvVar objects, nested maps, index-aligned slice restoration, and plain-string redaction artifact replacement. The test validates restoration inside a profiles array with mixed redacted, unredacted, and env.* reference header values.

Skills Path Parameter Decoding

Layer / File(s) Summary
decodeStringPathParam helper, call sites, and integration test
transports/bifrost-http/handlers/skills_serving.go, transports/bifrost-http/handlers/skills_serving_test.go
New decodeStringPathParam centralizes presence/type/empty checking and url.PathUnescape for route parameters; doServeFileContent and lookupSkillByPathParam are updated to call it. The test verifies a URL-encoded file path (containing spaces) is correctly decoded and serves the expected blob content with HTTP 200.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • maximhq/bifrost#3651: Introduced the original EnvVar redaction/expansion workflow (MarshalConfigForStorage, RedactConfig) that restoreRedactedFromExisting now more completely handles.
  • maximhq/bifrost#4486: Directly overlaps with the same restoreRedactedFromExisting redaction-restore fix and OTEL profiles test additions.

Suggested reviewers

  • akshaydeo
  • roroghost17

Poem

🐰 Hop hop, the redacted fields are found,
No placeholder strings shall stick around!
Through maps and slices I scurry with care,
URL-encoded paths? I'll decode them with flair.
The rabbit restores what was hidden before —
Every secret and space, stored safe evermore! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the bug fix: handling URL-encoded file names in URL parameters for the single file serving endpoint.
Description check ✅ Passed The description comprehensively follows the template with all major sections completed: Summary, Changes, Type of change, Affected areas, How to test, Breaking changes, Related issues, Security considerations, and Checklist.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/06-17-fix_handle_url_encoded_file_name_in_url_params_for_single_file_serving_endpoint

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai
coderabbitai Bot requested a review from roroghost17 June 17, 2026 12:49

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
transports/bifrost-http/handlers/plugins_test.go (1)

76-84: 💤 Low value

Orphaned doc comment for TestUpdatePlugin_ConfigMerge.

Lines 76-79 describe TestUpdatePlugin_ConfigMerge, but the new TestRestoreRedacted_OTELProfilesHeaders comment block (lines 80-84) was inserted between the doc comment and the test it now documents. The doc comment for TestUpdatePlugin_ConfigMerge should be moved to directly precede that function (line 143).

📝 Suggested fix

Move lines 76-79 to directly before TestUpdatePlugin_ConfigMerge at line 143:

-// TestUpdatePlugin_ConfigMerge verifies that updatePlugin merges the incoming
-// config over the existing DB config, preserving fields the caller did not send.
-// This is critical for the plugin_span_filter field: the OTEL config form in the
-// UI does not send plugin_span_filter, so it must survive a save without being wiped.
 // TestRestoreRedacted_OTELProfilesHeaders covers the two gaps that broke OTEL header
 // round-trips after the multi-profile change: (1) headers live inside the `profiles`
 // array (slice traversal), and (2) header values are plain redacted strings, not EnvVar
 // objects. Saving a config whose headers came back redacted must not overwrite the
 // stored credentials.
 func TestRestoreRedacted_OTELProfilesHeaders(t *testing.T) {

And before line 143:

+// TestUpdatePlugin_ConfigMerge verifies that updatePlugin merges the incoming
+// config over the existing DB config, preserving fields the caller did not send.
+// This is critical for the plugin_span_filter field: the OTEL config form in the
+// UI does not send plugin_span_filter, so it must survive a save without being wiped.
 func TestUpdatePlugin_ConfigMerge(t *testing.T) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@transports/bifrost-http/handlers/plugins_test.go` around lines 76 - 84, The
doc comment for the TestUpdatePlugin_ConfigMerge function (lines 76-79) is
currently orphaned because the TestRestoreRedacted_OTELProfilesHeaders comment
block was inserted between it and the actual TestUpdatePlugin_ConfigMerge
function definition. Move the TestUpdatePlugin_ConfigMerge comment block to
directly precede the TestUpdatePlugin_ConfigMerge function at line 143, ensuring
each test function has its doc comment immediately above it without any other
comments in between.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@transports/bifrost-http/handlers/plugins_test.go`:
- Around line 76-84: The doc comment for the TestUpdatePlugin_ConfigMerge
function (lines 76-79) is currently orphaned because the
TestRestoreRedacted_OTELProfilesHeaders comment block was inserted between it
and the actual TestUpdatePlugin_ConfigMerge function definition. Move the
TestUpdatePlugin_ConfigMerge comment block to directly precede the
TestUpdatePlugin_ConfigMerge function at line 143, ensuring each test function
has its doc comment immediately above it without any other comments in between.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: dd0d3f7c-3a7e-4cf1-9fd7-6013d7f78dda

📥 Commits

Reviewing files that changed from the base of the PR and between 8806020 and b56f3f7.

📒 Files selected for processing (4)
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/plugins_test.go
  • transports/bifrost-http/handlers/skills_serving.go
  • transports/bifrost-http/handlers/skills_serving_test.go

@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 4/5

The file-serving fix is correct and the integration test confirms end-to-end behaviour; the bundled plugins.go refactor is well-tested. The one gap is servePluginGit, which still uses the undecoded skill name, leaving git-endpoint access inconsistent for skills with spaces in their names.

Both changed code paths are correct and covered by tests. The servePluginGit handler was not updated alongside the other skill-name extraction sites, so percent-encoded skill names on the git smart-HTTP routes will still fail — a real but narrowly-scoped inconsistency. The plugins.go slice-restoration fix is correct though unrelated to the stated PR scope.

transports/bifrost-http/handlers/skills_serving.go — specifically the servePluginGit handler at the inline skill-name extraction block that was not converted to decodeStringPathParam.

Important Files Changed

Filename Overview
transports/bifrost-http/handlers/skills_serving.go Introduces decodeStringPathParam helper and applies it to doServeFileContent and lookupSkillByPathParam; the fix is correct and path traversal is safe because file lookups are exact-match against DB-stored paths that were already validated at creation time.
transports/bifrost-http/handlers/skills_serving_test.go New integration test exercises the full request path for a percent-encoded filepath and passes; does not cover a skill name with spaces (the other callsite refactored to decodeStringPathParam).
transports/bifrost-http/handlers/plugins.go Refactors restoreRedactedFromExisting to recurse through slices and handle plain-string redacted values (OTEL profiles headers). Logic is correct; this is a separate fix bundled with the URL-encoding PR.
transports/bifrost-http/handlers/plugins_test.go Adds TestRestoreRedacted_OTELProfilesHeaders covering slice traversal and plain-string restoration; the three sub-cases (masked, changed, env-ref) are thorough.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["GET /api/skills/serve/{skill-name}/files/{filepath:*}"] --> B["lookupSkillByPathParam"]
    B --> C["decodeStringPathParam(ctx, 'skill-name', ...)"]
    C --> D{val nil?}
    D -- yes --> E["400 Bad Request"]
    D -- no --> F["url.PathUnescape(raw)"]
    F --> G{error or empty?}
    G -- yes --> E
    G -- no --> H["store.GetSkillByName(decoded)"]
    H --> I["doServeFileContent"]
    I --> J["decodeStringPathParam(ctx, 'filepath', ...)"]
    J --> K["url.PathUnescape(raw filepath)"]
    K --> L["Match f.NormalizedPath() == decoded"]
    L -- found --> M["serveSkillFile → HTTP 200"]
    L -- not found --> N["404 Not Found"]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["GET /api/skills/serve/{skill-name}/files/{filepath:*}"] --> B["lookupSkillByPathParam"]
    B --> C["decodeStringPathParam(ctx, 'skill-name', ...)"]
    C --> D{val nil?}
    D -- yes --> E["400 Bad Request"]
    D -- no --> F["url.PathUnescape(raw)"]
    F --> G{error or empty?}
    G -- yes --> E
    G -- no --> H["store.GetSkillByName(decoded)"]
    H --> I["doServeFileContent"]
    I --> J["decodeStringPathParam(ctx, 'filepath', ...)"]
    J --> K["url.PathUnescape(raw filepath)"]
    K --> L["Match f.NormalizedPath() == decoded"]
    L -- found --> M["serveSkillFile → HTTP 200"]
    L -- not found --> N["404 Not Found"]
Loading

Comments Outside Diff (1)

  1. transports/bifrost-http/handlers/skills_serving.go, line 560-568 (link)

    P2 servePluginGit skipped the decodeStringPathParam refactor

    The git smart-HTTP handler still extracts skill-name with the old inline pattern (no URL-decoding). If a skill whose name contains a space is accessed via the git endpoint — e.g. GET /api/skills/serve/claudecode/plugins/bifrost-my%20skill/info/refs — the raw string bifrost-my%20skill is used to strip the bifrost- prefix and look up the skill, producing my%20skill instead of my skill, and the store lookup fails with 404. The lookupSkillByPathParam path was fixed, but this parallel extraction site was not.

Reviews (1): Last reviewed commit: "fix: handle url encoded file name in url..." | Re-trigger Greptile

Comment thread transports/bifrost-http/handlers/skills_serving_test.go

akshaydeo commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Merge activity

  • Jun 19, 6:55 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jun 19, 6:56 AM UTC: @akshaydeo merged this pull request with Graphite.

@akshaydeo
akshaydeo merged commit 50e0b53 into dev Jun 19, 2026
17 of 18 checks passed
@akshaydeo
akshaydeo deleted the fix/06-17-fix_handle_url_encoded_file_name_in_url_params_for_single_file_serving_endpoint branch June 19, 2026 06:56
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.

4 participants