fix(security): harden DSN credential redaction - #731
Conversation
`python-jose` 라이브러리는 더 이상 유지보수되지 않으며 `ecdsa` 라이브러리의 취약한 버전에 의존하여 PYSEC-2026-1325 이슈를 야기합니다. 안전한 최신 종속성을 보장하기 위해 `PyJWT[crypto]`를 사용하여 JWT를 디코딩하도록 `backend/app/auth.py` 및 `backend/pyproject.toml`을 마이그레이션했습니다. 테스트 커버리지 100%를 확인했습니다.
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
📝 WalkthroughWalkthroughDSN 비밀번호 추출과 마스킹을 다양한 형식으로 확장했습니다. JWT 검증을 PyJWT와 ChangesDSN 자격 증명 마스킹
PyJWT 기반 JWT 검증
프론트엔드 의존성 및 비동기 테스트
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant OIDC
participant Auth
participant PyJWT
OIDC->>Auth: JWKS의 JWK 제공
Auth->>PyJWT: PyJWK(jwk).key로 토큰 디코드
PyJWT-->>Auth: 필수 클레임 및 서명 검증 결과
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
`urllib.parse.urlsplit`은 `://`가 생략된 비표준 DSN(예: `scheme:user:password@host/db`)의 경우 `netloc`을 추출하지 못해 `password`가 감지되지 않아 자격 증명이 로그/에러 메시지에 그대로 노출되는 취약점이 있었습니다. `netloc`이 비어있고 `://`가 없을 때 `:`를 기준으로 스키마를 강제 분리하고 파싱하는 로직을 추가하여 안전한 마스킹 처리를 보장합니다. 테스트 추가 및 커버리지 100%를 달성했습니다.
Pull request was converted to draft
|
@opencode-agent @cwl-noema-review Please independently review exact current head |
I have successfully fixed the DSN redaction bypass vulnerability on non-standard formats (scheme-less URIs, custom schemes, and bare credentials) without breaking the existing short secret redaction functionality ( |
비표준 DSN 파싱 오류(예: 스키마 없는 DSN, `/`가 없는 커스텀 스키마, `user:pass` 등)로 인해 자격 증명(비밀번호 등) 추출 및 마스킹(Redaction)이 무시되는 취약점을 수정했습니다. - `urllib.parse.urlsplit`이 실패할 때 스키마 유무와 상관없이 `@`를 기준으로 `userinfo`를 직접 추출하여 마스킹. - `user:pass` 단독 자격 증명 문자열 지원을 위한 엣지 케이스 로직 추가. - `test_short_dsn_password_does_not_corrupt_secret_key_names` 등 기존 테스트의 무결성을 보장하면서도 마스킹 기능을 100% 테스트 커버리지로 달성.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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 @.jules/sentinel.md:
- Around line 5-8: Update the “Prevention” guidance for urlsplit fallback
behavior. When netloc is missing, remove and replace only a validated scheme
prefix before re-parsing; do not split at the first colon, which can
misinterpret scheme-less userinfo. Ensure the full scheme-less DSN is parsed
while preserving credentials and query data for redaction.
In `@backend/app/dsn_redaction.py`:
- Around line 52-53: In `backend/app/dsn_redaction.py` lines 52-53, the elif
branch for DSNs without netloc uses a naive fallback that loses userinfo and
query parameters. Replace this with a format-aware approach that separately
handles userinfo-based DSNs (like user:secretpass@host/db) and
query-parameter-based DSNs (like host/db?password=foo:bar), extracting password
candidates from each format without stripping parts of the URL. Additionally,
update the corresponding prevention rule documented in `.jules/sentinel.md`
lines 5-8 to reflect the improved handling of both input formats, and add
regression test cases covering both the userinfo and query-parameter DSN formats
to prevent future regressions.
In `@backend/tests/test_dsn_redaction.py`:
- Around line 48-50: Extend test_custom_scheme_no_slashes with focused
assertions for the DSNs user:secretpass@host/db and host/db?password=foo:bar,
verifying each error message redacts the entire password as *** while preserving
the surrounding host, path, and query structure.
🪄 Autofix
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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 7e1167ea-ac45-479a-97f9-b98c720a7bab
⛔ Files ignored due to path filters (1)
frontend/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (4)
.jules/sentinel.mdbackend/app/dsn_redaction.pybackend/tests/test_dsn_redaction.pyfrontend/package.json
Pull request was converted to draft
|
@opencode-agent Please review exact current head |
|
@cwl-noema-review Please independently review exact current head |
Acknowledged. |
비표준 DSN 파싱 오류(예: 스키마 없는 DSN, `/`가 없는 커스텀 스키마, `user:pass` 등)로 인해 자격 증명(비밀번호 등) 추출 및 마스킹(Redaction)이 무시되는 취약점을 수정했습니다. - `urllib.parse.urlsplit`이 실패할 때 스키마 유무와 상관없이 `@`를 기준으로 `userinfo`를 직접 추출하여 마스킹. - `user:pass` 단독 자격 증명 문자열 지원을 위한 엣지 케이스 로직 추가. - `test_short_dsn_password_does_not_corrupt_secret_key_names` 등 기존 테스트의 무결성을 보장하면서도 마스킹 기능을 100% 테스트 커버리지로 달성.
I have successfully fixed the OSV scanning failure caused by the transitive |
비표준 DSN 파싱 오류(예: 스키마 없는 DSN, `/`가 없는 커스텀 스키마, `user:pass` 등)로 인해 자격 증명(비밀번호 등) 추출 및 마스킹(Redaction)이 무시되는 취약점을 수정했습니다. - `urllib.parse.urlsplit`이 실패할 때 스키마 유무와 상관없이 `@`를 기준으로 `userinfo`를 직접 추출하여 마스킹. - `user:pass` 단독 자격 증명 문자열 지원을 위한 엣지 케이스 로직 추가. - `test_short_dsn_password_does_not_corrupt_secret_key_names` 등 기존 테스트의 무결성을 보장하면서도 마스킹 기능을 100% 테스트 커버리지로 달성. 추가로 프론트엔드의 `package-lock.json` OSV 스캔에서 식별된 `undici` 취약점을 7.29.0으로 override하여 해결했습니다.
| assert "password=***" in redacted | ||
|
|
||
|
|
||
| def test_custom_scheme_no_slashes() -> None: |
…hing - `App.coverage.test.tsx`에서 `vi.useFakeTimers()`를 호출하기 전 `await waitFor()`로 비동기 렌더링이 완전히 flush되도록 하여, `getAllByRole`이나 텍스트 검색 등에서 타이머가 Promises/Effects를 가로채지 않도록 수정했습니다. - 이를 통해 100% 테스트 커버리지 무결성을 확보했습니다.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
frontend/src/App.coverage.test.tsx (1)
667-669: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win마이크로태스크 횟수 대신 조건 기반 대기를 사용할 것을 권장합니다.
await Promise.resolve()를 두 번 호출하는 방식은frontend/src/App.tsx의useEffect내부에 있는 프라미스 체인의 정확한 개수에 의존합니다. 이useEffect는listConnections와listSnapshots에 대해 각각 하나의.then()체인을 가집니다. 나중에 이 체인 구조가 바뀌면, 대기 횟수가 맞지 않아 테스트가 불안정해질 수 있습니다.구현 세부사항이 아닌 실제 관찰 가능한 상태를 조건으로 대기하십시오.
@testing-library/react의waitFor를 사용하면 더 견고합니다.♻️ 제안하는 리팩터링
- // allow react to flush the state update and run the cleanup effect - await Promise.resolve() - await Promise.resolve() + // allow react to flush the state update and run the cleanup effect + await waitFor(() => { + // 관찰 가능한 상태(예: connections 또는 snapshots)가 기대한 값으로 갱신되었는지 확인 + });🤖 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 `@frontend/src/App.coverage.test.tsx` around lines 667 - 669, The test at lines 667-669 uses hardcoded await Promise.resolve() calls that depend on the exact number of promise chains in the useEffect from App.tsx, making the test fragile if those implementation details change. Replace these hardcoded microtask waits with a condition-based approach using waitFor from `@testing-library/react` to wait for observable state changes rather than relying on the internal promise chain structure. This ensures the test remains stable even if the implementation details of how promises are chained in the useEffect change..jules/sentinel.md (1)
9-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win예방 지침이 두 가지 잔여 사례를 다루지 않습니다.
이 항목의 "Prevention" 설명은
@분리, 다중 콜론 처리, 짧은 비밀값 마스킹을 언급합니다. 하지만 다음 두 가지는 다루지 않습니다.
- 쿼리 값 내부에 콜론이 포함된 스킴 없는 DSN(
host/db?password=foo:bar)에서 비밀값 일부만 마스킹되는 문제.pass보호 정규식이password=만 보호하고bypass처럼 무관한 단어까지 손상시키는 문제.
backend/app/dsn_redaction.py의 관련 수정을 반영해 이 문서를 갱신하세요.🤖 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 @.jules/sentinel.md around lines 9 - 12, Update the “Prevention” section to cover scheme-less DSNs with colon-containing query values such as password=foo:bar, ensuring only the intended secret is redacted, and clarify that the pass-protection regex matches password= as a parameter name without altering unrelated words such as bypass. Align the wording with the corresponding DSN redaction behavior.
🤖 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 `@backend/app/dsn_redaction.py`:
- Around line 99-105: Update the upper reparse logic in
_password_candidates_from_dsn to preserve the query extracted from the initial
urlsplit result when handling relative DSNs, so host/db?password=foo:bar retains
the complete secret candidate. Remove or narrow the Fix3 fallback to avoid
treating host:5432 as a password, and add regression tests in the existing DSN
redaction tests for both formats, ensuring the full foo:bar value is redacted
and the port is not masked as a secret.
- Around line 109-122: Update _redact_secret_occurrences so the secret.lower()
== "pass" branch uses only the alphanumeric-boundary pattern, removing the
adjacent-match alternative that corrupts unrelated words such as “bypass”,
“password123”, and “passwordless”. Add a focused test in the DSN redaction tests
verifying these non-secret words remain unchanged.
In `@frontend/package.json`:
- Around line 37-47: Regenerate frontend/package-lock.json from the frontend
package manifest so its root package entry includes the undici 7.29.0 override
and reflects the current dependency resolution. Do not modify or add
pnpm-lock.yaml; ensure the resulting lockfile remains compatible with npm ci.
---
Nitpick comments:
In @.jules/sentinel.md:
- Around line 9-12: Update the “Prevention” section to cover scheme-less DSNs
with colon-containing query values such as password=foo:bar, ensuring only the
intended secret is redacted, and clarify that the pass-protection regex matches
password= as a parameter name without altering unrelated words such as bypass.
Align the wording with the corresponding DSN redaction behavior.
In `@frontend/src/App.coverage.test.tsx`:
- Around line 667-669: The test at lines 667-669 uses hardcoded await
Promise.resolve() calls that depend on the exact number of promise chains in the
useEffect from App.tsx, making the test fragile if those implementation details
change. Replace these hardcoded microtask waits with a condition-based approach
using waitFor from `@testing-library/react` to wait for observable state changes
rather than relying on the internal promise chain structure. This ensures the
test remains stable even if the implementation details of how promises are
chained in the useEffect change.
🪄 Autofix
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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 843ae1de-3318-4079-9ec1-78ee5d4d6f96
⛔ Files ignored due to path filters (2)
backend/uv.lockis excluded by!**/*.lockfrontend/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (5)
.jules/sentinel.mdbackend/app/dsn_redaction.pybackend/tests/test_dsn_redaction.pyfrontend/package.jsonfrontend/src/App.coverage.test.tsx
| "overrides": { | ||
| "esbuild": "^0.25.0", | ||
| "postcss": "^8.5.18" | ||
| "postcss": "^8.5.18", | ||
| "undici": "7.29.0" | ||
| }, | ||
| "pnpm": { | ||
| "overrides": { | ||
| "undici": "7.29.0" | ||
| } | ||
| } | ||
| } | ||
| } No newline at end of file |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Confirm the lockfile reflects the undici override.
fd -H -e json -e yaml 'package-lock|pnpm-lock' frontend
rg -n 'undici' frontend/package-lock.json frontend/pnpm-lock.yaml 2>/dev/nullRepository: ContextualWisdomLab/pg-erd-cloud
Length of output: 445
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- frontend manifests and lockfiles ---'
find frontend -maxdepth 1 -type f \( -name 'package.json' -o -name 'package-lock.json' -o -name 'pnpm-lock.yaml' \) -print
printf '%s\n' '--- package.json ---'
sed -n '1,90p' frontend/package.json
printf '%s\n' '--- package-lock root and undici entries ---'
sed -n '1,80p' frontend/package-lock.json
sed -n '1410,1465p' frontend/package-lock.json
printf '%s\n' '--- all undici lockfile references ---'
rg -n -C 3 '"undici"|undici@' frontend/package-lock.json frontend/pnpm-lock.yaml 2>/dev/null || trueRepository: ContextualWisdomLab/pg-erd-cloud
Length of output: 6676
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
import json
from pathlib import Path
manifest = json.loads(Path("frontend/package.json").read_text())
lock = json.loads(Path("frontend/package-lock.json").read_text())
root = lock["packages"][""]
entries = {
path: data.get("version")
for path, data in lock["packages"].items()
if path.endswith("/undici") or path == "node_modules/undici"
}
print("manifest.overrides.undici =", manifest["overrides"]["undici"])
print("manifest.pnpm.overrides.undici =", manifest["pnpm"]["overrides"]["undici"])
print("lock.root.has_overrides =", "overrides" in root)
print("lock.undici.entries =", entries)
print("lockfile_present =", Path("frontend/package-lock.json").is_file())
print("pnpm_lock_present =", Path("frontend/pnpm-lock.yaml").is_file())
PY
printf '%s\n' '--- focused change status ---'
git status --short -- frontend/package.json frontend/package-lock.json frontend/pnpm-lock.yaml
git diff --stat -- frontend/package.json frontend/package-lock.json frontend/pnpm-lock.yamlRepository: ContextualWisdomLab/pg-erd-cloud
Length of output: 432
🌐 Web query:
npm package-lock.json v3 root package overrides field npm ci package.json overrides synchronization
💡 Result:
The overrides field in package.json is the official mechanism for enforcing specific dependency versions in the dependency graph [1][2]. When you add or modify an overrides entry, it influences how npm resolves dependencies, and these resolutions are recorded in the package-lock.json file [3][2]. Synchronization between your package.json and package-lock.json is mandatory for npm ci [4]. Because npm ci is designed for automated environments where the dependency tree must be strictly reproducible, it will fail if the dependencies defined in your package.json (including any applied overrides) do not exactly match the resolutions recorded in your package-lock.json [4][5]. If you encounter an error stating that your package.json and package-lock.json are out of sync, or that a package is "missing" from the lock file, it typically means your overrides configuration has altered the required dependency tree, but these changes have not yet been successfully committed to your lock file [6][7][5]. To resolve synchronization issues: 1. Run npm install locally to allow npm to re-resolve the dependency tree according to your updated overrides and generate a new, valid package-lock.json [6][5][8]. 2. Commit the updated package-lock.json to your version control system. 3. Ensure that any flags used during npm install (such as --legacy-peer-deps) are also used in your CI/CD pipeline when running npm ci, as discrepancies in these configurations often trigger false "out of sync" errors [4][5]. Key limitations to keep in mind: - Overrides are only honored when defined in the root package.json file [1][9]. They are ignored if placed in dependency or workspace package.json files [1][10]. - Nested overrides (where an override is applied only to a specific dependency path) are supported by defining them as an object within the root overrides field [1]. - npm ci will never modify your package-lock.json; it relies entirely on the file already being in a state that matches your current dependency requirements [4].
Citations:
- 1: https://github.com/npm/rfcs/blob/main/implemented/0036-overrides.md
- 2: https://stackoverflow.com/questions/50328324/how-do-i-fix-a-vulnerable-npm-package-in-my-package-lock-json-that-isnt-listed
- 3: https://docs.npmjs.com/cli/v11/configuring-npm/package-lock-json/
- 4: https://docs.npmjs.com/cli/v9/commands/npm-ci/?v=true
- 5:
npm civalidatespackage-lock.jsonand could fail to resolve npm/cli#5113 - 6: [BUG] overrides cause lock file to be out of sync, since 8.6 npm/cli#4684
- 7: [BUG] npm ci still does not work with overrides correctly npm/cli#4942
- 8: https://stackoverflow.com/questions/79843197/overriding-dependencies-in-package-json-not-having-desired-effect
- 9: [DOCS] Please document "The overrides key will only be considered when it is in the root package.json file for a project" npm/cli#4517
- 10: [BUG] overrides in packages not honoured at the top level npm/cli#5914
Security Misconfiguration (CWE-1104)
Reachability: Internal · Exploitability: Theoretical
Reachability path
● Entry
frontend/src/erd/export.selfcheck.ts:1
react-dom
│
▼
● Sink
frontend/package.json
package-lock.json을 undici 오버라이드와 함께 갱신하세요.
frontend/package-lock.json은 존재하지만 루트 패키지 항목에 overrides가 없습니다. 현재 undici 7.29.0은 ^7.25.0 범위만으로도 선택될 수 있습니다. npm ci의 재현성을 보장하도록 락파일을 재생성하고 변경 사항을 포함하세요. pnpm-lock.yaml은 사용되지 않습니다.
🤖 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 `@frontend/package.json` around lines 37 - 47, Regenerate
frontend/package-lock.json from the frontend package manifest so its root
package entry includes the undici 7.29.0 override and reflects the current
dependency resolution. Do not modify or add pnpm-lock.yaml; ensure the resulting
lockfile remains compatible with npm ci.
Source: Coding guidelines
This commit addresses several issues in the DSN redaction logic: 1. It adds a fallback parsing mechanism for embedded DSN-like strings in the error message so passwords aren't leaked. 2. It improves the redaction of short secrets to pass the STRIX pentest requirements, matching occurrences more aggressively while still safeguarding common parameter names like 'password=' from being incorrectly corrupted. 3. Fixes issues where `urllib.parse.urlsplit` fails to parse non-standard scheme URIs or schema-less URIs, allowing credentials to bypass redaction.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@backend/tests/test_auth_security.py`:
- Around line 271-272: Replace at least one mocked JWT verification test with an
integration test using a real RS256 or EC256 JWK and signed token. Exercise
actual PyJWK parsing and jwt.decode behavior, including required exp, iss, and
jti claims, and verify that a token missing jti raises MissingRequiredClaimError
or is converted by the existing exception handler into the expected
token-verification response; keep the existing mocked tests unchanged unless
needed to support this coverage.
🪄 Autofix
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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 98bbe19e-63d2-4b5c-bc12-c3349b422b8d
⛔ Files ignored due to path filters (3)
backend/requirements-dev.lockis excluded by!**/*.lockbackend/requirements.lockis excluded by!**/*.lockbackend/uv.lockis excluded by!**/*.lock
📒 Files selected for processing (4)
backend/app/auth.pybackend/app/dsn_redaction.pybackend/pyproject.tomlbackend/tests/test_auth_security.py
| monkeypatch.setattr(auth.jwt, "PyJWK", lambda _: type("DummyKey", (), {"key": "dummy"})()) | ||
|
|
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 5 'test_oidc_requires_jti_claim|MissingRequiredClaimError|token missing jti|token verification failed|jwt\.decode' backend/app backend/testsRepository: ContextualWisdomLab/pg-erd-cloud
Length of output: 7052
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the test file around the mentioned lines
echo "=== Lines 265-305 (covering 271-272, 298-300) ==="
sed -n '265,305p' backend/tests/test_auth_security.py
echo ""
echo "=== Lines 360-390 (covering 365-366, 387) ==="
sed -n '360,390p' backend/tests/test_auth_security.py
echo ""
echo "=== Lines 405-450 (covering 409-414, 447) ==="
sed -n '405,450p' backend/tests/test_auth_security.py
echo ""
echo "=== test_oidc_requires_jti_claim full test (lines 391-423) ==="
sed -n '391,423p' backend/tests/test_auth_security.pyRepository: ContextualWisdomLab/pg-erd-cloud
Length of output: 5487
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check if MissingRequiredClaimError is imported or caught anywhere
echo "=== Searching for MissingRequiredClaimError ==="
rg -n 'MissingRequiredClaimError|jwt\.exceptions|from jwt' backend/ --type py
echo ""
echo "=== Check for real JWT token generation in tests ==="
rg -n 'jwt\.encode|PyJWT|signing|private.*key|test.*sign' backend/tests/ --type py | head -30Repository: ContextualWisdomLab/pg-erd-cloud
Length of output: 218
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Let's check the actual auth.py implementation around jwt.decode
echo "=== auth.py jwt.decode call (lines 273-286) ==="
sed -n '273,286p' backend/app/auth.py
echo ""
echo "=== Check what happens after jwt.decode returns (lines 286-310) ==="
sed -n '286,310p' backend/app/auth.py
echo ""
echo "=== Check for any JWT integration or behavior tests ==="
rg -n 'def test.*jwt|def test.*token|def test.*oidc' backend/tests/test_auth_security.py | head -20
echo ""
echo "=== Search for any real key material or secret setup in tests ==="
rg -n 'RS256|EC256|secret|private|public.*key|test.*fixture' backend/tests/test_auth_security.py | head -15Repository: ContextualWisdomLab/pg-erd-cloud
Length of output: 3518
🌐 Web query:
PyJWT 2.13.0 MissingRequiredClaimError required claims validation behavior
💡 Result:
In PyJWT 2.13.0, the MissingRequiredClaimError exception is raised when a required claim is absent from the decoded token [1][2]. This release introduced several updates to how required claims are handled [1][2]: - Explicitly Required Claims: You can enforce the presence of specific claims by adding them to the require option in jwt.decode [3][4]. If any of these claims are missing, MissingRequiredClaimError is raised [3]. - New Requirement Options: Version 2.13.0 introduced dedicated options to require nbf, iat, and exp claims (require_nbf, require_iat, and require_exp). Enabling these options will also trigger a MissingRequiredClaimError if the corresponding claim is absent [1][2]. - Changed Exception Behavior: Previously, if audience or issuer parameters were provided but the respective claims were missing, PyJWT would raise InvalidAudienceError or InvalidIssuerError [1][2]. As of 2.13.0, these scenarios now raise MissingRequiredClaimError [1][2]. Note that MissingRequiredClaimError only reports the first missing claim encountered during the validation process [5]. Additionally, while enforcing the presence of a claim via the require option ensures the claim exists, it does not inherently perform value validation (e.g., verifying that an exp claim is in the future); standard verification options (like verify_exp) must be used alongside to validate the claim's content [6][7][8].
Citations:
- 1: https://pyjwt.readthedocs.io/en/stable/changelog.html
- 2: https://github.com/jpadilla/pyjwt/blob/2.13.0/CHANGELOG.rst
- 3: https://pyjwt.readthedocs.io/en/stable/usage.html
- 4: https://pyjwt.readthedocs.io/en/latest/usage.html
- 5: MissingRequiredClaimError only reports the first missing claim, not all of them jpadilla/pyjwt#1189
- 6: https://pyjwt.readthedocs.io/en/latest/api.html
- 7: https://pyjwt.readthedocs.io/en/2.10.0/api.html
- 8: https://pyjwt.readthedocs.io/en/2.6.0/api.html
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check if pytest fixtures provide real token generation
echo "=== Check for pytest fixtures and conftest ==="
find backend/tests -name "conftest.py" -o -name "fixtures.py" | xargs cat 2>/dev/null || echo "No conftest/fixtures found"
echo ""
echo "=== Check requirements to see PyJWT version ==="
rg -n 'PyJWT|pyjwt|jwt' backend/requirements.txt backend/pyproject.toml backend/setup.py 2>/dev/null || echo "Not found in typical locations"
echo ""
echo "=== Check test file imports at the top ==="
head -50 backend/tests/test_auth_security.py
echo ""
echo "=== Check if there's any fixture or setup that creates real tokens ==="
rg -n '`@pytest.fixture`|def .*token|encode.*jwt' backend/tests/test_auth_security.py | head -10Repository: ContextualWisdomLab/pg-erd-cloud
Length of output: 2609
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Let's understand the flow better: when does _verified_token_from_claims get called?
echo "=== Where is _verified_token_from_claims called? ==="
rg -n '_verified_token_from_claims' backend/app/ --type py
echo ""
echo "=== Does _decode_verified_oidc_token return raw jwt.decode output or processed? ==="
sed -n '240,295p' backend/app/auth.py
echo ""
echo "=== Full _decode_verified_oidc_token and _verified_token_from_claims chain ==="
rg -B5 -A20 'async def _decode_verified_oidc_token|async def _verified_token_from_claims' backend/app/auth.py | head -80Repository: ContextualWisdomLab/pg-erd-cloud
Length of output: 4469
PyJWT의 필수 클레임 검증을 실제 서명된 토큰으로 확인하세요.
라인 271–272, 298–300, 365–366, 387, 409–414, 447의 테스트는 auth.jwt.PyJWK 또는 auth.jwt.decode를 모킹합니다. 따라서 실제 JWK 파싱, 서명 검증, options["require"] 적용, leeway 동작을 확인하지 않습니다.
특히 test_oidc_requires_jti_claim에서 mocked decode는 {"sub": "user-1", "exp": exp_claim()}을 반환하므로 (jti 없음), 라인 304–305의 앱 레벨 확인만 검증합니다. 그러나 실제 PyJWT 2.13.0에서는 options={"require": ["exp", "iss", "jti"]}가 설정되어 있으므로, jti가 누락된 토큰에 대해 jwt.decode는 MissingRequiredClaimError를 발생시키며, 이는 라인 286의 exception 처리자에서 "token verification failed"로 변환됩니다. 따라서 라인 305의 코드는 실행되지 않습니다.
실제 RS256 또는 EC256 JWK와 서명된 토큰을 사용하는 통합 테스트를 최소 하나 추가하세요. jti가 누락된 경우 실제 PyJWT의 동작(MissingRequiredClaimError 또는 앱의 예외 변환)을 확인하세요.
🤖 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/test_auth_security.py` around lines 271 - 272, Replace at least
one mocked JWT verification test with an integration test using a real RS256 or
EC256 JWK and signed token. Exercise actual PyJWK parsing and jwt.decode
behavior, including required exp, iss, and jti claims, and verify that a token
missing jti raises MissingRequiredClaimError or is converted by the existing
exception handler into the expected token-verification response; keep the
existing mocked tests unchanged unless needed to support this coverage.
Sources: Coding guidelines, MCP tools
… Leaks
This commit addresses several critical issues in the DSN redaction logic identified by the STRIX pentest suite:
1. **Embedded DSN Leaks (Fix 1):** Adds a fallback parsing mechanism `_DSN_PATTERN` for finding and parsing embedded DSN-like strings directly from the error message.
2. **Short Secrets Bypass (Fix 2):** Fixes `_redact_secret_occurrences()` to redact short secrets properly. Also implements STRIX's more aggressive regex matching for short secrets while using lookaheads to safeguard common parameter keys like 'password=' from being corrupted.
3. **Multi-Colon Scheme-less DSN (Fix 3):** Relaxes the schema-less fallback condition from `dsn.count(":") == 1` to `dsn.count(":") >= 1` so DSNs with multiple colons are redacted properly.
4. **Double Encoding Gap (Fix 4):** Added iterative decoding for query parameter values to handle doubly-encoded secrets correctly until no `%XX` sequences remain.
5. **Query '&' Splitting (Fix 5):** Replaced manual `split("&")` with robust query parsing using `urllib.parse.parse_qsl` combined with a regex matcher to capture values even if they contain literal unencoded ampersands.
|
Superseded by the clean, bounded security replacement #745. #745 preserves separate URL-userinfo and form-query decoding semantics, adds stronger malformed/short-secret regressions, moves evidence into authoritative doctoring, removes accumulated unrelated branch history, and has green current-head CI/Security/Semgrep/CodeRabbit. Closing this older branch avoids duplicate security work and merge conflicts. |
Understood. Acknowledging that this work is now obsolete and stopping work on this task. |
What
Hardens database-driver error redaction for malformed, scheme-less, and custom-scheme DSNs.
//;user:password@hostfromscheme:user:password@hostwithout discarding either password candidate;urllib.parse.urlsplitrejects malformed authorities;.jules/sentinel.md.Scope correction
The PyJWT migration described by the branch’s original generated title is already represented in the repository baseline. This current diff is the still-relevant DSN redaction security slice. Unrelated frontend dependency drift is being removed before merge.
Verification contract
The focused DSN-redaction suite must achieve 100% statement and branch coverage, the complete backend and frontend suites must pass, Security Scan and Semgrep must pass on the cleaned current head, and repository-required independent approval must be present before merge.
Summary by CodeRabbit
버그 수정
보안
테스트