Skip to content

fix(tunnel): preserve resolved cloudflare account id across feature start#62

Merged
rbarazi merged 1 commit intomainfrom
hotfix/v0.9.2
Mar 5, 2026
Merged

fix(tunnel): preserve resolved cloudflare account id across feature start#62
rbarazi merged 1 commit intomainfrom
hotfix/v0.9.2

Conversation

@rbarazi
Copy link
Member

@rbarazi rbarazi commented Mar 5, 2026

Summary

  • keep .branchbox/config.json and .branchbox/registry.json out of the feature-start stash path so init --update config repairs are not moved into feature worktrees
  • make tunnel open ignore stale unsupported stored providers (like legacy manual) and fall back to configured provider
  • teach non-interactive init --update to resolve ${CLOUDFLARE_ACCOUNT_ID} from env, secure file, or Cloudflare API account lookup when unambiguous

Validation

  • cargo fmt --all
  • SDKROOT="$(xcrun --sdk macosx --show-sdk-path)" cargo test -p worktree-core tunnel_open_falls_back_to_configured_provider_when_stored_provider_is_unsupported
  • SDKROOT="$(xcrun --sdk macosx --show-sdk-path)" cargo test -p worktree-core test_execute_update_mode_resolves_cloudflare_account_id_from_api_token
  • SDKROOT="$(xcrun --sdk macosx --show-sdk-path)" cargo test -p worktree-core feature_start_keeps_branchbox_config_changes_in_main_worktree
  • SDKROOT="$(xcrun --sdk macosx --show-sdk-path)" cargo build -p branchbox-cli

@gemini-code-assist
Copy link

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 significantly improves the robustness and user experience of tunnel management and configuration within the system. It addresses issues related to configuration file persistence across feature worktrees, enhances the handling of tunnel provider states, and introduces automated Cloudflare account ID resolution during initialization, reducing manual intervention and potential configuration errors.

Highlights

  • Configuration File Stashing: Prevented .branchbox/config.json and .branchbox/registry.json from being stashed during feature-start operations, ensuring configuration repairs remain in the main worktree.
  • Tunnel Provider Fallback: Enhanced tunnel_open to gracefully handle and ignore unsupported tunnel providers stored in metadata, falling back to the configured default provider with a warning.
  • Cloudflare Account ID Resolution: Implemented automatic resolution of Cloudflare account IDs in non-interactive init --update mode, sourcing the ID from environment variables, secure credential files, or the Cloudflare API when unambiguous.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • core/src/workflows/feature.rs
    • Modified tunnel provider resolution logic to prioritize configured providers over unsupported stored providers, issuing a warning if a stored provider is ignored.
    • Updated the git stash push command in feature-start to explicitly exclude .branchbox/config.json and .branchbox/registry.json from being stashed.
    • Added a new private function stash_excluded_pathspecs to centralize the list of files to exclude from stashing.
    • Introduced normalize_tunnel_provider_name to standardize provider names and identify unsupported ones.
    • Added CloudflaredConfig import for test cases.
    • Included a new test tunnel_open_falls_back_to_configured_provider_when_stored_provider_is_unsupported to validate fallback behavior.
    • Added a new test feature_start_keeps_branchbox_config_changes_in_main_worktree to verify config file exclusion from stashing.
  • core/src/workflows/init.rs
    • Added reqwest client and header imports, along with constants for Cloudflare API base URL and user agent.
    • Defined new structs (CloudflareAccountsResponse, CloudflareAccountSummary, CloudflareApiError) for deserializing Cloudflare API responses.
    • Integrated logic to automatically resolve Cloudflare account IDs during init --update by checking environment variables, secure credential files, and the Cloudflare API.
    • Updated the validation for Cloudflare account ID input to prevent environment variable placeholders from being entered directly.
    • Added a helper function append_warning to consolidate warning messages.
    • Introduced utility functions is_valid_env_var_name and parse_env_reference for parsing environment variable placeholders.
    • Added resolve_cloudflare_api_base to determine the Cloudflare API endpoint.
    • Implemented cloudflared_credentials_paths, read_env_file_value, and read_cloudflare_api_token to manage and read Cloudflare credentials.
    • Added fetch_cloudflare_accounts to interact with the Cloudflare API for account lookup.
    • Introduced auto_resolve_cloudflare_account_id to orchestrate the account ID resolution process.
    • Added mockito::Server import for testing API interactions.
    • Included a new test test_execute_update_mode_resolves_cloudflare_account_id_from_api_token to verify the auto-resolution feature.
Activity
  • The pull request includes several test runs demonstrating the validation of the implemented changes.
  • Tests confirm that the tunnel open functionality correctly falls back to configured providers when stored ones are unsupported.
  • Validation shows that feature-start preserves branchbox config changes in the main worktree.
  • Tests verify that init --update successfully resolves Cloudflare account IDs from API tokens.
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.

@rbarazi rbarazi merged commit fda45ed into main Mar 5, 2026
2 checks passed
@rbarazi rbarazi deleted the hotfix/v0.9.2 branch March 5, 2026 11:38
Copy link

@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 several improvements to tunnel configuration handling, including robust fallback logic for unsupported tunnel providers in feature.rs, automatic resolution of Cloudflare account IDs during init --update in init.rs, and correct exclusion of BranchBox configuration files from git stash during feature creation. However, a critical security vulnerability was identified in the new read_env_file_value function in core/src/workflows/init.rs. This function follows symlinks and absolute paths, which could allow a malicious repository to read sensitive files or cause a Denial of Service (DoS). It is recommended to implement path validation and symlink checks before reading files from user-controlled paths. Additionally, I've provided a couple of suggestions in the specific comments to improve code clarity and efficiency.

return Ok(None);
}

let content = fs::read_to_string(path)?;

Choose a reason for hiding this comment

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

security-medium medium

The read_env_file_value function reads a file from a path specified in the repository's configuration (.branchbox/config.json) without validating that the path is safe. An attacker can point the api_token_path to an absolute location (e.g., /etc/shadow) or a symlink to a sensitive file by committing a malicious configuration file to the repository. While the tool only extracts values for specific keys (like CLOUDFLARE_API_TOKEN), this can lead to partial information leakage if the file contains keys the tool is looking for. Furthermore, pointing the path to a special device like /dev/zero or a named pipe (FIFO) can cause the tool to hang or consume excessive memory, leading to a Denial of Service (DoS).

Remediation: Validate that the path is within the workspace and is not a symlink or a special device before attempting to read it. Use fs::symlink_metadata to check the file type and ensure it is a regular file.

Comment on lines +869 to +882
let provider_name = stored_provider
.as_deref()
.and_then(Self::normalize_tunnel_provider_name)
.map(str::to_string)
.unwrap_or_else(|| configured_provider.clone());

if let Some(stored) = stored_provider.as_ref() {
if Self::normalize_tunnel_provider_name(stored).is_none() {
warnings.push(format!(
"Stored tunnel provider '{}' is unsupported; using configured provider '{}'",
stored, configured_provider
));
}
}

Choose a reason for hiding this comment

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

medium

The logic to determine the provider_name and issue a warning for unsupported providers can be simplified for better readability and to avoid calling normalize_tunnel_provider_name twice. You can combine the provider resolution and warning logic into a single block.

        let provider_name = if let Some(stored) = &stored_provider {
            if let Some(normalized) = Self::normalize_tunnel_provider_name(stored) {
                normalized.to_string()
            } else {
                warnings.push(format!(
                    "Stored tunnel provider '{}' is unsupported; using configured provider '{}'",
                    stored, &configured_provider
                ));
                configured_provider.clone()
            }
        } else {
            configured_provider.clone()
        };

Comment on lines +1317 to +1324
for line in content.lines() {
if let Some(value) = line.strip_prefix(&format!("{key}=")) {
let normalized = value.trim().trim_matches(['"', '\'']).trim().to_string();
if !normalized.is_empty() {
return Ok(Some(normalized));
}
}
}

Choose a reason for hiding this comment

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

medium

The format!("{key}=") is being called inside the loop on every iteration. This can be slightly inefficient for large files. You can create the prefix string once before the loop.

Suggested change
for line in content.lines() {
if let Some(value) = line.strip_prefix(&format!("{key}=")) {
let normalized = value.trim().trim_matches(['"', '\'']).trim().to_string();
if !normalized.is_empty() {
return Ok(Some(normalized));
}
}
}
let prefix = format!("{key}=");
for line in content.lines() {
if let Some(value) = line.strip_prefix(&prefix) {
let normalized = value.trim().trim_matches(['"', '\'']).trim().to_string();
if !normalized.is_empty() {
return Ok(Some(normalized));
}
}
}

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