Skip to content

Develop v2.1.0 - #1938

Merged
Ooka-Masashi merged 13 commits into
release_v2.1.0from
develop_v2.1.0
Oct 6, 2026
Merged

Ooka-Masashi merged 13 commits into
release_v2.1.0from
develop_v2.1.0

Conversation

@Ooka-Masashi

@Ooka-Masashi Ooka-Masashi commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

概要 (Summary)

関連Issue / チケット (Related Issues)

  • close #

変更タイプ (Type of Change)

  • 🚀 新機能追加 (Feature)
  • 🐛 バグ修正 (Bug Fix)
  • 🔒 セキュリティ修正 (Security Fix)
  • 🚫 機能のクローズ・非公開化・削除 (Feature Deprecation/Disable)
  • ⚠️ 破壊的変更・データ移行を伴う修正 (Breaking Change / Migration)
  • 📚 仕様書・マニュアル・APIリストの更新 (Documentation)

🤖 0. CI 自動チェック (API Inventory Drift)

PR ごとに WEKO3 コンテナを起動し、url_map のダンプ・台帳との突き合わせ・変更行の
到達可否測定を自動実行する。結果は PR コメントと Actions の artifact
(api-inventory-summary) に出る。

このリポジトリは public のため、台帳もベースラインも同梱していない。
実データはプライベートリポジトリ RCOSDP/weko-secret にあり、CI は Secret 経由で取得する。
以降この文書では、そこを単にプライベートリポジトリと呼ぶ。

台帳はブランチごとに内容が違うため、CI は weko 側と同名のブランチを
プライベートリポジトリから探して使う(head → base → 既定ブランチ の順)。
採用されたブランチ名は PR コメントの冒頭に出るので、件数を読む前にそこを見ること。
対応ブランチが無い場合は既定ブランチと比較され、コメント冒頭に警告が出る。
その件数は当てにならないので、PASS でも「確認済み」と読まないこと。
詳細: tools/api-inventory/ci/README.md §3a

  • CI が PASS している、または FAIL の各項目に対処済み
    Secret (API_INVENTORY_REPO / API_INVENTORY_SSH_KEY) が未設定のリポジトリ、
    および fork からの PR では、このジョブは何もせずスキップされる。

API を追加・変更した場合(必須)

  • プライベートリポジトリ側の作業ブランチを、この PR のブランチと同名で切った
    この PR が fix/issue62569 → develop_v2.0.4 なら、プライベート側も
    fix/issue62569 → develop_v2.0.4。同名にしておけば台帳 PR が未マージでも
    CI がそれを見るので、2つの PR のマージ順を気にしなくてよい。
  • プライベートリポジトリの api_snapshot.json を更新し、対応する PR を出した
    bash export WEKO_API_INVENTORY_DIR=/path/to/weko-secret ./install.sh python3 tools/api-inventory/scripts/snapshot.py --out "$WEKO_API_INVENTORY_DIR/api_snapshot.json"
    更新しないと CI が落ちる。公開リポジトリのコード変更とは別の PRになる。
  • プライベートリポジトリの weko3_api_list_full.tsv に行を追加・更新し、
    build_checklist.py で 24 列版を再生成した(未収載だと reconcile が FAIL する)
  • 台帳・スナップショット・実測結果をこの公開リポジトリにコミットしていない
    (git status に *.tsv / api_snapshot.json が出ていないこと)

FAIL したときの対処(要約)

まず PR コメント冒頭の台帳ブランチを見る。警告が出ていれば、件数を追う前に
プライベートリポジトリ側の対応ブランチを用意すること(比較相手が違うので件数に意味がない)。

ジョブが落ちる条件は 3 つある。PR コメントのどのセクションに件数が出ているかで切り分ける。

落ちた場所 落ちる条件
ベースラインとの差分 (drift.md) G1〜G7 のいずれかに該当
台帳との突き合わせ (reconcile.md) A + B + C + D + E の合計が 1 件以上
変更行の到達可否測定 (probe) G8 / G9 に該当(結果は artifact に含めないので Actions のログで件数を見る)
検出 意味 対処
G1 / G2 新規経路に認証系デコレータが無い / 認証系デコレータが削除された 実装を直す。意図的な公開なら台帳に根拠を書いてベースライン更新
G3 認証・認可デコレータのコメントアウトが増えた 原則やり直し。残す場合は理由をコード中のコメントに明記する
G4 *_PERMISSION_FACTORY / CSRF 保護 等が危険側の値に変わった 原則やり直し
G5 ModelView の can_delete / can_export が False → True 意図的なら台帳の data_op を更新
G6 静的解析で属性が取れない経路が追加された 台帳に行を追加してレビューする(外部ライブラリ由来など)
G7 依存パッケージの更新で経路が増減した 増えた経路は台帳に追加。消えた経路は行を削除するか allow に登録
G8 台帳で data_op が作成/更新/削除の経路に、未認証で到達した 原則やり直し。意図的な公開なら台帳の根拠を更新。data_op の記載誤りなら台帳を直す
G9 台帳では遮断なのに実測で到達(認可の回帰) 原則やり直し
reconcile A / E 実機にあるが台帳に無い(A: URI 単位 / E: 同一 URI の endpoint 単位) 台帳に行を追加する
reconcile B 台帳にあるが実機の url_map に無い 理由を確認し、正当なら allow に登録する
reconcile C / D メソッド・app 列の記載誤り 台帳を実機に合わせる

reconcile B のうち、実機に存在しないことが正当な行(プラグイン未登録・config で無効等)は
プライベートリポジトリの reconcile_allow.json に理由付きで登録する。理由なしの登録は不可。
登録済みの行は B'(既知・許容)として集計され、E'(endpoint が実機に無い)と併せてゲート対象外になる。

W1〜W6 は WARN でゲートは通るが、レビューでは見ること
(ModelView の追加 / 実装本体の変化 / HTTP メソッド・URL の変化 / 監視対象 config の変化 /
依存パッケージの版の変化)。特に W6(依存の版)は、ベースラインを CI と異なる環境で作ると
毎回出続けて形骸化するため、ベースラインは install.sh で作った環境から生成する。

CI の出力は件数のみ。該当した経路名は Actions には出ないので、プライベートリポジトリ側の
完全版レポート(--summary-only なしで再実行したもの)で確認すること
(このリポジトリは public で、ログ・artifact・PR コメントは誰でも読めるため)。


🔒 1. セキュリティ & API アクセス制御チェック (必須)

認証・認可 (Authentication & Authorization)

  • 新規/変更された Blueprint・View・REST リソースに適切なデコレータ / Permission を設定している
    • 例: @login_required, @pass_record, need(...), Invenio Access Action
    • /api/* では Permission.require(http_exception=403) を使うこと。
      @login_required は API アプリに security.login が無いため 401 ではなく 500 になる
    • CI: G1 / G2 が自動検出(デコレータの有無・削除)
  • 状態変更・破壊的メソッド (POST / PUT / PATCH / DELETE) の権限が正しく制限されている
    • CI: G8 が変更行を未認証で実測(使い捨て環境なので --allow-writes 付きで
      GET / HEAD 以外も叩く)
  • 未ログイン(Anonymous)状態でアクセスした際、意図しないデータ取得・変更が拒絶される
    • CI: G8 / G9 が変更行を実測。ただし測定対象は変更行のみ、かつ既定プロファイルで
      起動した経路のみ。ワークフロー系など未解決プレースホルダの行は skip される
  • 認可を config の permission factory に委ねている場合、None で無効化していない
    • CI: G4 が *_PERMISSION_FACTORY 等を監視

機能クローズ・非公開化の場合 (Feature Disable)

  • UI(画面・ボタン)の非表示だけでなく、バックエンド API(ルーティング・View)も完全に遮断されている
  • 無効化状態で直接 API を叩いた場合、404 Not Found または 403 Forbidden が返ることを確認した

🧪 2. テストコード観点チェック (pytest / Invenio Test Suite)

権限・異常系テスト (Negative & Authorization Tests)

  • 未認証アクセス (Anonymous): トークン/セッションなしのリクエストで 401 Unauthorized または 403 Forbidden / 404 Not Found が返ることを検証するテストがある
  • 権限不足ユーザー (Forbidden): 閲覧権限のみのユーザーが更新/削除 API を叩いた際に 403 になるテストがある
  • 無効化/非公開機能の遮断テスト: 対象機能が無効化されている場合、エンドポイントが 404 / 403 を返すテストがある

境界値・入力バリデーションテスト (Boundary & Validation)

  • 不正なパラメータ(巨大ファイル、異常な MIME タイプ、無効な JSON/XML スキーマ、SQLi/XSS ペイロード等)で適切に 400 Bad Request / バリデーションエラーが返るテストがある

データ整合性・トランザクションテスト (Integrity & Rollback)

  • ファイルストレージ(S3/ローカル)書き込み失敗時や DB エラー時に、中途半端なレコードやゴミファイルが残らずロールバックされるテストがある

🛡️ 3. データ保護 & 破壊的変更防止チェック (Data Safety)

  • 物理削除・上書きの安全性:
    • ファイル・アイテム・メタデータの完全削除/置換処理に、意図しない一括削除や別レコードへの誤適用リスクがない
    • 論理削除、バージョン管理、バックアップ等のロールバック機構が考慮されている
  • トランザクション整合性:
    • DB 更新とストレージ操作がアトミックに管理されている

⚙️ 4. マイグレーション & システム影響チェック (Invenio / WEKO3 Stack)

データベース (DB / Alembic)

  • invenio alembic upgrade(適用)および downgrade(ロールバック)スクリプトを作成・検証した
  • 既存データに対する破壊的変更(カラム削除、型変更、NOT NULL 制約追加等)の移行スクリプト/データパッチを用意した

検索インデックス (Elasticsearch / OpenSearch)

  • マッピング定義変更の有無を確認した
  • インデックス再作成(Reindex)やエイリアス切り替え手順を準備・検証した

設定 & 非同期処理 (Config / Celery / Cache)

  • invenio.cfg / 環境変数のデフォルト値を設定した
  • Celery タスクのシグネチャ変更によるキュー滞留・不整合が発生しない
  • キャッシュ(Redis/Memcached)のパージが必要か確認した

📚 5. ドキュメント・仕様書更新チェック (weko-document)

  • API インベントリ: ツールは本リポジトリの tools/api-inventory/、
    台帳・調査記録はプライベートリポジトリ(public リポジトリには置かない)。
    §0 のチェック項目で対応済みなら、ここは確認のみ。
    • エンドポイントの追加・変更・廃止、メソッド、認証・認可要件、リクエスト/レスポンス仕様を更新した
    • 調査記録(weko3_api_auth_findings.md)もプライベートリポジトリに置く。台帳は二重管理しない
  • WEKO3 機能仕様書:
    • 対象機能の仕様追加・変更・クローズ(非公開化)内容を反映した
  • 各種マニュアル (管理者 / 利用者マニュアル):
    • 画面導線・操作手順・権限仕様の変更を反映した
  • 更新不要な場合(理由):

📋 6. 動作検証エビデンス (Verification Evidence)

テスト実行結果

cd modules/<対象モジュール> && pytest
# -> PASS

CI の成果物 (artifact: api-inventory-summary)

ファイル 内容
drift.md ベースラインとの差分(件数のみ)
reconcile.md 台帳と実機の突き合わせ(件数のみ)

明細(該当した経路名・実測結果)は公開できないため artifact に含めていない。
プライベートリポジトリ側で同じコマンドを --summary-only なしで実行して確認する。

手動で確認したこと

Summary by Sourcery

Harden download and index access behavior while migrating restricted Nginx authentication from PHP to Python.

New Features:

  • Allow public access to widget files required by publicly visible pages.
  • Add database support for read-only file storage access credentials.
  • Migrate the restricted Nginx login endpoint from PHP to Python and support architecture-specific Shibboleth paths.

Bug Fixes:

  • Prevent secret and one-time download URLs from being reused for a different record or file.
  • Return intended 403 and 404 responses instead of converting handled HTTP errors into 500 responses.
  • Correct private-index detection for records without indexes and indexes with future or non-public visibility.
  • Remove obsolete PHP-based restricted login assets and configuration.

Enhancements:

  • Strengthen download URL validation and make mismatched URL targets indistinguishable from nonexistent URLs.
  • Update Nginx dependencies and FastCGI configuration for the Python login flow.

Build:

  • Update Nginx container dependencies and architecture-specific configuration for fcgiwrap and Python requests.

Deployment:

  • Replace PHP-FPM-based restricted login serving with fcgiwrap-backed Python serving in Nginx deployments.

Tests:

  • Expand authorization, download URL target-matching, private-index decision, and HTTP status handling coverage.

Chores:

  • Refresh the freeze file and remove obsolete PHP login files and configuration.

ivis-miyachi and others added 13 commits July 31, 2026 13:03
…e urls

- Add an identity check between the issued secret/one-time URL record (record_id, file_name) and the item/file actually being requested.
- Fix the four management-operation handlers: each caught its own abort(403)/abort(404) in a broad `except Exception` and turned it into a 500 response.
- Fix is_private_index() to correctly reject an item when it belongs to multiple indexes that are all non-public.
- Add and update unit tests accordingly.
pip freeze の出力に、取得時に使ったユーザ名とアクセストークンが
URL に埋め込まれたまま残っていた。RCOSDP/weko は public なので
認証なしで取得でき、認証情報は不要。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
d6d92a5 / 3de23b2(fix build error)で nginx/Dockerfile が旧
nginx/Dockerfile.arm64 と同じ内容に置き換わり、7e536799c で行った
login.php → login.py の切り替えが失われていた。weko.conf の /secure/、
supervisord.conf の fcgiwrap、WEKO_ACCOUNTS_SHIB_IDP_LOGIN_URL は
login.py を前提にしているため、このままでは Shibboleth でログインできない。

- nginx/Dockerfile, Dockerfile.arm64: fcgiwrap と python3-requests を入れ、
  secure/login.py を配置する
- login.php による運用は終わっているので PHP を取り除く
  - php-fpm / php-curl のインストールと php-fpm の設定書き換えを削除
  - supervisord.conf の php-fpm を削除
  - weko-ams.conf / weko-ams-restricted.conf の .php 用 location と、
    weko.conf のコメントアウト済みの同 location を削除
  - nginx/login.php, index.php, phpinfo.php を削除
- login.py が使う requests は pip ではなく apt の python3-requests で入れる
  (Dockerfile.ams の python3-pip / pip3 install requests も置き換え)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
supervisord.conf は shibauthorizer / shibresponder を
/usr/lib/x86_64-linux-gnu/shibboleth/ から起動するが、arm64 では
/usr/lib/aarch64-linux-gnu/shibboleth/ にあるため FATAL になり、
/secure/ の shib_request が通らず Shibboleth でログインできなかった。

supervisord.conf は Dockerfile と Dockerfile.arm64 で共有しているので、
ビルド時に uname -m でパスを書き換える。x86_64 では書き換わらない。
両 Dockerfile は同じ内容に保つ。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix issue 62892: record/file match on secret and one-time url
- incubator-pagespeed-ngx は x86_64 用の PSOL (1.13.35.2-x64) を取り込む
  ため arm64 ではコンパイルできない。weko-ams.conf などでも使っていない
  ので、Dockerfile と同じく組み込まないようにする
- ビルドした .deb を focal_amd64.deb と決め打ちせず、ワイルドカードで取る
- supervisord.conf の shibauthorizer / shibresponder のパスを、Dockerfile と
  同じくビルド時に uname -m で書き換える
- nginx 1.18.0 時代のコメントアウト行を削除する

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
facd5ab で /widget/uploaded/<filename>[/<community_id>] (uploaded_file)
に System/Repository/Community Administrator のロール必須が付いた。この URL は
ウィジェットに埋め込んだ画像などを公開ページのブラウザが直接読み込むもの
(WidgetBucket.save_file が返す URL) なので、未ログインでは 401、一般ユーザでは
403 になり、公開ページのウィジェット画像が表示されなくなっていた。

- uploaded_file のロール必須を外す。load_widget_type (管理画面の
  ウィジェット作成でだけ使う) は管理者限定のまま
- test_uploaded_file が同名で2つ定義され、後の定義が前を上書きしていたので、
  未ログイン・各ロールで取得できることを確かめる1つのテストにまとめる
- test_load_widget_type が users[id]['obj'] でログインしていたため、
  users[4] (generaluser) の obj が sysadmin になっている fixture では
  システム管理者としてログインし、403 の確認が失敗していた。email で
  ログインするように直す

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix: freeze.txt の URL から認証情報を除く
fix(nginx): Shibboleth ログインスクリプトを login.py に戻し、PHP を取り除く(arm64 対応を含む)
fix(weko-gridlayout): ウィジェットのアップロードファイルを未ログインでも取得できるようにする
@sourcery-ai

sourcery-ai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

This release-development PR combines download URL security hardening, public widget asset access, revised private-index evaluation, a read-only storage-credential migration, and migration of nginx/Shibboleth authentication from PHP-FPM to Python/fcgiwrap, with corresponding regression coverage and deployment configuration updates.

Sequence diagram for download URL target validation

sequenceDiagram
    participant Client
    participant DownloadView
    participant DownloadUtils
    participant Database

    Client->>DownloadView: copy_secret_url(pid, record, filename)
    DownloadView->>Database: FileSecretDownload.get_by_id(secret_url_id)
    Database-->>DownloadView: url_record
    DownloadView->>DownloadUtils: ensure_url_record_matches(url_record, pid, filename)
    alt target mismatch or missing URL
        DownloadView-->>Client: 404 Not Found
    else target matches
        DownloadView->>DownloadUtils: create_download_url(url_record)
        DownloadView-->>Client: download URL
    end

    Client->>DownloadUtils: validate_url_download(record, filename, token)
    DownloadUtils->>DownloadUtils: convert_token_into_obj(token, is_secret_url)
    DownloadUtils->>DownloadUtils: ensure_url_record_matches(url_obj, record_id, filename)
    alt token does not match requested file
        DownloadUtils-->>Client: unavailable response
    else token matches
        DownloadUtils-->>Client: continue download validation
    end
Loading

Entity relationship diagram for read-only file credentials

erDiagram
    FILES_LOCATION {
        string readonly_access_key
        string readonly_secret_key
    }
Loading

Flow diagram for private-index evaluation

flowchart TD
    Start["Record supplied to is_private_index"] --> Path["Read record path"]
    Path --> Empty{path is empty}
    Empty -->|yes| Private["Return True"]
    Empty -->|no| PublicCheck["Indexes.is_public_state_and_not_in_future(list_index)"]
    PublicCheck --> Result["Return not public state result"]
Loading

File-Level Changes

Change Details Files
Harden download URL validation and management against cross-record/file reuse while preserving intended HTTP error responses.
  • Require secret and one-time URL records to match the requested record ID and filename.
  • Return 404 for missing or mismatched URL records and re-raise Flask HTTP exceptions instead of converting them to 500.
  • Expand tests for token type isolation, mismatch cases, permissions, deletion safety, and error statuses.
modules/weko-records-ui/weko_records_ui/utils.py
modules/weko-records-ui/weko_records_ui/views.py
modules/weko-records-ui/tests/test_utils.py
modules/weko-records-ui/tests/test_views.py
Change widget-uploaded file delivery to support anonymous access for assets embedded in public pages.
  • Remove administrator/community role enforcement from the GET endpoint.
  • Cover anonymous, role-based, multiple-community, and default-community access paths in tests.
modules/weko-gridlayout/weko_gridlayout/views.py
modules/weko-gridlayout/tests/test_views.py
Delegate private-index determination to the index-tree API and clarify no-index behavior.
  • Treat records without an index path as private.
  • Use the index tree's public-state and publication-date evaluation.
  • Replace legacy path-list tests with a decision table for public, non-public, and missing-index cases.
modules/weko-records-ui/weko_records_ui/utils.py
modules/weko-records-ui/tests/test_utils.py
Add optional read-only object-storage credentials to file locations through a database migration.
  • Add nullable readonly access-key and secret-key columns.
  • Provide downgrade logic that removes both columns.
modules/invenio-files-rest/invenio_files_rest/alembic/faf4faa08601_add_column_file_readonly_keys.py
Replace the nginx/Shibboleth PHP login path with the Python/fcgiwrap path and improve architecture portability.
  • Remove PHP-FPM packages, processes, routes, and PHP scripts.
  • Install fcgiwrap and Python requests and use login.py.
  • Rewrite Shibboleth library paths according to the build architecture.
nginx/Dockerfile
nginx/Dockerfile.ams
nginx/Dockerfile.arm64
nginx/index.php
nginx/login.php
nginx/phpinfo.php
nginx/supervisord.conf
nginx/weko-ams-restricted.conf
nginx/weko-ams.conf
nginx/weko.conf
Update the frozen dependency/environment snapshot for the v2.1.0 development release.
  • Refresh pinned freeze data.
freeze.txt

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Oct 6, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

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: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b5843f37-9dd6-45a0-962e-fe7cae6f2a04

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
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@Ooka-Masashi
Ooka-Masashi merged commit 6dac559 into release_v2.1.0 Oct 6, 2026
145 of 158 checks passed
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown

🔍 Claude レビュー統合

Claude の追加指摘 4 件 — 🔴 高 4 / 🟠 中 0 / 🟡 低 0

# 出所 箇所 指摘 判定 修正案
1 Claude modules/invenio-files-rest/invenio_files_rest/alembic/faf4faa08601_add_column_file_readonly_keys.py:16 down_revision が存在しないリビジョンを指しており、alembic のチェーンが壊れる 🔴 追加指摘(高) あり
2 Claude freeze.txt:93 削除した GitHub PAT は履歴に残っているため、失効が必要 🔴 追加指摘(高) あり
3 Claude modules/invenio-files-rest/invenio_files_rest/alembic/faf4faa08601_add_column_file_readonly_keys.py:16 マイグレーションの down_revision が存在しないリビジョンを指している 🔴 追加指摘(高) あり
4 Claude freeze.txt:93 漏洩済み GitHub PAT の失効が必要 🔴 追加指摘(高) あり

1. 🔴 [高] down_revision が存在しないリビジョンを指しており、alembic のチェーンが壊れる(Claude の追加指摘)

modules/invenio-files-rest/invenio_files_rest/alembic/faf4faa08601_add_column_file_readonly_keys.py:16 (1/2 パス)

down_revision に指定された '8644b32a3eec' は、リポジトリ内のどのマイグレーションにも定義されていない。alembic は KeyError(Can't locate revision)で失敗するか、このファイルが別のブランチ起点になってしまう。そのため invenio alembic upgrade で readonly_access_key / readonly_secret_key 列が追加されない。モデル側は既にこれらの列を使っているので、実行時にカラム不足でエラーになる。

修正案 modules/invenio-files-rest/invenio_files_rest/alembic/faf4faa08601_add_column_file_readonly_keys.py:16-16

down_revision = '8ae99b034410'

他の system で別ブランチ由来の 8644b32a3eec が必要な場合は、そのリビジョンのファイルも追加すること。

根拠
down_revision = '8644b32a3eec'

確認: alembic ディレクトリを ls し、revision / down_revision を grep した。現在の最新リビジョンは 8ae99b034410(down_revision = f741aa746a7d)。'8644b32a3eec' はリポジトリ全体の *.py でこのファイルにしか出てこない。


2. 🔴 [高] 削除した GitHub PAT は履歴に残っているため、失効が必要(Claude の追加指摘)

freeze.txt:93 (1/2 パス)

このコミットで freeze.txt から ghp_ のトークンを消しても、git 履歴・フォーク・PR の差分には残る。トークンが生きている限り漏洩状態が続く。

修正案

GitHub 側で該当 PAT(mhaya アカウント)を直ちに失効させる。必要なら履歴の書き換えも検討する。

根拠
-e git+https://mhaya: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@github.com/RCOSDP/weko.git@55eea912...

確認: 差分の削除行を確認した。トークンが失効済みかどうかは確認できていない。


3. 🔴 [高] マイグレーションの down_revision が存在しないリビジョンを指している(Claude の追加指摘)

modules/invenio-files-rest/invenio_files_rest/alembic/faf4faa08601_add_column_file_readonly_keys.py:16 (1/2 パス)

down_revision = '8644b32a3eec' に対応するリビジョンが alembic ディレクトリにない。このため alembic のリビジョングラフが壊れ、invenio alembic upgrade が失敗するか、head が複数に分岐する。現在の head は 8ae99b034410 である。

修正案 modules/invenio-files-rest/invenio_files_rest/alembic/faf4faa08601_add_column_file_readonly_keys.py:16-16

down_revision = '8ae99b034410'

他ブランチで 8644b32a3eec が別途定義される予定なら、そのファイルをこの PR に含めること。

根拠
down_revision = '8644b32a3eec'

確認: alembic ディレクトリのファイル一覧と down_revision を grep し、8644b32a3eec が modules 配下のどこにも revision として定義されていないことを確認した。チェーンは 52ce868f33c3→2e97565eba72→f741aa746a7d→8ae99b034410 である。


4. 🔴 [高] 漏洩済み GitHub PAT の失効が必要(Claude の追加指摘)

freeze.txt:93 (1/2 パス)

この変更で freeze.txt から ghp_ トークンは消えるが、git 履歴には残る。履歴を書き換えない限り、トークンは有効なまま取得可能である。

修正案

GitHub 側で該当トークンを直ちに revoke する。

根拠
-e git+https://mhaya:ghp_OHLEHB6c...@github.com/RCOSDP/weko.git@55eea91...

確認: 差分の内容のみで判断した。トークンが失効済みかどうかはリポジトリからは確認できない。


次にすること: alembic マイグレーションの down_revision が実在せず(現在のヘッドは 8ae99b034410)、DB マイグレーションが壊れる。8ae99b034410 に直すこと。また freeze.txt から削除した PAT は履歴に残っているので、GitHub 側で失効させること。他の変更(URL とファイル名の照合、uploaded_file の認可緩和、is_private_index の委譲先の存在)は、読んだ範囲では問題なかった。


モデル sonnet / 2 回実行して和集合 / コスト $0.6078。同じ入力でも結果が揺れるため複数回まわし、一部のパスでしか挙がらなかったものには回数を添えています

他レビューを踏まえた自動レビューです。誤りが含まれることがあります。

@sourcery-ai sourcery-ai 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.

Hey - I've found 7 issues

Fixed security issues:

  • freeze.txt (link)
  • SSL verification is disabled but should not be (currently CURLOPT_SSL_VERIFYPEER= false) (link)
  • The 'phpinfo' function may reveal sensitive information about your environment. (link)
Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="modules/weko-gridlayout/weko_gridlayout/views.py" line_range="614-616" />
<code_context>
 @blueprint.route('/widget/uploaded/<string:filename>/<string:community_id>',
                  methods=["GET"]
                  )
-@roles_required([WEKO_ADMIN_PERMISSION_ROLE_SYSTEM,
-                 WEKO_ADMIN_PERMISSION_ROLE_REPO,
-                 WEKO_ADMIN_PERMISSION_ROLE_COMMUNITY])
 def uploaded_file(filename, community_id=0):
     """Get widget static file.
</code_context>
<issue_to_address>
**Private widget files are exposed**

When a private or unpublished widget file exists and an unauthenticated caller knows or guesses its filename and community ID, `uploaded_file()` passes the request to `WidgetBucket.get_file()` without checking community visibility or caller access, so the caller retrieves the private file without logging in.

Restore checks for community visibility and caller authorization before serving a widget file.
</issue_to_address>

### Comment 2
<location path="modules/invenio-files-rest/invenio_files_rest/alembic/faf4faa08601_add_column_file_readonly_keys.py" line_range="16" />
<code_context>
+
+# revision identifiers, used by Alembic.
+revision = 'faf4faa08601'
+down_revision = '8644b32a3eec'
+branch_labels = ()
+depends_on = None
</code_context>
<issue_to_address>
**Database upgrades cannot load**

When alembic loads the registered migration graph for an upgrade or downgrade, alembic cannot resolve `down_revision` `8644b32a3eec` because no migration with that revision exists, so the migration graph fails to load and database upgrades cannot run.

Set `down_revision` to the actual existing parent revision for this migration.
</issue_to_address>

### Comment 3
<location path="nginx/Dockerfile" line_range="88-89" />
<code_context>
-#ADD ./phpinfo.php /usr/share/nginx/html/secure/phpinfo.php
-#ADD ./index.php /usr/share/nginx/html/secure/index.php
-ADD ./login.php /usr/share/nginx/html/secure/login.php
+ADD ./login.py /usr/share/nginx/html/secure/login.py
+RUN chmod 755 /usr/share/nginx/html/secure/login.py

 ADD ./shib_clear_headers /etc/nginx/shib_clear_headers
</code_context>
<issue_to_address>
**Shibboleth login requests fail**

When a request reaches `login.py` and it returns a redirect or login page, `login.py` emits an HTTP status line as its first CGI output line instead of a valid CGI header. `fcgiwrap` passes it to Nginx as a FastCGI response header, which Nginx rejects and the login request fails.

Emit a valid CGI response header, such as `Status: 302 Found` or `Status: 200 OK`, instead of an HTTP status line.
</issue_to_address>

### Comment 4
<location path="nginx/Dockerfile" line_range="88-89" />
<code_context>
-#ADD ./phpinfo.php /usr/share/nginx/html/secure/phpinfo.php
-#ADD ./index.php /usr/share/nginx/html/secure/index.php
-ADD ./login.php /usr/share/nginx/html/secure/login.php
+ADD ./login.py /usr/share/nginx/html/secure/login.py
+RUN chmod 755 /usr/share/nginx/html/secure/login.py

 ADD ./shib_clear_headers /etc/nginx/shib_clear_headers
</code_context>
<issue_to_address>
**Login flow permits external redirects**

When a victim follows a Shibboleth login link whose `next` parameter names an external URL, `login.py` takes `next` from the request query and forwards it to `/weko/shib/login`; the login flow stores that value and later redirects to it. An attacker can send a victim to the Shibboleth login endpoint with an attacker-controlled `next`, then land the victim on a phishing site after login.

Accept only validated same-origin relative destinations for `next` and reject external URLs before storing or redirecting to them.
</issue_to_address>

### Comment 5
<location path="nginx/Dockerfile" line_range="88" />
<code_context>
-#ADD ./phpinfo.php /usr/share/nginx/html/secure/phpinfo.php
-#ADD ./index.php /usr/share/nginx/html/secure/index.php
-ADD ./login.php /usr/share/nginx/html/secure/login.php
+ADD ./login.py /usr/share/nginx/html/secure/login.py
+RUN chmod 755 /usr/share/nginx/html/secure/login.py

</code_context>
<issue_to_address>
**Temporary cookie files accumulate**

When the login handler processes repeated requests over the container's lifetime, `login.py` creates a named temporary cookie file for each login and never removes it, so files accumulate in `/tmp` until the container runs out of filesystem space or inodes and login requests fail.

Remove each temporary cookie file after it is no longer needed, including on error paths.
</issue_to_address>

### Comment 6
<location path="modules/weko-records-ui/weko_records_ui/utils.py" line_range="2394" />
<code_context>
+
+    # Check that the token was actually issued for the requested item/file.
+    # The same generic message as the file-access check above is used.
+    if not ensure_url_record_matches(url_obj, record.get('recid'), filename):
+        return False, _('This file is currently not available for this feature.')
+
</code_context>
<issue_to_address>
**Download limits can be exceeded**

When two requests for the same URL validate concurrently while its download count is below the limit, `validate_url_download()` lets both requests pass before `process_onetime_file_download()` or `file_download_secret` increments the count. Both requests can deliver the file, and the read-modify-write increments can persist only one count, leaving the URL usable after its limit is exceeded.

Atomically check and reserve a download against the limit before serving the file.
</issue_to_address>

### Comment 7
<location path="nginx/Dockerfile" line_range="88" />
<code_context>
-#ADD ./phpinfo.php /usr/share/nginx/html/secure/phpinfo.php
-#ADD ./index.php /usr/share/nginx/html/secure/index.php
-ADD ./login.php /usr/share/nginx/html/secure/login.php
+ADD ./login.py /usr/share/nginx/html/secure/login.py
+RUN chmod 755 /usr/share/nginx/html/secure/login.py

</code_context>
<issue_to_address>
**Login drops destination query parameters**

When a user starts Shibboleth login from a destination URL containing `&`-separated query parameters, `login.py` parses `next` and concatenates its decoded value into the internal `/weko/shib/login` URL without re-encoding it. Any `&` in the destination becomes a separate query parameter, so the post-login redirect loses the remaining destination parameters and links that depend on them fail.

URL-encode `next` when constructing the internal login URL, and use `/` as the unencoded fallback value.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 7 findings to address first, and removing the role check makes every widget-uploaded file reachable anonymously, so a mistaken access-policy decision could expose the full existing widget-file set immediately and expose files during the window cannot be undone by reverting. The URL-binding fixes and the Nginx/Shibboleth runtime migration also change security and production behavior, with rollback restoring code/config but not undoing any access or outages that already occurred.

Blocking findings: modules/weko-gridlayout/weko_gridlayout/views.py:616, modules/invenio-files-rest/invenio_files_rest/alembic/faf4faa08601_add_column_file_readonly_keys.py:16, nginx/Dockerfile:89, nginx/Dockerfile:89, nginx/Dockerfile:88, and 2 more


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment on lines -614 to -616
@roles_required([WEKO_ADMIN_PERMISSION_ROLE_SYSTEM,
WEKO_ADMIN_PERMISSION_ROLE_REPO,
WEKO_ADMIN_PERMISSION_ROLE_COMMUNITY])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High · Private widget files are exposed

When a private or unpublished widget file exists and an unauthenticated caller knows or guesses its filename and community ID, uploaded_file() passes the request to WidgetBucket.get_file() without checking community visibility or caller access, so the caller retrieves the private file without logging in.

Restore checks for community visibility and caller authorization before serving a widget file.

Prompt for AI agents
In `modules/weko-gridlayout/weko_gridlayout/views.py` at lines 614-616:

**Private widget files are exposed**

When a private or unpublished widget file exists and an unauthenticated caller knows or guesses its filename and community ID, `uploaded_file()` passes the request to `WidgetBucket.get_file()` without checking community visibility or caller access, so the caller retrieves the private file without logging in.

Restore checks for community visibility and caller authorization before serving a widget file.


# revision identifiers, used by Alembic.
revision = 'faf4faa08601'
down_revision = '8644b32a3eec'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Critical · Database upgrades cannot load

When alembic loads the registered migration graph for an upgrade or downgrade, alembic cannot resolve down_revision 8644b32a3eec because no migration with that revision exists, so the migration graph fails to load and database upgrades cannot run.

Set down_revision to the actual existing parent revision for this migration.

Prompt for AI agents
In `modules/invenio-files-rest/invenio_files_rest/alembic/faf4faa08601_add_column_file_readonly_keys.py` at line 16:

**Database upgrades cannot load**

When alembic loads the registered migration graph for an upgrade or downgrade, alembic cannot resolve `down_revision` `8644b32a3eec` because no migration with that revision exists, so the migration graph fails to load and database upgrades cannot run.

Set `down_revision` to the actual existing parent revision for this migration.

Comment thread nginx/Dockerfile
Comment on lines +88 to +89
ADD ./login.py /usr/share/nginx/html/secure/login.py
RUN chmod 755 /usr/share/nginx/html/secure/login.py

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High · Shibboleth login requests fail

When a request reaches login.py and it returns a redirect or login page, login.py emits an HTTP status line as its first CGI output line instead of a valid CGI header. fcgiwrap passes it to Nginx as a FastCGI response header, which Nginx rejects and the login request fails.

Emit a valid CGI response header, such as Status: 302 Found or Status: 200 OK, instead of an HTTP status line.

Prompt for AI agents
In `nginx/Dockerfile` at lines 88-89:

**Shibboleth login requests fail**

When a request reaches `login.py` and it returns a redirect or login page, `login.py` emits an HTTP status line as its first CGI output line instead of a valid CGI header. `fcgiwrap` passes it to Nginx as a FastCGI response header, which Nginx rejects and the login request fails.

Emit a valid CGI response header, such as `Status: 302 Found` or `Status: 200 OK`, instead of an HTTP status line.

Comment thread nginx/Dockerfile
Comment on lines +88 to +89
ADD ./login.py /usr/share/nginx/html/secure/login.py
RUN chmod 755 /usr/share/nginx/html/secure/login.py

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Login flow permits external redirects

When a victim follows a Shibboleth login link whose next parameter names an external URL, login.py takes next from the request query and forwards it to /weko/shib/login; the login flow stores that value and later redirects to it. An attacker can send a victim to the Shibboleth login endpoint with an attacker-controlled next, then land the victim on a phishing site after login.

Accept only validated same-origin relative destinations for next and reject external URLs before storing or redirecting to them.

Prompt for AI agents
In `nginx/Dockerfile` at lines 88-89:

**Login flow permits external redirects**

When a victim follows a Shibboleth login link whose `next` parameter names an external URL, `login.py` takes `next` from the request query and forwards it to `/weko/shib/login`; the login flow stores that value and later redirects to it. An attacker can send a victim to the Shibboleth login endpoint with an attacker-controlled `next`, then land the victim on a phishing site after login.

Accept only validated same-origin relative destinations for `next` and reject external URLs before storing or redirecting to them.

Comment thread nginx/Dockerfile
#ADD ./phpinfo.php /usr/share/nginx/html/secure/phpinfo.php
#ADD ./index.php /usr/share/nginx/html/secure/index.php
ADD ./login.php /usr/share/nginx/html/secure/login.php
ADD ./login.py /usr/share/nginx/html/secure/login.py

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Temporary cookie files accumulate

When the login handler processes repeated requests over the container's lifetime, login.py creates a named temporary cookie file for each login and never removes it, so files accumulate in /tmp until the container runs out of filesystem space or inodes and login requests fail.

Remove each temporary cookie file after it is no longer needed, including on error paths.

Prompt for AI agents
In `nginx/Dockerfile` at line 88:

**Temporary cookie files accumulate**

When the login handler processes repeated requests over the container's lifetime, `login.py` creates a named temporary cookie file for each login and never removes it, so files accumulate in `/tmp` until the container runs out of filesystem space or inodes and login requests fail.

Remove each temporary cookie file after it is no longer needed, including on error paths.


# Check that the token was actually issued for the requested item/file.
# The same generic message as the file-access check above is used.
if not ensure_url_record_matches(url_obj, record.get('recid'), filename):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High · Download limits can be exceeded

When two requests for the same URL validate concurrently while its download count is below the limit, validate_url_download() lets both requests pass before process_onetime_file_download() or file_download_secret increments the count. Both requests can deliver the file, and the read-modify-write increments can persist only one count, leaving the URL usable after its limit is exceeded.

Atomically check and reserve a download against the limit before serving the file.

Prompt for AI agents
In `modules/weko-records-ui/weko_records_ui/utils.py` at line 2394:

**Download limits can be exceeded**

When two requests for the same URL validate concurrently while its download count is below the limit, `validate_url_download()` lets both requests pass before `process_onetime_file_download()` or `file_download_secret` increments the count. Both requests can deliver the file, and the read-modify-write increments can persist only one count, leaving the URL usable after its limit is exceeded.

Atomically check and reserve a download against the limit before serving the file.

Comment thread nginx/Dockerfile
#ADD ./phpinfo.php /usr/share/nginx/html/secure/phpinfo.php
#ADD ./index.php /usr/share/nginx/html/secure/index.php
ADD ./login.php /usr/share/nginx/html/secure/login.php
ADD ./login.py /usr/share/nginx/html/secure/login.py

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Login drops destination query parameters

When a user starts Shibboleth login from a destination URL containing &-separated query parameters, login.py parses next and concatenates its decoded value into the internal /weko/shib/login URL without re-encoding it. Any & in the destination becomes a separate query parameter, so the post-login redirect loses the remaining destination parameters and links that depend on them fail.

URL-encode next when constructing the internal login URL, and use / as the unencoded fallback value.

Prompt for AI agents
In `nginx/Dockerfile` at line 88:

**Login drops destination query parameters**

When a user starts Shibboleth login from a destination URL containing `&`-separated query parameters, `login.py` parses `next` and concatenates its decoded value into the internal `/weko/shib/login` URL without re-encoding it. Any `&` in the destination becomes a separate query parameter, so the post-login redirect loses the remaining destination parameters and links that depend on them fail.

URL-encode `next` when constructing the internal login URL, and use `/` as the unencoded fallback value.

@Ooka-Masashi Ooka-Masashi left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

release v2.1.0

@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown

API インベントリ差分(件数のみ)

台帳ブランチ: develop_v2.1.0

明細は公開できないため件数のみ表示しています。該当箇所はプライベートリポジトリ側の台帳・レポートで確認してください。

ベースラインとの差分

(生成されませんでした)

台帳との突き合わせ

(生成されませんでした)

ソース由来の経路検知

(生成されませんでした)

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.

4 participants