Skip to content

fix(security): require CLOUD_AI_MEDIA_ROOT to be an existing directory (#1216 review) - #1218

Closed
groupthinking wants to merge 2 commits into
mainfrom
claude/determined-maxwell-03lus6
Closed

fix(security): require CLOUD_AI_MEDIA_ROOT to be an existing directory (#1216 review)#1218
groupthinking wants to merge 2 commits into
mainfrom
claude/determined-maxwell-03lus6

Conversation

@groupthinking

Copy link
Copy Markdown
Owner

Canonical issue

Closes #1209 (extends PR #1216; resolves its three open Copilot review threads)

Outcome

Closes a fail-closed hole in the cloud AI local-media guard shipped by #1216: a
misconfigured CLOUD_AI_MEDIA_ROOT that points at a regular file (e.g.
/etc/passwd) or a non-existent path no longer silently allows a read — it
now raises ConfigurationError loudly at resolution time.

Scope

Risk

  • Risk level: low — the change narrows an over-permissive configuration
    path and is covered by new tests. Behaviour for a correctly configured
    directory root is unchanged.
  • Failure mode: a deployment that (mis)configured CLOUD_AI_MEDIA_ROOT to a
    file or missing path now fails loudly at first use instead of reading that one
    file. That is the intended fail-closed correction.
  • Rollback: revert this commit; the change is one guard clause in
    get_media_root() plus tests.

Verification

Run against head b269555.

  • Focused teststests/unit/test_cloud_ai_media_paths.py: 48 passed
    (44 original + 4 new: file-as-root rejection, non-existent-root rejection,
    end-to-end file-root rejection, Google permitted-file read).
  • Regression — cloud AI provider suites
    (test_cloud_ai_media_paths, test_aws_rekognition_provider,
    test_azure_vision_provider, test_google_cloud_provider): 355 passed.
  • Lintruff check clean on both changed files.
  • Formatblack --check clean on both changed files.
  • Typesmypy --strict media_paths.py: no issues in the file (the
    repo-wide errors are pre-existing in unrelated integrations/* modules).
  • Required CI — pending on this head.
  • Review threads resolved — the three Copilot threads on fix(security): sandbox local media paths in cloud AI providers (#1209) #1216 are each
    addressed by a code or test change above.

Production evidence

Not applicable as a runtime artifact: this touches only the Python package
src/youtube_extension/integrations/cloud_ai/, which has no HTTP route on main
(no route calls analyze_image; see #1216's scoping note). Behavioural evidence
is the test suite, which exercises the real filesystem via tmp_path (real
files, real directories, real symlinks).

Agent handoff


Generated by Claude Code

groupthinking and others added 2 commits August 2, 2026 07:13
All three cloud AI providers dispatch `analyze_image(image_url, ...)` on the
string's prefix: `s3://` and `http(s)://` are treated as remote sources, and
anything else fell through to an unguarded `open()`. A caller-supplied
absolute path, `../` traversal, or symlink could therefore read any file
readable by the service account.

The same unguarded sink existed in all three providers, not just the one named
in the issue:

  - aws_rekognition.py  `_prepare_image_input`
  - azure_vision.py     `_prepare_image_input`
  - google_cloud.py     inline `open()` in `analyze_image`

Introduce `cloud_ai/media_paths.py` as the single policy for local reads:

  - Local reads are opt-in via `CLOUD_AI_MEDIA_ROOT`. Unset (the default)
    disables them entirely, restricting providers to `s3://`/`https://`.
    This is fail-closed, and answers the issue's open question.
  - When a root is configured, both root and candidate are fully resolved
    (`Path.resolve()` follows symlinks) and the candidate must be contained by
    the root -- covering symlink escapes, not just lexical `..` segments.
  - Non-regular files (FIFO, device, directory) are rejected, so a FIFO placed
    inside the root cannot pin a `to_thread` worker forever.
  - Rejection raises the new typed `UnsafeMediaPathError(CloudAIError)` instead
    of silently returning empty bytes. Each provider re-raises `CloudAIError`
    subclasses unchanged so the type survives to the caller.
  - Providers read from the resolved path, not the caller string, narrowing the
    check-to-open race.
  - Error messages echo only the caller-supplied value; the resolved path is
    logged server-side for forensics rather than returned.

Adds tests/unit/test_cloud_ai_media_paths.py (44 tests) covering absolute
paths, `../` traversal, symlink escape, non-regular files, the disabled
default, and per-provider propagation. Existing local-file tests now set
`CLOUD_AI_MEDIA_ROOT`. Full cloud AI suite: 505 passed.

Closes #1209

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…review)

Address the three unresolved Copilot review threads on PR #1216, which
hardens local media paths in the cloud AI providers:

- get_media_root() now rejects a CLOUD_AI_MEDIA_ROOT that resolves to a
  regular file or a non-existent path. Previously a misconfiguration such
  as CLOUD_AI_MEDIA_ROOT=/etc/passwd passed is_relative_to() against
  itself and was handed straight to the caller, defeating the fail-closed
  policy. Misconfiguration now raises ConfigurationError loudly.
- Correct the module docstring: remote-scheme recognition is
  provider-specific (only aws_rekognition honours s3://; azure_vision and
  google_cloud treat s3:// as a local path), not the uniform s3://+https://
  claimed before.
- Add Google-provider permitted-file coverage asserting the resolved bytes
  reach vision.Image.content, closing the gap the PR coverage table claimed.

Adds 4 tests (file-as-root, nonexistent-root, end-to-end file-root
rejection, Google permitted-file read); suite now 48 passed, full cloud AI
regression 355 passed.

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

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
v0-uvai Ready Ready Preview, v0 Aug 2, 2026 12:29pm

@github-actions github-actions Bot added the python label Aug 2, 2026
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

🔍 PR Validation

⚠️ Large PR detected (737 lines changed)

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (1)
  • [‘architecture-gap’, ‘bug’, ‘ci-cd’, ‘ci/cd’, ‘copilot-rabbit’, ‘documentation’, ‘duplicate’, ‘enhancement’, ‘frontend’, ‘github_actions’, ‘good first issue’, ‘help wanted’, ‘high-priority’, ‘invalid’, ‘javascript’, ‘ml-model’, ‘needs-triage’, ‘pipeline-critical’, ‘placeholder-code’, ‘priority:high’, ‘python’, ‘python:uv’, ‘question’, ‘styling’, ‘tests’, ‘v0’]

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2a860b64-eb4f-4140-a9ec-7a8fc40bf763

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA b269555.
Ensure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice.

Scanned Files

None

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

Agent Completion Truth Gate: BLOCKED

Reasons: invalid_payload

Machine-readable verdict
{
  "details": {
    "invalid_fields": [
      "policy.agent_login",
      "policy.run_id"
    ]
  },
  "reasons": [
    "invalid_payload"
  ],
  "verdict": "blocked"
}

Workflow evidence

Copy link
Copy Markdown
Owner Author

CI status — two failing checks, both the repo-wide agent-lock policy (not fixable in this PR)

Substantive code checks are green on head b269555: lint-python, lint-frontend, build, guards, bandit, python-safety, npm-audit, gitleaks (working tree), dependency-review, validate, Canonical issue and evidence, Security Scan - javascript. (test, trivy, Security Scan - python, coverage were still finishing at time of writing; locally the focused suite is 48 passed and the cloud AI regression 355 passed — I'll flag here if any of those regress.)

Two checks fail, and both are the same admin-gated policy — not something a PR change can clear:

  1. Agent completion enforcementmissing_trusted_publication. .github/workflows/agent-completion-enforcement.yml requires a check run named Agent Lock trusted publication published by a trusted GitHub App. .github/agent-lock/trusted-publishers.json ships "trusted_check_app_slugs": [] under "custom_role_policy": "fail_closed", and its own note says an empty allowlist "intentionally blocks rather than downgrading agent work to not_applicable." So this fails on every open PR in the repo (verified on fix(security): sandbox local media paths in cloud AI providers (#1209) #1216 and fix(security): require CLOUD_AI_MEDIA_ROOT to be an existing directory (#1216 review) #1218). It can only be cleared by a protected default-branch change that provisions the trusted App — an admin action, outside any PR.

  2. agent-completion/truth-gateinvalid_payload (policy.agent_login, policy.run_id). The truth gate parses an agent-lock-manifest block from the PR body and cross-checks it against a trusted publication. Since no trusted publisher exists (see chore(deps): bump the npm_and_yarn group across 10 directories with 7 updates #1), there is no valid run_id/agent_login to declare — fabricating a manifest to satisfy the parser would defeat the fail-closed design, so I'm deliberately not doing that.

Terminal state: HALTED(awaiting_merge_approval)

The code work is complete and green; the remaining blockers are structural. Required next step (human/admin): provision a trusted publisher in .github/agent-lock/trusted-publishers.json via a protected default-branch change (this simultaneously unblocks the entire open-PR queue), then merge. PRs #1151 and #1154 already propose changes toward this gate's behavior and are worth the maintainer's attention as the root-cause fix.


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

Closing as superseded by #1216's commit 5416171.

While this PR was open, the same three Copilot review threads on #1216 were fixed directly on that branch — with equivalent changes: get_media_root() now rejects a non-directory CLOUD_AI_MEDIA_ROOT (fail-closed), the Google permitted-file branch gained end-to-end coverage, and the provider-specific remote-scheme docstring was corrected. All three threads on #1216 are now resolved.

Since the fix belongs on the PR under review rather than a fork of its old head, keeping both open would be two competing PRs for one issue. #1216 is the canonical one, so this duplicate is closed. No code is lost — #1216 carries the equivalent fix (its focused suite: 47 passed; mine: 48 — the one-test delta was a redundant end-to-end variant of a case #1216 already covers at the unit level).

#1216 remains HALTED(awaiting_merge_approval) on the same repo-wide Agent completion enforcement gate documented earlier — an admin action (provisioning a trusted publisher on a protected-branch change), not a code fix.


Generated by Claude Code

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

security: restrict local image_url paths in AWSRekognitionProvider._prepare_image_input

2 participants