Skip to content

fix: security vulnerabilities, export checkExistenceBatch, and handle undefined color/bgTemp#108

Open
voioo wants to merge 4 commits intoLifeforge-app:mainfrom
voioo:fix/core-v2
Open

fix: security vulnerabilities, export checkExistenceBatch, and handle undefined color/bgTemp#108
voioo wants to merge 4 commits intoLifeforge-app:mainfrom
voioo:fix/core-v2

Conversation

@voioo
Copy link

@voioo voioo commented Feb 16, 2026

Summary

  • Security fixes (path traversal, SSRF, global state):

    • Path traversal protection with sanitized paths in module serving
    • SSRF protection blocking internal IPs in CORS proxy
    • Per-user state management for 2FA and OAuth flows
    • Proper error handling and logging
  • Performance improvements:

    • Batch database queries for record existence checks
    • Async file cleanup doesn't block error responses
  • Bug fixes:

    • Export checkExistenceBatch from database index (fixes server build)
    • Handle undefined color/bgTemp in UserPersonalizationProvider (fixes login crash)

Files changed

  • server/src/core/functions/database/validation.ts - Added checkExistenceBatch
  • server/src/core/functions/database/index.ts - Export checkExistenceBatch
  • server/src/lib/corsAnywhere/index.ts - SSRF protection
  • server/src/core/routes/index.ts - Path traversal protection
  • server/src/lib/user/routes/twoFA.ts - Per-user state
  • server/src/lib/user/routes/oauth.ts - Per-session state
  • client/src/providers/features/UserPersonalizationProvider.tsx - Handle undefined values

voioo and others added 3 commits February 16, 2026 23:14
…es (#1)

* fix(security): resolve critical vulnerabilities and architecture issues

- fix(path-traversal): validate file paths in module serving middleware
- fix(ssrf): add blocked hosts list to CORS proxy
- refactor(2fa): replace global state with per-user state management
- refactor(oauth): replace global codeVerifier with per-session state
- fix(auth): add proper error logging to silent catch blocks

Security improvements:
- Path traversal protection with sanitized paths and boundary checks
- SSRF protection blocking internal IPs and metadata endpoints
- Concurrent request safety for 2FA and OAuth flows
- Proper error handling and logging throughout

* perf: optimize performance and improve type safety

- perf(response): replace sync file operations with async cleanup
- perf(validation): add batch existence check to fix N+1 query pattern
- refactor(response): improve type safety and error handling
- refactor(validation): add proper TypeScript interfaces

Performance improvements:
- Async file cleanup doesn't block error responses
- Batch database queries for record existence checks
- Better error handling and logging throughout
- Add missing export for checkExistenceBatch function
- Fixes Docker server build failure
@vercel
Copy link

vercel bot commented Feb 16, 2026

@voioo is attempting to deploy a commit to the Codeblog Team on Vercel.

A member of the Team first needs to authorize it.

@lukashow
Copy link
Collaborator

Hi, thank you so much for your contribution!
I've also fixed one of the issue where the 2fa challenge is not properly decrypted.

@melvinchia3636 please review this PR and revert.

Copy link
Member

@melvinchia3636 melvinchia3636 left a comment

Choose a reason for hiding this comment

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

There are a lot of great minor improvements on the codebase, especially in the error validation part. Your contribution is highly valued and appreciated.

However, since LifeForge doesn't support multiple users per server instance and is not planning to do so anytime soon, the per-user implementations might not be necessary.

Also, it would be great if you could split this huge monolithic PR into multiple into several smaller ones that focus on a specific topic each, so that it could be easier for us to review the code.

Comment on lines +64 to 69
// Security: Use configured CORS origins instead of wildcard
const origin = req.headers.origin
if (origin && CORS_ALLOWED_ORIGINS.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin)
}
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin')
Copy link
Member

Choose a reason for hiding this comment

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

We already have the cors modules configured, so we shouldn't need to configure the cors header ourselves.

Comment on lines +71 to +73
} else if (value !== undefined && value !== null) {
// Handle other types - convert to string
isValid = await checkSingle(req.pb(module), collection, String(value))
Copy link
Member

Choose a reason for hiding this comment

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

will there actually be a scenario where the value isn't a string?

Comment on lines -32 to +40
.callback(async () => challenge)
.callback(async ({ pb }) => {
const userId = pb.instance.authStore.record?.id

const challenge = generateChallenge()

// Store per-user challenge so subsequent calls (like generateAuthenticatorLink)
// can reuse the same challenge value for encryption/decryption.
if (userId) {
twoFAStates.set(userId, {
...twoFAStates.get(userId),
challenge,
challengeExpiresAt: dayjs().add(5, 'minutes').toISOString()
})
}

return challenge
Copy link
Member

Choose a reason for hiding this comment

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

LifeForge is a single-user-single-instance project, so is it necessary to implement per-user challenge?

Comment on lines -72 to +90
canDisable2FA = true
setTimeout(
() => {
canDisable2FA = false
},
1000 * 60 * 5
)
// Store per-user state instead of global state
const userId = pb.instance.authStore.record?.id
if (userId) {
twoFAStates.set(userId, {
canDisable: true,
expiresAt: dayjs().add(5, 'minutes').toISOString()
})
}
Copy link
Member

Choose a reason for hiding this comment

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

LifeForge is a single-user-single-instance project, so is it necessary to implement per-user challenge?

Comment on lines +126 to +146
// Store per-user temp code instead of global
twoFAStates.set(userId, {
...twoFAStates.get(userId),
tempCode,
tempCodeExpiresAt: dayjs().add(5, 'minutes').toISOString()
})

// Prefer an existing per-user challenge created via `getChallenge()`
const existing = twoFAStates.get(userId)

const challenge = existing?.challenge || generateChallenge()

// If we generated a new challenge here, store it so the client can retrieve it
if (!existing) {
twoFAStates.set(userId, {
...twoFAStates.get(userId),
challenge,
challengeExpiresAt: dayjs().add(5, 'minutes').toISOString()
})
}

Copy link
Member

Choose a reason for hiding this comment

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

LifeForge is a single-user-single-instance project, so is it necessary to implement per-user challenge?

@melvinchia3636 melvinchia3636 added enhancement New feature or request infrastructure Stuff related to LifeForge's core system architecture labels Feb 18, 2026
@melvinchia3636
Copy link
Member

Perhaps the auth state management (challenges, twoFA states etc.) we could create a global state storage for modules to use, so that we don't need to reimplement the Map object in different modules that require one.

@lukashow
Copy link
Collaborator

@melvinchia3636 will it be good to have multiple users? IMO we already have user authentication system in the very first, so it's not tedious in future to implement multi users functionality. Perhaps, just leave the per-user challenge there. How do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request infrastructure Stuff related to LifeForge's core system architecture

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants