Skip to content

Resolve password-store's store_dir relative to file declaring#359

Open
1fexd wants to merge 8 commits intojdx:mainfrom
1fexd:relative-store-dir
Open

Resolve password-store's store_dir relative to file declaring#359
1fexd wants to merge 8 commits intojdx:mainfrom
1fexd:relative-store-dir

Conversation

@1fexd
Copy link
Copy Markdown

@1fexd 1fexd commented Mar 15, 2026

Hello,

I've run into an issue where declaring a password-store provider in a base config file with a relative store_dir path results in an error when the provider is used in a child directory, as the path is not resolved relative to the base config file, but apparently relative to the current directory?

Consider the following example, taken from the documentation, and adapted:

project/
├── fnox.toml              # Root config
├── password-store   # Password store directory
└── services/
    └── worker/
        └── fnox.toml      # Worker config

project/fnox.toml:

[providers.pass]
type = "password-store"
store_dir = "./password-store"

project/services/worker/fnox.toml:

[secrets]
API_KEY = { provider = "pass", value = "api-key" }

Running fnox get API_KEY in project/services/worker results in:

Error: fnox::provider::secret_not_found (link)

  x password-store: secret 'api-key' not found
  help: The secret 'api-key' does not exist in password-store.
        Check that the secret exists in your password store

This PR implements a PoC (I'm not familiar with Rust, so you'd probably have to rework the code) which works around this issue by resolving the store_dir relative to the project directory.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical path resolution bug affecting password-store providers. Previously, if a password-store provider was defined with a relative store_dir in a parent configuration file, and then referenced from a configuration in a subdirectory, the store_dir would not resolve correctly, leading to "secret not found" errors. The changes introduce a mechanism to correctly resolve these relative paths by considering the location of the configuration file where the provider is declared, ensuring consistent behavior across hierarchical configurations.

Highlights

  • Problem Addressed: Fixed an issue where password-store provider's store_dir was not resolved correctly when declared with a relative path in a base config file and used in a child directory.
  • Path Resolution Logic: Implemented a new join_path function to resolve relative store_dir paths against the declaring config file's location.
  • Provider Integration: Modified get_provider_resolved to utilize the new join_path function specifically for password-store providers.
  • Testing: Added a unit test for the join_path function to ensure correct behavior.
Changelog
  • src/providers/mod.rs
    • Imported std::path::PathBuf.
    • Modified get_provider_resolved to include specific logic for PasswordStore providers, calling join_path to resolve store_dir.
    • Added a new public function join_path to handle the resolution of relative paths based on a provider_source.
    • Included a unit test for the join_path function.
Activity
  • No human activity has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@1fexd 1fexd force-pushed the relative-store-dir branch from 33ba3b3 to 2bbe2ea Compare March 15, 2026 16:54
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a fix for resolving relative store_dir paths in the password-store provider. The approach of intercepting the resolved configuration and adjusting the path is a good proof-of-concept. My review includes a few suggestions to improve the implementation: a fix for a potential bug in path joining logic, some readability improvements, and suggestions for more comprehensive test coverage. These changes will make the new logic more robust and maintainable.

Comment thread src/providers/mod.rs
Comment on lines +300 to +315
pub fn join_path(store_dir: Option<String>, provider_source: Option<PathBuf>) -> Option<String> {
if let Some(store_path) = store_dir.as_deref() {
let path = PathBuf::from(store_path);
if path.is_relative() {
if let Some(provider_source_path) = provider_source {
if provider_source_path.is_file() {
if let Some(mut parent) = provider_source_path.parent().map(|x| x.to_path_buf()) {
parent.extend(&path);
return parent.into_os_string().to_str().map(|x| String::from(x));
}
}
}
}
}
store_dir
}
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.

high

This function has a potential bug and can be made more readable.

  1. Bug: If parent.into_os_string().to_str() returns None (which can happen with non-UTF-8 paths), the function will return None, effectively losing the store_dir configuration. It should fall back to the original store_dir.
  2. Readability: The nested if statements can be flattened to improve readability.
  3. Idiomatic Rust: Using parent.push(path) is more idiomatic for appending a path than parent.extend(&path).

Here is a suggested refactoring that addresses these points:

pub fn join_path(store_dir: Option<String>, provider_source: Option<PathBuf>) -> Option<String> {
    if let (Some(store_path_str), Some(provider_source_path)) = (store_dir.as_deref(), provider_source) {
        let path = PathBuf::from(store_path_str);
        if path.is_relative() && provider_source_path.is_file() {
            if let Some(parent) = provider_source_path.parent() {
                let mut new_path = parent.to_path_buf();
                new_path.push(path);
                if let Some(s) = new_path.to_str() {
                    return Some(s.to_string());
                }
            }
        }
    }
    store_dir
}

Comment thread src/providers/mod.rs Outdated
Comment on lines +293 to +294
let x = ResolvedProviderConfig::PasswordStore { prefix, store_dir: joined_path, gpg_opts };
return get_provider_from_resolved(provider_name, &x);
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.

medium

For better readability and maintainability, consider using a more descriptive variable name instead of x. For example, updated_resolved_config would make the code's intent clearer.

Suggested change
let x = ResolvedProviderConfig::PasswordStore { prefix, store_dir: joined_path, gpg_opts };
return get_provider_from_resolved(provider_name, &x);
let updated_resolved_config = ResolvedProviderConfig::PasswordStore { prefix, store_dir: joined_path, gpg_opts };
return get_provider_from_resolved(provider_name, &updated_resolved_config);

Comment thread src/providers/mod.rs
Comment on lines +317 to +327
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test() {
let result = join_path(Some(String::from("./password-store")), Some(PathBuf::from("/home/replace/me/with/a/real/path/fnox.toml")));
assert!(result.is_some());
assert_eq!(result.unwrap(), "/home/replace/me/with/a/real/path/./password-store")
}
}
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.

medium

The test for join_path is a good start, but it could be improved:

  1. Test Name: The name test is not descriptive. A better name would be test_join_path_with_relative_store_dir.
  2. Test Coverage: The test only covers the happy path for a relative store_dir. Consider adding more test cases to cover other scenarios, such as:
    • An absolute store_dir (should be returned unchanged).
    • store_dir is None.
    • provider_source is None.

Here's an example of how you could expand the tests:

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_join_path_with_relative_store_dir() {
        let result = join_path(Some(String::from("./password-store")), Some(PathBuf::from("/home/user/project/fnox.toml")));
        assert_eq!(result, Some("/home/user/project/./password-store".to_string()));
    }

    #[test]
    fn test_join_path_with_absolute_store_dir() {
        let store_dir = Some("/etc/passwords".to_string());
        let result = join_path(store_dir.clone(), Some(PathBuf::from("/home/user/project/fnox.toml")));
        assert_eq!(result, store_dir);
    }

    #[test]
    fn test_join_path_no_provider_source() {
        let store_dir = Some("./password-store".to_string());
        let result = join_path(store_dir.clone(), None);
        assert_eq!(result, store_dir);
    }

    #[test]
    fn test_join_path_no_store_dir() {
        let result = join_path(None, Some(PathBuf::from("/home/user/project/fnox.toml")));
        assert_eq!(result, None);
    }
}

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Mar 15, 2026

Greptile Summary

This PR fixes a real bug where a relative store_dir in a password-store provider config was resolved against the working directory instead of the directory containing the declaring config file, causing failures when running fnox get from subdirectories.

The fix is applied both at the top-level get_provider_resolved call site and inside the recursive resolve_secret_ref path in resolver.rs, so the main scenarios are covered. Several issues flagged in earlier review rounds remain open (failing unit test due to the is_file() guard evaluating to false against the fabricated test path, unnormalized joined paths retaining ./ segments, fragile runtime fallback if a config file disappears after load), and those should be addressed before merging.

Confidence Score: 4/5

Core logic is correct but the unit test will fail in CI due to open issues from the previous review rounds, making the PR not yet mergeable.

The approach correctly addresses the described bug and the recursive resolution code path in resolver.rs is now covered. However, issues identified in prior review rounds — the is_file() filesystem guard causing the unit test to always fail, the unnormalized path output retaining ./ segments, and the generic test name — remain unaddressed. These are P1 concerns that need resolution before merging.

src/providers/mod.rs — specifically the join_path function and its unit test.

Important Files Changed

Filename Overview
src/providers/mod.rs Adds get_provider_from_resolved2 wrapper and join_path to resolve relative store_dir against the declaring config file; unit test will fail in CI due to is_file() guard on a non-existent path, and the join produces unnormalized ./-containing paths.
src/providers/resolver.rs Switches resolve_secret_ref to call get_provider_from_resolved2, correctly applying path resolution in the recursive secret-reference resolution path.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[get_provider_resolved] --> B[resolve_provider_config]
    B --> C[get_provider_from_resolved2]
    C --> D{Is PasswordStore?}
    D -- Yes --> E[config.provider_sources.get provider_name]
    E --> F[join_path store_dir, provider_source]
    F --> G{store_dir relative AND\nprovider_source is_file?}
    G -- Yes --> H[parent_dir joined with store_dir]
    G -- No --> I[original store_dir unchanged]
    H --> J[get_provider_from_resolved]
    I --> J
    D -- No --> K[get_provider_from_resolved directly]
    L[resolve_secret_ref in resolver.rs] --> M[resolve_provider_config_with_context]
    M --> C
Loading

Reviews (4): Last reviewed commit: "Merge branch 'main' into relative-store-..." | Re-trigger Greptile

Comment thread src/providers/mod.rs Outdated
Comment thread src/providers/mod.rs Outdated
Comment thread src/providers/mod.rs
Comment thread src/providers/mod.rs Outdated
Comment thread src/providers/mod.rs
Comment thread src/providers/mod.rs Outdated
@jdx jdx marked this pull request as draft March 22, 2026 16:30
@1fexd
Copy link
Copy Markdown
Author

1fexd commented Mar 23, 2026

Does this change make sense, @jdx? Should I check out the LLM reviews?

@1fexd 1fexd marked this pull request as ready for review April 4, 2026 09:06
@jdx jdx enabled auto-merge (squash) April 4, 2026 13:12
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.

2 participants