Skip to content

fix: eliminate TOCTOU races in catalog_fetch() - #3910

Open
Quratulain-bilal wants to merge 1 commit into
github:mainfrom
Quratulain-bilal:fix/catalog-fetch-toctou
Open

Quratulain-bilal wants to merge 1 commit into
github:mainfrom
Quratulain-bilal:fix/catalog-fetch-toctou

Conversation

@Quratulain-bilal

Copy link
Copy Markdown
Contributor

Problem

catalog_fetch() checks exists() then calls read_text() for both file:// and bare path URLs. The file can be deleted between the two calls.

Fix

Remove the exists() pre-checks and catch FileNotFoundError from read_text().

Testing

  • Verified BundlerError is raised when catalog file is missing

Copilot AI left a comment

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.

Pull request overview

Eliminates TOCTOU races when reading local catalogs.

Changes:

  • Removes exists() pre-checks.
  • Converts FileNotFoundError into BundlerError.
Show a summary per file
File Description
src/specify_cli/bundler/services/adapters.py Safely handles disappearing local catalog files.

Review details

Tip

Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 1/1 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread src/specify_cli/bundles/adapters.py

@mnriem mnriem left a comment

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.

Please address Copilot feedback

Copilot AI left a comment

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.

Review details

Suppressed comments (1)

tests/unit/test_bundler_adapters.py:228

  • The test never verifies the stated removal of the exists() pre-check: an implementation that calls exists() and then catches FileNotFoundError from read_text() still passes. Assert that exists() was not called so this regression test actually guards the PR's TOCTOU fix.
    with patch.object(adapters.Path, "__new__", return_value=mock_path):
        with pytest.raises(BundlerError, match="Catalog file not found"):
            fetcher(_source(url))
  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment thread src/specify_cli/bundles/adapters.py
Comment thread tests/unit/test_bundler_adapters.py Outdated
@mnriem mnriem added the triage-can-wait Verdict: valid and in-scope but deprioritized; held behind the evidence gate label Sep 11, 2026
@mnriem
mnriem requested a balanced review from Copilot September 11, 2026 17:45

Copilot AI left a comment

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.

🟢 Approval recommended

The implementation is correct; only a non-blocking test assertion is missing.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread tests/unit/test_bundler_adapters.py Outdated
@mnriem

mnriem commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

Please address Copilot feedback

@Quratulain-bilal

Copy link
Copy Markdown
Contributor Author

Hi @mnriem — I've addressed Copilot's feedback: removed the unused exists() mock setup and added mock_path.exists.assert_not_called() as a regression guard to verify the pre-check was genuinely eliminated. All 20 tests pass. Ready for re-review when you get a chance.

Copilot AI left a comment

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.

Copilot review overview

🟢 Approval recommended

The race condition is addressed with regression coverage and no unresolved issues.

Review effort: Balanced
Findings: None

Resolved since last review (1)

…ath URLs

Remove exists() pre-checks and catch FileNotFoundError from read_text() for both file:// and bare path catalog sources. Also catches OSError/UnicodeError to preserve the decode-error wrapping contract.

Add regression test for the TOCTOU fix covering both file:// URLs and bare paths: the mocked Path raises FileNotFoundError from read_text(), proving the exists() removal eliminates the race window, with assert_not_called() as a regression guard that no pre-check remains.

Copilot AI left a comment

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.

Copilot review overview

🟢 Approval recommended

All reviewed changes are covered with no unresolved issues.

Review effort: Balanced
Findings: None

This branch has not been deployed

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

Labels

triage-can-wait Verdict: valid and in-scope but deprioritized; held behind the evidence gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants