POSA_LEAKSMS/php/.htaccess
cw2007 f063060e2d refactor(dashboard): 탭 구조 전면 재개편(실시간/추이/이력/시스템)
단일 1594줄 스크롤 페이지를 상시 영역 + 4탭 서버렌더(?tab=) 구조로 재개편한다.
5인 리뷰 패널(정보구조·관제도메인·UX·구현·접근성) 종합 스펙 기반.

상시 영역(전 탭 공통):
- 시스템 상태 히어로 — 누수(is_leak)·미복구 사고를 FORECAST_ENABLED 와 무관하게
  반영(기존: 누수 중에도 '정상 감시 중' 표시 가능한 결함). 경보 시 센서명·위치·
  현재값·경보 시작 시각·SMS 발송 결과를 한 줄로 명시.
- 알림 스트립(SMS 실패/미복구 누수/수집 거부 1줄 요약 + 탭 딥링크)
- 대응 필요 패널(플레이북·담당자) — 안전 알림은 탭과 무관하게 상시 노출
- 통합 KPI 스트립 6타일 — 모바일 퀵타일·듀얼 KPI·게이지 배지를 하나로 통합
- 탭바(nav+aria-current, 경보 배지)

탭:
- 실시간 현황: 온습도/누수 카드(예보 여유 상시 표시), 미복구 사고, SMS 채널·오늘 요약
- 추이·통계: 온습도 추이 차트, 7일 경보 추이, 경보 종류 도넛(단위 명기), 가동률, 리더보드
- 이력 조회: 누수 사고·측정·이벤트·SMS 이력, 30일 센서별 평균 온습도, SMS 발송 통계
- 시스템 점검: 자가진단, 수집 거부 관측성, SMS 채널 점검, 플레이북 사본, 임계 설정 요약
- ?tab=all: 인쇄용 전체 렌더(details 강제 펼침, 자동 새로고침 없음)

구현:
- 데이터 계층 분리(dashboard_data.php): 전역 vs 탭 전용 쿼리 — 로드당 쿼리 ~50→전역
  14+탭 5~8개(약 60% 절감). ops_checks 파일 캐시(TTL 120초, 시스템 탭은 우회).
- 탭 partial(tabs/*.php)은 dash_render_tab() 함수 스코프 격리(EXTR_SKIP)로 렌더.
- 30초 자동 새로고침은 URL 쿼리 보존으로 탭·기간 유지. 스크롤/details 복원은 탭별 키.
- 폐쇄망 유지: 외부 CDN/JS 없음(인라인 SVG). app.css 는 기존 규칙 불변, 추가만.
- 보안: dashboard_data·forecast·tabs 를 .htaccess FilesMatch + tabs/.htaccess +
  PHP 직접접근 가드로 삼중 차단.

security_hardening_test: 대시보드 표면을 분리 구조(셸+데이터+탭)에 맞춰 검사하도록 보정.
2026-07-11 11:23:14 +09:00

56 lines
3.1 KiB
ApacheConf

# =============================================================================
# .htaccess - 누수감지 시스템 보안 설정
# =============================================================================
# ─── HTTPS 강제 리다이렉트 ─────────────────────────────────
# cafe24 호스팅 관리자 패널에서 SSL 리다이렉트 설정 사용
# (.htaccess 리다이렉트는 리버스 프록시 환경에서 무한 루프 유발)
<IfModule mod_rewrite.c>
RewriteEngine On
# var/ 디렉토리 접근 차단
RewriteRule ^var/ - [F,L]
# [v2] uploads/ 디렉토리 PHP 실행 차단
RewriteRule ^uploads/.*\.ph(p[3457]?|t|tml)$ - [F,L]
# 불필요 HTTP 메소드 차단
RewriteCond %{REQUEST_METHOD} ^(TRACE|DELETE|PUT|PATCH) [NC]
RewriteRule .* - [F,L]
</IfModule>
# ─── 민감 PHP 파일 직접 접근 차단 ─────────────────────────
# (dashboard_data·forecast·live·trend·history·system 은 include 전용 — tabs/.htaccess 와 이중 차단)
<FilesMatch "^(config|config\.local|config\.local\.example|sms_send|incidents|ops_checks|setup_hash|test_mobile|dashboard_data|forecast|live|trend|history|system)\.php$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
</FilesMatch>
# ─── 보안 헤더 ───────────────────────────────────────────
<IfModule mod_headers.c>
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header always set X-XSS-Protection "1; mode=block"
# 내부 관리 시스템 — 검색엔진 색인 금지(HTTP 레벨, 전 응답 공통).
# robots.txt 는 쓰지 않는다: 공개 파일이라 난독화 경로를 오히려 광고하게 됨.
Header always set X-Robots-Tag "noindex, nofollow, noarchive"
# 외부로 Referer 자체를 보내지 않음(도메인 확인 유출 차단). 내부 이동은 영향 없음.
Header always set Referrer-Policy "no-referrer"
# TODO(follow-up): 'unsafe-inline' 제거 — 대시보드 인라인 스크립트/스타일을 외부
# 파일로 분리한 후 script-src/style-src 에서 'unsafe-inline' 을 삭제해야 한다.
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; form-action 'self'"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
Header always unset X-Powered-By
</IfModule>
# ─── 디렉토리 목록 비활성화 ──────────────────────────────
<IfModule mod_autoindex.c>
Options -Indexes
</IfModule>