chore: apply ruff formatting and fix whitespace issues
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
+4
-4
@@ -71,16 +71,16 @@ def make_api_request(url, max_retries=3):
|
||||
"""Make API request with rate limit handling."""
|
||||
for attempt in range(max_retries):
|
||||
response = requests.get(url)
|
||||
|
||||
|
||||
if response.status_code == 429:
|
||||
# Rate limit exceeded
|
||||
retry_after = int(response.headers.get('Retry-After', 60))
|
||||
print(f"Rate limit exceeded. Retrying after {retry_after} seconds...")
|
||||
time.sleep(retry_after)
|
||||
continue
|
||||
|
||||
|
||||
return response
|
||||
|
||||
|
||||
raise Exception("Max retries exceeded")
|
||||
```
|
||||
|
||||
@@ -203,7 +203,7 @@ The DocuElevate browser extension uses this endpoint to send files directly from
|
||||
|
||||
Upload one or more files from your computer for processing.
|
||||
|
||||
**Request**:
|
||||
**Request**:
|
||||
- Multipart form data with file(s)
|
||||
|
||||
**Response**:
|
||||
|
||||
@@ -79,7 +79,7 @@ For larger deployments or when you need more advanced authentication features, O
|
||||
### 1. Create an Application in Authentik
|
||||
|
||||
1. Log in to your Authentik admin interface
|
||||
2. Navigate to "Applications" > "Applications"
|
||||
2. Navigate to "Applications" > "Applications"
|
||||
3. Click "Create"
|
||||
4. Fill in the following details:
|
||||
- **Name**: DocuElevate
|
||||
|
||||
@@ -243,7 +243,7 @@ docker build -t docuelevate .
|
||||
|
||||
**Cause:** Building outside of a Git repository
|
||||
|
||||
**Solution:**
|
||||
**Solution:**
|
||||
- Clone the repository properly with `.git` directory
|
||||
- Or set `GIT_COMMIT_SHA` environment variable
|
||||
|
||||
@@ -261,7 +261,7 @@ docker build --no-cache -t docuelevate .
|
||||
|
||||
**Cause:** Files listed in `.dockerignore`
|
||||
|
||||
**Solution:**
|
||||
**Solution:**
|
||||
- Check `.dockerignore` doesn't block `BUILD_DATE`, `GIT_SHA`, or `RUNTIME_INFO`
|
||||
- The `VERSION` file should always be committed to git
|
||||
|
||||
|
||||
@@ -352,7 +352,7 @@ PAPERLESS_CUSTOM_FIELDS_MAPPING='{"absender": "Sender", "empfaenger": "Recipient
|
||||
|
||||
### Dropbox
|
||||
|
||||
| **Variable** | **Description** |
|
||||
| **Variable** | **Description** |
|
||||
|-------------------------|--------------------------------------------------|
|
||||
| `DROPBOX_APP_KEY` | Dropbox API app key. |
|
||||
| `DROPBOX_APP_SECRET` | Dropbox API app secret. |
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# DocuElevate Configuration
|
||||
# DocuElevate Configuration
|
||||
|
||||
This section contains detailed documentation about configuring DocuElevate for your environment.
|
||||
|
||||
|
||||
@@ -106,17 +106,17 @@ Add security headers to your Nginx configuration:
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name docuelevate.example.com;
|
||||
|
||||
|
||||
# SSL configuration
|
||||
ssl_certificate /etc/nginx/ssl/cert.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/key.pem;
|
||||
|
||||
|
||||
# Security headers
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:;" always;
|
||||
add_header X-Frame-Options "DENY" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:8000;
|
||||
proxy_set_header Host $host;
|
||||
@@ -185,7 +185,7 @@ For production use, we recommend setting up a reverse proxy (like Nginx or Traef
|
||||
server {
|
||||
listen 80;
|
||||
server_name docuelevate.example.com;
|
||||
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:8000;
|
||||
proxy_set_header Host $host;
|
||||
|
||||
@@ -4,7 +4,7 @@ This guide explains how to set up the Dropbox integration for DocuElevate.
|
||||
|
||||
## Required Configuration Parameters
|
||||
|
||||
| **Variable** | **Description** |
|
||||
| **Variable** | **Description** |
|
||||
|-------------------------|--------------------------------------------------|
|
||||
| `DROPBOX_APP_KEY` | Dropbox API app key |
|
||||
| `DROPBOX_APP_SECRET` | Dropbox API app secret |
|
||||
@@ -64,11 +64,11 @@ The wizard handles all the token exchange steps and provides you with the exact
|
||||
1. Go to the "OAuth 2" tab in your app settings
|
||||
2. Add a redirect URI: `http://localhost` (this is for the authorization flow)
|
||||
3. Generate an authorization URL with these instructions:
|
||||
|
||||
|
||||
```
|
||||
https://www.dropbox.com/oauth2/authorize?client_id=YOUR_APP_KEY&response_type=code&token_access_type=offline
|
||||
```
|
||||
|
||||
|
||||
4. Replace `YOUR_APP_KEY` with your app key
|
||||
5. Open this URL in your browser
|
||||
6. Authorize the app when prompted
|
||||
@@ -78,7 +78,7 @@ The wizard handles all the token exchange steps and provides you with the exact
|
||||
### 5. Exchange the Code for a Refresh Token
|
||||
|
||||
1. Use this curl command to exchange the code for tokens:
|
||||
|
||||
|
||||
```bash
|
||||
curl -X POST https://api.dropboxapi.com/oauth2/token \
|
||||
-d code=YOUR_AUTH_CODE \
|
||||
@@ -87,7 +87,7 @@ The wizard handles all the token exchange steps and provides you with the exact
|
||||
-d client_secret=YOUR_APP_SECRET \
|
||||
-d redirect_uri=http://localhost
|
||||
```
|
||||
|
||||
|
||||
2. From the response, copy the `refresh_token` value
|
||||
|
||||
### 6. Configure DocuElevate
|
||||
|
||||
@@ -108,4 +108,3 @@ Text extraction logs to check for `extracted_text`:
|
||||
- `process_with_azure_document_intelligence` - Azure OCR processing
|
||||
|
||||
Both may contain extracted text in the `detail` field when `status = 'success'`.
|
||||
|
||||
|
||||
@@ -215,9 +215,9 @@ for step in ["hash_file", "create_file_record", "check_text", ...]:
|
||||
# Start
|
||||
update_step_status(db, file.id, step, "in_progress", started_at=now())
|
||||
log_event(db, file.id, step, "in_progress", "Starting...")
|
||||
|
||||
|
||||
# Do work...
|
||||
|
||||
|
||||
# Complete
|
||||
update_step_status(db, file.id, step, "success", completed_at=now())
|
||||
log_event(db, file.id, step, "success", "Completed successfully")
|
||||
@@ -349,6 +349,6 @@ For deploying to existing system:
|
||||
- [ ] Run migration utility: `migrate_all_files(db, dry_run=True)` to test
|
||||
- [ ] Run actual migration: `migrate_all_files(db, dry_run=False)`
|
||||
- [ ] Verify: Check a few files with `verify_migration()`
|
||||
- [ ] Update workers to call `update_step_status()`
|
||||
- [ ] Update workers to call `update_step_status()`
|
||||
- [ ] Update file creation to call `initialize_file_steps()`
|
||||
- [ ] Monitor dashboard for correct status display
|
||||
|
||||
+35
-35
@@ -16,19 +16,19 @@ on: [push, pull_request]
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install -r requirements-dev.txt
|
||||
|
||||
|
||||
- name: Run OAuth tests (mock)
|
||||
run: |
|
||||
pytest tests/test_oauth_integration_flows.py -v
|
||||
@@ -48,19 +48,19 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
# Only run if secrets are available (not on external PRs)
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install -r requirements-dev.txt
|
||||
|
||||
|
||||
- name: Run OAuth tests (real)
|
||||
env:
|
||||
AUTHENTIK_CLIENT_ID: ${{ secrets.AUTHENTIK_CLIENT_ID }}
|
||||
@@ -84,43 +84,43 @@ jobs:
|
||||
test-mock-oauth:
|
||||
name: OAuth Tests (Mock)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install -r requirements-dev.txt
|
||||
|
||||
|
||||
- name: Run mock OAuth tests
|
||||
run: |
|
||||
pytest tests/test_oauth_integration_flows.py \
|
||||
-v \
|
||||
-m "not requires_external"
|
||||
|
||||
|
||||
test-real-oauth:
|
||||
name: OAuth Tests (Real - Internal Only)
|
||||
runs-on: ubuntu-latest
|
||||
# Only run on internal commits where secrets are available
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install -r requirements-dev.txt
|
||||
|
||||
|
||||
- name: Run real OAuth tests
|
||||
env:
|
||||
AUTHENTIK_CLIENT_ID: ${{ secrets.AUTHENTIK_CLIENT_ID }}
|
||||
@@ -157,7 +157,7 @@ on: [push, pull_request]
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
services:
|
||||
mock-oauth:
|
||||
image: ghcr.io/navikt/mock-oauth2-server:2.1.1
|
||||
@@ -168,24 +168,24 @@ jobs:
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install -r requirements-dev.txt
|
||||
|
||||
|
||||
- name: Configure OAuth to use service
|
||||
run: |
|
||||
export OAUTH_MOCK_URL=http://localhost:8080
|
||||
export USE_MOCK_OAUTH=true
|
||||
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
pytest tests/test_oauth_integration_flows.py -v
|
||||
@@ -255,7 +255,7 @@ Add this step to debug mock OAuth server issues:
|
||||
|
||||
**Cause**: Real OAuth credentials not configured or not accessible.
|
||||
|
||||
**Solution**:
|
||||
**Solution**:
|
||||
- For local dev: Use mock mode (default)
|
||||
- For CI: Add secrets to GitHub repository settings
|
||||
- Check secret availability: `if github.event_name == 'push'`
|
||||
@@ -291,20 +291,20 @@ on: [push, pull_request]
|
||||
jobs:
|
||||
# Fast mock OAuth tests (always run)
|
||||
mock-oauth-tests:
|
||||
name: OAuth Tests (Mock)
|
||||
name: OAuth Tests (Mock)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
|
||||
- name: Install dependencies
|
||||
run: pip install -r requirements-dev.txt
|
||||
|
||||
|
||||
- name: Run mock OAuth tests
|
||||
run: |
|
||||
pytest tests/test_oauth_integration_flows.py \
|
||||
@@ -312,7 +312,7 @@ jobs:
|
||||
-m "not requires_external" \
|
||||
--cov=app.auth \
|
||||
--cov-report=term-missing
|
||||
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
@@ -324,18 +324,18 @@ jobs:
|
||||
name: OAuth Tests (Real - Internal)
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
|
||||
- name: Install dependencies
|
||||
run: pip install -r requirements-dev.txt
|
||||
|
||||
|
||||
- name: Run real OAuth tests
|
||||
env:
|
||||
AUTHENTIK_CLIENT_ID: ${{ secrets.AUTHENTIK_CLIENT_ID }}
|
||||
@@ -345,7 +345,7 @@ jobs:
|
||||
pytest tests/test_oauth_integration_flows.py \
|
||||
-v \
|
||||
-m requires_external
|
||||
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
|
||||
@@ -138,7 +138,7 @@ If you're using a work/school account provided by your organization:
|
||||
### 2. Configuration based on use case
|
||||
|
||||
**Option A: Access your own OneDrive (Interactive Login)**
|
||||
|
||||
|
||||
This option requires a refresh token:
|
||||
1. Use the auth wizard with your tenant ID, or
|
||||
2. Follow the same manual steps as for personal accounts, but use your work email to sign in
|
||||
@@ -146,7 +146,7 @@ This option requires a refresh token:
|
||||
4. Set the refresh token you receive as `ONEDRIVE_REFRESH_TOKEN`
|
||||
|
||||
**Option B: Access OneDrive as a system service (App-only access)**
|
||||
|
||||
|
||||
This option is for service accounts or automated systems with no user interaction:
|
||||
1. In API permissions, add "Application permissions" instead of "Delegated permissions"
|
||||
2. Add `Files.ReadWrite.All` permission under "Application permissions"
|
||||
@@ -158,15 +158,15 @@ This option is for service accounts or automated systems with no user interactio
|
||||
|
||||
If you encounter errors during authentication:
|
||||
|
||||
1. **Check account permissions**:
|
||||
1. **Check account permissions**:
|
||||
- Ensure your Microsoft account has the necessary permissions to grant access
|
||||
- For corporate accounts, check if your admin has restricted third-party app access
|
||||
|
||||
2. **Permission errors**:
|
||||
2. **Permission errors**:
|
||||
- Verify the app registration has the correct API permissions
|
||||
- For corporate accounts, ensure an admin has consented to the permissions
|
||||
|
||||
3. **Refresh token expired**:
|
||||
3. **Refresh token expired**:
|
||||
- If uploads stop working, you can generate a new refresh token using the auth wizard
|
||||
- Click on "Refresh Token" in the OneDrive setup page
|
||||
|
||||
|
||||
@@ -271,7 +271,7 @@ from locust import HttpUser, task, between
|
||||
|
||||
class APIUser(HttpUser):
|
||||
wait_time = between(0.1, 0.5)
|
||||
|
||||
|
||||
@task
|
||||
def get_files(self):
|
||||
self.client.get("/api/files")
|
||||
|
||||
@@ -93,7 +93,7 @@ The current value displayed is **always** the effective value after applying pre
|
||||
6. Successfully saved settings will show a 🟢 DB badge
|
||||
7. If any changed setting requires a restart, you'll be notified
|
||||
|
||||
**Important**:
|
||||
**Important**:
|
||||
- You don't need to fill all fields - only change what you want to override
|
||||
- Saving a setting to the database makes it override environment variables
|
||||
- Empty fields are ignored (won't clear existing values)
|
||||
@@ -196,7 +196,7 @@ Settings are stored in the `application_settings` table with:
|
||||
### Can't Access Settings Page
|
||||
|
||||
- **Check authentication**: Make sure you're logged in
|
||||
- **Check admin status**:
|
||||
- **Check admin status**:
|
||||
- Local auth: Verify `ADMIN_USERNAME` and `ADMIN_PASSWORD` are correct
|
||||
- OAuth: Verify your user is in the admin group (configurable via `ADMIN_GROUP_NAME`)
|
||||
- **Check logs**: Look for "Non-admin user attempted to access settings page" messages
|
||||
@@ -243,5 +243,5 @@ python3 test_integration.py
|
||||
## Related Documentation
|
||||
|
||||
- [Configuration Guide](./ConfigurationGuide.md) - Environment variable reference
|
||||
- [Deployment Guide](./DeploymentGuide.md) - Production deployment
|
||||
- [Deployment Guide](./DeploymentGuide.md) - Production deployment
|
||||
- [API Documentation](./API.md) - Full API reference
|
||||
|
||||
@@ -223,7 +223,7 @@ curl -X POST "http://localhost:8000/api/files/123/reprocess-with-cloud-ocr" \
|
||||
```python
|
||||
class FileRecord(Base):
|
||||
__tablename__ = "files"
|
||||
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
filehash = Column(String, unique=True, nullable=False)
|
||||
original_filename = Column(String) # User's original name
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Repository Analysis & Improvement Summary
|
||||
|
||||
**Date:** 2026-02-06
|
||||
**Repository:** christianlouis/DocuElevate
|
||||
**Date:** 2026-02-06
|
||||
**Repository:** christianlouis/DocuElevate
|
||||
**Current Version:** v0.5.0
|
||||
|
||||
## Executive Summary
|
||||
@@ -343,6 +343,6 @@ httpx>=0.26.0
|
||||
|
||||
---
|
||||
|
||||
**Prepared by:** GitHub Copilot Agent
|
||||
**Review Status:** Ready for maintainer review
|
||||
**Prepared by:** GitHub Copilot Agent
|
||||
**Review Status:** Ready for maintainer review
|
||||
**Recommended Action:** Merge and continue with TODO.md priorities
|
||||
|
||||
@@ -61,7 +61,7 @@ Created comprehensive unit tests to verify:
|
||||
1. **Test 1**: Original filename is preserved when parameter is provided
|
||||
- Uploads a file with UUID-based path but provides original filename "Apostille Sverige.pdf"
|
||||
- Verifies the database stores the original filename, not the UUID-based path
|
||||
|
||||
|
||||
2. **Test 2**: Backward compatibility is maintained
|
||||
- Calls `process_document` without the optional parameter
|
||||
- Verifies it falls back to extracting filename from path
|
||||
|
||||
@@ -170,7 +170,7 @@ Phase 1 (Current - MVP):
|
||||
Settings: DB + ENV + DEFAULT
|
||||
Encryption: Fernet (app-level)
|
||||
UI: Custom settings page
|
||||
|
||||
|
||||
Phase 2 (Production - Optional):
|
||||
Settings: DB + ENV + DEFAULT (keep)
|
||||
Secrets: HashiCorp Vault (add)
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
**Status: 100% COMPLETE (Code Implementation)** ✅
|
||||
|
||||
✅ Core settings functionality: 100% complete
|
||||
✅ Encryption implementation: 100% complete
|
||||
✅ Encryption implementation: 100% complete
|
||||
✅ Setup wizard: 100% complete
|
||||
⚠️ Testing: Manual testing recommended
|
||||
⚠️ Documentation: Enhancement recommended
|
||||
|
||||
@@ -121,7 +121,7 @@ Created comprehensive `docs/SettingsManagement.md` covering:
|
||||
# Non-admin users
|
||||
/settings → @require_login → @require_admin_access → Redirect to /
|
||||
|
||||
# Admin users
|
||||
# Admin users
|
||||
/settings → @require_login → @require_admin_access → Settings page renders
|
||||
```
|
||||
|
||||
|
||||
@@ -10,11 +10,11 @@ A comprehensive security audit was conducted on all file path operations in Docu
|
||||
|
||||
### 1. Critical: Path Traversal via GPT Metadata Filename
|
||||
|
||||
**Severity:** CRITICAL
|
||||
**Location:** `app/tasks/embed_metadata_into_pdf.py` (line 144)
|
||||
**Severity:** CRITICAL
|
||||
**Location:** `app/tasks/embed_metadata_into_pdf.py` (line 144)
|
||||
**Status:** ✅ FIXED
|
||||
|
||||
**Description:**
|
||||
**Description:**
|
||||
The `metadata["filename"]` field extracted by GPT was used directly in file path construction without sanitization. A malicious document could be crafted to make GPT return metadata containing path traversal sequences (e.g., `../../etc/passwd`), allowing file writes outside the intended directory.
|
||||
|
||||
**Attack Scenario:**
|
||||
@@ -39,11 +39,11 @@ final_path = os.path.join(processed_dir, suggested_filename) # Safe
|
||||
|
||||
### 2. Medium: Insecure String-Based Path Validation
|
||||
|
||||
**Severity:** MEDIUM
|
||||
**Location:** `app/tasks/embed_metadata_into_pdf.py` (line 188)
|
||||
**Severity:** MEDIUM
|
||||
**Location:** `app/tasks/embed_metadata_into_pdf.py` (line 188)
|
||||
**Status:** ✅ FIXED
|
||||
|
||||
**Description:**
|
||||
**Description:**
|
||||
Used insecure string-based `startswith()` check to validate file paths before deletion. This approach is vulnerable to:
|
||||
- Partial directory name matches
|
||||
- Symlink attacks (symlinks not resolved)
|
||||
@@ -78,11 +78,11 @@ if original_file_path.is_relative_to(workdir_tmp_resolved):
|
||||
|
||||
### 3. Medium: Insufficient GPT Filename Validation
|
||||
|
||||
**Severity:** MEDIUM
|
||||
**Location:** `app/tasks/extract_metadata_with_gpt.py`
|
||||
**Severity:** MEDIUM
|
||||
**Location:** `app/tasks/extract_metadata_with_gpt.py`
|
||||
**Status:** ✅ FIXED
|
||||
|
||||
**Description:**
|
||||
**Description:**
|
||||
While the GPT prompt requested specific filename format, there was no enforcement. GPT could return filenames with path separators or traversal patterns.
|
||||
|
||||
**Fix Applied:**
|
||||
@@ -221,7 +221,7 @@ All identified path traversal vulnerabilities have been successfully remediated
|
||||
|
||||
---
|
||||
|
||||
**Audit Date:** February 10, 2026
|
||||
**Auditor:** GitHub Copilot Agent
|
||||
**Scope:** All Python file path operations
|
||||
**Audit Date:** February 10, 2026
|
||||
**Auditor:** GitHub Copilot Agent
|
||||
**Scope:** All Python file path operations
|
||||
**Next Review:** Recommended within 6 months or after significant file handling changes
|
||||
|
||||
Reference in New Issue
Block a user