Skip to content

fix: allow-list git_url before install_extension spawns git clone#168

Merged
jmaxdev merged 2 commits intoTrixtyAI:mainfrom
matiaspalmac:fix/install-extension-url-validation
Apr 21, 2026
Merged

fix: allow-list git_url before install_extension spawns git clone#168
jmaxdev merged 2 commits intoTrixtyAI:mainfrom
matiaspalmac:fix/install-extension-url-validation

Conversation

@matiaspalmac
Copy link
Copy Markdown
Contributor

[Fix]: Allow-list git_url before install_extension spawns git clone

Description

install_extension forwarded the caller-supplied git_url straight
to git clone. Any scheme went through — file://, Windows UNC,
ssh://, git://, and the especially dangerous ext:: transport
helpers. A compromised catalog entry could therefore trigger arbitrary
code at install time via git helpers, symlink tricks, or
credential-embedding URLs. Combined with the plain-HTTP registry fixed
in #162 and the fragile .replace derivation fixed in #157, this was
the final gate between a MITM and code execution on the user's
machine.

Change

apps/desktop/src-tauri/src/extensions.rs — new
validate_git_clone_url is called before install_extension
builds a target directory, and the clone invocation now runs with
defensive git -c flags.

URL validation

  • Strict https:// scheme (case-sensitive). Catches HTTP://,
    http://, file://, ssh://, git://, UNC \\host\share, etc.
  • Host allowlist: github.com, gitlab.com, bitbucket.org.
    Every legitimate catalog entry today already satisfies this, and
    the frontend helper resolveGitRepoUrl produces exactly this
    shape.
  • Path: exactly <owner>/<repo> or <owner>/<repo>.git.
  • Segment content: ASCII alphanumeric + -, _, .; no leading
    - (prevents --upload-pack / --config= flag injection through
    the path).
  • No credentials or alternate-host tricks: rejects any @ in the
    authority section (https://user@github.com/...,
    https://github.com@evil.example/...).
  • No transport-helper syntax: rejects any :: anywhere in the
    URL.
  • No control characters or whitespace.

Clone hardening

Clone is now invoked as:

git -c protocol.ext.allow=never \
    -c protocol.file.allow=never \
    -c protocol.git.allow=never \
    clone --depth 1 <url> <target>

The allow-listed https host is unaffected. If anything ever gets
past the URL check, git itself refuses those transports at runtime.

Tests

Added git_url_validation_tests with 12 cases covering the
accept/reject matrix. cargo test git_url_validation → 12/12 pass.

Trade-offs

  • Allowlist is conservative. Only three hosts are accepted today.
    Catalog entries on other git hosts (codeberg, sourcehut, self-hosted
    instances) would be rejected. That matches the marketplace's
    current scope; widening it later is a one-line diff.
  • No .git suffix required. I considered requiring .git on the
    repo segment, but GitHub/GitLab both redirect …/owner/repo to the
    .git form for git clients, and resolveGitRepoUrl on the
    frontend already appends .git when it normalizes URLs. Both
    shapes are accepted.
  • update_extension / uninstall_extension not touched. Those
    operate on the already-cloned directory and don't take a URL
    argument, so they're not part of the git clone attack surface
    the issue describes. Hardening them (e.g. adding the same -c
    flags to git pull) is a separate improvement.

Verification

  • cargo check, cargo clippy --tests -- -D warnings → clean.
  • cargo test git_url_validation → 12 passed.
  • Manual trace against the in-tree marketplace entry (the example
    addon on github.com/TrixtyAI) → clone still succeeds.
  • Each rejection branch exercised by a unit test, listed in
    git_url_validation_tests.

Related Issue

Fixes #55

Checklist

  • I have tested this on the latest version.
  • I have followed the project's coding guidelines.
  • My changes generate no new warnings or errors.
  • I have verified the fix on:
    • OS: Windows
    • Version: v1.0.10

Copilot AI review requested due to automatic review settings April 21, 2026 02:42
@github-actions github-actions Bot added bug Something isn't working enhancement New feature or request labels Apr 21, 2026
@github-actions
Copy link
Copy Markdown

Thanks for the contribution! I'll review it as soon as possible. If you have still changes, please mark this PR as draft and all reviews will be cancelled. Tests reviews will be re-run only when the PR is marked as ready for review.

Copy link
Copy Markdown

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

Hardens extension installation by validating marketplace-provided git_url before invoking git clone, and adding defensive Git protocol configuration to reduce transport-helper abuse risk.

Changes:

  • Add validate_git_clone_url with a strict https:// + host allowlist + path/character constraints.
  • Invoke git clone with -c protocol.*.allow=never flags to disable risky transports at runtime.
  • Add unit tests covering accepted/rejected URL cases.
Comments suppressed due to low confidence (1)

apps/desktop/src-tauri/src/extensions.rs:344

  • target_dir.to_str().unwrap() can panic if the path contains non-UTF8 data. Prefer passing the path as an OsStr/Path argument (e.g., build the Command with .arg(&git_url) and .arg(&target_dir)), or return a proper error if UTF-8 conversion fails instead of unwrapping.
            "1",
            &git_url,
            target_dir.to_str().unwrap(),
        ])

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/desktop/src-tauri/src/extensions.rs
@matiaspalmac matiaspalmac force-pushed the fix/install-extension-url-validation branch from d8cc1cf to eb34cbe Compare April 21, 2026 02:53
matiaspalmac added a commit to matiaspalmac/ide that referenced this pull request Apr 21, 2026
Addresses review feedback on TrixtyAI#168:

`id` was forwarded straight into `ext_dir.join(&id)` across
install/uninstall/update/is_active/toggle/read_script. A compromised
catalog entry could therefore set `id = "..\..\..\Windows\foo"` and
cause `git clone` (or `remove_dir_all`, or a file read) to operate
**outside** the app-data extensions sandbox.

Added `validate_extension_id` that enforces:

- Non-empty, not `.`, not `..`.
- No path separators (`/`, `\`) and no `..` substring.
- No leading `-` (so the id can never be reparsed as a flag elsewhere).
- Characters restricted to ASCII alphanumeric + `-`, `_`, `.` — the
  safe marketplace-slug charset that every current catalog entry
  already satisfies.

Applied to every Tauri command that takes an `id: String`:
`install_extension`, `uninstall_extension`, `update_extension`,
`is_extension_active`, `toggle_extension_state`,
`read_extension_script`. The string-only commands (`is_active`,
`toggle_state`) don't join `id` to a path today, but validating them
keeps a malicious catalog entry from poisoning
`disabled_extensions.json` with invalid entries that later code may
still path-join.

Added 5 unit tests covering accept/reject cases including the
explicit traversal shapes (`../evil`, `..\\..\\Windows`, `foo..bar`).
install_extension forwarded the caller-supplied git_url straight to
git clone. Any scheme went through — file://, Windows UNC, ssh://,
git://, and ext:: transport helpers — which meant a compromised
catalog entry could trigger arbitrary code at install time via git
helpers, symlink tricks, or credential-embedding URLs.

Added validate_git_clone_url before the clone call. It enforces:

- Strict https:// scheme (case-sensitive).
- Host in a small allowlist (github.com, gitlab.com, bitbucket.org).
  Every legitimate catalog entry today already satisfies this, and
  resolveGitRepoUrl on the frontend produces exactly this shape.
- Path is exactly `<owner>/<repo>` or `<owner>/<repo>.git`, with
  ASCII-safe segments that cannot start with `-` (no flag injection
  via --upload-pack and friends).
- No credentials (`@`), no transport-helper syntax (`::`), no
  whitespace or control characters.

As belt-and-suspenders, the clone invocation now also passes
`-c protocol.ext.allow=never -c protocol.file.allow=never
-c protocol.git.allow=never` so git itself refuses those transports
even if the URL check is ever bypassed. The allow-listed https host
is unaffected.

Added 12 unit tests covering the accept/reject matrix.
Addresses review feedback on TrixtyAI#168:

`id` was forwarded straight into `ext_dir.join(&id)` across
install/uninstall/update/is_active/toggle/read_script. A compromised
catalog entry could therefore set `id = "..\..\..\Windows\foo"` and
cause `git clone` (or `remove_dir_all`, or a file read) to operate
**outside** the app-data extensions sandbox.

Added `validate_extension_id` that enforces:

- Non-empty, not `.`, not `..`.
- No path separators (`/`, `\`) and no `..` substring.
- No leading `-` (so the id can never be reparsed as a flag elsewhere).
- Characters restricted to ASCII alphanumeric + `-`, `_`, `.` — the
  safe marketplace-slug charset that every current catalog entry
  already satisfies.

Applied to every Tauri command that takes an `id: String`:
`install_extension`, `uninstall_extension`, `update_extension`,
`is_extension_active`, `toggle_extension_state`,
`read_extension_script`. The string-only commands (`is_active`,
`toggle_state`) don't join `id` to a path today, but validating them
keeps a malicious catalog entry from poisoning
`disabled_extensions.json` with invalid entries that later code may
still path-join.

Added 5 unit tests covering accept/reject cases including the
explicit traversal shapes (`../evil`, `..\\..\\Windows`, `foo..bar`).
@jmaxdev jmaxdev force-pushed the fix/install-extension-url-validation branch from 1790178 to 30fbcec Compare April 21, 2026 03:28
@jmaxdev jmaxdev merged commit 26eeb2a into TrixtyAI:main Apr 21, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Fix]: install_extension passes git_url to git clone without scheme/host validation

3 participants