Skip to content
Merged
4 changes: 0 additions & 4 deletions apps/client/public/firebase-messaging-sw.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ firebase.initializeApp(firebaseConfig);
const messaging = firebase.messaging();

messaging.onBackgroundMessage((payload) => {
console.log('Received background message ', payload);

const url = payload.data?.url || 'https://www.pinback.today';
const notificationTitle = payload.notification?.title || 'pinback';
const notificationOptions = {
Expand All @@ -50,8 +48,6 @@ messaging.onBackgroundMessage((payload) => {
});

self.addEventListener('notificationclick', (event) => {
console.log('🔔 알림 클릭됨:', event);

const targetUrl = event.notification.data?.url || 'https://www.pinback.today';

fetch(
Expand Down
16 changes: 10 additions & 6 deletions apps/client/src/pages/onBoarding/GoogleCallback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,17 @@ const GoogleCallback = () => {
if (accessToken) {
localStorage.setItem('token', accessToken);

if (typeof chrome !== 'undefined' && chrome.storage?.local) {
chrome.storage.local.set({ token: accessToken }, () => {
console.log('Token saved to chrome storage');
});
}
const sendTokenToExtension = (token: string) => {
window.postMessage(
{
type: 'SET_TOKEN',
token,
},
window.location.origin
);
};
sendTokenToExtension(accessToken);
}

navigate('/');
} else {
navigate('/onboarding?step=ALARM');
Expand Down
26 changes: 10 additions & 16 deletions apps/client/src/shared/apis/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,26 +69,20 @@ export const usePostSignUp = () => {
mutationFn: (data: postSignUpRequest) => postSignUp(data),
onSuccess: (data) => {
const newToken = data?.data?.token || data?.token;
const sendTokenToExtension = (token: string) => {
window.postMessage(
{
type: 'SET_TOKEN',
token,
},
window.location.origin
);
};

if (newToken) {
localStorage.setItem('token', newToken);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

보안: localStorage에 토큰 저장 검토 필요

정적 분석 도구가 올바르게 지적한 것처럼, localStorage에 인증 토큰을 평문으로 저장하는 것은 XSS 공격에 취약합니다(CWE-312). 모든 스크립트가 접근 가능하므로, 악성 스크립트가 토큰을 탈취할 수 있습니다.

다음 대안을 고려해주세요:

  1. HttpOnly 쿠키 사용 (서버측 설정 필요)
  2. 짧은 만료 시간의 토큰 + 리프레시 토큰 전략
  3. 메모리 내 저장 (새로고침 시 재로그인 필요)
localStorage vs cookies for JWT token storage security best practices 2026
🤖 Prompt for AI Agents
In @apps/client/src/shared/apis/queries.ts at line 74, Replace the direct
client-side persistence of the JWT by removing the localStorage.setItem('token',
newToken) call and instead either send the newToken to the backend to set an
HttpOnly, Secure cookie (preferred) or store it in ephemeral in-memory state
(e.g., an auth store) combined with a short-lived access token + refresh token
flow; update the code path handling newToken (reference: localStorage.setItem
and variable newToken) to call the backend endpoint that issues HttpOnly cookies
or to populate your in-memory auth store and implement refresh-token handling so
tokens are not stored in localStorage.

Comment on lines 73 to 74
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n apps/client/src/shared/apis/queries.ts | sed -n '65,90p'

Repository: Pinback-Team/pinback-client

Length of output: 899


🏁 Script executed:

# Search for httpOnly cookie usage or alternative token storage patterns
rg -i "httponly|http-only|cookie.*secure|document\.cookie" --type ts --type js

Repository: Pinback-Team/pinback-client

Length of output: 53


🏁 Script executed:

# Check for XSS protection mechanisms like CSP, sanitization, or security headers
rg -i "content-security-policy|csp|sanitize|xss" --type ts --type js --type json

Repository: Pinback-Team/pinback-client

Length of output: 50385


🏁 Script executed:

# Search for token storage patterns across the codebase
rg "localStorage.*token|sessionStorage.*token|setItem.*token" --type ts --type js -A 2 -B 2

Repository: Pinback-Team/pinback-client

Length of output: 2568


🏁 Script executed:

# Check if there's any security configuration file or documentation
fd -e "json" -e "ts" -e "js" | xargs grep -l "security\|csrf\|xss" 2>/dev/null | head -20

Repository: Pinback-Team/pinback-client

Length of output: 113


localStorage 토큰 저장 방식의 보안 위험성 확인

localStorage에 인증 토큰을 저장하는 방식이 정적 분석 결과와 일치합니다. 이는 XSS 공격에 취약한 구조입니다 (CWE-312). JavaScript에 접근 가능한 저장소에 민감한 인증 정보가 저장되므로, 악의적인 스크립트가 토큰을 탈취할 수 있습니다.

현재 아키텍처에서는:

  • axiosInstance의 요청 인터셉터가 localStorage에서 토큰을 읽음
  • httpOnly 쿠키 사용 없음
  • XSS 방어 메커니즘(CSP 등) 미적용 상태

토큰 저장 방식을 보안 기준에 맞게 개선하거나, XSS 방어 메커니즘을 적용하여 이 위험을 완화해야 합니다.

🧰 Tools
🪛 ast-grep (0.40.4)

[warning] 73-73: Detected potential storage of sensitive information in browser localStorage. Sensitive data like email addresses, personal information, or authentication tokens should not be stored in localStorage as it's accessible to any script.
Context: localStorage.setItem('token', newToken)
Note: [CWE-312] Cleartext Storage of Sensitive Information [REFERENCES]
- https://owasp.org/www-community/vulnerabilities/HTML5_Security_Cheat_Sheet
- https://cwe.mitre.org/data/definitions/312.html

(browser-storage-sensitive-data)

if (typeof chrome !== 'undefined' && chrome.storage?.local) {
chrome.storage.local.set({ token: newToken }, () => {
console.log('Token saved to chrome storage');
});
}
const sendTokenToExtension = (token: string) => {
window.postMessage(
{
type: 'SET_TOKEN',
token,
},
window.location.origin
);
};
sendTokenToExtension(newToken);
}

console.log('회원가입 성공:', data);
},
onError: (error) => {
console.error('회원가입 실패:', error);
Expand Down
1 change: 1 addition & 0 deletions apps/extension/public/logout_chippi.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion apps/extension/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const App = () => {

const [isDuplicatePop, setIsDuplicatePop] = useState(false);
const [mainPopType, setMainPopType] = useState<'add' | 'edit'>('add');
const [isToken, setIsToken] = useState<boolean | null>(null);
const [isToken, setIsToken] = useState(false);

useEffect(() => {
chrome.storage.local.get('token', (result) => {
Expand Down
13 changes: 0 additions & 13 deletions apps/extension/src/apis/axiosInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ apiRequest.interceptors.request.use(async (config) => {
});

// TODO: 환경변수로 분리
// eslint-disable-next-line turbo/no-undeclared-env-vars
const onboardingUrl = import.meta.env.DEV
? 'http://localhost:5173/onboarding?step=SOCIAL_LOGIN'
: 'https://pinback.today/onboarding?step=SOCIAL_LOGIN';

let isRedirecting = false;

Expand All @@ -50,19 +46,10 @@ apiRequest.interceptors.response.use(
if (
error.response &&
(error.response.status === 401 || error.response.status === 403) &&
!originalRequest._retry &&
!isNoAuth
) {
if (!isRedirecting) {
isRedirecting = true;

chrome.storage.local.remove(['token', 'email'], () => {});

chrome.tabs.create({ url: onboardingUrl }, () => {
setTimeout(() => {
isRedirecting = false;
}, 2000);
});
}
}

Expand Down
14 changes: 6 additions & 8 deletions apps/extension/src/background.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
console.log('백그라운드 기능');
chrome.runtime.onInstalled.addListener((details) => {
if (details.reason === 'install') {
chrome.identity.getProfileUserInfo(function (info) {
chrome.storage.local.set({ 'email': info.email }, () => {
console.log(info.email);
});
chrome.storage.local.set({ email: info.email }, () => {
console.log('User email saved:');

Check warning on line 5 in apps/extension/src/background.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
});
setTimeout(() => {
chrome.tabs.create({
url: `https://www.pinback.today/onboarding?email=${info.email}`,
Expand All @@ -14,11 +13,10 @@
}
});


chrome.runtime.onMessage.addListener((message) => {
if (message.type === 'SET_TOKEN') {
chrome.storage.local.set({ 'token': message.token }, () => {
console.log('Token saved!', message.token);
chrome.storage.local.set({ token: message.token }, () => {
console.log('Token saved!');

Check warning on line 19 in apps/extension/src/background.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
});
}
});
});
6 changes: 4 additions & 2 deletions apps/extension/src/content.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
console.log('컨텐츠 스크립트 로드됨');
window.addEventListener('message', (event) => {
if (event.source !== window) return;
if (event.data.type === 'SET_TOKEN') {
chrome.runtime.sendMessage({
type: 'SET_TOKEN',
token: event.data.token,
});
chrome.storage.local.set({ token: event.data.token }, () => {
console.log('Token saved!', event.data.token);

Check warning on line 9 in apps/extension/src/content.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

디버그용 console.log 제거 필요

토큰과 같은 민감한 정보를 콘솔에 출력하지 않도록 해주세요.

🧹 제거 제안
-    chrome.storage.local.set({ token: event.data.token }, () => {
-      console.log('Token saved!', event.data.token);
-    });
+    chrome.storage.local.set({ token: event.data.token });

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @apps/extension/src/content.ts at line 10, Remove the debug console.log that
prints the token (console.log('Token saved!', event.data.token)); do not output
event.data.token or other sensitive values to the console—either delete the
statement entirely or replace it with a non-sensitive confirmation message
(e.g., "Token saved" without the token) or a secure/log- redacted call that
omits event.data.token.

});
Comment on lines +8 to +10
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "background.ts" -o -name "content.ts" | head -20

Repository: Pinback-Team/pinback-client

Length of output: 137


🏁 Script executed:

cat -n apps/extension/src/content.ts

Repository: Pinback-Team/pinback-client

Length of output: 550


🏁 Script executed:

cat -n apps/extension/src/background.ts

Repository: Pinback-Team/pinback-client

Length of output: 903


중복 저장 로직 제거 필요

content.ts에서 chrome.storage.local.set으로 토큰을 저장한 후, 동일한 토큰을 chrome.runtime.sendMessage로 background 스크립트에 전달하고 있습니다. background.ts에서는 SET_TOKEN 메시지를 수신하여 동일한 저장소에 다시 저장합니다(9-11줄과 background.ts 19-21줄).

이는 불필요한 중복입니다. 토큰 저장은 background에서만 담당하도록 통일하세요:

  • content.ts에서 9-11줄의 chrome.storage.local.set 호출 제거
  • message 전달만 유지하여 background에서 저장 처리
적용 예시
 window.addEventListener('message', (event) => {
   if (event.source !== window) return;
   if (event.data.type === 'SET_TOKEN') {
     chrome.runtime.sendMessage({
       type: 'SET_TOKEN',
       token: event.data.token,
     });
-    chrome.storage.local.set({ token: event.data.token }, () => {
-      console.log('Token saved!', event.data.token);
-    });
   }
 });
🤖 Prompt for AI Agents
In @apps/extension/src/content.ts around lines 9 - 11, Remove the duplicate
local storage write in content.ts: delete the chrome.storage.local.set(...) call
that writes event.data.token and keep only the chrome.runtime.sendMessage(...)
that sends the token; ensure the background message handler for "SET_TOKEN" (the
handler that currently calls chrome.storage.local.set in background.ts) remains
responsible for persisting event.data.token so the token is stored only once in
the SET_TOKEN handling code.

}
});
});
8 changes: 6 additions & 2 deletions apps/extension/src/pages/LogOutPop.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { Icon } from '@pinback/design-system/icons';

import LogOutImg from '/logout_chippi.svg';
const LogOutPop = () => {
return (
<div className="bg-white-bg common-shadow flex h-[35.5rem] w-[31.2rem] flex-col items-center justify-center rounded-[1.2rem] px-[3.2rem] py-[2.4rem]">
<div className="mr-auto">
<Icon name="main_logo" width={72} height={20} />
</div>
<div className="flex items-center justify-center pb-[1rem] pt-[0.8rem] text-center">
<Icon name="logout_chippi.2512" width={132} height={132} />
<img
src={LogOutImg}
alt="로그아웃 치삐"
className="h-[13.2rem] w-[13.2rem]"
/>
</div>
<p className="sub2-sb mb-[0.4rem] text-black">
치삐를 만나려면 로그인이 필요해요!
Expand Down
4 changes: 0 additions & 4 deletions packages/design-system/src/components/gtag/utils/gtag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare global {
interface Window {
gtag?: (...args: any[]) => void;

Check warning on line 5 in packages/design-system/src/components/gtag/utils/gtag.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
__gtagInitialized?: boolean;
}
}
Expand All @@ -15,7 +15,7 @@
if (typeof window.gtag === 'function') return true;
await new Promise((res) => setTimeout(res, delay));
}
console.warn('⚠️ gtag not loaded after retries');

Check warning on line 18 in packages/design-system/src/components/gtag/utils/gtag.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
return false;
};

Expand All @@ -40,8 +40,6 @@
event_label: label,
value,
});

console.log(`✅ GA 이벤트 전송됨: ${action}`);
};

/**
Expand All @@ -59,6 +57,4 @@
page_title: title ?? document.title,
page_path: window.location.pathname,
});

console.log('📍 페이지뷰 트래킹:', window.location.pathname);
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

이미지 사라졌을까요?? 😢

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading