Skip to content

fix(security): hash password-reset tokens at rest (#313)#331

Open
dmytrocraft wants to merge 8 commits into
mainfrom
security/313-reset-confirm-tokens-plaintext
Open

fix(security): hash password-reset tokens at rest (#313)#331
dmytrocraft wants to merge 8 commits into
mainfrom
security/313-reset-confirm-tokens-plaintext

Conversation

@dmytrocraft

@dmytrocraft dmytrocraft commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Security fix — Closes #313

Vulnerability (CWE-256, HIGH)

Password-reset tokens were persisted verbatim as the password_reset_tokens
MongoDB document id (<id field-name="tokenValue" strategy="NONE">) and looked
up by exact equality on the raw value (findOneBy(['tokenValue' => $token])).
The stored value was identical to the bearer credential emailed to the user, so
any read access to the collection (backup, replica snapshot, query log, read-only
breach, or a NoSQL-injection sink) yielded directly usable, unexpired reset
tokens for every account with a pending reset — enabling account takeover with no
further interaction. The refresh-token subsystem already hashed at rest
(AuthRefreshTokenhash('sha256', …)); the reset path did not follow it.

Fix

  • PasswordResetToken now stores only hash('sha256', $plainToken) as its
    identifier and keeps the plaintext transiently (never persisted) for the
    reset link. Adds hashToken() and matchesToken() (constant-time hash_equals).
  • MongoDBPasswordResetTokenRepository::findByToken() hashes the incoming
    candidate before querying, so lookups are by stored hash, never raw plaintext.
  • The request → email flow carries the plaintext via the domain event and
    re-attaches it to the reconstituted entity (attachPlainToken) so the emailed
    reset link stays usable.
  • Seeders, in-memory fixtures, and E2E/Memory helpers replay the plaintext
    captured at creation time, keeping all flows green.

A DB-read attacker now only sees a SHA-256 hash; submitting it to the confirm
endpoint fails (hash-of-hash never matches). Generation still uses random_bytes
(256-bit entropy) with unchanged single-use / short-expiry semantics.

Out of scope: email-confirmation tokens (ConfirmationToken via Redis) —
low severity, different cache key/serialization contract; documented in the spec.

Local verification (one-off containers)

  • phpunit Unit: OK (2265 tests, 6234 assertions)
  • deptrac: Violations 0
  • psalm (changed files): No errors found
  • php-cs-fixer (changed files): 0 of 19 files need fixing

BMAD spec

specs/security-313-reset-confirm-tokens-plaintext/ (prd.md, stories.md)

🤖 Generated with Claude Code


Summary by cubic

Hashes password-reset tokens at rest to remove plaintext exposure (CWE-256). Lookups use SHA-256; plaintext exists only transiently for email links, and E2E flows replay it across processes via a Redis-backed recorder.

  • Bug Fixes

    • PasswordResetToken stores only hash('sha256', $plain) and keeps a transient plainToken (default null); adds hashToken(), constant-time matchesToken(), and attachPlainToken().
    • MongoDBPasswordResetTokenRepository::findByToken() hashes the candidate (hash('sha256', $token)) before querying.
    • Request → email flow uses the plaintext and fails fast if it’s missing: handler and email-event factory throw LogicException; subscriber re-attaches the plaintext before sending.
    • Behat/E2E: added PasswordResetTokenRecorder that mirrors the plaintext to Redis to cross web/CLI boundaries; wired in services_test.yaml. Contexts read the recorder first, then transient/in-memory values.
  • Refactors

    • Tests: extracted a helper in RequestPasswordResetCommandHandlerTest to satisfy phpinsights FunctionLength; CI passes.

Written for commit d479844. Summary will update on new commits.

Review in cubic

Password-reset tokens were persisted verbatim as the password_reset_tokens
MongoDB document id and looked up by exact equality on the raw value. The
stored value equalled the bearer credential emailed to the user, so any
read access to the collection (backup, replica snapshot, query log,
read-only breach, NoSQL-injection sink) yielded directly usable, unexpired
reset tokens for every account with a pending reset, enabling account
takeover without further interaction (CWE-256).

Fix (mirrors the existing AuthRefreshToken hash-at-rest precedent):
- PasswordResetToken now stores only hash('sha256', $plainToken) as its
  identifier and keeps the plaintext transiently (never persisted) for the
  reset link; adds hashToken()/matchesToken() (constant-time hash_equals).
- MongoDBPasswordResetTokenRepository::findByToken() hashes the candidate
  before querying, so lookups are by stored hash, never by raw plaintext.
- The request->email flow carries the plaintext via the domain event and
  re-attaches it to the reconstituted entity so the reset link stays usable.
- Seeders/fixtures/E2E helpers replay the plaintext captured at creation.

Email-confirmation tokens (Redis-backed) are out of scope (low severity,
different storage/serialization contract); documented in the BMAD spec.

Local verification (one-off containers):
- phpunit Unit: OK (2265 tests, 6234 assertions)
- deptrac: Violations 0
- psalm (changed files): No errors found
- php-cs-fixer (changed files): 0 of 19 files need fixing

BMAD spec: specs/security-313-reset-confirm-tokens-plaintext/

Closes #313

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

More reviews will be available in 2 minutes and 1 second. Learn how PR review limits work.

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

⌛ 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e7bfd634-ff5b-4ae8-a98c-af3387668c63

📥 Commits

Reviewing files that changed from the base of the PR and between fe53ccb and d479844.

📒 Files selected for processing (23)
  • config/services_test.yaml
  • specs/security-313-reset-confirm-tokens-plaintext/prd.md
  • specs/security-313-reset-confirm-tokens-plaintext/stories.md
  • src/User/Application/CommandHandler/RequestPasswordResetCommandHandler.php
  • src/User/Application/EventSubscriber/PasswordResetRequestedEventSubscriber.php
  • src/User/Application/Factory/Event/PasswordResetEmailSendEventFactory.php
  • src/User/Domain/Entity/PasswordResetToken.php
  • src/User/Domain/Entity/PasswordResetTokenInterface.php
  • src/User/Infrastructure/Repository/MongoDBPasswordResetTokenRepository.php
  • tests/Behat/Support/PasswordResetTokenRecorder.php
  • tests/Behat/UserContext/UserContext.php
  • tests/Behat/UserContext/UserPasswordResetRequestContext.php
  • tests/Memory/GraphQL/GraphQLMemoryWebTestCase.php
  • tests/Memory/Rest/PasswordResetMemoryTest.php
  • tests/Unit/DataFixtures/Command/SeedSchemathesisDataCommandTest.php
  • tests/Unit/DataFixtures/Seeder/PasswordResetTokenSeederTest.php
  • tests/Unit/Shared/Application/Command/Fixture/InMemoryPasswordResetTokenRepository.php
  • tests/Unit/User/Application/CommandHandler/RequestPasswordResetCommandHandlerTest.php
  • tests/Unit/User/Application/EventSubscriber/PasswordResetRequestedEventSubscriberTest.php
  • tests/Unit/User/Domain/Entity/PasswordResetTokenHashingTest.php
  • tests/Unit/User/Domain/Factory/Event/PasswordResetEmailSendEventFactoryTest.php
  • tests/Unit/User/Domain/Factory/PasswordResetTokenFactoryTest.php
  • tests/Unit/User/Infrastructure/Repository/MongoDBPasswordResetTokenRepositoryTest.php
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch security/313-reset-confirm-tokens-plaintext

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.

@cubic-dev-ai cubic-dev-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.

3 issues found across 21 files

Confidence score: 3/5

  • There is meaningful regression risk in the password reset flow: src/User/Domain/Entity/PasswordResetToken.php leaves nullable transient plainToken uninitialized, which can trigger runtime fatals on hydrated entities.
  • src/User/Application/Factory/Event/PasswordResetEmailSendEventFactory.php may fall back to getTokenValue() and email a hashed token, which can produce invalid reset links for users when plaintext is missing.
  • This lands at a moderate risk score because the top issues are concrete and user-facing (reset failures/errors), but they appear localized and fixable before or shortly after merge.
  • Pay close attention to src/User/Domain/Entity/PasswordResetToken.php, src/User/Application/Factory/Event/PasswordResetEmailSendEventFactory.php, and src/User/Application/CommandHandler/RequestPasswordResetCommandHandler.php - token nullability/fallback handling can cause fatals, invalid links, or empty published tokens.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/User/Application/CommandHandler/RequestPasswordResetCommandHandler.php">

<violation number="1" location="src/User/Application/CommandHandler/RequestPasswordResetCommandHandler.php:48">
P2: Casting nullable `getPlainToken()` to string can silently publish an empty reset token (`null` -> `''`) instead of failing fast.</violation>
</file>

<file name="src/User/Application/Factory/Event/PasswordResetEmailSendEventFactory.php">

<violation number="1" location="src/User/Application/Factory/Event/PasswordResetEmailSendEventFactory.php:22">
P2: Fallback to `getTokenValue()` can send the hashed token in reset emails, producing invalid reset links when plaintext was not attached.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant Client as Web/Mobile Client
    participant API as Password Reset API
    participant Handler as RequestPasswordResetHandler
    participant Factory as PasswordResetTokenFactory
    participant Token as PasswordResetToken
    participant Repo as MongoDBPasswordResetTokenRepository
    participant DB as MongoDB
    participant EventBus as Event Bus
    participant Subscriber as PasswordResetRequestedEventSubscriber
    participant EmailEvent as PasswordResetEmailSendEventFactory
    participant EmailService as Email Delivery Service

    Note over Client,EmailService: Password Reset Request Flow

    Client->>API: POST /reset-password (email)
    API->>Handler: __invoke()
    Handler->>Factory: create(userId)
    Factory->>Factory: generate random_bytes(32)
    Factory->>Token: new PasswordResetToken(plainToken, userId, ...)
    Token->>Token: hashToken(plainToken) = SHA-256
    Token->>Token: store hash as tokenValue, keep plainToken transient
    Factory-->>Handler: PasswordResetToken

    Handler->>Repo: save(token)
    Repo->>DB: persist hash (sha256 hex) only
    DB-->>Repo: OK

    Note over Handler,EventBus: Emit event with plainToken
    Handler->>EventBus: publish PasswordResetRequestedEvent(plainToken)
    EventBus->>Subscriber: __invoke(event)

    Subscriber->>Repo: findByToken(event.plainToken)
    Repo->>DB: query by hash(plainToken)
    DB-->>Repo: matched entity (hash only)
    Repo-->>Subscriber: PasswordResetToken (no plainToken)

    Note over Subscriber: Re-attach plaintext for email
    Subscriber->>Token: attachPlainToken(event.plainToken)
    Subscriber->>EmailEvent: create(token, user)
    EmailEvent->>Token: getPlainToken() → plaintext
    EmailEvent->>Token: getTokenValue() → hash (NOT used)
    EmailEvent-->>Subscriber: PasswordResetEmailSentEvent(plainToken)
    Subscriber->>EmailService: dispatch email with plainToken in link

    Note over Client,EmailService: Password Reset Confirm Flow

    Client->>API: POST /confirm-reset (token=plainToken)
    API->>Repo: findByToken(plainToken)
    Repo->>Repo: hashToken(plainToken)
    Repo->>DB: query by ['tokenValue' => hash]
    DB-->>Repo: matched entity
    Repo-->>API: PasswordResetToken

    API->>Token: matchesToken(plainToken)
    Token->>Token: hash_equals(storedHash, hash(plainToken))
    alt Matches
        API->>DB: update password, invalidate token
        DB-->>API: OK
        API-->>Client: 200 Success
    else No match (hash-of-hash submitted)
        API-->>Client: 401 Invalid token
    end

    Note over Token,DB: Attacker with DB read access
    Note over DB: Attacker sees: hash(plainToken) = "a3f8c2..."
    Note over API,Token: Attacker submits hash as token
    API->>Token: matchesToken("a3f8c2...")
    Token->>Token: hash("a3f8c2...") != stored hash
    Token-->>API: false → reject
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/User/Domain/Entity/PasswordResetToken.php Outdated
Comment thread src/User/Application/CommandHandler/RequestPasswordResetCommandHandler.php Outdated
Comment thread src/User/Application/Factory/Event/PasswordResetEmailSendEventFactory.php Outdated
Signed-off-by: Vadym <kostiukdsfv@gmail.com>

@cubic-dev-ai cubic-dev-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.

0 issues found across 2 files (changes from recent commits).

Requires human review: Auto-approval blocked by 3 unresolved issues from previous reviews.

Re-trigger cubic

@codecov

codecov Bot commented Jun 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (fe53ccb) to head (d479844).

Additional details and impacted files
@@              Coverage Diff              @@
##                main      #331     +/-   ##
=============================================
  Coverage     100.00%   100.00%             
- Complexity         0      2772   +2772     
=============================================
  Files            551       551             
  Lines           9541      9558     +17     
=============================================
+ Hits            9541      9558     +17     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Resolve two CI failures introduced by the token-hashing fix:
- Replace static PasswordResetToken::hashToken() call in the repository
  with the inline hash('sha256', ...) used by the sibling refresh-token
  repository, clearing the PHPMD StaticAccess violation that broke the
  Bats "make phpinsights" check.
- Default the transient $plainToken property to null so reads stay safe
  after Doctrine ODM hydration (which bypasses the constructor), fixing
  the Behat fatal error in the password-reset confirm flow.
- Add unit regression tests covering constructorless hydration; Infection
  keeps 100% MSI on both changed files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 10, 2026

@cubic-dev-ai cubic-dev-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.

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/User/Infrastructure/Repository/MongoDBPasswordResetTokenRepository.php">

<violation number="1" location="src/User/Infrastructure/Repository/MongoDBPasswordResetTokenRepository.php:43">
P3: Token hashing logic is duplicated in the repository instead of using the domain’s canonical `hashToken()` helper, creating a drift risk.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

{
return $this->findOneBy(['tokenValue' => $token]);
return $this->findOneBy([
'tokenValue' => hash('sha256', $token),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Token hashing logic is duplicated in the repository instead of using the domain’s canonical hashToken() helper, creating a drift risk.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/User/Infrastructure/Repository/MongoDBPasswordResetTokenRepository.php, line 43:

<comment>Token hashing logic is duplicated in the repository instead of using the domain’s canonical `hashToken()` helper, creating a drift risk.</comment>

<file context>
@@ -40,7 +40,7 @@ public function save(
     {
         return $this->findOneBy([
-            'tokenValue' => PasswordResetToken::hashToken($token),
+            'tokenValue' => hash('sha256', $token),
         ]);
     }
</file context>
Suggested change
'tokenValue' => hash('sha256', $token),
'tokenValue' => PasswordResetToken::hashToken($token),

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.

Keeping the inline hash('sha256', $token) here intentionally. Routing through PasswordResetToken::hashToken() triggers the project's PHPMD cleancode StaticAccess rule (commit ba4c710 had already switched away from the static call for exactly that CI reason). Drift is not silent: MongoDBPasswordResetTokenRepositoryTest::testFindByTokenLooksUpByHashNotPlaintext pins the repository lookup to PasswordResetToken::hashToken(), so any divergence in the hashing algorithm fails the test suite. Added a comment documenting this.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Got it — thanks for the clarification.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-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.

0 issues found across 3 files (changes from recent commits).

Requires human review: Auto-approval blocked by 3 unresolved issues from previous reviews.

Re-trigger cubic

The HTTP password-reset flow persists only the hashed token, so the
session-integration scenario confirmed with the stored hash and got 404.
Record the plaintext emitted on PasswordResetRequestedEvent during the
request and replay it on confirmation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-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.

0 issues found across 3 files (changes from recent commits).

Requires human review: Auto-approval blocked by 3 unresolved issues from previous reviews.

Re-trigger cubic

…ns for #313

Behat drives the API over real HTTP (browserkit_http), so the
PasswordResetRequestedEvent is dispatched in the web-server process while
the confirmation step resolves the plaintext in the Behat CLI process. The
in-process PasswordResetTokenRecorder therefore never captured the token for
HTTP-driven reset flows, raising 'No plaintext password reset token was
captured for confirmation.' Mirror the plaintext through the shared Redis
instance (both processes use the same REDIS_URL) so it survives the process
boundary; in-memory capture still serves in-process flows. Production hashing
is untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-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.

0 issues found across 1 file (changes from recent commits).

Requires human review: Auto-approval blocked by 3 unresolved issues from previous reviews.

Re-trigger cubic

Fail fast when the transient plaintext password-reset token is missing
instead of silently publishing an empty token or emailing the stored
hash. Document why the repository hashes inline (PHPMD cleancode forbids
static access) and rely on the existing repository test to pin both
sides to the same algorithm against drift.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@dmytrocraft

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 10, 2026

@cubic-dev-ai cubic-dev-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.

1 issue found across 5 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/Unit/User/Application/CommandHandler/RequestPasswordResetCommandHandlerTest.php">

<violation number="1" location="tests/Unit/User/Application/CommandHandler/RequestPasswordResetCommandHandlerTest.php:90">
P3: Missing expectation on `passwordResetTokenRepository->save()` — the handler calls `save()` before the plain-token check, so this call is exercised during the test but never explicitly verified. This inconsistency with the existing success test's `setupTokenFactoryExpectations()` reduces the test's regression-detection value.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

->with($email)
->willReturn($user);

$this->passwordResetTokenFactory

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Missing expectation on passwordResetTokenRepository->save() — the handler calls save() before the plain-token check, so this call is exercised during the test but never explicitly verified. This inconsistency with the existing success test's setupTokenFactoryExpectations() reduces the test's regression-detection value.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/Unit/User/Application/CommandHandler/RequestPasswordResetCommandHandlerTest.php, line 90:

<comment>Missing expectation on `passwordResetTokenRepository->save()` — the handler calls `save()` before the plain-token check, so this call is exercised during the test but never explicitly verified. This inconsistency with the existing success test's `setupTokenFactoryExpectations()` reduces the test's regression-detection value.</comment>

<file context>
@@ -70,6 +70,43 @@ public function testRequestPasswordResetForExistingUser(): void
+            ->with($email)
+            ->willReturn($user);
+
+        $this->passwordResetTokenFactory
+            ->expects($this->once())
+            ->method('create')
</file context>

Extract the missing-plain-token arrange block in
RequestPasswordResetCommandHandlerTest into a private helper so the test
method stays under the 20-line FunctionLength limit, restoring the
phpinsights tests architecture score to 100.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-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.

0 issues found across 1 file (changes from recent commits).

Requires human review: Auto-approval blocked by 1 unresolved issue from previous reviews.

Re-trigger cubic

@dmytrocraft

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

[Security] high: Password-reset and email-confirmation tokens stored in plaintext at rest

2 participants