Skip to content

Security Fix PR Report#293

Merged
yusuke0610 merged 3 commits into
mainfrom
feat/openAPI-typescript
May 31, 2026
Merged

Security Fix PR Report#293
yusuke0610 merged 3 commits into
mainfrom
feat/openAPI-typescript

Conversation

@yusuke0610

@yusuke0610 yusuke0610 commented May 30, 2026

Copy link
Copy Markdown
Owner

Summary

  • Markdown プレビューの XSS を DOMPurify でサニタイズし、script / event handler / javascript: URL の回帰テストを追加。
  • GitHub Actions のタグ参照(ci.yml / opentofu-ci.yml の全 uses)を full commit SHA に固定し、backend の上限なし依存 3 件を == pin 化。
  • GitHub API client の owner/repo/username 入力を検証し、パス操作・SSRF の多層防御を追加。
  • Cloud Tasks 内部 API に OIDC audience / service account 検証を追加し、dispatcher から X-Internal-Secret と OIDC audience を送るよう補強。
  • rate limit / mass-assignment / SSRF / OIDC の exploit-style テストを追加。

Applied Fixes

Critical

  • なし。

High

  • [frontend/src/components/forms/MarkdownTextarea.tsx:30] marked.parse() の HTML を DOMPurify.sanitize() に通してから dangerouslySetInnerHTML に渡すよう修正。marked@v5+ は sanitize オプション廃止のため <script> / onerror / javascript: が実行可能だった。(違反ルール: security.md「Frontend XSS 対策まとめ」「dangerouslySetInnerHTML は原則禁止 / Markdown レンダラーは sanitize 有効化」)
  • [frontend/package.json:24] dompurify@^3 を追加(v3 は型同梱のため @types 不要)し、frontend/package-lock.json に integrity 付き lock を反映。

Medium

  • [.github/workflows/ci.yml] actions/checkout / actions/setup-node / actions/upload-artifact / dorny/paths-filter / astral-sh/setup-uv / cloudflare/wrangler-action / google-github-actions/auth / google-github-actions/setup-gcloud を full commit SHA に固定(# vN コメント付与)。
  • [.github/workflows/opentofu-ci.yml] actions/checkout / dorny/paths-filter / opentofu/setup-opentofu を full commit SHA に固定。
  • [backend/requirements.txt:23] 上限なし >= を実インストール版へ固定: google-genai>=1.0.0==1.46.0 / pyasn1>=0.6.3==0.6.3 / python-multipart>=0.0.27==0.0.27google-cloud-tasks>=2.16,<3 は上限ありのため据え置き。

Low

  • [backend/app/services/intelligence/github/api_client.py:20] _OWNER_PATTERN(^[A-Za-z0-9-]{1,39}$) / _REPO_PATTERN(^[A-Za-z0-9._-]{1,100}$) を追加し、API パス補間前に検証。
  • [backend/app/routers/internal.py:38] /internal/tasks/* で Cloud Tasks OIDC token の audience と service account email を検証。
  • [backend/app/services/tasks/cloud_tasks.py:35] Cloud Tasks の callback に X-Internal-Secret と OIDC audience(= CLOUD_TASKS_SERVICE_URL)を明示付与。

Design-level Fixes

  • [backend/app/services/intelligence/github/api_client.py:120] 不正な username は HTTP 発行前に NonRetryableError で拒否し、../..// を含むパス操作を遮断。対応テスト: test_ssrf_github.py::test_fetch_repos_raw_rejects_bad_username_without_http
  • [backend/app/services/intelligence/github/api_client.py:194] languages / contents / raw file 取得は owner/repo 不正時に HTTP を発行せず空結果で継続(パイプライン継続)。対応テスト: test_ssrf_github.py::test_fetch_languages_skips_bad_owner_without_http
    • ※ SSRF 自体は base URL 固定 + パス位置補間 + follow_redirects=False で従前から実質不成立。username は認証ユーザー自身の github_login 由来で任意入力でないことも確認済み。本変更は多層防御。
  • [backend/app/routers/internal.py:67] TASK_RUNNER=cloud_tasks ではキューヘッダーだけでなく OIDC を必須化し、共有シークレット単独依存を緩和。対応テスト: test_admin_authorization.py::test_missing_cloud_tasks_oidc_returns_403 / test_invalid_cloud_tasks_oidc_claims_return_403 / test_valid_cloud_tasks_oidc_reaches_handler
  • 初版 SEC_review の High①(内部 EP 公開)は 誤検出と確定(InternalSecretMiddleware/health 以外の全パスで X-Internal-Secret を timing-safe 検証・fail-closed、test_internal_secret_middleware.py で担保)。IDOR/OAuth/CSRF/権限分離は従前から良好で実装修正なし。

Dependency & Supply Chain

  • CVE 更新: なし。npm audit --audit-level=high / pip-audit で High 以上なし。
  • バージョン固定 / lockfile: [backend/requirements.txt:23] google-genai==1.46.0 / pyasn1==0.6.3 / python-multipart==0.0.27(いずれも venv 実バージョンと一致確認済み)。
  • GitHub Actions SHA 固定: ci.yml / opentofu-ci.yml のタグ参照を解消。rg -n 'uses: .*@(v[0-9]|main|master)' .github/workflows は検出なし。全 9 件の SHA が実在し対応 major ref を指すことを RV で再確認(下表)。
  • 拒否した不審依存: なし。新規追加は dompurify のみ。

Exploit Tests Added

  • [frontend/src/components/forms/MarkdownTextarea.test.tsx] XSS 回帰 4 ケース(script 除去 / onerror 除去 / javascript: 無害化 / 通常 Markdown 描画維持)。
  • [backend/tests/security/test_ssrf_github.py] SSRF / パス操作 8 ケース(不正 owner/username で NonRetryableErrorclient.get.assert_not_called())。
  • [backend/tests/security/test_rate_limit.py] 業務経路 429 を 2 ケース(/api/github-link/run 5/min・/api/blog/accounts/{id}/sync 10/min)。
  • [backend/tests/security/test_mass_assignment.py] resume / blog account の作成・更新で user_id 偽装しても所有権が移らない 4 ケース(base.create がサーバ側で user_id=self.user_id 固定)。
  • [backend/tests/security/test_admin_authorization.py] Cloud Tasks OIDC 検証 3 ケース(token 欠如 403 / SA 不一致 403 / 正当な OIDC でハンドラ到達)。
  • 修正前赤・修正後緑: XSS / SSRF / OIDC / mass-assignment / rate-limit は修正後 green を確認。修正前赤は一時 revert 未実施のため未確認。

Secrets Remediation

  • 除去したファイル / .gitignore 追加: なし。
  • rotate 要否: 不要。今回の適用範囲で秘密情報混入なし。

Skipped / Follow-ups

  • get_environment() の fail-closed 化(設計ハードニング): get_environment() は未設定時 "local" を返し、localInternalSecretMiddleware を全スキップする fail-open。本番 infra は ENVIRONMENT を必ず注入するため現状穴ではないが、設定漏れ時に認証が全無効化される。production 既定(fail-closed)化はローカル DX に影響するため別途検討。
  • npm moderate 6 件brace-expansion / postcss / qs / ws 経由): 採用レポートおよび CI 閾値は High 以上のため本 PR では未更新。別 PR で更新検討。
  • requirements.txt の hash 固定(uv lock / pip-tools): 中期課題。
  • デプロイ後確認: Cloud Tasks OIDC 検証は本番で CLOUD_TASKS_SERVICE_URLCLOUD_TASKS_SERVICE_ACCOUNT が正しく注入されている前提。デプロイ後に Cloud Tasks 実行ログを確認する。

Validation

  • make lint-backend: pass。
  • make test-backend: pass(463 passed)。
  • make lint-frontend: pass。
  • make lint-frontend-messages: pass。
  • make test-frontend: pass。
  • make build-frontend: pass(dompurify 型解決含む)。
  • make infra-fmt-check / make infra-validate(dev / stg / prod): pass。
  • npm audit --audit-level=high: pass(High 以上なし、moderate 6 件残あり)。
  • pip-audit -r requirements.txt: pass(No known vulnerabilities found)。

Review (RV) 結果 — 2026-05-30

統合にあたりワークツリーの実コードを再検証した。

コードレビュー

  • OIDC 検証(internal.py): audience(CLOUD_TASKS_SERVICE_URL)/ issuer(accounts.google.com)/ email(期待 SA 一致)/ email_verified is True の 4 点を検証し、いずれか欠落で 403。dispatcher 側の oidc_token.audience = self._service_url と一致しており整合。ValueError は warning ログ + False 返却で握りつぶしなし。問題なし
  • SSRF(api_client.py): fetch_repos_raw は事前 raise、補助系 3 関数は warning + 空返却でパイプライン継続。CLAUDE.md「黙って return 禁止」に対しても logger.warning を残しており準拠。問題なし
  • env_keys: INTERNAL_SECRET / TASK_RUNNER / CLOUD_TASKS_SERVICE_URL / CLOUD_TASKS_SERVICE_ACCOUNT の 4 定数が env_keys.py に実在。リテラル直書きなし。問題なし
  • XSS(MarkdownTextarea.tsx): DOMPurify.sanitize()dangerouslySetInnerHTML の前段に挿入。問題なし

SHA 実在性検証(1042 が懸念した破損の確認)

git ls-remote で全 9 件が実在し対応 major ref を指すことを確認(破損なし):

action pinned SHA resolves to
actions/checkout 34e1148… releases/v4
actions/setup-node 49933ea… releases/v4
actions/upload-artifact ea165f8… tags/v4
dorny/paths-filter d1c1ffe… releases/v3
astral-sh/setup-uv 38f3f10… tags/v4
cloudflare/wrangler-action 9acf94a… tags/v3
google-github-actions/auth c200f36… release/v2
google-github-actions/setup-gcloud e427ad8… release/v2
opentofu/setup-opentofu 9d84900… tags/v1

注: 1042 レポートが「参考値」として挙げた SHA とは異なるが、両者とも当該 major タグが指していた有効な commit。現行ワークツリー(1852 適用分)の SHA が正であり、上記のとおり全件実在を確認済み。

テスト再実行(RV 時)

  • pytest tests/security/: 104 passed(新規 OIDC / SSRF / rate-limit / mass-assignment 含む)。
  • vitest run MarkdownTextarea.test.tsx: 4 passed

結論

両 PR レポートの記載は実コードと一致し、1042 で延期された 2 件(SHA 固定・OIDC 検証)は 1852 で正しく完了している。残課題は get_environment() の fail-closed 化のみ。マージ可。

Summary by CodeRabbit

  • Security Enhancements

    • Cloud Tasks callbacks now require OIDC Bearer token authentication
    • GitHub API requests validate owner and repository inputs
    • Markdown component sanitizes rendered HTML
  • Tests

    • Added security regression tests for mass assignment, rate limiting, and SSRF vulnerabilities
    • Added XSS sanitization tests for Markdown rendering
  • Documentation

    • New security review and remediation workflow documentation
  • Dependency Management

    • Package versions pinned to exact specifications
    • Added HTML sanitization library

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@yusuke0610, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 26 minutes and 22 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 10f40c9b-98b4-4385-921c-046bb8752cf5

📥 Commits

Reviewing files that changed from the base of the PR and between aeefbeb and fe51dcf.

📒 Files selected for processing (6)
  • .claude/skills/SEC_review/SKILL.md
  • .github/workflows/ci.yml
  • backend/app/services/intelligence/github/api_client.py
  • backend/tests/security/test_admin_authorization.py
  • backend/tests/security/test_rate_limit.py
  • backend/tests/security/test_ssrf_github.py
📝 Walkthrough

Walkthrough

This PR implements comprehensive security hardening across authentication, supply chain, input validation, and exploit-style regression testing. It introduces Cloud Tasks OIDC callback verification, GitHub Actions SHA pinning, GitHub API SSRF guards, frontend markdown XSS sanitization, and new regression tests covering mass assignment and rate limiting attacks.

Changes

Security Hardening Implementation

Layer / File(s) Summary
Security review and apply skill definitions
.claude/skills/SEC_review/SKILL.md, .claude/skills/SEC_apply/SKILL.md
SEC_review scans for design flaws (IDOR/SSRF/OAuth), secrets, exploit-style tests, and outputs structured findings; SEC_apply implements fixes with explicit rules for secrets rotation, authorization hardening, dependency pinning, and exploit test creation.
GitHub Actions and dependency pinning
.github/workflows/ci.yml, .github/workflows/opentofu-ci.yml, backend/requirements.txt
GitHub Actions upgraded from floating version tags to commit SHA pinning across 8 actions (checkout, setup-node, paths-filter, upload-artifact, setup-uv, auth, setup-gcloud, wrangler-action); backend dependencies pinned to exact versions (google-genai, pyasn1, python-multipart).
Cloud Tasks OIDC authentication
backend/app/routers/internal.py, backend/app/services/tasks/cloud_tasks.py, docs/api.md, backend/tests/security/test_admin_authorization.py
Cloud Tasks callbacks now require Bearer OIDC token verification against issuer (accounts.google.com), audience (CLOUD_TASKS_SERVICE_URL), and claims (email matching service account); dispatcher includes X-Internal-Secret header and OIDC audience; tests verify 403 on missing/invalid token and success on valid claims.
GitHub API input validation and SSRF protection
backend/app/services/intelligence/github/api_client.py, backend/tests/security/test_ssrf_github.py
GitHub API client adds regex-based validation for owner/repo inputs; fetch functions reject invalid values and skip HTTP calls, returning empty results; tests exercise malicious owner/repo patterns and verify no HTTP calls occur for invalid inputs.
Frontend markdown XSS sanitization
frontend/package.json, frontend/src/components/forms/MarkdownTextarea.tsx, frontend/src/components/forms/MarkdownTextarea.test.tsx
MarkdownTextarea sanitizes marked.parse() output using dompurify before injection; tests verify script removal, event handler stripping, javascript: href neutralization, and normal markdown preservation.
Mass assignment and rate limit regression tests
backend/tests/security/test_mass_assignment.py, backend/tests/security/test_rate_limit.py
New exploit-style tests verify that /api/resumes and /api/blog/accounts do not transfer ownership when attacker embeds victim user_id in payloads; verify HTTP 429 enforcement on /api/github-link/run and /api/blog/accounts/{id}/sync after exceeding rate limits.

🎯 4 (Complex) | ⏱️ ~60 minutes

🐰 With OIDC tokens verified and actions pinned to SHAs so tight,
GitHub API guards against SSRF with regex might,
Mass-assignment and rate limits caught by exploit-style test,
Frontend markdown scrubbed clean—security hardened best! ✨🔒

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Security Fix PR Report' is vague and does not clearly convey the specific security improvements made in this substantial changeset covering frontend XSS sanitization, backend authentication, dependency pinning, and multiple security test additions. Consider a more specific title such as 'Add security hardening: XSS sanitization, OIDC auth, input validation, and security tests' to better reflect the multi-faceted changes in this PR.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 80.95% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/openAPI-typescript

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 and usage tips.

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

🧹 Nitpick comments (5)
backend/app/services/intelligence/github/api_client.py (1)

20-24: 💤 Low value

_REPO_PATTERN allows .. as a valid repository name.

The pattern [A-Za-z0-9._-] permits strings like .. or ... which, while likely rejected by GitHub's API, could cause unexpected URL path normalization (e.g., /repos/owner/../ normalizing to /repos/). Consider explicitly excluding consecutive dots or pure-dot strings.

♻️ Optional: Reject consecutive dots in repo names
-_REPO_PATTERN = re.compile(r"^[A-Za-z0-9._-]{1,100}$")
+# Reject names that are only dots or contain ".."
+_REPO_PATTERN = re.compile(r"^(?!\.+$)(?!.*\.\.)[A-Za-z0-9._-]{1,100}$")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/services/intelligence/github/api_client.py` around lines 20 - 24,
The _REPO_PATTERN currently allows strings like ".."; update its regex to
explicitly reject consecutive dots and names made only of dots by adding
negative lookaheads (e.g., a (?!.*\.\.) to forbid consecutive dots and a
(?!^[.]+$) to forbid pure-dot names) while preserving the existing allowed
characters and length constraints; modify the constant _REPO_PATTERN in
api_client.py (and keep _OWNER_PATTERN unchanged) so repo validation fails for
inputs containing ".." or only dots before any URL interpolation.
backend/tests/security/test_ssrf_github.py (1)

34-41: 💤 Low value

Minor resource leak: event loop created but never closed.

Line 41 creates a new event loop that remains open until process exit. While not problematic for tests, a cleaner pattern would avoid creating the replacement loop or use pytest-asyncio directly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/tests/security/test_ssrf_github.py` around lines 34 - 41, The helper
_run creates a new event loop then replaces it with another new loop (leaking
one); to fix, avoid creating the replacement loop—either capture the original
loop before creating the temporary one and restore it after closing (e.g., save
original = asyncio.get_event_loop(); set the new loop to run coro; close it;
asyncio.set_event_loop(original)), or simply remove the final
asyncio.set_event_loop(asyncio.new_event_loop()) and call
asyncio.set_event_loop(None) after closing; update the _run function
accordingly.
backend/app/services/tasks/cloud_tasks.py (1)

28-28: ⚡ Quick win

X-Internal-Secret is dead config unless the receiver enforces it.

CloudTasksDispatcher now sends this secret on every callback, but _verify_request() in backend/app/routers/internal.py only checks X-CloudTasks-QueueName plus OIDC. Right now this header adds secret-handling overhead without contributing to authorization, so either validate it server-side or drop it from the request contract.

Also applies to: 35-38

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/services/tasks/cloud_tasks.py` at line 28, CloudTasksDispatcher
is sending X-Internal-Secret but the server-side _verify_request() in
backend/app/routers/internal.py doesn't validate it; either remove the header
from CloudTasksDispatcher or add validation: read the same INTERNAL_SECRET env
var (or internal_secret) in _verify_request() and compare it to the
X-Internal-Secret header value (fail the request if missing/mismatched); update
the verification path used by _verify_request() to include this header check
alongside the existing X-CloudTasks-QueueName and OIDC checks so the header
actually contributes to authorization.
backend/tests/security/test_admin_authorization.py (1)

91-107: ⚡ Quick win

Assert the expected audience in the patched OIDC verifier.

These doubles ignore the audience argument, so the tests still pass if _verify_cloud_tasks_oidc() stops forwarding CLOUD_TASKS_SERVICE_URL into verify_oauth2_token(). That drops coverage for one of the main security properties added in this PR.

Suggested fix
         monkeypatch.setattr(
             "app.routers.internal.id_token.verify_oauth2_token",
-            lambda token, request, audience: {
-                "iss": "https://accounts.google.com",
-                "email": "attacker@example.iam.gserviceaccount.com",
-                "email_verified": True,
-            },
+            lambda token, request, audience: (
+                {
+                    "iss": "https://accounts.google.com",
+                    "email": "attacker@example.iam.gserviceaccount.com",
+                    "email_verified": True,
+                }
+                if audience == "https://backend.example.com"
+                else (_ for _ in ()).throw(AssertionError(f"unexpected audience: {audience}"))
+            ),
         )
@@
         monkeypatch.setattr(
             "app.routers.internal.id_token.verify_oauth2_token",
-            lambda token, request, audience: {
-                "iss": "https://accounts.google.com",
-                "email": "tasks@example.iam.gserviceaccount.com",
-                "email_verified": True,
-            },
+            lambda token, request, audience: (
+                {
+                    "iss": "https://accounts.google.com",
+                    "email": "tasks@example.iam.gserviceaccount.com",
+                    "email_verified": True,
+                }
+                if audience == "https://backend.example.com"
+                else (_ for _ in ()).throw(AssertionError(f"unexpected audience: {audience}"))
+            ),
         )

Also applies to: 119-135

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/tests/security/test_admin_authorization.py` around lines 91 - 107,
The patched OIDC verifier used in the tests (monkeypatch.setattr on
app.routers.internal.id_token.verify_oauth2_token) ignores the audience argument
so the test will pass even if _verify_cloud_tasks_oidc() stops passing
CLOUD_TASKS_SERVICE_URL; update the test doubles to assert or validate that the
incoming audience equals CLOUD_TASKS_SERVICE_URL (or raise an error if it does
not) so the test fails when the verifier is called with the wrong audience,
ensuring the CLOUD_TASKS_SERVICE_URL audience check in
_verify_cloud_tasks_oidc() is covered.
backend/tests/security/test_rate_limit.py (1)

23-35: ⚡ Quick win

Always reset the shared limiter in finally.

These tests mutate global rate-limit state and only clean it up on the success path. If one of the requests or assertions fails early, later tests can inherit a poisoned bucket and start failing nondeterministically.

Suggested fix
-    limiter.reset()
-    statuses: list[int] = []
-    for _ in range(8):
-        resp = client.post(
-            "/api/github-link/run",
-            json={"include_forks": False},
-            headers=headers,
-        )
-        statuses.append(resp.status_code)
-        if resp.status_code == 429:
-            break
-    assert 429 in statuses, f"429 が観測されなかった: {statuses}"
-    limiter.reset()
+    limiter.reset()
+    try:
+        statuses: list[int] = []
+        for _ in range(8):
+            resp = client.post(
+                "/api/github-link/run",
+                json={"include_forks": False},
+                headers=headers,
+            )
+            statuses.append(resp.status_code)
+            if resp.status_code == 429:
+                break
+        assert 429 in statuses, f"429 が観測されなかった: {statuses}"
+    finally:
+        limiter.reset()
@@
-    limiter.reset()
-    statuses: list[int] = []
-    for _ in range(13):
-        resp = client.post("/api/blog/accounts/nonexistent/sync", headers=headers)
-        statuses.append(resp.status_code)
-        if resp.status_code == 429:
-            break
-    assert 429 in statuses, f"429 が観測されなかった: {statuses}"
-    limiter.reset()
+    limiter.reset()
+    try:
+        statuses: list[int] = []
+        for _ in range(13):
+            resp = client.post("/api/blog/accounts/nonexistent/sync", headers=headers)
+            statuses.append(resp.status_code)
+            if resp.status_code == 429:
+                break
+        assert 429 in statuses, f"429 が観測されなかった: {statuses}"
+    finally:
+        limiter.reset()

Also applies to: 45-53

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/tests/security/test_rate_limit.py` around lines 23 - 35, The test
mutates global rate-limit state by calling limiter.reset() only on the success
path; wrap the request loop and assertions in a try/finally and move the shared
limiter.reset() into the finally block so the limiter is always reset even if a
request or assertion fails; apply the same change to the second similar block
(the one around lines 45-53) that uses client.post, statuses, and limiter.reset
so both test sections always call limiter.reset() regardless of exceptions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/skills/SEC_review/SKILL.md:
- Around line 34-35: The scan scopes currently limit checks to backend/**
frontend/** infra/** and thus omit workflow files; update the SKILL.md entries
that define the default "差分(デフォルト)" and the "全体(`full`)" scopes to also include
`.github/workflows/**` so GitHub Actions YAMLs are audited for SHA pinning
(i.e., add `.github/workflows/**` alongside backend/** frontend/** infra/** in
both scope definitions referenced in the diff text).

In @.github/workflows/ci.yml:
- Line 311: The workflow step using
actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 (the setup-node step
in the deploy-frontend* jobs) currently enables npm caching which can be
poisoned; remove the npm cache option (omit cache: npm) or explicitly disable
package-manager caching (set package-manager-cache: false) in those setup-node
steps referenced (the uses: actions/setup-node... occurrences at the
deploy-frontend* jobs) so deployment jobs that access secrets do not restore
untrusted cached npm data.

---

Nitpick comments:
In `@backend/app/services/intelligence/github/api_client.py`:
- Around line 20-24: The _REPO_PATTERN currently allows strings like "..";
update its regex to explicitly reject consecutive dots and names made only of
dots by adding negative lookaheads (e.g., a (?!.*\.\.) to forbid consecutive
dots and a (?!^[.]+$) to forbid pure-dot names) while preserving the existing
allowed characters and length constraints; modify the constant _REPO_PATTERN in
api_client.py (and keep _OWNER_PATTERN unchanged) so repo validation fails for
inputs containing ".." or only dots before any URL interpolation.

In `@backend/app/services/tasks/cloud_tasks.py`:
- Line 28: CloudTasksDispatcher is sending X-Internal-Secret but the server-side
_verify_request() in backend/app/routers/internal.py doesn't validate it; either
remove the header from CloudTasksDispatcher or add validation: read the same
INTERNAL_SECRET env var (or internal_secret) in _verify_request() and compare it
to the X-Internal-Secret header value (fail the request if missing/mismatched);
update the verification path used by _verify_request() to include this header
check alongside the existing X-CloudTasks-QueueName and OIDC checks so the
header actually contributes to authorization.

In `@backend/tests/security/test_admin_authorization.py`:
- Around line 91-107: The patched OIDC verifier used in the tests
(monkeypatch.setattr on app.routers.internal.id_token.verify_oauth2_token)
ignores the audience argument so the test will pass even if
_verify_cloud_tasks_oidc() stops passing CLOUD_TASKS_SERVICE_URL; update the
test doubles to assert or validate that the incoming audience equals
CLOUD_TASKS_SERVICE_URL (or raise an error if it does not) so the test fails
when the verifier is called with the wrong audience, ensuring the
CLOUD_TASKS_SERVICE_URL audience check in _verify_cloud_tasks_oidc() is covered.

In `@backend/tests/security/test_rate_limit.py`:
- Around line 23-35: The test mutates global rate-limit state by calling
limiter.reset() only on the success path; wrap the request loop and assertions
in a try/finally and move the shared limiter.reset() into the finally block so
the limiter is always reset even if a request or assertion fails; apply the same
change to the second similar block (the one around lines 45-53) that uses
client.post, statuses, and limiter.reset so both test sections always call
limiter.reset() regardless of exceptions.

In `@backend/tests/security/test_ssrf_github.py`:
- Around line 34-41: The helper _run creates a new event loop then replaces it
with another new loop (leaking one); to fix, avoid creating the replacement
loop—either capture the original loop before creating the temporary one and
restore it after closing (e.g., save original = asyncio.get_event_loop(); set
the new loop to run coro; close it; asyncio.set_event_loop(original)), or simply
remove the final asyncio.set_event_loop(asyncio.new_event_loop()) and call
asyncio.set_event_loop(None) after closing; update the _run function
accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e7b062c9-436b-4bc9-9eec-1ef96a70961b

📥 Commits

Reviewing files that changed from the base of the PR and between 922acc5 and aeefbeb.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (16)
  • .claude/skills/SEC_apply/SKILL.md
  • .claude/skills/SEC_review/SKILL.md
  • .github/workflows/ci.yml
  • .github/workflows/opentofu-ci.yml
  • backend/app/routers/internal.py
  • backend/app/services/intelligence/github/api_client.py
  • backend/app/services/tasks/cloud_tasks.py
  • backend/requirements.txt
  • backend/tests/security/test_admin_authorization.py
  • backend/tests/security/test_mass_assignment.py
  • backend/tests/security/test_rate_limit.py
  • backend/tests/security/test_ssrf_github.py
  • docs/api.md
  • frontend/package.json
  • frontend/src/components/forms/MarkdownTextarea.test.tsx
  • frontend/src/components/forms/MarkdownTextarea.tsx

Comment thread .claude/skills/SEC_review/SKILL.md Outdated
Comment thread .github/workflows/ci.yml
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