Skip to content

Fix allowed_local_media_path psirt#4199

Merged
michalkulakowski merged 14 commits into
mainfrom
mkulakow/allowed_local_media_path_fix
May 14, 2026
Merged

Fix allowed_local_media_path psirt#4199
michalkulakowski merged 14 commits into
mainfrom
mkulakow/allowed_local_media_path_fix

Conversation

@michalkulakowski
Copy link
Copy Markdown
Collaborator

🛠 Summary

JIRA/Issue if applicable.
Describe the changes.

🧪 Checklist

  • Unit tests added.
  • The documentation updated.
  • Change follows security best practices.
    ``

Copilot AI review requested due to automatic review settings May 11, 2026 11:03
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens allowed_local_media_path validation for OpenAI image inputs to prevent prefix-based bypasses (e.g., /allowed_path_private/... being treated as within /allowed_path), and adds a regression test for the bypass case.

Changes:

  • Add a new unit test covering a prefix/sibling-path bypass attempt for local filesystem image URLs.
  • Normalize allowed_local_media_path before doing the prefix comparison in loadImage() to ensure directory-boundary enforcement.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/test/http_openai_handler_test.cpp Adds a regression test ensuring sibling-prefix paths are rejected.
src/llm/apis/openai_api_handler.cpp Introduces path normalization for allowed_local_media_path prior to prefix checking.

Comment thread src/llm/apis/openai_api_handler.cpp Outdated
Comment on lines +51 to +59
std::string normalizeAllowedLocalMediaPath(const std::string& allowedLocalMediaPath) {
if (allowedLocalMediaPath.empty()) {
return allowedLocalMediaPath;
}
const char lastCharacter = allowedLocalMediaPath.back();
if (lastCharacter == '/' || lastCharacter == '\\') {
return allowedLocalMediaPath;
}
return allowedLocalMediaPath + FileSystem::getOsSeparator();
Comment thread src/llm/apis/openai_api_handler.cpp Outdated
Comment on lines 252 to 255
const auto normalizedAllowedLocalMediaPath = normalizeAllowedLocalMediaPath(allowedLocalMediaPath.value());
const auto firstMissmatch = std::mismatch(imageSource.begin(), imageSource.end(), normalizedAllowedLocalMediaPath.begin(), normalizedAllowedLocalMediaPath.end());
if (firstMissmatch.second != normalizedAllowedLocalMediaPath.end()) {
return absl::InvalidArgumentError("Given filepath is not subpath of allowed_local_media_path");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Good point.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

Comment thread src/llm/apis/openai_api_handler.cpp Outdated
Comment on lines +51 to +66
std::string normalizePathSeparators(const std::string& path) {
std::string normalizedPath = path;
std::replace(normalizedPath.begin(), normalizedPath.end(), '\\', '/');
return normalizedPath;
}

std::string normalizeAllowedLocalMediaPath(const std::string& allowedLocalMediaPath) {
std::string normalizedAllowedLocalMediaPath = normalizePathSeparators(allowedLocalMediaPath);
if (normalizedAllowedLocalMediaPath.empty()) {
return normalizedAllowedLocalMediaPath;
}
if (normalizedAllowedLocalMediaPath.back() == '/') {
return normalizedAllowedLocalMediaPath;
}
return normalizedAllowedLocalMediaPath + '/';
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Good point.

Comment thread src/llm/apis/openai_api_handler.cpp Outdated
Comment on lines 258 to 263
const auto normalizedAllowedLocalMediaPath = normalizeAllowedLocalMediaPath(allowedLocalMediaPath.value());
const auto normalizedImageSource = normalizePathSeparators(imageSource);
const auto firstMismatch = std::mismatch(normalizedImageSource.begin(), normalizedImageSource.end(), normalizedAllowedLocalMediaPath.begin(), normalizedAllowedLocalMediaPath.end());
if (firstMismatch.second != normalizedAllowedLocalMediaPath.end()) {
return absl::InvalidArgumentError("Given filepath is not subpath of allowed_local_media_path");
}
Comment thread src/llm/apis/openai_api_handler.cpp Outdated
Comment on lines 260 to 263
const auto firstMismatch = std::mismatch(normalizedImageSource.begin(), normalizedImageSource.end(), normalizedAllowedLocalMediaPath.begin(), normalizedAllowedLocalMediaPath.end());
if (firstMismatch.second != normalizedAllowedLocalMediaPath.end()) {
return absl::InvalidArgumentError("Given filepath is not subpath of allowed_local_media_path");
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we add ut for that?

@dtrawins dtrawins added this to the 2026.2_rc milestone May 13, 2026
@michalkulakowski michalkulakowski requested a review from rasapala May 13, 2026 11:58
@rasapala rasapala requested a review from Copilot May 14, 2026 07:10
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Comment thread src/llm/apis/openai_api_handler.cpp Outdated
Comment on lines +281 to +286
const auto imagePath = std::filesystem::path(normalizePathSeparators(imageSource));
if (imagePath.is_relative()) {
return absl::InvalidArgumentError("Relative paths are not allowed for local filesystem access");
}
const auto resolvedAllowedPath = normalizeAndResolvePath(normalizedAllowedLocalMediaPath);
const auto resolvedImagePath = normalizeAndResolvePath(imageSource);
Comment thread src/test/http_openai_handler_test.cpp Outdated

TEST_F(HttpOpenAIHandlerParsingTest, ParsingMessagesImageLocalFilesystemInvalidPath) {
const std::string allowedPath = getGenericFullPathForSrcTest("/ovms/src/test/");
const std::string imageUrl = getGenericFullPathForSrcTest("/ovms/src/test/not_exisiting.jpeg");
Comment thread src/llm/apis/openai_api_handler.cpp Outdated
Comment on lines +52 to +80
std::string normalizePathSeparators(const std::string& path) {
std::string normalizedPath = path;
std::replace(normalizedPath.begin(), normalizedPath.end(), '\\', '/');
return normalizedPath;
}

std::string normalizeAllowedLocalMediaPath(const std::string& allowedLocalMediaPath) {
std::string normalizedAllowedLocalMediaPath = normalizePathSeparators(allowedLocalMediaPath);
if (normalizedAllowedLocalMediaPath.empty()) {
return normalizedAllowedLocalMediaPath;
}
if (normalizedAllowedLocalMediaPath.back() == '/') {
return normalizedAllowedLocalMediaPath;
}
return normalizedAllowedLocalMediaPath + '/';
}

std::filesystem::path normalizeAndResolvePath(const std::string& pathString) {
std::filesystem::path path = normalizePathSeparators(pathString);
if (path.is_relative()) {
path = std::filesystem::current_path() / path;
}
path = path.lexically_normal();
std::error_code ec;
auto weakCanonicalPath = std::filesystem::weakly_canonical(path, ec);
if (!ec) {
return weakCanonicalPath.lexically_normal();
}
return path;
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comment on lines +2175 to +2176
std::string mixedSeparatorAllowedPath = getGenericFullPathForSrcTest("/ovms/src/test/binaryutils");
std::replace(mixedSeparatorAllowedPath.begin(), mixedSeparatorAllowedPath.end(), '/', '\\');
Comment on lines +252 to +255
const std::filesystem::path resolvedAllowedPath = FileSystem::normalizeConfiguredPath(allowedLocalMediaPath.value());
const std::string resolvedImagePathStr = FileSystem::normalizeConfiguredPath(imageSource);
const std::filesystem::path resolvedImagePath = resolvedImagePathStr;
if (!isPathInsideDirectory(resolvedImagePath, resolvedAllowedPath)) {
@michalkulakowski michalkulakowski requested a review from Copilot May 14, 2026 09:15
Michal Kulakowski added 2 commits May 14, 2026 11:19
@michalkulakowski michalkulakowski merged commit 2569d17 into main May 14, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants