-
Notifications
You must be signed in to change notification settings - Fork 0
๐ก๏ธ Sentinel: [CRITICAL] Fix hardcoded admin credentials #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
|
|
||
| ## 2024-05-24 - [CRITICAL] Fix Hardcoded Admin Credentials in Authentication Logic | ||
| **Vulnerability:** Found hardcoded `ADMIN_USERNAME` and `ADMIN_PASSWORD` in `packages/web/src/lib/server/admin-auth.ts`. | ||
| **Learning:** Hardcoding credentials in source code exposes them to anyone with read access to the repository and makes it impossible to securely manage or rotate these secrets across different environments. | ||
| **Prevention:** Always use environment variables for secrets and credentials. Use tools like `zod` to validate their presence at runtime (e.g., in `env.ts`) and ensure proper placeholder values are added to `.env.example` and CI workflows to prevent build regressions. | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,19 +1,37 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import 'server-only' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { createHmac, randomBytes, timingSafeEqual } from 'crypto' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync, pbkdf2 } from 'crypto' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { promisify } from 'util' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { cookies } from 'next/headers' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { NextRequest, NextResponse } from 'next/server' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { env } from './env' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const ADMIN_USERNAME = 'admin' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const ADMIN_PASSWORD = 'og9oRajx7h88v1RIj3eDgdrh9jgLYVV3' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const ADMIN_USERNAME = env.ADMIN_USERNAME | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const ADMIN_PASSWORD = env.ADMIN_PASSWORD | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ADMIN_SESSION_COOKIE = 'argos_admin_session' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ADMIN_SESSION_TTL_MS = 12 * 60 * 60 * 1000 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ADMIN_IMPERSONATION_TTL_MS = 60 * 1000 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ADMIN_IMPERSONATION_PREFIX = 'argos_imp' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const pbkdf2Async = promisify(pbkdf2) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // PBKDF2 parameters for secure password hashing | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const HASH_SALT = 'argos_admin_salt' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const HASH_ITERATIONS = 100000 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const HASH_KEYLEN = 64 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const HASH_DIGEST = 'sha512' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+20
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์ ์ ์ํธ ์ฌ์ฉ์ ๋ณด์ ๋ชจ๋ฒ ์ฌ๋ก ์๋ฐ
๊ถ์ฅ ํด๊ฒฐ์ฑ :
๋จ์ผ ๊ด๋ฆฌ์ ๊ณ์ ์ 100k iterations๋ฅผ ์ฌ์ฉํ๋ฏ๋ก ์ฆ๊ฐ์ ์ธ ์ํ์ ์๋์ง๋ง, ๋ณด์ ์ฌ์ธต ๋ฐฉ์ด ์ธก๋ฉด์์ ๊ฐ์ ์ด ํ์ํฉ๋๋ค. ๐ก๏ธ ํ๊ฒฝ๋ณ์ ๊ธฐ๋ฐ ์ํธ ์ฌ์ฉ ์ ์
const EnvSchema = z.object({
DATABASE_URL: z.string().min(1),
DIRECT_URL: z.string().min(1),
JWT_SECRET: z.string().min(32),
ADMIN_USERNAME: z.string().trim().min(1),
ADMIN_PASSWORD: z.string().trim().min(8),
+ ADMIN_PASSWORD_SALT: z.string().min(16),
})admin-auth.ts ์์ : -const HASH_SALT = 'argos_admin_salt'
+const HASH_SALT = env.ADMIN_PASSWORD_SALT๐ค Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Pre-compute the target hash of the admin password at module initialization | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const TARGET_PASSWORD_HASH = pbkdf2Sync( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ADMIN_PASSWORD, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HASH_SALT, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HASH_ITERATIONS, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HASH_KEYLEN, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HASH_DIGEST | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function safeEqual(a: string, b: string): boolean { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const aHash = createHmac('sha256', env.JWT_SECRET).update(a).digest() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const bHash = createHmac('sha256', env.JWT_SECRET).update(b).digest() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -24,14 +42,24 @@ function sign(payload: string): string { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| return createHmac('sha256', env.JWT_SECRET).update(payload).digest('base64url') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function verifyAdminCredentials(input: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export async function verifyAdminCredentials(input: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| username: string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| password: string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }): boolean { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| safeEqual(input.username, ADMIN_USERNAME) && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| safeEqual(input.password, ADMIN_PASSWORD) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }): Promise<boolean> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!safeEqual(input.username, ADMIN_USERNAME)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Hash the incoming password asynchronously to avoid blocking the event loop | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const inputPasswordHash = await pbkdf2Async( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| input.password, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HASH_SALT, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HASH_ITERATIONS, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HASH_KEYLEN, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HASH_DIGEST | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return timingSafeEqual(inputPasswordHash, TARGET_PASSWORD_HASH) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+49
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ํ์ด๋ฐ ์ค๋ผํด์ ํตํ ์ฌ์ฉ์๋ช ์ด๊ฑฐ ๊ฐ๋ฅ์ฑ ์ฌ์ฉ์๋ช ๋ถ์ผ์น ์ ์ฆ์ ๋ฐํํ๊ณ (Line 49-51), ๋น๋ฐ๋ฒํธ ๊ฒ์ฆ์ PBKDF2 ํด์ ๊ณ์ฐ ํ ์ํ๋ฉ๋๋ค. ์ด ์๊ฐ ์ฐจ์ด(~์๋ฐฑms)๋ก ๊ณต๊ฒฉ์๊ฐ ์ ํจํ ์ฌ์ฉ์๋ช ์ ์ด๊ฑฐํ ์ ์์ต๋๋ค. ๋จ์ผ ๊ด๋ฆฌ์ ๊ณ์ ์์คํ ์์๋ ์ํ๋๊ฐ ๋ฎ์ง๋ง, ์์ ํ ์์ ์๊ฐ ๊ฒ์ฆ์ ์ํด ์ฌ์ฉ์๋ช ๊ณผ ๊ด๊ณ์์ด ํญ์ PBKDF2๋ฅผ ์ํํ๋ ๊ฒ์ด ์ข์ต๋๋ค. โฑ๏ธ ์์ ์๊ฐ ๊ฒ์ฆ ์ ์ export async function verifyAdminCredentials(input: {
username: string
password: string
}): Promise<boolean> {
- if (!safeEqual(input.username, ADMIN_USERNAME)) {
- return false
- }
-
// Hash the incoming password asynchronously to avoid blocking the event loop
const inputPasswordHash = await pbkdf2Async(
input.password,
HASH_SALT,
HASH_ITERATIONS,
HASH_KEYLEN,
HASH_DIGEST
)
- return timingSafeEqual(inputPasswordHash, TARGET_PASSWORD_HASH)
+ const usernameMatch = safeEqual(input.username, ADMIN_USERNAME)
+ const passwordMatch = timingSafeEqual(inputPasswordHash, TARGET_PASSWORD_HASH)
+ return usernameMatch && passwordMatch
}๐ Committable suggestion
Suggested change
๐ค Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function createAdminSessionCookieValue(): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
์ผํฐ๋ฌ ๊ธฐ๋ก์ ๋ ์ง๋ฅผ ์์ ํ์ธ์.
๋ฌธ์์ ๊ธฐ๋ก๋ ๋ ์ง๊ฐ
2024-05-24์ด์ง๋ง ์ด PR์ 2026-06-07์ ์์ฑ๋์์ต๋๋ค. ์ผํฐ๋ฌ ๊ธฐ๋ก์ ๋ ์ง๋ ์ค์ ์์ ์ด ์ด๋ฃจ์ด์ง ๋ ์ง๋ฅผ ์ ํํ๊ฒ ๋ฐ์ํด์ผ ํฉ๋๋ค.๐ ๋ ์ง ์์ ์ ์
๐ Committable suggestion
๐ค Prompt for AI Agents