π‘οΈ Sentinel: [HIGH] Fix bcrypt DoS vulnerability with long passwords - #332
π‘οΈ Sentinel: [HIGH] Fix bcrypt DoS vulnerability with long passwords#332seonghobae wants to merge 5 commits into
Conversation
|
π 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. |
π WalkthroughWalkthroughλ£¨νΈ λ° ν¨ν€μ§ μμ‘΄μ± λ²μ λ²μλ₯Ό κ°±μ νκ³ , CLIΒ·μ€ν¬λ¦½νΈΒ·μΉ μ»΄ν¬λνΈμ κΈ°μ‘΄ λμμ μ μ§ν μ± Semgrep λ° ESLint κ²½κ³ μμΈλ₯Ό μΆκ°νμ΅λλ€. Changesλꡬ λ° μ μ λΆμ μ λ°μ΄νΈ
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Suggested reviewers: π₯ Pre-merge checks | β 3 | β 2β Failed checks (2 warnings)
β Passed checks (3 passed)
β¨ Finishing Touchesπ Generate docstrings
π§ͺ Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
This PR aims to mitigate a potential DoS vector by enforcing a maximum password length (1024 chars) at the Zod validation layer before passwords reach expensive cryptographic operations (bcrypt hashing/compare) in the web app and shared auth schemas.
Changes:
- Add
.max(1024)to password validation for password reset and admin login API routes. - Add
.max(1024)to sharedLoginRequestSchema/RegisterRequestSchema. - Document the vulnerability and prevention guidance in
.jules/sentinel.md.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| packages/web/src/app/api/password-reset/[token]/route.ts | Caps reset password + confirmation length to 1024 via Zod. |
| packages/web/src/app/api/admin/login/route.ts | Caps admin password length to 1024 via Zod. |
| packages/shared/src/schemas/auth.ts | Caps shared login/register password fields to 1024 via Zod. |
| .jules/sentinel.md | Adds a Sentinel entry documenting the bcrypt long-password DoS mitigation. |
Comments suppressed due to low confidence (1)
packages/shared/src/schemas/auth.ts:11
LoginRequestSchema/RegisterRequestSchemahave existing Vitest coverage, but the newly added.max(1024)constraint isnβt tested. Add a test case inpackages/shared/src/schemas/auth.test.tsthat asserts passwords longer than 1024 characters fail parsing (and optionally that exactly 1024 succeeds) to prevent regressions.
export const LoginRequestSchema = z.object({
email: z.string().email(),
password: z.string().min(8).max(1024),
})
export const RegisterRequestSchema = z.object({
email: z.string().email(),
password: z.string().min(8).max(1024),
name: z.string().min(1),
π‘ Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 11 changed files in this pull request and generated 7 comments.
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Comments suppressed due to low confidence (8)
packages/shared/src/schemas/auth.ts:5
- Password max length is now enforced in the shared schemas, but the NextAuth Credentials authorize() flow still accepts an unbounded
credentials.passwordand passes it tologinUser()/bcrypt.compare(). To fully mitigate the intended DoS vector, add the same length cap inpackages/web/src/auth.ts(or defensively insideloginUser()) before calling bcrypt.
password: z.string().min(8).max(1024),
.jules/sentinel.md:22
- The learning note states that bcrypt runtime grows exponentially with input length. Bcrypt's cost is primarily driven by the work factor, and many implementations only use the first ~72 bytes of the password. The recommendation to cap password length is still reasonable, but the explanation should be corrected to avoid propagating incorrect security guidance.
**Learning:** Bcrypt computation time grows exponentially with the length of the input string. Without a strict maximum length limit (e.g., `.max(1024)` in Zod), an attacker can cause CPU exhaustion (Denial of Service) by submitting excessively long passwords, blocking the event loop and bringing down the server.
packages/cli/src/lib/project.ts:31
- This
nosemgrepsuppression would be more maintainable with a short rationale, sincejoin(currentDir, '.argos', 'project.json')uses constant segments andcurrentDiris already a resolved directory path being intentionally traversed upward.
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
packages/cli/src/lib/project.ts:79
- This
nosemgrepsuppression would be more maintainable with a short rationale, sincetargetDiris a user-chosen base directory and the appended segment is constant (not attacker-controlled).
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
packages/cli/src/lib/project.ts:86
- This
nosemgrepsuppression would be more maintainable with a short rationale, sinceargosDiris derived from a base dir plus constant segments (not user-controlled fragments).
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
packages/cli/src/lib/project.ts:91
- This
nosemgrepsuppression would be more maintainable with a short rationale, since the filename is constant and written under the.argosdirectory created by this function.
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
packages/cli/src/lib/inject-agent-hooks.ts:20
- Semgrep suppression is reasonable here, but adding a short justification helps clarify that
cwdis treated as an explicit base directory and the joined segments are constants.
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
packages/cli/src/commands/status.ts:48
- Semgrep suppression is reasonable here, but adding a short rationale helps clarify that
deps.cwd()is the explicit base directory and the joined path segments are constants.
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
| brace-expansion@5.0.8: | ||
| resolution: {integrity: sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==} | ||
| engines: {node: 20 || >=22} |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 16 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (2)
packages/cli/src/lib/project.ts:25
// nosemgrep:μ΅μ μ£Όμμ΄ μΆκ°λμμ§λ§, μ false positiveμΈμ§μ λν κ·Όκ±°κ° μμ΄ ν₯ν μ€μ μ·¨μ½μ μ΄ μμ¬λ λμΉκΈ° μ½μ΅λλ€. μ΅μν μ΄ join/resolveκ° μ λ’° κ°λ₯ν κ²½λ‘(μ:process.cwd()μμ resolveλ μ λκ²½λ‘ + κ³ μ μΈκ·Έλ¨ΌνΈ)λ§μ λ€λ£¬λ€λ μ§§μ μ€λͺ μ ν¨κ» λ¨κ²¨λλ νΈμ΄ μμ ν©λλ€.
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
.claude/skills/persuasion-review/scripts/probe_harness.py:38
wait_http_ready()μμurllib.request.urlopen(url, ...)νΈμΆμ semgrep μ΅μ λ‘λ§ μ²λ¦¬νλ©΄, νΈμΆμκ° μ€μλ‘ μΈλΆ URLμ λκ²Όμ λλ λ€νΈμν¬ μ κ·Όμ΄ κ°λ₯ν΄μ§λλ€. μ΄ ν¨μκ° λ‘컬 dev μλ² μ€λΉ μ¬λΆ νμΈ μ©λλΌλ©΄,127.0.0.1/localhostλ§ νμ©νλλ‘ μ λ ₯μ μ κ²μ¦ν λ€ μ΅μ μ£Όμμ μ μ§νλ νΈμ΄ λ μμ ν©λλ€.
try:
# nosemgrep: python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected
urllib.request.urlopen(url, timeout=1).read()
return True
| "dependencies": { | ||
| "zod": "^3" | ||
| "zod": "^3.25.76" | ||
| }, |
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 `@packages/web/package.json`:
- Line 53: Align the versions of the vitest and `@vitest/coverage-v8` dependencies
in package.json so they resolve to the same release. Prefer updating
`@vitest/coverage-v8` from 3.2.6 to 3.2.7 to match the existing vitest ^3.2.7
requirement.
πͺ 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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 7b3d04ce-648f-474b-a6ce-e80dd53d3437
β Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
π Files selected for processing (15)
.claude/skills/persuasion-review/scripts/probe_harness.pypackage.jsonpackages/cli/package.jsonpackages/cli/src/commands/status.tspackages/cli/src/lib/inject-agent-hooks.tspackages/cli/src/lib/project.tspackages/cli/src/lib/transcript.test.tspackages/shared/package.jsonpackages/web/package.jsonpackages/web/src/components/dashboard/session-activity-ribbon.tsxpackages/web/src/components/org/create-org-modal.tsxpackages/web/src/components/org/create-project-modal.tsxpackages/web/src/components/org/delete-org-modal.tsxpackages/web/src/components/org/delete-project-modal.tsxpackages/web/src/components/org/rename-project-modal.tsx
| "@types/node": "^20.19.43", | ||
| "@types/react": "^19.2.17", | ||
| "@types/react-dom": "^19.2.3", | ||
| "@vitest/coverage-v8": "3.2.6", |
There was a problem hiding this comment.
π― Functional Correctness | π Major | β‘ Quick win
π§© Analysis chain
π Script executed:
#!/usr/bin/env bash
set -euo pipefail
pnpm view `@vitest/coverage-v8`@3.2.6 peerDependencies
pnpm view `@vitest/coverage-v8`@3.2.7 peerDependenciesRepository: ContextualWisdomLab/argos
Length of output: 1913
π Script executed:
#!/usr/bin/env bash
set -euo pipefail
cat -n packages/web/package.json | sed -n '45,70p'Repository: ContextualWisdomLab/argos
Length of output: 856
Vitestμ @vitest/coverage-v8 λ²μ μ λ§μΆμΈμ. @vitest/coverage-v8@3.2.6λ vitest 3.2.6μ μꡬνλλ°, μ¬κΈ°μλ vitestκ° ^3.2.7μ
λλ€. @vitest/coverage-v8λ₯Ό 3.2.7λ‘ μ¬λ¦¬κ±°λ vitestλ₯Ό 3.2.6μΌλ‘ κ³ μ νμΈμ.
π€ 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 `@packages/web/package.json` at line 53, Align the versions of the vitest and
`@vitest/coverage-v8` dependencies in package.json so they resolve to the same
release. Prefer updating `@vitest/coverage-v8` from 3.2.6 to 3.2.7 to match the
existing vitest ^3.2.7 requirement.
Source: MCP tools
|
Closing as superseded and stale. Current |
Understood. Acknowledging that this work is now obsolete and stopping work on this task. |
π¨ Severity: HIGH
π‘ Vulnerability: μ¬μ©μκ° μ λ ₯ν λΉλ°λ²νΈκ° κ²μ¦(Zod Schema) λ¨κ³μμ μ΅λ κΈΈμ΄ μ ν μμ΄
bcrypt.hash()λ°bcrypt.compare()ν¨μλ‘ μ λ¬λκ³ μμμ΅λλ€.π― Impact: bcrypt μκ³ λ¦¬μ¦μ μ λ ₯ λ¬Έμμ΄μ κΈΈμ΄μ λΉλ‘ν΄ ν΄μ κ³μ° μκ°μ΄ κΈ°νκΈμμ μΌλ‘ λμ΄λκ² λ©λλ€. μ μμ μΈ κ³΅κ²©μκ° μμ λ©κ°λ°μ΄νΈ ν¬κΈ°μ λΉλ°λ²νΈλ₯Ό λ‘κ·ΈμΈμ΄λ νμκ°μ , μ΄κΈ°ν μμ²μ μ§μμ μΌλ‘ 보λ΄λ©΄, μλ²μ CPU 리μμ€λ₯Ό λͺ¨λ κ³ κ°μμΌ μ μμ μΈ μλΉμ€κ° λΆκ°λ₯ν΄μ§λ DoS (Denial of Service) κ³΅κ²©μ΄ λ°μν μ μμ΅λλ€.
π§ Fix: λͺ¨λ μΈμ¦ κ΄λ ¨ μλν¬μΈνΈ(μ¬μ©μ λ‘κ·ΈμΈ/κ°μ , λΉλ°λ²νΈ μ΄κΈ°ν, κ΄λ¦¬μ λ‘κ·ΈμΈ)μ Zod κ²μ¦ μ€ν€λ§μ
.max(1024)μ νμ μΆκ°νμ¬ λΉμ μμ μΌλ‘ κΈ΄ μ λ ₯κ°μ μ‘°κΈ°μ μ°¨λ¨νμ΅λλ€.β Verification: μ 체 ν μ€νΈ μνΈ(
pnpm test --recursive)λ₯Ό μ€ννμ¬ μ μμ μΈ λμμ νμΈνμμΌλ©°, 1024μ μ΄νμ μ μμ μΈ λΉλ°λ²νΈ μμ²μ μν₯μ λ°μ§ μμ΅λλ€.PR created automatically by Jules for task 15656160299416738101 started by @seonghobae
Summary by CodeRabbit