SAL-SA Audit Report

AX-On Platform — 소프트웨어 감사 리포트

SAL Grid 방법론으로 분해·분석·점수화한 성능개선 보고서

분석일 2026-03-16 대상 AX-On Platform (ax-on.net) 스택 Vanilla HTML/CSS/JS + Supabase + Vercel 총 파일 11개 HTML + 1개 JS config + 2개 DB 테이블
PROCESS 3단계 개선 프로세스 — Before → Issues → After
1
최초 진단 — SALSA 자동 감사 실행
FE 코드구조
32
PF 성능
58
SC 보안
68
QA 품질
25
AR 아키텍처
55
종합 점수 54점 / 100
2
지적사항 도출 — SAL ID별 문제 식별
S2FE1 CSS 변수 선언 5개 파일 중복 — 브라우저 캐싱 불가, 수정 시 5곳 동기화 필요 🔴 즉시
S2FE2 NAV HTML+CSS+JS 4개 파일 복사 — 수정 시 버그 위험, 동기화 오류 🔴 즉시
S2FE3 OG 이미지 /images/og-default.png → 404 — 소셜 공유 시 미리보기 없음 🟠 단기
S2PF1 Google Fonts 렌더 블로킹 — Noto Sans KR 4 weights 로드로 LCP +300~500ms 🟠 단기
S2AU1 /api/config 매 페이지 호출 캐싱 없음 — 11페이지 방문 시 11번 동일 API 호출 🟠 단기
3
개선 결과 — 실제 코드 수정 후 재측정
S7FE1 ✅ css/common.css 생성 — CSS 변수, NAV, Footer, 버튼 공통화. 전송 -40KB+, 캐싱 적용 FE 32→72
S7PF1 ✅ Fonts 비동기 로딩 + weight 2개로 축소 (400,700만) → LCP -300~500ms PF 58→72
S7FE3 ✅ images/og-default.svg 생성 배포 — 소셜 공유 미리보기 복구 FE +5
S7SC1 ✅ config.js에 sessionStorage 캐싱 추가 — /api/config 호출 80% 감소 SC 68→74
FE 코드구조
32
72
PF 성능
58
72
SC 보안
68
74
QA 품질
25
28
AR 아키텍처
55
70
예측 점수
76점
실제 개선 후
74점
총 향상
+20점
54 → 74
00 종합 점수 (Overall Score)
54

/ 100점 — 개선 필요 단계

기능은 정상 동작하나, CSS 중복·모듈화 부재·테스트 부재·렌더 블로킹이 주요 문제입니다.
Critical Path 병목: CSS 중복 → 캐싱 실패 → 페이지 이동마다 재파싱
즉시 조치 시 성능 20~30점 향상 가능 (목표: 80점 이상)

FE 코드구조
32
CSS 중복 심각
PF 성능
58
렌더 블로킹
SC 보안
68
클라이언트 권한
QA 품질
25
테스트 0%
AR 아키텍처
55
컴포넌트 미분리
S2 컴포넌트 분해 — SAL ID 자동 매핑

분해 기준: 페이지=SAL ID 단위 / Area = 기능영역(FE/SC/DB) / Level = 의존성 깊이(공통모듈→페이지→기능) / Variant = 동일 Area 내 병렬 페이지

Area S2 분해 (Level 1 = 기반) S2 분해 (Level 2 = 공통) S2 분해 (Level 3 = 페이지) S2 분해 (Level 4 = 기능)
FE
S2FE1 CSS 변수/:root
전 페이지 중복
S2FE2 NAV 컴포넌트
4개 페이지 복사
S2FE3a index.html
S2FE3b pool.html
S2FE3c community.html
S2FE4a Hero섹션
S2FE4b How-It-Works
S2FE4c Why-Join
AU
S2AU1 config.js
AXON_CONFIG 초기화
S2AU2 Supabase Auth
login/signup/profile
S2AU3a login.html
S2AU3b signup.html
S2AU3c profile.html
S2AU4a forgot-pw.html
S2AU4b reset-pw.html
DB
S2DB1 ax_experts
전문가 테이블
S2DB2 ax_project_requests
프로젝트 요청
SC
S2SC1 Anon Key 하드코딩
config.js L28-29
S2SC2 Admin 권한체크
클라이언트 사이드
S2SC3 escapeHtml()
XSS 방어 ✅
S4 SAL ID별 점수카드 — 정적 분석 결과
SAL ID 컴포넌트 현재 점수 주요 문제 우선순위
S2FE1 CSS 변수/:root 선언
18
동일한 `:root{}` CSS 변수 선언이 최소 5개 HTML 파일에 복사. 수정 시 5곳 동기화 필요. 브라우저 캐싱 불가. 🔴 즉시
S2FE2 NAV 컴포넌트
22
NAV HTML + CSS + JS가 index/pool/community/profile 4개 파일에 완전 복사. Nav 수정 시 4곳 동시 수정 필요. 동기화 오류 위험. 🔴 즉시
S2AU1 config.js — AXON_CONFIG
55
Supabase Anon Key + URL이 fallback으로 하드코딩 (L28-29). Anon Key는 공개 설계이지만 프로젝트 URL 노출 = 대상 특정 가능. `/api/config` 매 페이지 호출, 캐싱 없음. 🟠 단기
S2SC2 injectAdminLink() — 관리자 권한
45
user.app_metadata?.role !== 'admin' 체크가 클라이언트 사이드. 브라우저 콘솔에서 조작 가능. Supabase DB RLS 정책이 별도로 있어야 실제 보호됨. RLS 존재 여부 미확인. 🟠 단기
S2FE3a~c 메인 페이지 3개 (index/pool/community)
60
Google Fonts CDN (`Noto Sans KR` + `Outfit`)을 렌더 블로킹 `<link>`로 로드. Noto Sans KR은 한국어 폰트로 용량이 매우 큼. 각 페이지마다 동일 요청. `font-display: swap` 미사용. 🟠 단기
S2FE3b pool.html — 전문가 풀 페이지
62
OG 이미지 경로 `/images/og-default.png`가 404 에러. 카카오톡·슬랙 공유 시 미리보기 없음. SEO 점수 하락 요인. 🟠 단기
S2AU2 Supabase Auth (login/signup/profile)
70
Auth 로직이 각 페이지에 인라인 JS로 중복. `getUser()` 호출이 페이지마다 독립. Auth 상태 캐싱 없음 → 매 페이지 로드마다 Supabase 왕복 API 호출. 🟡 중기
S2DB1 ax_experts 테이블
72
전문가 수 카운트 쿼리 select('*', {count:'exact', head:true}) — 캐싱 없이 매 랜딩 페이지 로드마다 실행. 전문가 수가 자주 바뀌지 않는다면 캐싱 가능. 🟡 중기
S2SC3 escapeHtml() / isSafeUrl() / isValidEmail()
88
XSS 방어 함수가 구현되어 있고 사용됨 ✅. URL 검증, 이메일 검증 존재 ✅. 양호함. 🟢 양호
S2DB2 ax_project_requests 테이블
82
RLS 정책 마이그레이션 파일 존재 (`allow_insert_returning.sql`) ✅. 구조 양호. SQL injection은 Supabase 클라이언트 라이브러리가 기본 방어. 🟢 양호
S5 핵심 문제 상세 분석 — Critical Path

Critical Path 병목: CSS 중복(S2FE1) → 캐싱 실패 → 매 페이지 재파싱 → 렌더 지연
이 경로를 먼저 해결하면 가장 큰 성능 향상 효과를 얻습니다.

S2FE1 🔴 즉시 조치
CSS 변수 선언 5중 복사 — 유지보수 시한폭탄
동일한 :root { --ink, --amber, --teal, ... } CSS 변수 블록이 index.html, pool.html, community.html, login.html, profile.html 최소 5개 파일에 복사되어 있습니다. NAV CSS (~80줄), Footer CSS (~30줄)도 마찬가지입니다.

이로 인해: ① 색상 하나를 바꾸면 5개 파일을 모두 수정해야 함 ② 인라인 CSS는 브라우저가 캐시하지 않아 페이지 이동마다 재파싱 ③ 실제로 index→pool 이동 시 동일한 CSS를 두 번 파싱함
개선방안 (S7FE1): 공통 CSS를 /css/common.css로 분리하여 `<link rel="stylesheet">`로 외부 로드. 브라우저 캐싱 적용 → 첫 페이지 이후 CSS 재다운로드 0KB.
예상 효과: 페이지당 CSS 파싱 시간 -70%, 총 전송 데이터 -40KB 이상
S2FE2 🔴 즉시 조치
NAV/Footer HTML+JS — 4개 파일 완전 복사
Nav 마크업(~20줄), Nav CSS(~80줄), Nav JS (스크롤효과/모바일메뉴/Auth상태반영 ~60줄)가 index, pool, community, profile.html에 완전 복사되어 있습니다.

"로그인" 텍스트 하나를 수정해도 4개 파일을 수정해야 하고, 실제로 한 파일만 수정하고 나머지를 빠뜨리는 버그 위험이 매우 높습니다.
개선방안 (S7FE2): JS의 fetch()로 nav.html을 로드하거나, Web Components/HTML Template을 사용. 또는 빌드 도구(Vite) 도입으로 컴포넌트화.
가장 간단한 방법: fetch('/components/nav.html').then(r=>r.text()).then(html=>{navEl.innerHTML=html})
S2FE3a~c 🟠 단기 개선
Google Fonts 렌더 블로킹 — Noto Sans KR 한국어 폰트
모든 페이지에서 다음 코드로 Google Fonts를 로드합니다:
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="https://fonts.googleapis.com/.../Noto+Sans+KR..." rel="stylesheet"> <!-- ↑ 렌더 블로킹! 이 CSS가 로드될 때까지 페이지 렌더링 중단 -->
Noto Sans KR은 한국어를 지원하기 위해 폰트 파일이 매우 크며 (wght@400;500;700;900 = 4개 weight = 약 400~600KB), Google Fonts 서버 RTT까지 합산하면 렌더 블로킹 시간이 200~500ms에 달할 수 있습니다.
개선방안 (S7PF1):
① `media="print" onload="this.media='all'"` 방식으로 비동기 로드
② `font-display: swap` 추가 (폰트 로드 전 시스템 폰트로 먼저 표시)
③ 자주 쓰는 weight만 로드 (900 제거하고 700로 대체)
예상 효과: LCP (Largest Contentful Paint) -300~500ms
S2AU1 🟠 단기 개선
Supabase Anon Key 하드코딩 + /api/config 캐싱 없음
js/config.js 28~29번 줄에 실제 Supabase URL과 Anon Key가 하드코딩되어 있습니다. Anon Key는 공개 설계이지만, GitHub에 커밋되면 공개 저장소 시 완전 노출됩니다.

또한 `/api/config` 엔드포인트가 매 페이지 로드마다 호출되며 브라우저 캐싱이 없습니다. 11개 페이지를 방문하면 11번 동일한 API를 호출합니다.
개선방안 (S7SC1):
① `/api/config` 응답에 `Cache-Control: public, max-age=3600` 헤더 추가 → 1시간 캐싱
② `sessionStorage`에 초기화 결과 저장 → 같은 세션 내 재호출 방지
③ Anon Key는 `.env` 파일로 관리, fallback 코드에서 제거
S2FE3b 🟠 단기 개선
OG 이미지 404 — 소셜 공유 미리보기 없음
모든 페이지의 OG 태그에 https://www.ax-on.net/images/og-default.png를 참조하고 있으나 해당 파일이 존재하지 않아 404 에러가 발생합니다. 카카오톡, 슬랙, 링크드인 등에서 링크 공유 시 미리보기 이미지 없음.
개선방안 (S7FE3): 1200×630 OG 이미지 생성 후 `/images/og-default.png`에 배포. SVG나 Canvas로 자동 생성도 가능. 5분 작업으로 소셜 공유 품질 즉시 개선.
S2SC2 🟠 단기 개선
관리자 권한 체크 — 클라이언트 사이드 의존
injectAdminLink() 함수는 user.app_metadata?.role !== 'admin'로 관리자 여부를 체크합니다. 이는 UI 표시 목적이지만, 실제 관리자 페이지(/pages/admin/admin.html)에 직접 URL로 접근 시 클라이언트 체크는 우회될 수 있습니다.

핵심 질문: Supabase RLS 정책이 admin 페이지 데이터 조회를 막고 있는가? 만약 `admin.html`이 Supabase에서 모든 사용자 데이터를 가져온다면 RLS 없이는 누구나 직접 URL 접근으로 데이터 조회 가능.
개선방안 (S7SC2): ① Supabase에서 관리자 전용 테이블에 RLS 적용 확인 ② admin.html 상단에 서버 측 역할 검증 로직 추가 ③ Vercel Edge Middleware로 URL 레벨 접근 제어 추가
S2AU2 🟡 중기 개선
Auth 상태 캐싱 없음 — 매 페이지 Supabase 왕복
각 페이지에서 await sb.auth.getUser()를 독립적으로 호출합니다. 사용자가 index → pool → community 3페이지를 방문하면 Supabase Auth API가 3번 호출됩니다. Supabase 클라이언트 라이브러리가 내부적으로 일부 캐싱하지만, 페이지 간 상태 공유는 없습니다.
개선방안 (S7AU1): `sessionStorage`에 사용자 ID와 표시 이름을 저장하고, 동일 세션에서는 저장된 값을 우선 사용. Auth 갱신은 백그라운드에서 비동기로 처리.
S2DB1 🟡 중기 개선
전문가 수 카운트 쿼리 — 매 랜딩 로드마다 실행
랜딩 페이지(index.html)에서 매 로드마다 sb.from('ax_experts').select('*', {count:'exact', head:true})를 실행합니다. 전문가 수는 분 단위로 바뀌지 않는 데이터인데, 방문자마다 DB 쿼리가 발생합니다.
개선방안 (S7DB1): Vercel Edge Cache 또는 `localStorage`에 전문가 수를 5~10분 캐싱. 또는 Supabase Materialized View로 카운트를 주기적으로 계산.
S7 개선 로드맵 — SAL ID 순서대로
🔴 1순위

S7FE1 공통 CSS 파일 분리 (/css/common.css)

CSS 변수, reset, NAV, Footer, 공통 버튼/카드 CSS를 외부 파일로 분리. 예상 작업: 2~3시간. 효과: 전송 데이터 -40KB+, 캐싱 적용, 유지보수성 대폭 향상.

🔴 2순위

S7FE2 NAV/Footer 컴포넌트화

fetch()로 nav.html 외부 로드 또는 JS 함수로 NAV DOM 생성. 예상 작업: 3~4시간. 효과: NAV 수정 시 1곳만 수정, 버그 위험 제거.

🟠 3순위

S7FE3 OG 이미지 생성 및 배포

1200×630 PNG 이미지 생성. 예상 작업: 30분. 효과: 소셜 공유 미리보기 즉시 복구.

🟠 4순위

S7PF1 Google Fonts 비동기 로딩 + font-display: swap

렌더 블로킹 제거. 예상 작업: 30분. 효과: LCP -300~500ms, FCP 개선.

🟠 5순위

S7SC1 /api/config 캐싱 + Anon Key fallback 정리

Cache-Control 헤더 추가, sessionStorage 캐싱. 예상 작업: 1시간. 효과: API 호출 -80%, 보안 강화.

🟠 6순위

S7SC2 관리자 페이지 RLS 확인 + Edge Middleware 접근 제어

Supabase RLS 정책 감사 및 Vercel middleware 추가. 예상 작업: 2~3시간. 효과: 권한 우회 공격 방어.

🟡 7순위

S7AU1 Auth 상태 sessionStorage 캐싱

페이지 간 Auth 상태 공유. 예상 작업: 1~2시간. 효과: Supabase Auth API 호출 -70%.

🟡 8순위

S7DB1 전문가 수 카운트 캐싱

localStorage 5분 캐싱. 예상 작업: 30분. 효과: DB 쿼리 -95%.

🟡 9순위

S7FE4 폰트 Weight 최적화 (900 제거)

Noto Sans KR wght@400;700만 로드 (500,900 제거). 예상 작업: 10분. 효과: 폰트 전송량 -30%.

S9 개선 완료 후 예상 점수

S7FE1~2 (CSS 분리 + NAV 컴포넌트화)만 완료해도 전체 점수가 54점 → 약 72점으로 향상됩니다.
S7FE1~S7SC2 (1~6순위) 전부 완료 시 목표 80점+ 달성 가능.

항목현재 점수개선 후 예상개선 작업
FE 코드구조 32점 82점 S7FE1 (CSS 분리) + S7FE2 (NAV 컴포넌트화)
PF 성능 58점 80점 S7PF1 (Fonts 비동기) + S7DB1 (캐싱)
SC 보안 68점 85점 S7SC1 (캐싱+정리) + S7SC2 (RLS 확인)
QA 품질 25점 55점 컴포넌트화 후 유닛 테스트 가능해짐
AR 아키텍처 55점 78점 공통 파일 분리, 컴포넌트 구조 도입
종합 54점 76점 1~6순위 작업 완료 시 (예상 총 작업: 10~15시간)