허용목록이 플러그인 코드 상수라, 새 확장이 저장 키를 도입할 때마다 이 플러그인을 고쳐야 했다. 고치지 않으면 그 키는 방문마다 파기되는데 예외도 로그도 남지 않아 운영자에게는 "설정이 저장되지 않는다" 는 증상만 보인다. 허용목록을 운영자 설정(necessary_storage_allowlist)으로 옮기고, 관리자 환경설정에 브라우저 저장소·세션 저장소·쿠키 세 카드를 두어 직접 편집하게 했다. 확장은 GDPR 을 알 필요가 없고 GDPR 도 확장을 알 필요가 없다 — 연결은 운영자가 화면에서 한다. 저장 키를 플러그인이 관측·수집하지 않는 것도 같은 이유다: 관측 자체가 추적이다. 판정은 서버(미들웨어)와 클라이언트 셋(인터셉터 둘 + 정리기)이 같은 설정을 같은 매칭 규칙으로 읽는다. 잠금 항목(로그인 토큰·CSRF·세션 쿠키·동의 기록)만 설정 밖에 두고 판정 시점에 합집합으로 얹는다 — 설정에 담기면 저장 요청 한 번으로 지워져 잠금이 아니게 된다. 기설치본은 업그레이드 스텝이 현재 카탈로그를 설정 파일에 못박되, 운영자가 이미 편집했으면 그대로 둔다. 함께 고친 선재 결함 셋: - 차단 도메인 추천 목록이 응답에 실리지 않아 자동완성이 동작하지 않던 문제 - 세션 쿠키 이름을 기본값에서 바꾼 사이트에서 브라우저 측 목록의 그 항목이 죽던 문제 - 쿠키 목록에만 앞부분 매칭(`이름_*`)이 적용되지 않던 문제
112 lines
3.9 KiB
TypeScript
112 lines
3.9 KiB
TypeScript
/**
|
|
* GDPR functional 카테고리 cleanup 함수 (functionalCleaner — Phase 2 단순화)
|
|
*
|
|
* 사용자가 functional 동의를 철회하거나 부팅 시 functional 미동의 상태인 경우,
|
|
* **strictly necessary allowlist 외 모든** localStorage / sessionStorage / cookie
|
|
* 를 즉시 파기. EDPB Guidelines 05/2020 §117 "동의 철회 시 즉시 중단" 충족.
|
|
*
|
|
* cleanup 정책 (Phase 2 단순화):
|
|
* - 운영자 등록 표 (functional_storage_keys / functional_cookies) 불필요
|
|
* - strictly necessary 허용목록(운영자 설정 ∪ 잠금 집합) 외 모든 키/이름을 자동 파기
|
|
* - GDPR 원칙 "strictly necessary 외 비-필수는 동의 전 차단" 의 cleanup 측 적용
|
|
*
|
|
* 본 함수는 인터셉터 install 이후 호출되어도 안전 — removeItem 은 인터셉터가
|
|
* 통과시키며, cookie 파기 패턴 (Max-Age=0) 도 cookieInterceptor 의 `isClearingCookie`
|
|
* 가드로 통과.
|
|
*
|
|
* @module sirsoft-gdpr/functionalCleaner
|
|
*/
|
|
|
|
import {
|
|
LOCKED_FALLBACK,
|
|
isNecessary,
|
|
type NecessaryAllowlist,
|
|
} from './necessaryAllowlist';
|
|
import { type StorageKind } from './storageInterceptor';
|
|
|
|
/**
|
|
* cleanup 옵션 (선택).
|
|
*
|
|
* @property allowlist strictly necessary 허용목록 (운영자 설정 ∪ 잠금 집합).
|
|
* 생략 시 잠금 집합만 남는 최소 폴백 — 설정을 읽지 못한 상황에서도
|
|
* 로그인 토큰·CSRF·동의 쿠키는 파기하지 않는다.
|
|
*/
|
|
export interface FunctionalCleanupOptions {
|
|
allowlist?: NecessaryAllowlist;
|
|
}
|
|
|
|
/**
|
|
* 한 Storage 인스턴스에서 strictly necessary allowlist 외 모든 키를 파기합니다.
|
|
*
|
|
* 순회 중 removeItem 으로 storage.length 가 줄어드는 문제를 피하기 위해 키 목록을 먼저 수집.
|
|
*
|
|
* @param storage 대상 Storage (localStorage 또는 sessionStorage)
|
|
* @param storageKind storage 종류 (allowlist 매칭용)
|
|
* @param allowlist necessary allowlist
|
|
* @return void
|
|
*/
|
|
function purgeStorage(
|
|
storage: Storage,
|
|
storageKind: StorageKind,
|
|
allowlist: NecessaryAllowlist,
|
|
): void {
|
|
const keys: string[] = [];
|
|
for (let i = 0; i < storage.length; i++) {
|
|
const k = storage.key(i);
|
|
if (k !== null) keys.push(k);
|
|
}
|
|
|
|
for (const key of keys) {
|
|
if (isNecessary(key, storageKind, allowlist)) {
|
|
continue;
|
|
}
|
|
try {
|
|
storage.removeItem(key);
|
|
} catch {
|
|
// SecurityError / QuotaExceeded 등 — 조용히 무시.
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* document.cookie 에서 strictly necessary allowlist 외 모든 cookie 를 Max-Age=0 으로 파기합니다.
|
|
*
|
|
* @param allowlist necessary 허용목록 (`cookie` 스코프만 사용)
|
|
* @return void
|
|
*/
|
|
function purgeCookies(allowlist: NecessaryAllowlist): void {
|
|
const raw = document.cookie;
|
|
if (!raw) return;
|
|
|
|
const cookies = raw.split(';');
|
|
for (const cookie of cookies) {
|
|
const eq = cookie.indexOf('=');
|
|
const name = (eq > -1 ? cookie.substring(0, eq) : cookie).trim();
|
|
if (!name) continue;
|
|
if (isNecessary(name, 'cookie', allowlist)) continue;
|
|
|
|
// 표준 cookie 파기 패턴 — Max-Age=0 + 빈 값 + Path=/
|
|
// cookieInterceptor 의 isClearingCookie 가드로 통과.
|
|
try {
|
|
document.cookie = `${name}=; Max-Age=0; Path=/`;
|
|
} catch {
|
|
// 무시.
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* functional 카테고리 cleanup — strictly necessary allowlist 외 모든 1st-party 저장소 파기.
|
|
*
|
|
* 부팅 시점 (재방문 + 미동의) 또는 동의 철회 시점에 호출.
|
|
*
|
|
* @param options 옵션 (생략 시 기본 allowlist 사용)
|
|
* @return void
|
|
*/
|
|
export function cleanupFunctionalArtifacts(options: FunctionalCleanupOptions = {}): void {
|
|
const allowlist = options.allowlist ?? LOCKED_FALLBACK;
|
|
|
|
purgeStorage(window.localStorage, 'localStorage', allowlist);
|
|
purgeStorage(window.sessionStorage, 'sessionStorage', allowlist);
|
|
purgeCookies(allowlist);
|
|
} |