Skip to content

fix: client-side security hardening against API weaknesses - #145

Open
NodeJSmith wants to merge 4 commits into
mainfrom
security/client-hardening
Open

fix: client-side security hardening against API weaknesses#145
NodeJSmith wants to merge 4 commits into
mainfrom
security/client-hardening

Conversation

@NodeJSmith

@NodeJSmith NodeJSmith commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Security Hardening: Client-Side Protections Against API Weaknesses

Pentest of the OTF API surface revealed that the backend has weak input validation (MySQL errors leak through, endpoints accept "almost anything"). This PR adds client-side guardrails so the library protects users even when the API doesn't.

Changes

1. Path segment validation (utils.py, studio_client.py, workout_client.py)

  • New validate_identifier() function rejects path traversal (../), slashes, null bytes, percent-encoding, whitespace, and oversized strings (>200 chars)
  • Applied at all choke points where user-supplied strings enter URL paths: get_booking_uuid(), get_booking_id(), get_class_uuid(), get_class_id(), get_studio_detail(), get_studio_services(), get_performance_summary(), get_telemetry()
  • Verified: yarl.URL.build() silently normalizes ../ instead of rejecting it, so the library was constructing unintended request paths

2. Credential redaction in exceptions (exceptions.py)

  • OtfRequestError now strips Authorization, x-amz-security-token, and x-amz-date headers from stored request objects
  • Prevents token leakage when exceptions are logged or sent to error-reporting services (Sentry, Datadog, etc.)

3. Name field validation (member_api.py)

  • update_member_name() now rejects control characters, HTML-like content (<>), empty/whitespace-only strings, and names over 50 characters
  • Guards against the API's weak validation layer (the SMS endpoint already demonstrated it passes raw MySQL errors through)

4. IDOR response ownership verification (member_api.py)

  • get_member_detail() now verifies the returned member_uuid matches the authenticated user
  • Guards against potential IDOR in the v1 API which uses explicit member UUIDs in request paths rather than token-derived identity (/me/)

5. Response size cap (client.py)

  • _handle_response() rejects responses over 10 MB before attempting JSON parsing
  • Prevents OOM from unexpectedly large API responses

6. TrendType enum enforcement (trend_api.py)

  • get_workout_stats() parameter narrowed from TrendType | str to TrendType
  • Closes a path injection vector — arbitrary strings previously reached /users/me/workout-stats/{stats_key}

7. Cache directory permissions (cache.py)

  • Cache directory now created with mode 0700 (owner-only access)
  • Re-secures on each access to fix directories created by previous versions with 0755
  • Prevents other local users from reading cached Cognito tokens and device credentials

Testing

  • All 214 existing tests pass
  • Smoke-tested all new validation paths (empty strings, path traversal, slashes, percent-encoding, null bytes, oversized strings, None request handling, header redaction)
  • Lint and format checks clean

🤖 Generated with Claude Code

https://claude.ai/code/session_01LCQ31EaGsi9Kxr8J588kfc

Summary by CodeRabbit

  • Bug Fixes

    • Added safeguards against oversized API responses.
    • Improved validation for member names, identifiers, and studio, workout, booking, and trend requests.
    • Prevented mismatched member details from being accepted.
  • Security

    • Restricted cache directory access to the local user.
    • Redacted sensitive request headers in error details.
    • Blocked unsafe path values, harmful names, and invalid request data.

Seven protections added:

1. Path segment validation (validate_identifier) — rejects path traversal,
   slashes, null bytes, percent-encoding, and oversized strings in all
   user-supplied identifiers (booking_uuid, booking_id, class_uuid, class_id,
   studio_uuid, performance_summary_id). Applied at extraction choke points
   in utils.py and directly in studio_client.py and workout_client.py.

2. Credential redaction in exceptions — OtfRequestError now strips
   Authorization, x-amz-security-token, and x-amz-date headers from
   stored request objects, preventing token leakage through error reporting
   services (Sentry, Datadog) or exception logging.

3. Name field validation — update_member_name rejects control characters,
   HTML-like content (<>), empty/whitespace-only strings, and names over
   50 characters before sending to the API's weak validation layer.

4. Response ownership verification — get_member_detail checks that the
   returned member_uuid matches the authenticated user's UUID, guarding
   against potential IDOR in the v1 API's explicit-UUID path pattern.

5. Response size cap — _handle_response rejects responses over 10 MB
   before JSON parsing to prevent OOM from unexpectedly large payloads.

6. TrendType enum enforcement — get_workout_stats no longer accepts raw
   strings, closing a path injection vector via the stats_key URL segment.

7. Cache directory permissions — cache directory created with 0700 and
   re-secured on each access, preventing other local users from reading
   cached Cognito tokens and device credentials.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCQ31EaGsi9Kxr8J588kfc
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 5, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-05T19:21:00.297152Z 013e3d5 New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The changes add shared identifier validation, response-size limits, member validation, cache permission enforcement, and request-header redaction. The trend API now requires TrendType, and Claude worktree directories are ignored.

Changes

Security hardening

Layer / File(s) Summary
Identifier validation and endpoint wiring
src/otf_api/api/utils.py, src/otf_api/api/bookings/booking_client.py, src/otf_api/api/studios/studio_client.py, src/otf_api/api/workouts/workout_client.py, src/otf_api/api/trends/trend_api.py
The API validates identifiers before requests and when extracting booking or class identifiers. get_workout_stats now requires TrendType.
Response and member validation
src/otf_api/api/client.py, src/otf_api/api/members/member_api.py
Responses larger than 10 MB are rejected before JSON parsing. Member names are validated, and member details must match the authenticated client.
Secure local data and exception requests
src/otf_api/cache.py, src/otf_api/exceptions.py, .gitignore
Cache directories use owner-only permissions. Sensitive request headers and unread request bodies are sanitized in stored exceptions. Claude worktree directories are ignored.
Security validation coverage and conventions
tests/test_api/test_security_hardening.py, .claude/agent-memory/*
Tests cover identifier, request, member, cache, and trend validation. Agent memory documents the related validation conventions.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: 🟡 Moderate · up to 013e3

The hardening changes improve validation and redaction, but failed requests can still retain session or proxy credentials in exception-related request and response objects. Those credentials may reach logs or error-reporting consumers, so the remaining redaction gaps should be resolved before merge.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.03% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 69 functions across 10 files. (4 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the pull request's main change: client-side security hardening against API weaknesses.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 42.03% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 69 functions across 10 files. (4 skipped: 4 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@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: fb352e3160

ℹ️ 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/otf_api/exceptions.py Outdated
Comment thread src/otf_api/api/trends/trend_api.py
Comment thread src/otf_api/api/client.py

@coderabbitai coderabbitai 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.

Actionable comments posted: 7


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Team

Run ID: f5fec942-0768-4378-9be7-cdabe514e3d6

📥 Commits

Reviewing files that changed from the base of the PR and between 6e2f11d and fb352e3.

📒 Files selected for processing (9)
  • .gitignore
  • src/otf_api/api/client.py
  • src/otf_api/api/members/member_api.py
  • src/otf_api/api/studios/studio_client.py
  • src/otf_api/api/trends/trend_api.py
  • src/otf_api/api/utils.py
  • src/otf_api/api/workouts/workout_client.py
  • src/otf_api/cache.py
  • src/otf_api/exceptions.py

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment thread src/otf_api/api/client.py
Comment thread src/otf_api/api/members/member_api.py Outdated
Comment thread src/otf_api/api/trends/trend_api.py
Comment thread src/otf_api/cache.py Outdated
Comment thread src/otf_api/cache.py Outdated
Comment thread src/otf_api/exceptions.py Outdated
Comment thread src/otf_api/exceptions.py Outdated
- Widen OtfRequestError type annotations to Response | None / Request | None,
  drop type: ignore at IDOR call site
- Move httpx import to top of exceptions.py (no lazy imports rule)
- Preserve request body in _sanitize_request (include content=)
- Drop dead hasattr guard on MemberDetail.member_uuid (required field)
- Add runtime isinstance check for TrendType enforcement
- Fix MAX_RESPONSE_SIZE comment to accurately describe protection boundary
- Add validate_identifier call in BookingApi.get_booking (missed gap)
- Use ord(c) < 32 instead of c < ' ' for control char check clarity
- Add 30 unit tests for all new security validation functions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCQ31EaGsi9Kxr8J588kfc

@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: 6cbe74a64a

ℹ️ 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/otf_api/exceptions.py Outdated
Comment thread src/otf_api/api/members/member_api.py Outdated
NodeJSmith and others added 2 commits September 5, 2026 13:06
LLM patterns:
- Remove HTML bracket check from validate_name (context-blind XSS defense
  in a JSON API client with no rendering surface)
- Reframe IDOR comment as sanity-check, raise ValueError instead of
  OtfRequestError (not an HTTP error shape)
- Revert OtfRequestError type widening — response/request stay non-Optional
- Replace TrendType type narrowing with validate_identifier (consistent
  pattern, non-breaking API change)

Deferred debt:
- Add validate_identifier to BookingClient (delete_booking, get_booking,
  delete_booking_new) to match StudioClient/WorkoutClient convention

Nitpick:
- Extract _MAX_NAME_LENGTH, _MAX_IDENTIFIER_LENGTH, _CACHE_DIR_MODE constants
- Drop underscore prefixes on ensure_secure_directory and validate_name
  (both directly tested, not behaving as private)
- Unify parameter naming (field_name → name)
- Remove redundant TYPE_CHECKING import block from exceptions.py
- Remove redundant validate_identifier call in booking_api.py (now in client)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCQ31EaGsi9Kxr8J588kfc
…forcement, cache hardening

Six fixes from Codex and CodeRabbit review feedback:

1. Add koji-member-email and koji-member-id to _SENSITIVE_HEADERS
2. Sanitize response.request and original_exception.request references
   to close all credential leak paths through retained objects
3. Guard request.content access against RequestNotRead for streaming bodies
4. Enforce TrendType enum at runtime (isinstance check + TypeError)
5. Raise OSError on cache chmod failure instead of silently continuing
6. Re-check cache directory permissions on every get_cache() call

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCQ31EaGsi9Kxr8J588kfc

@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: 013e3d56b1

ℹ️ 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/otf_api/api/client.py
Comment on lines +241 to +242
content_length = len(response.content)
if content_length > MAX_RESPONSE_SIZE:

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 Apply the response cap before parsing HTTP errors

The fresh narrowing of MAX_RESPONSE_SIZE to a JSON-parsing limit still misses every non-2xx response: in the inspected OtfClient.do() flow, raise_for_status() branches to get_json_from_response(e.response) at lines 147–149, which calls response.json(), and _handle_response() never runs. Thus a server returning a very large 4xx/5xx JSON body—especially a retryable 5xx—can still incur the parsing cost this cap is intended to prevent; enforce the limit before parsing the error response as well.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai 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.

Actionable comments posted: 8


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Team

Run ID: 4f1e8695-3909-4198-83b3-5f76c1e74f51

📥 Commits

Reviewing files that changed from the base of the PR and between fb352e3 and 013e3d5.

📒 Files selected for processing (12)
  • .claude/agent-memory/code-reviewer/MEMORY.md
  • .claude/agent-memory/code-reviewer/exceptions_optional_fields.md
  • .claude/agent-memory/integration-reviewer/MEMORY.md
  • .claude/agent-memory/integration-reviewer/identifier-validation-convention.md
  • src/otf_api/api/bookings/booking_client.py
  • src/otf_api/api/client.py
  • src/otf_api/api/members/member_api.py
  • src/otf_api/api/trends/trend_api.py
  • src/otf_api/api/utils.py
  • src/otf_api/cache.py
  • src/otf_api/exceptions.py
  • tests/test_api/test_security_hardening.py

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.

---

<!-- 2026-09-05 (updated) -->
`src/otf_api/exceptions.py`'s `OtfRequestError` declares `response: httpx.Response` and

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a top-level heading after the front matter.

markdownlint-cli2 reports MD041 because the first prose line is not a level-one heading. Add # OtfRequestError optional fields before this text.

🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 9-9: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

Source: Linters/SAST tools

@@ -0,0 +1,2 @@
# Memory Index

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a blank line after the heading.

markdownlint-cli2 reports MD022 because the list starts immediately after the level-one heading.

🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 1-1: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

Source: Linters/SAST tools

@@ -0,0 +1,2 @@
# Memory Index
- [OtfRequestError optional fields](exceptions_optional_fields.md) — response/request typed non-Optional but sometimes passed as None with type:ignore; crashes documented `e.request.method` usage.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Describe the None call pattern as historical.

This index says response and request are “sometimes passed as None.” The linked memory file states that this call site was removed. Use past tense so future reviews do not treat the stale pattern as current behavior.

type: project
---

<!-- 2026-09-05 -->

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a top-level heading after the YAML front matter.

markdownlint reports MD041 because the document body does not start with a level-one heading. Add # Identifier validation convention after the front matter.

Source: Linters/SAST tools

Comment thread src/otf_api/exceptions.py
Comment on lines +37 to +42
"authorization",
"x-amz-security-token",
"x-amz-date",
"koji-member-email",
"koji-member-id",
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Sensitive Data Exposure (CWE-200): Exposure of Sensitive Information to an Unauthorized Actor

Reachability: Internal · Exploitability: Moderate

Redact all retained request credentials.

_sanitize_request copies unlisted credential headers and readable request bodies. A failed request can retain Cookie, Proxy-Authorization, or body credentials in stored exception requests and error reports.

Redact standard credential headers, drop request bodies, and add tests for both cases.

Proposed fix
             "authorization",
+            "cookie",
+            "proxy-authorization",
             "x-amz-security-token",
             "x-amz-date",
             "koji-member-email",
             "koji-member-id",
         }
     )

-        try:
-            content = request.content
-        except httpx.RequestNotRead:
-            content = b""
-
         return httpx.Request(
             method=request.method,
             url=request.url,
             headers=sanitized_headers,
-            content=content,
+            content=b"",
         )

Comment thread src/otf_api/exceptions.py
Comment on lines 55 to +61
self.response = response
self.request = request
self.request = sanitized_request

# The response and original exception hold references to the raw request
# with unsanitized auth headers. Mutating these shared objects is intentional —
# error-reporting tools serialize them, and we must close every leak path.
self.response.request = sanitized_request

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🤖 get_repo_knowledge executed:

get_repo_knowledge NodeJSmith/otf-api /tmp/coderabbit-repo-knowledge/nodejsmith-otf-api-52973f72/learnings /tmp/coderabbit-repo-knowledge/nodejsmith-otf-api-52973f72/conventions

Length of output: 3836


🏁 Script executed:

#!/bin/bash
printf '%s\n' '--- exceptions.py ---'
cat -n src/otf_api/exceptions.py | sed -n '20,95p'
printf '%s\n' '--- client call sites ---'
cat -n src/otf_api/api/client.py | sed -n '195,265p'
printf '%s\n' '--- response/error serialization references ---'
rg -n --glob '*.py' 'OtfRequestError|\.response\b|Set-Cookie|error.?report|capture_exception|sentry|serialize' src tests | head -200

Repository: NodeJSmith/otf-api

Length of output: 11726


Sensitive Data Exposure (CWE-200): Exposure of Sensitive Information to an Unauthorized Actor

Reachability: Internal · Exploitability: Moderate

Sanitize response credential headers before retaining the response.

The constructor stores the raw httpx.Response. Replacing response.request does not redact response.headers. If an error response contains Set-Cookie: session=..., the credential remains in err.response.headers.

Store a sanitized response, or redact every Set-Cookie value before retaining it. Add a test with an error response that contains Set-Cookie.

class TestValidateIdentifier:
"""Tests for the path segment validation helper."""

def test_valid_uuid(self):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
set -euo pipefail
ruff check tests/test_api/test_security_hardening.py

Repository: NodeJSmith/otf-api

Length of output: 50374


🤖 get_repo_knowledge executed:

get_repo_knowledge NodeJSmith/otf-api /tmp/coderabbit-repo-knowledge/nodejsmith-otf-api-52973f72/conventions /tmp/coderabbit-repo-knowledge/nodejsmith-otf-api-52973f72/learnings

Length of output: 2893


🏁 Script executed:

#!/bin/bash
set -euo pipefail
printf '%s\n' '--- tracked config files ---'
git ls-files | rg '(^|/)(pyproject\.toml|ruff\.toml|\.ruff\.toml|setup\.cfg|tox\.ini)$'
printf '%s\n' '--- Ruff configuration references ---'
rg -n -C 3 'ruff|lint|D102|ANN201|ANN205|PTH103|SIM117|extend-select|select|ignore|per-file-ignores|unfixable' pyproject.toml ruff.toml .ruff.toml setup.cfg tox.ini 2>/dev/null || true
printf '%s\n' '--- target file structure and first 230 lines ---'
wc -l tests/test_api/test_security_hardening.py
sed -n '1,230p' tests/test_api/test_security_hardening.py

Repository: NodeJSmith/otf-api

Length of output: 11442


Make the test module pass the repository’s Ruff policy.

ruff.toml enables ANN and D, but this file has non-auto-fixable ANN201, ANN205, ANN001, and D102 findings. Add the required annotations and docstrings, or exclude these rules for test methods.

🧰 Tools
🪛 Ruff (0.16.3)

[warning] 22-22: Missing docstring in public method

(D102)

Sources: Coding guidelines, Linters/SAST tools

def test_tightens_existing_loose_permissions(self):
with tempfile.TemporaryDirectory() as tmp:
path = f"{tmp}/test_cache"
os.makedirs(path, mode=0o755)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Force loose permissions before testing permission tightening.

os.makedirs(path, mode=0o755) is filtered by the process umask. With umask 077, it creates mode 0700, so this test passes even if ensure_secure_directory() stops correcting existing directory permissions.

Proposed fix
-            os.makedirs(path, mode=0o755)
+            Path(path).mkdir()
+            Path(path).chmod(0o755)
             ensure_secure_directory(path)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
os.makedirs(path, mode=0o755)
Path(path).mkdir()
Path(path).chmod(0o755)
ensure_secure_directory(path)
🧰 Tools
🪛 Ruff (0.16.3)

[warning] 194-194: os.makedirs() should be replaced by Path.mkdir(parents=True)

(PTH103)

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