Skip to content

Harden Git metadata project identity - #403

Merged
bcdonadio merged 9 commits into
mainfrom
fix/git-metadata-security
Aug 1, 2026
Merged

Harden Git metadata project identity#403
bcdonadio merged 9 commits into
mainfrom
fix/git-metadata-security

Conversation

@bcdonadio

@bcdonadio bcdonadio commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Summary

  • contain repository-local common-directory metadata within its authenticated Git administration root
  • require standalone Git metadata to prove its exact checkout relationship before selecting project identity
  • replace ambiguous worktree configuration matching with bounded linear parsing and fail-closed syntax validation
  • preserve normal repositories, linked worktrees, submodules, and verified separate-Git-directory layouts

Verification

  • focused Git identity tests: exact 100% statements, branches, functions, and lines
  • npm run lint
  • npm run typecheck
  • npm run build
  • npm pack --dry-run --json
  • npm run test:ci under Node 25.9.0: 235 files, 4,849 passed, 2 skipped; exact 100% complete coverage
  • independent code and security reviews: no actionable findings

Release

Includes a patch changeset and user-facing identity documentation.

Fixes #342
Fixes #401
Fixes #402

Greptile Summary

This PR hardens Git metadata project identity resolution against path substitution, TOCTOU races, and config injection attacks by replacing regex-based config scanning with a full bounded parser, adding descriptor-bound directory authentication, and requiring cryptographically-verified core.worktree backlinks for every standalone external .git pointer.

  • New authenticateDirectory: opens directories with O_RDONLY | O_DIRECTORY | O_NOFOLLOW on supported platforms, then cross-checks fstatSync dev/ino against a subsequent lstatSync to close the TOCTOU window; falls back to a multi-lstat path on Windows.
  • New parseGitConfig: replaces simple regexes with a complete, linear config parser covering line continuation, quoted values, subsection syntax, and integer booleans — rejecting include/includeif paths and marking the config invalid on any ambiguous syntax.
  • readExternalWorktreeEvidence: for every .git file pointer where commonDir === gitDir, requires core.worktree to resolve and authenticate to the exact same inode as the caller's worktree root; the canonical result is now fail-closed rather than falling back to an unverified anchor.
  • Containment check: normal .git directories whose commondir resolves outside the .git root are now rejected outright, eliminating the local-indirection escape path.

Confidence Score: 5/5

Safe to merge. All topology paths (normal repo, linked worktree, submodule, separate-git-dir) are preserved and each is exercised by the expanded test suite at 100% coverage.

Every changed code path has been analyzed: the new config parser correctly handles all Git config syntax edge cases; the fd-based directory authenticator correctly falls back on platforms without O_DIRECTORY/O_NOFOLLOW; the external-evidence flow correctly requires core.worktree for all standalone .git file pointers while preserving linked-worktree and submodule layouts. No logic gaps or race-window expansions were found.

Files Needing Attention: No files require special attention.

Important Files Changed

Filename Overview
src/git-project.ts Major security hardening: adds fd-based directory authentication, full git config parser, core.worktree backlink verification for standalone external metadata, and commondir containment check for normal repos. No logic bugs found.
test/git-project.test.ts Extensive new test coverage for TOCTOU races, case-variant paths, directory descriptor authentication, config parsing edge cases, and standalone metadata backlink validation.
docs/project-identity.md Updated to document the new core.worktree backlink requirement, case-insensitive filesystem rules, config include/includeif rejection, and commondir containment policy.
.changeset/secure-git-metadata-identity.md Correct patch bump for security-hardening behavior change.

Reviews (8): Last reviewed commit: "fix(git): enforce section header grammar" | Re-trigger Greptile

Context used:

  • Context used - CLAUDE.md (source)
  • Context used - AGENTS.md (source)

Copilot AI review requested due to automatic review settings August 1, 2026 03:58
@bcdonadio bcdonadio self-assigned this Aug 1, 2026
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Harden Git metadata project identity resolution

🐞 Bug fix ✨ Enhancement 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Require authenticated backlinks for standalone Git metadata before selecting project identity.
• Contain repository-local commondir under the authenticated Git admin root; reject
 aliases/symlinks.
• Replace ambiguous config matching with linear, fail-closed parsing; add exhaustive tests and docs.
Diagram

graph TD
  A["resolveGitProjectAnchor"] --> B["inspectGitMarker"] --> C{".git is file?"}
  C -->|"dir"| D["resolve commonDir"] --> F["validate git dir + topology"] --> H["revalidate + return anchor"]
  C -->|"file"| D --> E{"linked worktree?"}
  E -->|"linked"| F --> H
  E -->|"standalone"| G["verify core.worktree backlink"] --> F --> H

  subgraph Legend
    direction LR
    _p["Process"] ~~~ _d{"Decision"}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Shell out to `git rev-parse`/`git config --null --list`
  • ➕ Leverages Git’s own parser/topology rules instead of reimplementing config parsing
  • ➕ Potentially reduces subtle syntax edge cases and maintenance burden
  • ➖ Adds a process-execution dependency and potential PATH/tooling variability
  • ➖ Harder to make descriptor-bound, no-follow, fail-closed guarantees for all inputs
  • ➖ More complex error handling and performance characteristics in tight loops
2. Use a dedicated Git config parsing library
  • ➕ Avoids bespoke parsing code while still parsing in-process
  • ➕ May already handle quoting/escapes/continuations robustly
  • ➖ Introduces a new dependency in a security-critical path
  • ➖ Must still enforce strict fail-closed semantics (Git-compatible but bounded) and reject includes for proof
  • ➖ Library behavior/versioning could subtly change identity rules across releases
3. Constrain accepted syntax further (minimal parser)
  • ➕ Smaller implementation surface area
  • ➕ Easier to audit
  • ➖ Risk of rejecting valid real-world repositories/worktrees unexpectedly
  • ➖ Would likely require a migration/compat story for users with complex configs

Recommendation: The PR’s approach (descriptor-bound reads + strict containment + explicit backlink proof + linear fail-closed parsing) is appropriate for a security boundary where identity must not be influenced by ambiguous or attacker-controlled indirection. If maintenance cost becomes a concern, the most viable alternative is adopting a well-audited config parser library—but only if it can be wrapped to preserve the current strict fail-closed and ‘no include/includeIf for proof’ guarantees.

Files changed (4) +1220 / -60

Bug fix (1) +457 / -34
git-project.tsFail-closed Git identity resolution with directory authentication and linear config parsing +457/-34

Fail-closed Git identity resolution with directory authentication and linear config parsing

• Hardens Git metadata resolution by rejecting symlink/alias gitdir/commondir targets, requiring repository-local 'commondir' containment, and authenticating directories via dev/inode + realpath checks. Adds a bounded linear Git-config parser to safely derive 'core.worktree' and 'extensions.worktreeConfig', rejects include directives for identity proof, and revalidates pointers/config bytes to detect TOCTOU races before returning an anchor.

src/git-project.ts

Tests (1) +720 / -15
git-project.test.tsAdd exhaustive Git identity security and race-condition coverage +720/-15

Add exhaustive Git identity security and race-condition coverage

• Adds comprehensive tests covering standalone external metadata backlink proof, commondir containment, symlink/alias rejection, include/includeIf rejection, malformed/ambiguous config handling, and TOCTOU-style race revalidation. Introduces deterministic seams/mocks to simulate metadata swaps and directory substitution during validation.

test/git-project.test.ts

Documentation (1) +38 / -11
project-identity.mdDocument authenticated Git identity proof and strict config parsing +38/-11

Document authenticated Git identity proof and strict config parsing

• Expands project identity documentation to require exact 'core.worktree' backlinks for separate Git directories, reject 'include'/'includeIf' for proof, and describe containment and revalidation semantics. Adds user guidance for explicitly setting 'core.worktree' when Git doesn’t write it.

docs/project-identity.md

Other (1) +5 / -0
secure-git-metadata-identity.mdAdd patch changeset for Git identity hardening +5/-0

Add patch changeset for Git identity hardening

• Introduces a patch-level changeset describing the new authenticated-backlink and contained-common-dir identity requirements.

.changeset/secure-git-metadata-identity.md

@codecov

codecov Bot commented Aug 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

Copilot AI left a comment

Copy link
Copy Markdown

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 Git-based project identity resolution to prevent repository-controlled metadata from selecting another checkout’s persistent LCM state, while keeping supported Git layouts working and ensuring Git config parsing stays bounded and linear.

Changes:

  • Enforces authenticated, bidirectional topology evidence for external/standalone Git metadata (via core.worktree) and contains repository-local commondir within the authenticated Git admin root.
  • Replaces ambiguous worktree-config matching with a bounded, linear Git config parser that implements “final assignment wins” and rejects includes/ambiguous syntax.
  • Adds focused adversarial/race tests, updates user documentation, and includes a patch changeset.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
test/git-project.test.ts Adds adversarial/race tests for directory authentication, external metadata backlinks, and config parsing semantics.
src/git-project.ts Implements directory authentication/revalidation, external backlink proof, commondir containment checks, and a new linear config parser.
docs/project-identity.md Documents the new identity proof requirements and parsing rules (including rejecting include/includeIf).
.changeset/secure-git-metadata-identity.md Patch changeset describing the user-facing hardening and parsing behavior.

Comment thread src/git-project.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 71247aa56c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/git-project.ts
Comment thread src/git-project.ts Outdated
Comment thread test/git-project.test.ts
greptile-apps[bot]
greptile-apps Bot previously approved these changes Aug 1, 2026
@qodo-code-review

qodo-code-review Bot commented Aug 1, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
Review mode: 🧠 Deep: This security-sensitive change introduces substantial new metadata authentication, race validation, bounded config parsing, and topology logic across many independent edit sites, making multiple review passes materially valuable.

Grey Divider


Remediation recommended

1. Invalid subsection escapes accepted 🐞 Bug ≡ Correctness
Description
parseSectionName accepts every escaped character inside quoted subsection names, so malformed
syntax such as [remote "bad\q"] does not invalidate a configuration containing otherwise valid
core.worktree evidence. Standalone metadata can therefore pass identity validation despite the
documented requirement to fail closed on malformed configuration.
Code

src/git-project.ts[R424-426]

+    if (escaped) {
+      escaped = false;
+      continue;
Evidence
The quoted-subsection parser clears escaped for any following character and can return `valid:
true, whereas value parsing explicitly rejects unsupported escapes. That valid` result gates
external worktree evidence, and the identity documentation says malformed or unsupported config must
fail closed.

src/git-project.ts[361-443]
src/git-project.ts[446-490]
src/git-project.ts[609-642]
docs/project-identity.md[42-49]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description

The Git config parser accepts arbitrary backslash escapes inside quoted subsection names. Unsupported escapes must invalidate the entire configuration so malformed standalone metadata cannot be accepted alongside valid `core.worktree` evidence.

## Issue Context

`parseSectionName` currently clears the escape state without checking the escaped character. Update both section scanning and parsing as needed, and add coverage for malformed subsection escapes combined with an otherwise valid external-worktree backlink.

## Fix Focus Areas

- src/git-project.ts[361-443]
- test/git-project.test.ts[1006-1108]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

2. Directory authentication not descriptor-bound like files ✓ Resolved 🐞 Bug ☼ Reliability
Description
authenticateDirectory (and existingRealDirectory used to confirm core.worktree in
readExternalWorktreeEvidence) validates a directory using separate lstatSync/realpathSync/statSync
calls on a path string rather than holding an open file descriptor across the checks, unlike the
file-reading path in security-files.ts which binds an O_NOFOLLOW descriptor before re-validating.
This leaves a narrower TOCTOU window for directory swaps between the lstat and realpath/stat calls
that is only partially mitigated by the later revalidateDirectory second-pass check.
Code

src/git-project.ts[R63-74]

+function authenticateDirectory(path: string, label: string): DirectoryIdentity {
+  const stat = lstatSync(path);
+  if (!stat.isDirectory() || stat.isSymbolicLink()) {
+    throw new Error(`invalid ${label} at ${path}`);
+  }
+  const real = realpathSync(path);
+  const current = statSync(real);
+  if (real !== path || current.dev !== stat.dev || current.ino !== stat.ino) {
+    throw new Error(`${label} changed during validation: ${path}`);
+  }
+  return { dev: stat.dev, ino: stat.ino, path };
+}
Evidence
Unlike readBoundedRegularFileWithStat (src/security-files.ts) which opens a descriptor with
O_NOFOLLOW and re-validates via fstat on that same descriptor, authenticateDirectory relies on
separate lstatSync/realpathSync/statSync calls on a path string with no equivalent descriptor
binding for directories; the initial trust decision in readExternalWorktreeEvidence (configuredPath
!== worktreeRoot check) is made using these racy path-based checks before the later
revalidateDirectory pass.

src/git-project.ts[629-637]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Directory authentication (authenticateDirectory / existingRealDirectory) uses separate lstat/realpath/stat calls without holding an open descriptor, unlike the file-read path which binds a descriptor with O_NOFOLLOW before re-validating. This is a narrower TOCTOU window than file reads and is only partially mitigated by the later revalidateDirectory call.

## Issue Context
The PR adds authenticateDirectory/revalidateDirectory to catch races, invoked in inspectGitMarker and readExternalWorktreeEvidence, but the initial identity-establishing checks are not descriptor-bound the way readBoundedRegularFileWithStat is for files.

## Fix Focus Areas
- src/git-project.ts[63-74]
- src/git-project.ts[629-637]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. parseGitConfig CRLF/backslash interaction diverges from Git 🐞 Bug ⚙ Maintainability
Description
logicalConfigLines strips a trailing \r from every physical line before scanning for
escape/continuation/comment characters, which can misinterpret an escaped carriage return at end of
a physical line compared to Git's own config parser, causing accepted/rejected results to diverge
from real Git semantics in rare CRLF+escape edge cases. This is a low-likelihood parity gap rather
than a demonstrated exploit, since the surrounding logic still fails closed on most malformed input.
Code

src/git-project.ts[R329-359]

+function logicalConfigLines(config: string): { readonly lines: string[]; readonly valid: boolean } {
+  const lines: string[] = [];
+  const continued: string[] = [];
+  let continuedQuote = false;
+  let valid = true;
+  for (const rawLine of config.split("\n")) {
+    const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
+    const scanned = scanPhysicalConfigLine(line, continuedQuote);
+    if (scanned.continues) {
+      continued.push(line.slice(0, -1));
+      continuedQuote = scanned.quoted;
+      continue;
+    }
+    continued.push(line);
+    const logicalLine = continued.join("");
+    if (continued.length > 1) {
+      let cursor = 0;
+      while (
+        cursor < logicalLine.length
+        && isConfigWhitespace(logicalLine[cursor]!)
+      ) {
+        cursor += 1;
+      }
+      if (logicalLine[cursor] === "[") valid = false;
+    }
+    lines.push(logicalLine);
+    continued.length = 0;
+    continuedQuote = false;
+  }
+  return { lines, valid: valid && continued.length === 0 };
+}
Evidence
logicalConfigLines unconditionally strips a trailing \r from every raw line (line.endsWith("\r") ?
rawLine.slice(0,-1) : rawLine) before calling scanPhysicalConfigLine, which decides
continuation/quoting/comment state; this ordering can misclassify an escaped-CR sequence relative to
Git's own parser, which the PR's docs claim to match exactly ('CRLF input... parsed in one bounded
linear pass').

src/git-project.ts[334-336]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
CRLF handling strips \r before scanning for line continuation/comment/escape state, which may diverge from Git's own config parser for edge cases combining escaped characters with CRLF line endings.

## Issue Context
logicalConfigLines strips \r before scanning in scanPhysicalConfigLine; Git's own config parser may treat an escaped CR differently since CR is stripped before backslash-escape processing here.

## Fix Focus Areas
- src/git-project.ts[329-359]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread src/git-project.ts
Comment thread src/git-project.ts
Comment thread src/git-project.ts Outdated
Copilot AI review requested due to automatic review settings August 1, 2026 04:25
@greptile-apps
greptile-apps Bot dismissed their stale review August 1, 2026 04:25

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

Copilot AI left a comment

Copy link
Copy Markdown

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 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/git-project.ts:567

  • const bool = asciiLower(parsedValue!.value!) uses a double non-null assertion. Even if currently safe, it makes the invariant implicit and would turn a future bug into silently wrong behavior. Add a small guard that treats a missing parsed value as invalid worktreeConfig syntax (fail-closed) and removes the assertions.
    const bool = asciiLower(parsedValue!.value!);

src/git-project.ts:561

  • In parseGitConfig(), coreWorktree = parsedValue!.value relies on a non-null assertion even though the code path depends on parseConfigValue() returning a value when valid. Adding an explicit parsedValue?.value guard here both documents the invariant and prevents future refactors from accidentally accepting an undefined value as a valid core.worktree.

This issue also appears on line 567 of the same file.

      if (implicit) {
        valid = false;
      } else {
        coreWorktree = parsedValue!.value;
      }

test/git-project.test.ts:142

  • makeStandaloneMetadata() builds the relative .git pointer with metadata.split("/"), which is not path-separator safe (breaks on Windows) and implicitly assumes checkout and metadata are siblings. This can produce an invalid pointer and make tests pass for the wrong reason. Consider guarding the sibling-layout precondition and splitting on both / and \\ when constructing the relative pointer.
  writeFileSync(
    join(checkout, ".git"),
    `gitdir: ${absolutePointer ? metadata : `../${metadata.split("/").at(-1)!}`}\n`,
  );

greptile-apps[bot]
greptile-apps Bot previously approved these changes Aug 1, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 436217dd3c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/git-project.ts Outdated
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Copilot AI review requested due to automatic review settings August 1, 2026 05:26
@bcdonadio
bcdonadio force-pushed the fix/git-metadata-security branch from 436217d to 7105e86 Compare August 1, 2026 05:26
@greptile-apps
greptile-apps Bot dismissed their stale review August 1, 2026 05:26

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

Comment thread src/git-project.ts Fixed

Copilot AI left a comment

Copy link
Copy Markdown

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 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/git-project.ts:453

  • parseSectionName() currently allows a section header like [extensions]#comment by treating #/; as a comment even when it is not preceded by whitespace. Per git-config syntax, comment markers must be at the beginning of the line or preceded by whitespace; without whitespace this should fail closed as invalid syntax to avoid diverging from Git’s parsing rules.
  let tail = close + 1;
  while (tail < line.length && isConfigWhitespace(line[tail]!)) tail += 1;
  if (tail < line.length && line[tail] !== "#" && line[tail] !== ";") {
    return { valid: false };

src/git-project.ts:538

  • parseConfigValue() treats any unquoted #/; as starting an inline comment. Git only recognizes inline comments when the comment marker is preceded by whitespace; otherwise it is part of the value (e.g. true#comment). Treating it as a comment here can make LCM accept configs as valid/true that Git would treat as a different (often invalid) value.
    if (!quoted && (char === "#" || char === ";")) break;

src/git-project.ts:386

  • Git config comments (#/;) are only recognized at the start of a line or when preceded by whitespace. scanPhysicalConfigLine() currently treats any unquoted #/; as starting a comment, so values like worktreeConfig = true#not-a-comment (which Git interprets as literal true#not-a-comment) get truncated and may be mis-parsed, diverging from Git semantics used for identity hardening.

This issue also appears in the following locations of the same file:

  • line 450
  • line 538
    if (!quoted && (char === "#" || char === ";")) {
      return { continues: false, quoted };
    }

test/git-project.test.ts:1421

  • The config-syntax regression tests don’t currently cover Git’s rule that #/; start inline comments only when preceded by whitespace. Adding cases like worktreeConfig = true#comment and [extensions]#comment helps ensure the parser stays aligned with git-config semantics for ambiguous/adversarial inputs.
    for (const config of [
      "[extensions]\nworktreeConfig = false\n",
      "[extensions]\nworktreeConfig = maybe\n",
      "[extensions]\nworktreeConfig # invalid implicit comment\n",
      "[extensions]\nworktreeConfig trailing\n",
      "[extensions]\nworktreeConfig = \\\n",
      "[extensions]\nworktreeConfig = \\\\q\n",

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7105e86c35

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/git-project.ts Outdated
Comment thread src/git-project.ts
Accept verified case-only spellings without admitting bind, symlink, ambiguous, or cross-checkout aliases. Linearize directory authentication on a no-follow descriptor and preserve bounded fallback validation.

Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Copilot AI review requested due to automatic review settings August 1, 2026 06:13

Copilot AI left a comment

Copy link
Copy Markdown

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 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/git-project.ts:778

  • When config.worktree parsing is invalid, this error still points at ${gitDir}/config, which can mislead debugging (the invalid bytes are in config.worktree). Consider selecting the path based on which parse failed.
  const values = readConfigValues(gitDir);
  if (!values.commonValues.valid || values.worktreeValues?.valid === false) {
    throw new Error(`invalid Git config metadata at ${join(gitDir, "config")}: ambiguous worktree configuration`);
  }

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eecde00646

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/git-project.ts Outdated
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Copilot AI review requested due to automatic review settings August 1, 2026 06:44

Copilot AI left a comment

Copy link
Copy Markdown

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 4 out of 4 changed files in this pull request and generated no new comments.

Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
greptile-apps[bot]
greptile-apps Bot previously approved these changes Aug 1, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 581614621f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/git-project.ts Outdated
Comment thread src/git-project.ts Outdated
Copilot AI review requested due to automatic review settings August 1, 2026 06:56
@greptile-apps
greptile-apps Bot dismissed their stale review August 1, 2026 06:57

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

Copilot AI left a comment

Copy link
Copy Markdown

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 4 out of 4 changed files in this pull request and generated no new comments.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6a48569a85

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/git-project.ts Outdated
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Copilot AI review requested due to automatic review settings August 1, 2026 07:32

Copilot AI left a comment

Copy link
Copy Markdown

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 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/git-project.ts:963

  • readExternalWorktreeEvidence() authenticates configuredPath with authenticateDirectory(configuredPath, ...) using the default expectedRealPath=configuredPath. On case-insensitive filesystems where realpathSync normalizes the stored on-disk casing, a case-only core.worktree value can resolve to the same directory but still fail authentication because realpathSync(configuredPath) will likely return worktreeRoot (canonical casing) rather than the configured spelling. That would incorrectly reject valid case-only backlinks (and contradict the documented support for case-only spellings).
    configuredIdentity = authenticateDirectory(
      configuredPath,
      "Git core.worktree directory",
    );

Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
greptile-apps[bot]
greptile-apps Bot previously approved these changes Aug 1, 2026
Copilot AI review requested due to automatic review settings August 1, 2026 07:43
@greptile-apps
greptile-apps Bot dismissed their stale review August 1, 2026 07:43

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

Copilot AI left a comment

Copy link
Copy Markdown

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 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/git-project.ts:368

  • resolveCommonDir() calls lstatSync(target) without wrapping errors. For missing/unreadable commondir targets, this will throw a raw Node errno error rather than a consistent “invalid Git common directory …” error, which makes failures harder to diagnose and less consistent with other metadata validation errors.
  const target = resolve(gitDir, relative);
  const targetStat = lstatSync(target);
  if (targetStat.isSymbolicLink()) {
    throw new Error(`invalid Git common directory at ${target}`);
  }

src/git-project.ts:307

  • parseGitDir() calls lstatSync(targetPath) without error wrapping. If the gitdir target is missing or unreadable (ENOENT/EACCES), this will bubble up as a raw Node errno error and lose the “invalid Git …” context used elsewhere in this module for repository-controlled metadata failures.

This issue also appears on line 364 of the same file.

  const targetPath = resolve(worktreeRoot, target);
  const targetStat = lstatSync(targetPath);
  if (targetStat.isSymbolicLink()) {
    throw new Error(`invalid Git directory at ${targetPath}`);
  }

@bcdonadio
bcdonadio merged commit b6604a3 into main Aug 1, 2026
22 checks passed
@bcdonadio
bcdonadio deleted the fix/git-metadata-security branch August 1, 2026 07:56

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4268cf1d47

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/git-project.ts
Comment on lines +960 to +963
configuredIdentity = authenticateDirectory(
configuredPath,
"Git core.worktree directory",
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Pass the canonical path when authenticating case variants

When core.worktree differs from worktreeRoot only by casing on a case-insensitive host whose realpathSync canonicalizes component names, this call retains the default expectedRealPath = configuredPath; authenticateDirectory therefore rejects as soon as the real path equals worktreeRoot, before the device/inode comparison can accept it. Fresh evidence after the earlier claimed fix is that the final tree still omits worktreeRoot here, whereas parseGitDir explicitly passes its canonical gitDir. Standalone pointers and submodules with valid case-only backlinks consequently still fail on such hosts.

Useful? React with 👍 / 👎.

Comment thread src/git-project.ts
Comment on lines +840 to +843
if (
key === "path"
&& (sectionBase === "include" || sectionBase === "includeif")
) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject only actual Git include directives

For a standalone pointer whose otherwise valid config contains a custom subsection such as [include "metadata"] followed by path = value (or deprecated [include.foo]), sectionBase is include, so this rejects the entire repository even though Git does not treat that key as an include directive. git config -h describes --includes as “respect include directives on lookup”; checked with Git 2.43, git config --includes --file ... --list leaves this as include.metadata.path and does not load the target. Restrict this fail-closed branch to the exact [include] section and the actual conditional [includeIf "..."] form.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants