From ba9ef32808f57e45a8b1b2bab65a8d2b783c5bf8 Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Fri, 10 Apr 2026 09:44:21 +0900 Subject: [PATCH] =?UTF-8?q?security:=20=EB=B3=B4=EC=95=88=20=EA=B0=95?= =?UTF-8?q?=EC=A0=9C=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EA=B5=AC=EC=B6=95=20?= =?UTF-8?q?+=20=ED=95=98=EB=93=9C=EC=BD=94=EB=94=A9=20=EB=B9=84=EB=B0=80?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 보안 감사 결과 CRITICAL 2건, HIGH 5건 발견 → 수정 완료 + 자동화 구축. [보안 수정] - issue-view.js: 하드코딩 비밀번호 → crypto.getRandomValues() 랜덤 생성 - pushSubscriptionController.js: ntfy 비밀번호 → process.env.NTFY_SUB_PASSWORD - DEPLOY-GUIDE.md/PROGRESS.md/migration SQL: 평문 비밀번호 → placeholder - docker-compose.yml/.env.example: NTFY_SUB_PASSWORD 환경변수 추가 [보안 강제 시스템 - 신규] - scripts/security-scan.sh: 8개 규칙 (CRITICAL 2, HIGH 4, MEDIUM 2) 3모드(staged/all/diff), severity, .securityignore, MEDIUM 임계값 - .githooks/pre-commit: 로컬 빠른 피드백 - .githooks/pre-receive-server.sh: Gitea 서버 최종 차단 bypass 거버넌스([SECURITY-BYPASS: 사유] + 사용자 제한 + 로그) - SECURITY-CHECKLIST.md: 10개 카테고리 자동/수동 구분 - docs/SECURITY-GUIDE.md: 운영자 가이드 (워크플로우, bypass, FAQ) Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/WORKFLOW-GUIDE.md | 2 +- .env.example | 12 + .githooks/pre-commit | 5 + .githooks/pre-receive-server.sh | 168 +++++++++ .securityignore | 24 ++ DEPLOY-GUIDE.md | 22 +- PROGRESS.md | 2 +- SECURITY-CHECKLIST.md | 39 ++ docker-compose.yml | 1 + docs/SECURITY-GUIDE.md | 157 ++++++++ scripts/security-scan.sh | 355 ++++++++++++++++++ ...260313001000_create_push_subscriptions.sql | 2 +- system1-factory/web/logo 2.png | Bin 14999 -> 0 bytes .../components/sections/admin-sections.html | 0 .../web/{ => public}/css/daily-patrol.css | 0 .../web/{ => public}/css/daily-status.css | 0 .../css/daily-work-report-mobile.css | 0 .../{ => public}/css/daily-work-report.css | 0 .../web/{ => public}/css/equipment-detail.css | 0 .../{ => public}/css/equipment-management.css | 0 .../web/{ => public}/css/mobile.css | 0 .../web/{ => public}/css/modern-dashboard.css | 0 .../{ => public}/css/monthly-comparison.css | 0 .../{ => public}/css/my-monthly-confirm.css | 0 .../{ => public}/css/production-dashboard.css | 0 .../web/{ => public}/css/proxy-input.css | 0 .../web/{ => public}/css/purchase-mobile.css | 0 .../web/{ => public}/css/tbm-mobile.css | 0 system1-factory/web/{ => public}/css/tbm.css | 0 .../{ => public}/css/vacation-allocation.css | 0 .../web/{ => public}/css/work-analysis.css | 0 .../web/{ => public}/css/work-report.css | 0 .../{ => public}/css/workplace-management.css | 0 .../web/{ => public}/css/zone-detail.css | 0 .../web/{ => public}/img/favicon.png | Bin .../web/{ => public}/img/icon-192x192.png | Bin .../web/{ => public}/img/icon-512x512.png | Bin .../web/{ => public}/img/login-bg.jpeg | Bin system1-factory/web/{ => public}/img/logo.png | Bin .../{ => public}/img/technicalkorea Logo.jpg | Bin .../{ => public}/img/technicalkorea_Logo.ai | 0 .../{ => public}/img/technicalkorea_Logo.eps | 0 .../{ => public}/img/technicalkorea_Logo.svg | 0 system1-factory/web/{ => public}/index.html | 0 .../web/{ => public}/js/api-base.js | 0 .../web/{ => public}/js/api-config.js | 0 .../{ => public}/js/attendance-validation.js | 0 .../web/{ => public}/js/attendance.js | 0 system1-factory/web/{ => public}/js/auth.js | 0 .../web/{ => public}/js/calendar.js | 0 .../web/{ => public}/js/change-password.js | 0 .../web/{ => public}/js/common/base-state.js | 0 .../web/{ => public}/js/common/utils.js | 0 system1-factory/web/{ => public}/js/config.js | 0 .../web/{ => public}/js/daily-issue-api.js | 0 .../web/{ => public}/js/daily-issue-ui.js | 0 .../web/{ => public}/js/daily-issue.js | 0 .../web/{ => public}/js/daily-patrol.js | 0 .../web/{ => public}/js/daily-status.js | 0 .../js/daily-work-report-mobile.js | 0 .../web/{ => public}/js/daily-work-report.js | 0 .../{ => public}/js/daily-work-report/api.js | 0 .../js/daily-work-report/state.js | 0 .../js/daily-work-report/utils.js | 0 .../{ => public}/js/department-management.js | 0 .../web/{ => public}/js/equipment-detail.js | 0 .../{ => public}/js/equipment-management.js | 0 .../web/{ => public}/js/factory-upload.js | 0 .../web/{ => public}/js/factory-view.js | 0 .../{ => public}/js/group-leader-dashboard.js | 0 .../{ => public}/js/issue-category-manage.js | 0 system1-factory/web/{ => public}/js/login.js | 0 .../web/{ => public}/js/manage-issue.js | 0 .../web/{ => public}/js/manage-pipespec.js | 0 .../web/{ => public}/js/manage-project.js | 0 .../web/{ => public}/js/manage-worker.js | 0 .../{ => public}/js/management-dashboard.js | 0 .../web/{ => public}/js/meeting-detail.js | 0 .../web/{ => public}/js/meetings.js | 0 .../web/{ => public}/js/mobile-dashboard.js | 0 .../web/{ => public}/js/modern-dashboard.js | 0 .../web/{ => public}/js/monthly-comparison.js | 0 .../web/{ => public}/js/my-attendance.js | 0 .../web/{ => public}/js/my-dashboard.js | 0 .../web/{ => public}/js/my-monthly-confirm.js | 0 .../web/{ => public}/js/my-profile.js | 0 .../web/{ => public}/js/navigation.js | 0 .../web/{ => public}/js/nonconformity-list.js | 0 .../{ => public}/js/production-dashboard.js | 0 .../{ => public}/js/project-analysis-api.js | 0 .../{ => public}/js/project-analysis-ui.js | 0 .../web/{ => public}/js/project-analysis.js | 0 .../web/{ => public}/js/project-management.js | 0 .../web/{ => public}/js/proxy-input.js | 0 .../web/{ => public}/js/schedule.js | 0 .../web/{ => public}/js/sso-relay.js | 0 .../web/{ => public}/js/system-dashboard.js | 0 .../web/{ => public}/js/task-management.js | 0 .../web/{ => public}/js/tbm-create.js | 0 .../web/{ => public}/js/tbm-mobile.js | 0 system1-factory/web/{ => public}/js/tbm.js | 0 .../web/{ => public}/js/tbm/api.js | 0 .../web/{ => public}/js/tbm/state.js | 0 .../web/{ => public}/js/tbm/utils.js | 0 .../web/{ => public}/js/user-dashboard.js | 0 .../{ => public}/js/vacation-allocation.js | 0 .../web/{ => public}/js/vacation-common.js | 0 .../web/{ => public}/js/work-analysis.js | 0 .../js/work-analysis/api-client.js | 0 .../js/work-analysis/chart-renderer.js | 0 .../js/work-analysis/data-processor.js | 0 .../js/work-analysis/main-controller.js | 0 .../js/work-analysis/module-loader.js | 0 .../js/work-analysis/state-manager.js | 0 .../js/work-analysis/table-renderer.js | 0 .../web/{ => public}/js/work-management.js | 0 .../web/{ => public}/js/work-report-manage.js | 0 .../web/{ => public}/js/work-report-review.js | 0 .../web/{ => public}/js/work-review.js | 0 .../js/worker-individual-report.js | 0 .../{ => public}/js/workplace-layout-map.js | 0 .../{ => public}/js/workplace-management.js | 0 .../js/workplace-management/api.js | 0 .../js/workplace-management/index.js | 0 .../js/workplace-management/state.js | 0 .../js/workplace-management/utils.js | 0 .../web/{ => public}/js/workplace-status.js | 0 .../web/{ => public}/js/zone-detail.js | 0 system1-factory/web/{ => public}/logo.png | Bin .../web/{ => public}/manifest.json | 0 .../web/{ => public}/pages/admin/.gitkeep | 0 .../pages/admin/attendance-report.html | 0 .../{ => public}/pages/admin/departments.html | 0 .../pages/admin/equipment-detail.html | 0 .../{ => public}/pages/admin/equipments.html | 0 .../pages/admin/issue-categories.html | 0 .../pages/admin/notifications.html | 0 .../{ => public}/pages/admin/projects.html | 0 .../pages/admin/purchase-analysis.html | 0 .../pages/admin/repair-management.html | 0 .../web/{ => public}/pages/admin/tasks.html | 0 .../{ => public}/pages/admin/workplaces.html | 0 .../pages/attendance/annual-overview.html | 0 .../pages/attendance/checkin.html | 0 .../{ => public}/pages/attendance/daily.html | 0 .../pages/attendance/monthly-comparison.html | 0 .../pages/attendance/monthly.html | 0 .../pages/attendance/my-monthly-confirm.html | 0 .../pages/attendance/my-vacation-info.html | 0 .../pages/attendance/vacation-allocation.html | 0 .../pages/attendance/vacation-approval.html | 0 .../pages/attendance/vacation-input.html | 0 .../pages/attendance/vacation-management.html | 0 .../pages/attendance/vacation-request.html | 0 .../pages/attendance/work-status.html | 0 .../web/{ => public}/pages/dashboard-new.html | 0 .../web/{ => public}/pages/dashboard.html | 0 .../pages/inspection/daily-patrol.html | 0 .../pages/inspection/zone-detail.html | 0 .../web/{ => public}/pages/profile/info.html | 0 .../{ => public}/pages/profile/password.html | 0 .../pages/purchase/request-mobile.html | 0 .../{ => public}/pages/purchase/request.html | 0 .../web/{ => public}/pages/work/.gitkeep | 0 .../web/{ => public}/pages/work/analysis.html | 0 .../{ => public}/pages/work/daily-status.html | 0 .../pages/work/meeting-detail.html | 0 .../web/{ => public}/pages/work/meetings.html | 0 .../{ => public}/pages/work/proxy-input.html | 0 .../pages/work/report-create-mobile.html | 0 .../pages/work/report-create.html | 0 .../web/{ => public}/pages/work/schedule.html | 0 .../{ => public}/pages/work/tbm-create.html | 0 .../{ => public}/pages/work/tbm-mobile.html | 0 .../web/{ => public}/pages/work/tbm.html | 0 .../web/{ => public}/static/css/tkfb.css | 0 .../static/js/purchase-analysis.js | 0 .../static/js/purchase-request-mobile.js | 0 .../static/js/purchase-request.js | 0 .../static/js/shared-bottom-nav.js | 0 .../web/{ => public}/static/js/tkfb-core.js | 0 .../{ => public}/static/js/tkfb-dashboard.js | 0 system1-factory/web/{ => public}/sw.js | 0 .../web/{ => public}/css/chat-report.css | 0 .../web/{ => public}/css/common.css | 0 .../web/{ => public}/css/design-system.css | 0 .../{ => public}/css/project-management.css | 0 .../web/{ => public}/img/favicon.png | Bin .../web/{ => public}/js/api-base.js | 0 .../web/{ => public}/js/app-init.js | 0 .../web/{ => public}/js/chat-report.js | 0 .../web/{ => public}/js/cross-nav.js | 0 .../{ => public}/js/issue-category-manage.js | 0 .../web/{ => public}/js/issue-detail.js | 0 .../web/{ => public}/js/issue-report.js | 0 .../web/{ => public}/js/safety-report-list.js | 0 .../web/{ => public}/js/sso-relay.js | 0 .../web/{ => public}/js/work-issue-report.js | 0 .../pages/safety/chat-report.html | 0 .../pages/safety/issue-detail.html | 0 .../pages/safety/issue-report.html | 0 .../pages/safety/report-status.html | 0 system2-report/web/{ => public}/push-sw.js | 0 .../web/{ => public}/ai-assistant.html | 0 .../web/{ => public}/app.html | 0 .../web/{ => public}/favicon.ico | Bin .../web/{ => public}/issue-view.html | 2 +- .../web/{ => public}/issues-archive.html | 0 .../web/{ => public}/issues-dashboard.html | 0 .../web/{ => public}/issues-inbox.html | 0 .../web/{ => public}/issues-management.html | 0 .../web/{ => public}/m/dashboard.html | 0 .../web/{ => public}/m/inbox.html | 0 .../web/{ => public}/m/management.html | 0 .../web/{ => public}/push-sw.js | 0 .../web/{ => public}/reports-daily.html | 0 .../web/{ => public}/reports-monthly.html | 0 .../web/{ => public}/reports-weekly.html | 0 .../web/{ => public}/reports.html | 0 .../{ => public}/static/css/ai-assistant.css | 0 .../{ => public}/static/css/issue-view.css | 0 .../static/css/issues-archive.css | 0 .../static/css/issues-dashboard.css | 0 .../{ => public}/static/css/issues-inbox.css | 0 .../static/css/issues-management.css | 0 .../web/{ => public}/static/css/m-common.css | 0 .../static/css/mobile-calendar.css | 0 .../{ => public}/static/css/tkqc-common.css | 0 .../web/{ => public}/static/js/api.js | 0 .../web/{ => public}/static/js/app.js | 0 .../static/js/components/common-header.js | 0 .../static/js/components/mobile-bottom-nav.js | 0 .../static/js/components/mobile-calendar.js | 0 .../static/js/core/auth-manager.js | 0 .../static/js/core/keyboard-shortcuts.js | 0 .../static/js/core/page-manager.js | 0 .../static/js/core/page-preloader.js | 0 .../static/js/core/permissions.js | 0 .../web/{ => public}/static/js/date-utils.js | 0 .../web/{ => public}/static/js/image-utils.js | 0 .../{ => public}/static/js/lib/purify.min.js | 0 .../web/{ => public}/static/js/m/m-common.js | 0 .../{ => public}/static/js/m/m-dashboard.js | 0 .../web/{ => public}/static/js/m/m-inbox.js | 0 .../{ => public}/static/js/m/m-management.js | 0 .../static/js/pages/ai-assistant.js | 0 .../static/js/pages/issue-view.js | 11 +- .../static/js/pages/issues-archive.js | 0 .../static/js/pages/issues-dashboard.js | 0 .../static/js/pages/issues-inbox.js | 0 .../static/js/pages/issues-management.js | 0 .../web/{ => public}/static/js/sso-relay.js | 0 .../static/js/utils/issue-helpers.js | 0 .../static/js/utils/photo-modal.js | 0 .../web/{ => public}/static/js/utils/toast.js | 0 system3-nonconformance/web/{ => public}/sw.js | 0 .../controllers/pushSubscriptionController.js | 2 +- 257 files changed, 786 insertions(+), 18 deletions(-) create mode 100755 .githooks/pre-commit create mode 100755 .githooks/pre-receive-server.sh create mode 100644 .securityignore create mode 100644 SECURITY-CHECKLIST.md create mode 100644 docs/SECURITY-GUIDE.md create mode 100755 scripts/security-scan.sh delete mode 100644 system1-factory/web/logo 2.png rename system1-factory/web/{ => public}/components/sections/admin-sections.html (100%) rename system1-factory/web/{ => public}/css/daily-patrol.css (100%) rename system1-factory/web/{ => public}/css/daily-status.css (100%) rename system1-factory/web/{ => public}/css/daily-work-report-mobile.css (100%) rename system1-factory/web/{ => public}/css/daily-work-report.css (100%) rename system1-factory/web/{ => public}/css/equipment-detail.css (100%) rename system1-factory/web/{ => public}/css/equipment-management.css (100%) rename system1-factory/web/{ => public}/css/mobile.css (100%) rename system1-factory/web/{ => public}/css/modern-dashboard.css (100%) rename system1-factory/web/{ => public}/css/monthly-comparison.css (100%) rename system1-factory/web/{ => public}/css/my-monthly-confirm.css (100%) rename system1-factory/web/{ => public}/css/production-dashboard.css (100%) rename system1-factory/web/{ => public}/css/proxy-input.css (100%) rename system1-factory/web/{ => public}/css/purchase-mobile.css (100%) rename system1-factory/web/{ => public}/css/tbm-mobile.css (100%) rename system1-factory/web/{ => public}/css/tbm.css (100%) rename system1-factory/web/{ => public}/css/vacation-allocation.css (100%) rename system1-factory/web/{ => public}/css/work-analysis.css (100%) rename system1-factory/web/{ => public}/css/work-report.css (100%) rename system1-factory/web/{ => public}/css/workplace-management.css (100%) rename system1-factory/web/{ => public}/css/zone-detail.css (100%) rename system1-factory/web/{ => public}/img/favicon.png (100%) rename system1-factory/web/{ => public}/img/icon-192x192.png (100%) rename system1-factory/web/{ => public}/img/icon-512x512.png (100%) rename system1-factory/web/{ => public}/img/login-bg.jpeg (100%) rename system1-factory/web/{ => public}/img/logo.png (100%) rename system1-factory/web/{ => public}/img/technicalkorea Logo.jpg (100%) rename system1-factory/web/{ => public}/img/technicalkorea_Logo.ai (100%) rename system1-factory/web/{ => public}/img/technicalkorea_Logo.eps (100%) rename system1-factory/web/{ => public}/img/technicalkorea_Logo.svg (100%) rename system1-factory/web/{ => public}/index.html (100%) rename system1-factory/web/{ => public}/js/api-base.js (100%) rename system1-factory/web/{ => public}/js/api-config.js (100%) rename system1-factory/web/{ => public}/js/attendance-validation.js (100%) rename system1-factory/web/{ => public}/js/attendance.js (100%) rename system1-factory/web/{ => public}/js/auth.js (100%) rename system1-factory/web/{ => public}/js/calendar.js (100%) rename system1-factory/web/{ => public}/js/change-password.js (100%) rename system1-factory/web/{ => public}/js/common/base-state.js (100%) rename system1-factory/web/{ => public}/js/common/utils.js (100%) rename system1-factory/web/{ => public}/js/config.js (100%) rename system1-factory/web/{ => public}/js/daily-issue-api.js (100%) rename system1-factory/web/{ => public}/js/daily-issue-ui.js (100%) rename system1-factory/web/{ => public}/js/daily-issue.js (100%) rename system1-factory/web/{ => public}/js/daily-patrol.js (100%) rename system1-factory/web/{ => public}/js/daily-status.js (100%) rename system1-factory/web/{ => public}/js/daily-work-report-mobile.js (100%) rename system1-factory/web/{ => public}/js/daily-work-report.js (100%) rename system1-factory/web/{ => public}/js/daily-work-report/api.js (100%) rename system1-factory/web/{ => public}/js/daily-work-report/state.js (100%) rename system1-factory/web/{ => public}/js/daily-work-report/utils.js (100%) rename system1-factory/web/{ => public}/js/department-management.js (100%) rename system1-factory/web/{ => public}/js/equipment-detail.js (100%) rename system1-factory/web/{ => public}/js/equipment-management.js (100%) rename system1-factory/web/{ => public}/js/factory-upload.js (100%) rename system1-factory/web/{ => public}/js/factory-view.js (100%) rename system1-factory/web/{ => public}/js/group-leader-dashboard.js (100%) rename system1-factory/web/{ => public}/js/issue-category-manage.js (100%) rename system1-factory/web/{ => public}/js/login.js (100%) rename system1-factory/web/{ => public}/js/manage-issue.js (100%) rename system1-factory/web/{ => public}/js/manage-pipespec.js (100%) rename system1-factory/web/{ => public}/js/manage-project.js (100%) rename system1-factory/web/{ => public}/js/manage-worker.js (100%) rename system1-factory/web/{ => public}/js/management-dashboard.js (100%) rename system1-factory/web/{ => public}/js/meeting-detail.js (100%) rename system1-factory/web/{ => public}/js/meetings.js (100%) rename system1-factory/web/{ => public}/js/mobile-dashboard.js (100%) rename system1-factory/web/{ => public}/js/modern-dashboard.js (100%) rename system1-factory/web/{ => public}/js/monthly-comparison.js (100%) rename system1-factory/web/{ => public}/js/my-attendance.js (100%) rename system1-factory/web/{ => public}/js/my-dashboard.js (100%) rename system1-factory/web/{ => public}/js/my-monthly-confirm.js (100%) rename system1-factory/web/{ => public}/js/my-profile.js (100%) rename system1-factory/web/{ => public}/js/navigation.js (100%) rename system1-factory/web/{ => public}/js/nonconformity-list.js (100%) rename system1-factory/web/{ => public}/js/production-dashboard.js (100%) rename system1-factory/web/{ => public}/js/project-analysis-api.js (100%) rename system1-factory/web/{ => public}/js/project-analysis-ui.js (100%) rename system1-factory/web/{ => public}/js/project-analysis.js (100%) rename system1-factory/web/{ => public}/js/project-management.js (100%) rename system1-factory/web/{ => public}/js/proxy-input.js (100%) rename system1-factory/web/{ => public}/js/schedule.js (100%) rename system1-factory/web/{ => public}/js/sso-relay.js (100%) rename system1-factory/web/{ => public}/js/system-dashboard.js (100%) rename system1-factory/web/{ => public}/js/task-management.js (100%) rename system1-factory/web/{ => public}/js/tbm-create.js (100%) rename system1-factory/web/{ => public}/js/tbm-mobile.js (100%) rename system1-factory/web/{ => public}/js/tbm.js (100%) rename system1-factory/web/{ => public}/js/tbm/api.js (100%) rename system1-factory/web/{ => public}/js/tbm/state.js (100%) rename system1-factory/web/{ => public}/js/tbm/utils.js (100%) rename system1-factory/web/{ => public}/js/user-dashboard.js (100%) rename system1-factory/web/{ => public}/js/vacation-allocation.js (100%) rename system1-factory/web/{ => public}/js/vacation-common.js (100%) rename system1-factory/web/{ => public}/js/work-analysis.js (100%) rename system1-factory/web/{ => public}/js/work-analysis/api-client.js (100%) rename system1-factory/web/{ => public}/js/work-analysis/chart-renderer.js (100%) rename system1-factory/web/{ => public}/js/work-analysis/data-processor.js (100%) rename system1-factory/web/{ => public}/js/work-analysis/main-controller.js (100%) rename system1-factory/web/{ => public}/js/work-analysis/module-loader.js (100%) rename system1-factory/web/{ => public}/js/work-analysis/state-manager.js (100%) rename system1-factory/web/{ => public}/js/work-analysis/table-renderer.js (100%) rename system1-factory/web/{ => public}/js/work-management.js (100%) rename system1-factory/web/{ => public}/js/work-report-manage.js (100%) rename system1-factory/web/{ => public}/js/work-report-review.js (100%) rename system1-factory/web/{ => public}/js/work-review.js (100%) rename system1-factory/web/{ => public}/js/worker-individual-report.js (100%) rename system1-factory/web/{ => public}/js/workplace-layout-map.js (100%) rename system1-factory/web/{ => public}/js/workplace-management.js (100%) rename system1-factory/web/{ => public}/js/workplace-management/api.js (100%) rename system1-factory/web/{ => public}/js/workplace-management/index.js (100%) rename system1-factory/web/{ => public}/js/workplace-management/state.js (100%) rename system1-factory/web/{ => public}/js/workplace-management/utils.js (100%) rename system1-factory/web/{ => public}/js/workplace-status.js (100%) rename system1-factory/web/{ => public}/js/zone-detail.js (100%) rename system1-factory/web/{ => public}/logo.png (100%) rename system1-factory/web/{ => public}/manifest.json (100%) rename system1-factory/web/{ => public}/pages/admin/.gitkeep (100%) rename system1-factory/web/{ => public}/pages/admin/attendance-report.html (100%) rename system1-factory/web/{ => public}/pages/admin/departments.html (100%) rename system1-factory/web/{ => public}/pages/admin/equipment-detail.html (100%) rename system1-factory/web/{ => public}/pages/admin/equipments.html (100%) rename system1-factory/web/{ => public}/pages/admin/issue-categories.html (100%) rename system1-factory/web/{ => public}/pages/admin/notifications.html (100%) rename system1-factory/web/{ => public}/pages/admin/projects.html (100%) rename system1-factory/web/{ => public}/pages/admin/purchase-analysis.html (100%) rename system1-factory/web/{ => public}/pages/admin/repair-management.html (100%) rename system1-factory/web/{ => public}/pages/admin/tasks.html (100%) rename system1-factory/web/{ => public}/pages/admin/workplaces.html (100%) rename system1-factory/web/{ => public}/pages/attendance/annual-overview.html (100%) rename system1-factory/web/{ => public}/pages/attendance/checkin.html (100%) rename system1-factory/web/{ => public}/pages/attendance/daily.html (100%) rename system1-factory/web/{ => public}/pages/attendance/monthly-comparison.html (100%) rename system1-factory/web/{ => public}/pages/attendance/monthly.html (100%) rename system1-factory/web/{ => public}/pages/attendance/my-monthly-confirm.html (100%) rename system1-factory/web/{ => public}/pages/attendance/my-vacation-info.html (100%) rename system1-factory/web/{ => public}/pages/attendance/vacation-allocation.html (100%) rename system1-factory/web/{ => public}/pages/attendance/vacation-approval.html (100%) rename system1-factory/web/{ => public}/pages/attendance/vacation-input.html (100%) rename system1-factory/web/{ => public}/pages/attendance/vacation-management.html (100%) rename system1-factory/web/{ => public}/pages/attendance/vacation-request.html (100%) rename system1-factory/web/{ => public}/pages/attendance/work-status.html (100%) rename system1-factory/web/{ => public}/pages/dashboard-new.html (100%) rename system1-factory/web/{ => public}/pages/dashboard.html (100%) rename system1-factory/web/{ => public}/pages/inspection/daily-patrol.html (100%) rename system1-factory/web/{ => public}/pages/inspection/zone-detail.html (100%) rename system1-factory/web/{ => public}/pages/profile/info.html (100%) rename system1-factory/web/{ => public}/pages/profile/password.html (100%) rename system1-factory/web/{ => public}/pages/purchase/request-mobile.html (100%) rename system1-factory/web/{ => public}/pages/purchase/request.html (100%) rename system1-factory/web/{ => public}/pages/work/.gitkeep (100%) rename system1-factory/web/{ => public}/pages/work/analysis.html (100%) rename system1-factory/web/{ => public}/pages/work/daily-status.html (100%) rename system1-factory/web/{ => public}/pages/work/meeting-detail.html (100%) rename system1-factory/web/{ => public}/pages/work/meetings.html (100%) rename system1-factory/web/{ => public}/pages/work/proxy-input.html (100%) rename system1-factory/web/{ => public}/pages/work/report-create-mobile.html (100%) rename system1-factory/web/{ => public}/pages/work/report-create.html (100%) rename system1-factory/web/{ => public}/pages/work/schedule.html (100%) rename system1-factory/web/{ => public}/pages/work/tbm-create.html (100%) rename system1-factory/web/{ => public}/pages/work/tbm-mobile.html (100%) rename system1-factory/web/{ => public}/pages/work/tbm.html (100%) rename system1-factory/web/{ => public}/static/css/tkfb.css (100%) rename system1-factory/web/{ => public}/static/js/purchase-analysis.js (100%) rename system1-factory/web/{ => public}/static/js/purchase-request-mobile.js (100%) rename system1-factory/web/{ => public}/static/js/purchase-request.js (100%) rename system1-factory/web/{ => public}/static/js/shared-bottom-nav.js (100%) rename system1-factory/web/{ => public}/static/js/tkfb-core.js (100%) rename system1-factory/web/{ => public}/static/js/tkfb-dashboard.js (100%) rename system1-factory/web/{ => public}/sw.js (100%) rename system2-report/web/{ => public}/css/chat-report.css (100%) rename system2-report/web/{ => public}/css/common.css (100%) rename system2-report/web/{ => public}/css/design-system.css (100%) rename system2-report/web/{ => public}/css/project-management.css (100%) rename system2-report/web/{ => public}/img/favicon.png (100%) rename system2-report/web/{ => public}/js/api-base.js (100%) rename system2-report/web/{ => public}/js/app-init.js (100%) rename system2-report/web/{ => public}/js/chat-report.js (100%) rename system2-report/web/{ => public}/js/cross-nav.js (100%) rename system2-report/web/{ => public}/js/issue-category-manage.js (100%) rename system2-report/web/{ => public}/js/issue-detail.js (100%) rename system2-report/web/{ => public}/js/issue-report.js (100%) rename system2-report/web/{ => public}/js/safety-report-list.js (100%) rename system2-report/web/{ => public}/js/sso-relay.js (100%) rename system2-report/web/{ => public}/js/work-issue-report.js (100%) rename system2-report/web/{ => public}/pages/safety/chat-report.html (100%) rename system2-report/web/{ => public}/pages/safety/issue-detail.html (100%) rename system2-report/web/{ => public}/pages/safety/issue-report.html (100%) rename system2-report/web/{ => public}/pages/safety/report-status.html (100%) rename system2-report/web/{ => public}/push-sw.js (100%) rename system3-nonconformance/web/{ => public}/ai-assistant.html (100%) rename system3-nonconformance/web/{ => public}/app.html (100%) rename system3-nonconformance/web/{ => public}/favicon.ico (100%) rename system3-nonconformance/web/{ => public}/issue-view.html (99%) rename system3-nonconformance/web/{ => public}/issues-archive.html (100%) rename system3-nonconformance/web/{ => public}/issues-dashboard.html (100%) rename system3-nonconformance/web/{ => public}/issues-inbox.html (100%) rename system3-nonconformance/web/{ => public}/issues-management.html (100%) rename system3-nonconformance/web/{ => public}/m/dashboard.html (100%) rename system3-nonconformance/web/{ => public}/m/inbox.html (100%) rename system3-nonconformance/web/{ => public}/m/management.html (100%) rename system3-nonconformance/web/{ => public}/push-sw.js (100%) rename system3-nonconformance/web/{ => public}/reports-daily.html (100%) rename system3-nonconformance/web/{ => public}/reports-monthly.html (100%) rename system3-nonconformance/web/{ => public}/reports-weekly.html (100%) rename system3-nonconformance/web/{ => public}/reports.html (100%) rename system3-nonconformance/web/{ => public}/static/css/ai-assistant.css (100%) rename system3-nonconformance/web/{ => public}/static/css/issue-view.css (100%) rename system3-nonconformance/web/{ => public}/static/css/issues-archive.css (100%) rename system3-nonconformance/web/{ => public}/static/css/issues-dashboard.css (100%) rename system3-nonconformance/web/{ => public}/static/css/issues-inbox.css (100%) rename system3-nonconformance/web/{ => public}/static/css/issues-management.css (100%) rename system3-nonconformance/web/{ => public}/static/css/m-common.css (100%) rename system3-nonconformance/web/{ => public}/static/css/mobile-calendar.css (100%) rename system3-nonconformance/web/{ => public}/static/css/tkqc-common.css (100%) rename system3-nonconformance/web/{ => public}/static/js/api.js (100%) rename system3-nonconformance/web/{ => public}/static/js/app.js (100%) rename system3-nonconformance/web/{ => public}/static/js/components/common-header.js (100%) rename system3-nonconformance/web/{ => public}/static/js/components/mobile-bottom-nav.js (100%) rename system3-nonconformance/web/{ => public}/static/js/components/mobile-calendar.js (100%) rename system3-nonconformance/web/{ => public}/static/js/core/auth-manager.js (100%) rename system3-nonconformance/web/{ => public}/static/js/core/keyboard-shortcuts.js (100%) rename system3-nonconformance/web/{ => public}/static/js/core/page-manager.js (100%) rename system3-nonconformance/web/{ => public}/static/js/core/page-preloader.js (100%) rename system3-nonconformance/web/{ => public}/static/js/core/permissions.js (100%) rename system3-nonconformance/web/{ => public}/static/js/date-utils.js (100%) rename system3-nonconformance/web/{ => public}/static/js/image-utils.js (100%) rename system3-nonconformance/web/{ => public}/static/js/lib/purify.min.js (100%) rename system3-nonconformance/web/{ => public}/static/js/m/m-common.js (100%) rename system3-nonconformance/web/{ => public}/static/js/m/m-dashboard.js (100%) rename system3-nonconformance/web/{ => public}/static/js/m/m-inbox.js (100%) rename system3-nonconformance/web/{ => public}/static/js/m/m-management.js (100%) rename system3-nonconformance/web/{ => public}/static/js/pages/ai-assistant.js (100%) rename system3-nonconformance/web/{ => public}/static/js/pages/issue-view.js (98%) rename system3-nonconformance/web/{ => public}/static/js/pages/issues-archive.js (100%) rename system3-nonconformance/web/{ => public}/static/js/pages/issues-dashboard.js (100%) rename system3-nonconformance/web/{ => public}/static/js/pages/issues-inbox.js (100%) rename system3-nonconformance/web/{ => public}/static/js/pages/issues-management.js (100%) rename system3-nonconformance/web/{ => public}/static/js/sso-relay.js (100%) rename system3-nonconformance/web/{ => public}/static/js/utils/issue-helpers.js (100%) rename system3-nonconformance/web/{ => public}/static/js/utils/photo-modal.js (100%) rename system3-nonconformance/web/{ => public}/static/js/utils/toast.js (100%) rename system3-nonconformance/web/{ => public}/sw.js (100%) diff --git a/.claude/WORKFLOW-GUIDE.md b/.claude/WORKFLOW-GUIDE.md index cb23489..241c499 100644 --- a/.claude/WORKFLOW-GUIDE.md +++ b/.claude/WORKFLOW-GUIDE.md @@ -74,7 +74,7 @@ ```bash # DB 접속 (NAS) -ssh hyungi@100.71.132.52 "docker exec tk-mariadb mysql -uhyungi_user -p'hyung-ddfdf3-D341@' hyungi" +ssh hyungi@100.71.132.52 "docker exec tk-mariadb mysql -uhyungi_user -p\"\$MYSQL_PASSWORD\" hyungi" # 로그 확인 (NAS) ssh hyungi@100.71.132.52 "cd /volume1/docker/tk-factory-services && export PATH=\$PATH:/volume2/@appstore/ContainerManager/usr/bin && docker compose logs -f --tail=50 <서비스>" diff --git a/.env.example b/.env.example index 40f26cb..7fed366 100644 --- a/.env.example +++ b/.env.example @@ -99,4 +99,16 @@ OLLAMA_TIMEOUT=120 # tkfb.technicalkorea.net → http://tk-gateway:80 # tkreport.technicalkorea.net → http://tk-system2-web:80 # tkqc.technicalkorea.net → http://tk-system3-web:80 +# ------------------------------------------------------------------- +# ntfy 푸시 알림 서버 +# ------------------------------------------------------------------- +NTFY_BASE_URL=http://ntfy:80 +NTFY_PUBLISH_TOKEN=change_this_ntfy_publish_token +NTFY_EXTERNAL_URL=https://ntfy.technicalkorea.net +NTFY_SUB_PASSWORD=change_this_ntfy_subscriber_password +TKFB_BASE_URL=https://tkfb.technicalkorea.net + +# ------------------------------------------------------------------- +# Cloudflare Tunnel +# ------------------------------------------------------------------- CLOUDFLARE_TUNNEL_TOKEN=your_tunnel_token_here diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..000433f --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,5 @@ +#!/bin/bash +# pre-commit hook — 로컬 빠른 피드백 +# 역할: 커밋 전 보안 검사 (staged 파일만) +# 우회: git commit --no-verify (서버 pre-receive에서 최종 차단됨) +exec "$(git rev-parse --show-toplevel)/scripts/security-scan.sh" --staged diff --git a/.githooks/pre-receive-server.sh b/.githooks/pre-receive-server.sh new file mode 100755 index 0000000..3873b41 --- /dev/null +++ b/.githooks/pre-receive-server.sh @@ -0,0 +1,168 @@ +#!/bin/bash +# ============================================================================= +# pre-receive-server.sh — Gitea 서버용 보안 게이트 +# ============================================================================= +# 설치: Gitea 웹 관리자 → 저장소 → Settings → Git Hooks → pre-receive +# 또는: cp pre-receive-server.sh $REPO_PATH/custom/hooks/pre-receive +# +# 동작: push 시 변경 내용을 regex 검사, 위반 시 push 차단 +# bypass: 커밋 메시지에 [SECURITY-BYPASS: 사유] 포함 시 통과 + 로그 +# ============================================================================= + +set -uo pipefail + +# --- 설정 --- +BYPASS_LOG="/data/gitea/security-bypass.log" +ALLOWED_BYPASS_EMAILS="ahn@hyungi.net hyungi@technicalkorea.net" +MEDIUM_THRESHOLD=5 +ZERO_REV="0000000000000000000000000000000000000000" + +# --- 검출 규칙 (security-scan.sh와 동일, 자체 내장) --- +RULES=( + 'SECRET_HARDCODE|CRITICAL|비밀정보 하드코딩|(password|token|apiKey|secret|api_key)\s*[:=]\s*[\x27"][^${\x27"][^\x27"]{3,}' + 'SECRET_KNOWN|CRITICAL|알려진 비밀번호 패턴|(fukdon-riwbaq|hyung-ddfdf3|djg3-jj34|tkfactory-sub)' + 'LOCALSTORAGE_AUTH|HIGH|localStorage 인증정보|localStorage\.(setItem|getItem).*\b(token|password|auth|credential)' + 'INNERHTML_XSS|HIGH|innerHTML XSS 위험|\.innerHTML\s*[+=]' + 'CORS_WILDCARD|HIGH|CORS 와일드카드|origin:\s*[\x27"`]\*[\x27"`]' + 'SQL_INTERPOLATION|HIGH|SQL 문자열 보간|query\(`.*\$\{' + 'LOG_SECRET|MEDIUM|로그에 비밀정보|console\.(log|error|warn).*\b(password|token|secret|apiKey)' + 'ENV_HARDCODE|MEDIUM|환경설정 하드코딩|NODE_ENV\s*[:=]\s*[\x27"]development[\x27"]' +) + +EXCLUDE_PATTERNS="node_modules|\.git|__pycache__|package-lock\.json|\.min\.js|\.min\.css" + +# --- 메인 --- +while read -r oldrev newrev refname; do + # 브랜치 삭제 시 스킵 + if [[ "$newrev" == "$ZERO_REV" ]]; then + continue + fi + + # 신규 브랜치 + if [[ "$oldrev" == "$ZERO_REV" ]]; then + # 첫 push: 최근 커밋만 검사 (또는 스킵) + echo "[SECURITY] New branch detected — scanning latest commit only" + oldrev=$(git rev-parse "${newrev}~1" 2>/dev/null || echo "$ZERO_REV") + if [[ "$oldrev" == "$ZERO_REV" ]]; then + continue + fi + fi + + # --- bypass 확인 --- + BYPASS_FOUND=false + BYPASS_REASON="" + while IFS= read -r msg; do + if echo "$msg" | grep -qP '\[SECURITY-BYPASS:\s*.+\]'; then + BYPASS_FOUND=true + BYPASS_REASON=$(echo "$msg" | grep -oP '\[SECURITY-BYPASS:\s*\K[^\]]+') + elif echo "$msg" | grep -q '\[SECURITY-BYPASS\]'; then + echo "[SECURITY] ERROR: Bypass requires reason: [SECURITY-BYPASS: hotfix 사유]" + exit 1 + fi + done < <(git log --format='%s' "$oldrev".."$newrev" 2>/dev/null) + + if [[ "$BYPASS_FOUND" == "true" ]]; then + AUTHOR=$(git log -1 --format='%ae' "$newrev" 2>/dev/null || echo "unknown") + # 사용자 제한 + ALLOWED=false + for email in $ALLOWED_BYPASS_EMAILS; do + if [[ "$AUTHOR" == "$email" ]]; then + ALLOWED=true + break + fi + done + if [[ "$ALLOWED" != "true" ]]; then + echo "[SECURITY] Bypass not allowed for: $AUTHOR" + exit 1 + fi + # 로그 기록 + echo "$(date -Iseconds) | user=$AUTHOR | ref=$refname | commits=$oldrev..$newrev | reason=$BYPASS_REASON | TODO=24h내 수정 필수" \ + >> "$BYPASS_LOG" 2>/dev/null || true + echo "[SECURITY] ⚠ Bypass accepted — reason: $BYPASS_REASON (logged, 24h 내 수정 필수)" + continue + fi + + # --- diff 기반 보안 검사 --- + VIOLATIONS=0 + MEDIUM_COUNT=0 + OUTPUT="" + + DIFF_OUTPUT=$(git diff -U0 --diff-filter=ACMRT "$oldrev" "$newrev" 2>/dev/null || true) + if [[ -z "$DIFF_OUTPUT" ]]; then + continue + fi + + CURRENT_FILE="" + CURRENT_LINE=0 + + while IFS= read -r line; do + if [[ "$line" =~ ^diff\ --git\ a/(.+)\ b/(.+)$ ]]; then + CURRENT_FILE="${BASH_REMATCH[2]}" + continue + fi + if [[ "$line" =~ ^\+\+\+\ b/(.+)$ ]]; then + CURRENT_FILE="${BASH_REMATCH[1]}" + continue + fi + if [[ "$line" =~ ^@@.*\+([0-9]+) ]]; then + CURRENT_LINE="${BASH_REMATCH[1]}" + continue + fi + if [[ "$line" =~ ^\+ ]] && [[ ! "$line" =~ ^\+\+\+ ]]; then + local_content="${line:1}" + + # 제외 패턴 + if echo "$CURRENT_FILE" | grep -qEi "$EXCLUDE_PATTERNS" 2>/dev/null; then + CURRENT_LINE=$((CURRENT_LINE + 1)) + continue + fi + + # 인라인 ignore 체크 + 규칙 검사 + for i in "${!RULES[@]}"; do + IFS='|' read -r r_name r_sev r_desc r_pat <<< "${RULES[$i]}" + if echo "$local_content" | grep -qP "$r_pat" 2>/dev/null; then + # 라인 단위 ignore + if echo "$local_content" | grep -qP "security-ignore:\s*$r_name" 2>/dev/null; then + continue + fi + RNUM=$((i + 1)) + TRIMMED=$(echo "$local_content" | sed 's/^[[:space:]]*//' | head -c 100) + if [[ "$r_sev" == "CRITICAL" || "$r_sev" == "HIGH" ]]; then + OUTPUT+="$(printf "\n ✗ [%s] #%d %s — %s\n → %s:%d\n %s\n" \ + "$r_sev" "$RNUM" "$r_name" "$r_desc" "$CURRENT_FILE" "$CURRENT_LINE" "$TRIMMED")" + VIOLATIONS=$((VIOLATIONS + 1)) + else + OUTPUT+="$(printf "\n ⚠ [%s] #%d %s — %s\n → %s:%d\n %s\n" \ + "$r_sev" "$RNUM" "$r_name" "$r_desc" "$CURRENT_FILE" "$CURRENT_LINE" "$TRIMMED")" + MEDIUM_COUNT=$((MEDIUM_COUNT + 1)) + fi + fi + done + CURRENT_LINE=$((CURRENT_LINE + 1)) + fi + done <<< "$DIFF_OUTPUT" + + TOTAL=$((VIOLATIONS + MEDIUM_COUNT)) + if [[ $TOTAL -gt 0 ]]; then + echo "" + echo "[SECURITY] $TOTAL issue(s) found in push to $refname:" + echo "$OUTPUT" + echo "" + + if [[ $MEDIUM_COUNT -gt $MEDIUM_THRESHOLD ]]; then + echo "[SECURITY] MEDIUM violations ($MEDIUM_COUNT) exceed threshold ($MEDIUM_THRESHOLD) — blocking" + VIOLATIONS=$((VIOLATIONS + 1)) + fi + + if [[ $VIOLATIONS -gt 0 ]]; then + echo "Push rejected. Fix violations or use [SECURITY-BYPASS: 사유] in commit message." + echo "" + exit 1 + else + echo "Warnings only ($MEDIUM_COUNT MEDIUM) — push allowed." + fi + fi + +done + +exit 0 diff --git a/.securityignore b/.securityignore new file mode 100644 index 0000000..a478d85 --- /dev/null +++ b/.securityignore @@ -0,0 +1,24 @@ +# ============================================================================= +# .securityignore — 보안 스캔 제외 목록 +# ============================================================================= +# 규칙: +# - 모든 항목에 사유 주석 필수 (없으면 경고) +# - 월 1회 정기 검토 → 불필요 항목 제거 +# - 날짜 표기 권장 +# ============================================================================= + +# 스캔 스크립트 자체 (규칙 패턴 포함) +scripts/security-scan.sh # 규칙 정의 자체 (2026-04-10) +.githooks/pre-receive-server.sh # 규칙 정의 자체 (2026-04-10) + +# 환경변수 템플릿 (placeholder만 포함) +.env.example # placeholder 값만 (2026-04-10) + +# 보안 감사 보고서 (발견된 패턴 인용) +SECURITY-AUDIT-20260402.md # 감사 보고서 인용 (2026-04-10) +SECURITY-FINDINGS-SUMMARY.txt # 감사 요약 인용 (2026-04-10) +SECURITY-CODE-SNIPPETS.md # 코드 스니펫 인용 (2026-04-10) + +# 보안 가이드/체크리스트 (규칙 예시 포함) +SECURITY-CHECKLIST.md # 규칙 참조 예시 (2026-04-10) +docs/SECURITY-GUIDE.md # 가이드 예시 코드 (2026-04-10) diff --git a/DEPLOY-GUIDE.md b/DEPLOY-GUIDE.md index 71d993b..656d6cc 100644 --- a/DEPLOY-GUIDE.md +++ b/DEPLOY-GUIDE.md @@ -95,7 +95,7 @@ cat "/volume1/Technicalkorea Document/tkfb-package/.env" | grep MYSQL cat /volume1/docker/tkqc/tkqc-package/.env | grep POSTGRES # Cloudflare Tunnel 토큰 -echo 'fukdon-riwbaq-fiQfy2' | sudo -S /usr/local/bin/docker inspect tkfb_cloudflared | grep TUNNEL_TOKEN +echo "${NAS_SUDO_PASSWORD}" | sudo -S /usr/local/bin/docker inspect tkfb_cloudflared | grep TUNNEL_TOKEN ``` --- @@ -112,12 +112,12 @@ ssh hyungi@192.168.0.3 mkdir -p /volume1/docker/backups/$(date +%Y%m%d) # MariaDB 백업 -echo 'fukdon-riwbaq-fiQfy2' | sudo -S /usr/local/bin/docker exec tkfb_db \ +echo "${NAS_SUDO_PASSWORD}" | sudo -S /usr/local/bin/docker exec tkfb_db \ mysqldump -u root -p --all-databases > \ /volume1/docker/backups/$(date +%Y%m%d)/mariadb-all.sql # PostgreSQL 백업 -echo 'fukdon-riwbaq-fiQfy2' | sudo -S /usr/local/bin/docker exec tkqc-db \ +echo "${NAS_SUDO_PASSWORD}" | sudo -S /usr/local/bin/docker exec tkqc-db \ pg_dumpall -U mproject > \ /volume1/docker/backups/$(date +%Y%m%d)/postgres-all.sql @@ -167,11 +167,11 @@ rm -rf ../tk-factory-services.bak # NAS SSH # TK-FB 중지 cd "/volume1/Technicalkorea Document/tkfb-package" -echo 'fukdon-riwbaq-fiQfy2' | sudo -S /usr/local/bin/docker compose down +echo "${NAS_SUDO_PASSWORD}" | sudo -S /usr/local/bin/docker compose down # TKQC 중지 cd /volume1/docker/tkqc/tkqc-package -echo 'fukdon-riwbaq-fiQfy2' | sudo -S /usr/local/bin/docker compose down +echo "${NAS_SUDO_PASSWORD}" | sudo -S /usr/local/bin/docker compose down ``` ### Step 4: 통합 서비스 기동 @@ -180,10 +180,10 @@ echo 'fukdon-riwbaq-fiQfy2' | sudo -S /usr/local/bin/docker compose down cd /volume1/docker/tk-factory-services # Docker 이미지 빌드 + 서비스 기동 -echo 'fukdon-riwbaq-fiQfy2' | sudo -S /usr/local/bin/docker compose up --build -d +echo "${NAS_SUDO_PASSWORD}" | sudo -S /usr/local/bin/docker compose up --build -d # 로그 확인 -echo 'fukdon-riwbaq-fiQfy2' | sudo -S /usr/local/bin/docker compose logs -f --tail=50 +echo "${NAS_SUDO_PASSWORD}" | sudo -S /usr/local/bin/docker compose logs -f --tail=50 ``` ### Step 5: DB 마이그레이션 @@ -196,7 +196,7 @@ echo 'fukdon-riwbaq-fiQfy2' | sudo -S /usr/local/bin/docker compose logs -f --ta # (system3-nonconformance/api/migrations/ → PostgreSQL init) # 헬스체크 확인 -echo 'fukdon-riwbaq-fiQfy2' | sudo -S /usr/local/bin/docker compose ps +echo "${NAS_SUDO_PASSWORD}" | sudo -S /usr/local/bin/docker compose ps ``` ### Step 6: Cloudflare Tunnel 설정 @@ -291,15 +291,15 @@ git log --oneline -10 ```bash # 통합 서비스 중지 cd /volume1/docker_1/tk-factory-services -echo 'fukdon-riwbaq-fiQfy2' | sudo -S /usr/local/bin/docker compose down +echo "${NAS_SUDO_PASSWORD}" | sudo -S /usr/local/bin/docker compose down # TK-FB 복원 cd "/volume1/Technicalkorea Document/tkfb-package" -echo 'fukdon-riwbaq-fiQfy2' | sudo -S /usr/local/bin/docker compose up -d +echo "${NAS_SUDO_PASSWORD}" | sudo -S /usr/local/bin/docker compose up -d # TKQC 복원 cd /volume1/docker/tkqc/tkqc-package -echo 'fukdon-riwbaq-fiQfy2' | sudo -S /usr/local/bin/docker compose up -d +echo "${NAS_SUDO_PASSWORD}" | sudo -S /usr/local/bin/docker compose up -d ``` --- diff --git a/PROGRESS.md b/PROGRESS.md index df0b2bc..887f86e 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -206,7 +206,7 @@ TK-FB-Project(공장관리+신고)와 M-Project(부적합관리)를 **3개 독 ### NAS (192.168.0.3) - TK-FB 운영: `/volume1/Technicalkorea Document/tkfb-package/` - TKQC 운영: `/volume1/docker/tkqc/tkqc-package/` -- SSH: `hyungi` / `fukdon-riwbaq-fiQfy2` +- SSH: `hyungi` / `${SSH_PASSWORD}` (비밀번호는 비밀관리 시스템 참조) --- diff --git a/SECURITY-CHECKLIST.md b/SECURITY-CHECKLIST.md new file mode 100644 index 0000000..319b580 --- /dev/null +++ b/SECURITY-CHECKLIST.md @@ -0,0 +1,39 @@ +# 보안 PR 체크리스트 — TK Factory Services + +> 공통 원칙: `claude-config/memory/feedback_security_pr_checklist.md` +> 자동 검증: `scripts/security-scan.sh` (pre-commit + pre-receive) + +## 체크리스트 + +| # | 카테고리 | 검증 | 확인 항목 | 참조 파일 | +|---|---------|------|----------|----------| +| 1 | 비밀 정보 | **자동** #1,#2 | 코드/문서에 비밀번호·토큰·API키 하드코딩 없음 | `.env.example` | +| 2 | 인증 | 수동 | 모든 라우트에 `requireAuth` 적용 | `shared/middleware/auth.js` | +| 3 | 권한 RBAC | 수동 | 쓰기(POST/PUT/DELETE)에 `requirePage()` 또는 `requireRole()` | `shared/middleware/pagePermission.js` | +| 4 | 입력 검증 | 수동 | path traversal(`../`), 타입, 길이 검증 | `system1-factory/api/utils/validator.js` | +| 5 | 파일 업로드 | 수동 | magic number + 확장자 + MIME + 크기 제한 | `system1-factory/api/utils/fileUploadSecurity.js` | +| 6 | 네트워크 | **자동** #5 | CORS 와일드카드 없음, rate limiting 적용 | `system1-factory/api/config/cors.js` | +| 7 | DB 쿼리 | **자동** #6 | 파라미터화(`?`), `await`, `COALESCE` 패턴 | CLAUDE.md 주의사항 | +| 8 | 에러/로그 | **자동** #7 | 로그에 비밀정보 없음, 스택트레이스 prod 비노출 | `shared/utils/errors.js` | +| 9 | 보안 헤더 | 수동 | CSP, HSTS, X-Frame-Options | `system1-factory/api/config/security.js` | +| 10 | 자동 검증 | **자동** | pre-commit + pre-receive 통과 | `scripts/security-scan.sh` | + +## 자동 검출 규칙 + +| 규칙# | 이름 | 심각도 | 동작 | +|-------|------|--------|------| +| 1 | SECRET_HARDCODE | CRITICAL | 차단 | +| 2 | SECRET_KNOWN | CRITICAL | 차단 | +| 3 | LOCALSTORAGE_AUTH | HIGH | 차단 | +| 4 | INNERHTML_XSS | HIGH | 차단 | +| 5 | CORS_WILDCARD | HIGH | 차단 | +| 6 | SQL_INTERPOLATION | HIGH | 차단 | +| 7 | LOG_SECRET | MEDIUM | 경고 (5개 초과 시 차단) | +| 8 | ENV_HARDCODE | MEDIUM | 경고 (5개 초과 시 차단) | + +## 수동 확인 필요 항목 (자동화 한계) + +- RBAC 설계 오류 / 인증 흐름 +- 비즈니스 로직 / race condition +- third-party dependency 취약점 (`npm audit`) +- 환경변수 값 강도 diff --git a/docker-compose.yml b/docker-compose.yml index 4c7c9b3..5502789 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -309,6 +309,7 @@ services: - NTFY_BASE_URL=${NTFY_BASE_URL:-http://ntfy:80} - NTFY_PUBLISH_TOKEN=${NTFY_PUBLISH_TOKEN} - NTFY_EXTERNAL_URL=${NTFY_EXTERNAL_URL:-https://ntfy.technicalkorea.net} + - NTFY_SUB_PASSWORD=${NTFY_SUB_PASSWORD} - TKFB_BASE_URL=${TKFB_BASE_URL:-https://tkfb.technicalkorea.net} healthcheck: test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/health"] diff --git a/docs/SECURITY-GUIDE.md b/docs/SECURITY-GUIDE.md new file mode 100644 index 0000000..4d74967 --- /dev/null +++ b/docs/SECURITY-GUIDE.md @@ -0,0 +1,157 @@ +# 보안 시스템 운영 가이드 + +## 개요 + +TK Factory Services에는 2계층 보안 검사 시스템이 적용되어 있습니다. + +| 계층 | 위치 | 역할 | 우회 가능 | +|------|------|------|----------| +| pre-commit | 로컬 (개발자 PC) | 빠른 피드백 | `--no-verify` | +| pre-receive | Gitea 서버 | 최종 차단 | `[SECURITY-BYPASS: 사유]`만 | + +## 개발 워크플로우 + +``` +코드 작성 → git add → git commit + ↓ + pre-commit hook + (security-scan.sh --staged) + ↓ + 위반 있으면 → 커밋 차단 + 상세 출력 + 위반 없으면 → 커밋 성공 + ↓ + git push + ↓ + pre-receive hook (서버) + (diff 기반 검사) + ↓ + 위반 있으면 → push 차단 + 위반 없으면 → push 성공 +``` + +## 위반 발생 시 대처 + +### 에러 메시지 읽기 + +``` +[SECURITY] 2 issue(s) found: + + ✗ [CRITICAL] #1 SECRET_HARDCODE — 비밀정보 하드코딩 + → src/controllers/auth.js:64 + password: 'my-secret-123' +``` + +- `[CRITICAL]` / `[HIGH]` → 차단됨, 반드시 수정 +- `[MEDIUM]` → 경고, 5개 초과 시 차단 +- `→ 파일:라인번호` → 수정할 위치 +- 아래 줄 → 문제가 된 코드 + +### 수정 방법 (규칙별) + +| 규칙 | 수정 방법 | +|------|----------| +| SECRET_HARDCODE | `process.env.변수명`으로 이동, `.env`에 추가 | +| LOCALSTORAGE_AUTH | HttpOnly 쿠키 또는 Authorization 헤더 사용 | +| INNERHTML_XSS | `textContent` 사용 또는 DOMPurify 적용 | +| CORS_WILDCARD | 허용 도메인을 명시적으로 나열 | +| SQL_INTERPOLATION | 파라미터화 쿼리(`?` placeholder) 사용 | +| LOG_SECRET | 로그에서 비밀정보 제거 | + +## bypass 사용법 (긴급 시) + +### 형식 +``` +git commit -m "fix: 긴급 장애 대응 [SECURITY-BYPASS: prod 서비스 다운 긴급 핫픽스]" +``` + +### 규칙 +- **사유 필수**: `[SECURITY-BYPASS]`만으로는 거부됨 +- **허용 사용자만**: 운영담당자(ahn@hyungi.net)만 bypass 가능 +- **24시간 내 수정**: bypass 후 반드시 보안 이슈 수정 PR 제출 +- **로그 기록**: 모든 bypass는 서버에 자동 기록됨 + +### bypass 후 조치 +1. bypass한 코드의 보안 이슈 파악 +2. 24시간 내 수정 커밋 +3. `security-scan.sh --all`로 전체 검증 + +## 규칙 추가/수정 방법 + +### 새 규칙 추가 +`scripts/security-scan.sh`의 RULES 배열에 추가: +```bash +'RULE_NAME|SEVERITY|설명|REGEX_PATTERN' +``` + +예시: +```bash +'EVAL_USAGE|HIGH|eval 사용 위험|eval\s*\(' +``` + +### 같은 규칙을 서버에도 반영 +`.githooks/pre-receive-server.sh`의 RULES 배열에도 동일하게 추가. +Gitea 서버의 hook 파일도 업데이트 필요. + +## false positive 등록 + +### 파일 단위 제외 +`.securityignore`에 추가 (주석 필수): +``` +path/to/file.js # 사유 설명 (날짜) +``` + +### 라인 단위 제외 +소스 코드에 인라인 주석: +```javascript +const pattern = /password/; // security-ignore: SECRET_HARDCODE — regex 패턴 정의 +``` + +### 주의 +- 주석 없는 항목은 스캔 시 경고 +- 월 1회 `.securityignore` 검토하여 불필요 항목 제거 + +## 수동 검사 + +### 전체 프로젝트 스캔 +```bash +./scripts/security-scan.sh --all +``` + +### 엄격 모드 (MEDIUM도 차단) +```bash +./scripts/security-scan.sh --all --strict +``` + +### 두 커밋 간 비교 +```bash +./scripts/security-scan.sh --diff HEAD~5 HEAD +``` + +## 초기 설정 (새 머신) + +```bash +# 1. git hooks 경로 설정 +git config core.hooksPath .githooks + +# 2. 전체 스캔 확인 +./scripts/security-scan.sh --all + +# 3. 테스트 (선택) +echo "password: 'test'" >> /tmp/test.js +git add /tmp/test.js +git commit -m "test" # → 차단되어야 함 +``` + +## FAQ + +**Q: pre-commit이 너무 느리다** +A: staged 파일만 검사하므로 보통 1초 이내. 파일이 많으면 `--no-verify`로 우회 후 push 시 서버에서 검사. + +**Q: false positive가 계속 뜬다** +A: `.securityignore`에 등록하거나 라인에 `// security-ignore: RULE_NAME` 추가. + +**Q: 규칙을 비활성화하고 싶다** +A: RULES 배열에서 해당 규칙을 주석 처리. 단, CRITICAL 규칙 비활성화는 비권장. + +**Q: 새 서비스 추가 시** +A: 추가 설정 불필요. `.securityignore`에 제외할 파일이 있으면 등록. diff --git a/scripts/security-scan.sh b/scripts/security-scan.sh new file mode 100755 index 0000000..2bc2f90 --- /dev/null +++ b/scripts/security-scan.sh @@ -0,0 +1,355 @@ +#!/bin/bash +# ============================================================================= +# security-scan.sh — TK Factory Services 보안 스캔 엔진 +# ============================================================================= +# 용도: pre-commit hook, pre-receive hook, 수동 전체 검사 +# 모드: +# --staged staged 파일만 검사 (pre-commit 기본) +# --all 프로젝트 전체 파일 검사 +# --diff OLD NEW 두 커밋 간 변경 검사 (pre-receive용) +# --strict MEDIUM도 차단 +# +# 커버리지 한계 (PR 리뷰에서 수동): +# - RBAC 설계 오류 / 인증 흐름 +# - 비즈니스 로직 / race condition +# - third-party dependency (npm audit 영역) +# - 환경변수 값 강도 +# ============================================================================= + +set -euo pipefail + +# --- 색상 --- +RED='\033[0;31m' +YELLOW='\033[0;33m' +GREEN='\033[0;32m' +CYAN='\033[0;36m' +BOLD='\033[1m' +NC='\033[0m' + +# --- 설정 --- +MEDIUM_THRESHOLD=5 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +IGNORE_FILE="$PROJECT_ROOT/.securityignore" + +# --- 검출 규칙: NAME|SEVERITY|설명|PATTERN --- +RULES=( + 'SECRET_HARDCODE|CRITICAL|비밀정보 하드코딩|(password|token|apiKey|secret|api_key)\s*[:=]\s*[\x27"][^${\x27"][^\x27"]{3,}' + 'SECRET_KNOWN|CRITICAL|알려진 비밀번호 패턴|(fukdon-riwbaq|hyung-ddfdf3|djg3-jj34|tkfactory-sub)' + 'LOCALSTORAGE_AUTH|HIGH|localStorage 인증정보|localStorage\.(setItem|getItem).*\b(token|password|auth|credential)' + 'INNERHTML_XSS|HIGH|innerHTML XSS 위험|\.innerHTML\s*[+=]' + 'CORS_WILDCARD|HIGH|CORS 와일드카드|origin:\s*[\x27"`]\*[\x27"`]' + 'SQL_INTERPOLATION|HIGH|SQL 문자열 보간|query\(`.*\$\{' + 'LOG_SECRET|MEDIUM|로그에 비밀정보|console\.(log|error|warn).*\b(password|token|secret|apiKey)' + 'ENV_HARDCODE|MEDIUM|환경설정 하드코딩|NODE_ENV\s*[:=]\s*[\x27"]development[\x27"]' +) + +# --- 제외 패턴 --- +EXCLUDE_DIRS="node_modules|\.git|__pycache__|\.next|dist|build|coverage" +EXCLUDE_FILES="package-lock\.json|yarn\.lock|\.min\.js|\.min\.css|\.map" + +# --- 파싱 함수 --- +parse_rule() { + local rule="$1" + RULE_NAME=$(echo "$rule" | cut -d'|' -f1) + RULE_SEVERITY=$(echo "$rule" | cut -d'|' -f2) + RULE_DESC=$(echo "$rule" | cut -d'|' -f3) + RULE_PATTERN=$(echo "$rule" | cut -d'|' -f4-) +} + +# --- .securityignore 로드 --- +load_ignore_list() { + IGNORED_FILES=() + if [[ -f "$IGNORE_FILE" ]]; then + while IFS= read -r line; do + # 빈 줄, 순수 주석 스킵 + [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue + # 파일명 추출 (주석 앞부분) + local filepath + filepath=$(echo "$line" | sed 's/#.*$//' | xargs) + [[ -z "$filepath" ]] && continue + # 주석 없는 항목 경고 + if ! echo "$line" | grep -q '#'; then + echo -e "${YELLOW}[WARN] .securityignore: '$filepath' 에 사유 주석이 없습니다${NC}" >&2 + fi + IGNORED_FILES+=("$filepath") + done < "$IGNORE_FILE" + fi +} + +is_ignored_file() { + local file="$1" + for ignored in "${IGNORED_FILES[@]}"; do + [[ "$file" == "$ignored" || "$file" == *"/$ignored" ]] && return 0 + done + return 1 +} + +is_line_ignored() { + local line_content="$1" + local rule_name="$2" + echo "$line_content" | grep -qP "security-ignore:\s*$rule_name" && return 0 + return 1 +} + +# --- diff 파싱 + 검사 --- +scan_diff() { + local diff_input="$1" + local violations=0 + local medium_count=0 + local current_file="" + local current_line=0 + local results="" + + while IFS= read -r line; do + # 파일명 추출 + if [[ "$line" =~ ^diff\ --git\ a/(.+)\ b/(.+)$ ]]; then + current_file="${BASH_REMATCH[2]}" + continue + fi + # +++ b/filename + if [[ "$line" =~ ^\+\+\+\ b/(.+)$ ]]; then + current_file="${BASH_REMATCH[1]}" + continue + fi + # hunk header → 라인 번호 + if [[ "$line" =~ ^@@.*\+([0-9]+) ]]; then + current_line="${BASH_REMATCH[1]}" + continue + fi + # 추가된 라인만 검사 + if [[ "$line" =~ ^\+ ]] && [[ ! "$line" =~ ^\+\+\+ ]]; then + local content="${line:1}" # + 제거 + current_line=$((current_line)) + + # 제외 디렉토리/파일 체크 + if echo "$current_file" | grep -qEi "($EXCLUDE_DIRS)" 2>/dev/null; then + current_line=$((current_line + 1)) + continue + fi + if echo "$current_file" | grep -qEi "($EXCLUDE_FILES)" 2>/dev/null; then + current_line=$((current_line + 1)) + continue + fi + # .securityignore 체크 + if is_ignored_file "$current_file"; then + current_line=$((current_line + 1)) + continue + fi + + # 각 규칙 검사 + for i in "${!RULES[@]}"; do + parse_rule "${RULES[$i]}" + if echo "$content" | grep -qP "$RULE_PATTERN" 2>/dev/null; then + # 라인 단위 ignore 체크 + if is_line_ignored "$content" "$RULE_NAME"; then + continue + fi + local rule_num=$((i + 1)) + local trimmed + trimmed=$(echo "$content" | sed 's/^[[:space:]]*//' | head -c 100) + if [[ "$RULE_SEVERITY" == "CRITICAL" || "$RULE_SEVERITY" == "HIGH" ]]; then + results+="$(printf "\n ${RED}✗ [%s] #%d %s${NC} — %s\n → %s:%d\n %s\n" \ + "$RULE_SEVERITY" "$rule_num" "$RULE_NAME" "$RULE_DESC" \ + "$current_file" "$current_line" "$trimmed")" + violations=$((violations + 1)) + else + results+="$(printf "\n ${YELLOW}⚠ [%s] #%d %s${NC} — %s\n → %s:%d\n %s\n" \ + "$RULE_SEVERITY" "$rule_num" "$RULE_NAME" "$RULE_DESC" \ + "$current_file" "$current_line" "$trimmed")" + medium_count=$((medium_count + 1)) + fi + fi + done + current_line=$((current_line + 1)) + fi + done <<< "$diff_input" + + # 결과 출력 + local total=$((violations + medium_count)) + if [[ $total -gt 0 ]]; then + echo "" + echo -e "${BOLD}[SECURITY] ${total} issue(s) found:${NC}" + echo -e "$results" + echo "" + + # MEDIUM 임계값 체크 + if [[ $medium_count -gt $MEDIUM_THRESHOLD ]]; then + echo -e "${RED}[SECURITY] MEDIUM violations ($medium_count) exceed threshold ($MEDIUM_THRESHOLD) — blocking${NC}" + violations=$((violations + 1)) + fi + + # strict 모드 + if [[ "${STRICT_MODE:-false}" == "true" && $medium_count -gt 0 ]]; then + echo -e "${RED}[SECURITY] --strict mode: MEDIUM violations also block${NC}" + violations=$((violations + 1)) + fi + + if [[ $violations -gt 0 ]]; then + echo -e "${RED}Push/commit rejected. Fix violations or use [SECURITY-BYPASS: 사유] in commit message.${NC}" + echo "" + return 1 + else + echo -e "${YELLOW}Warnings only — commit/push allowed.${NC}" + echo "" + return 0 + fi + fi + return 0 +} + +# --- 전체 파일 검사 (--all 모드) --- +scan_all() { + local violations=0 + local medium_count=0 + local results="" + + load_ignore_list + + local files + files=$(find "$PROJECT_ROOT" -type f \ + \( -name "*.js" -o -name "*.py" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" \ + -o -name "*.md" -o -name "*.sql" -o -name "*.yml" -o -name "*.yaml" \ + -o -name "*.json" -o -name "*.sh" -o -name "*.html" \) \ + ! -path "*/node_modules/*" \ + ! -path "*/.git/*" \ + ! -path "*/__pycache__/*" \ + ! -path "*/dist/*" \ + ! -path "*/build/*" \ + ! -path "*/coverage/*" \ + ! -path "*/.claude/worktrees/*" \ + ! -name "package-lock.json" \ + ! -name "*.min.js" \ + ! -name "*.min.css" \ + 2>/dev/null || true) + + while IFS= read -r filepath; do + [[ -z "$filepath" ]] && continue + local relpath="${filepath#$PROJECT_ROOT/}" + + # .securityignore 체크 + if is_ignored_file "$relpath"; then + continue + fi + + for i in "${!RULES[@]}"; do + parse_rule "${RULES[$i]}" + + local matches + matches=$(grep -nP "$RULE_PATTERN" "$filepath" 2>/dev/null || true) + [[ -z "$matches" ]] && continue + + while IFS= read -r match; do + local linenum content + linenum=$(echo "$match" | cut -d: -f1) + content=$(echo "$match" | cut -d: -f2- | sed 's/^[[:space:]]*//' | head -c 100) + + # 라인 단위 ignore + local full_line + full_line=$(sed -n "${linenum}p" "$filepath" 2>/dev/null || true) + if is_line_ignored "$full_line" "$RULE_NAME"; then + continue + fi + + local rule_num=$((i + 1)) + if [[ "$RULE_SEVERITY" == "CRITICAL" || "$RULE_SEVERITY" == "HIGH" ]]; then + results+="$(printf "\n ${RED}✗ [%s] #%d %s${NC} — %s\n → %s:%s\n %s\n" \ + "$RULE_SEVERITY" "$rule_num" "$RULE_NAME" "$RULE_DESC" \ + "$relpath" "$linenum" "$content")" + violations=$((violations + 1)) + else + results+="$(printf "\n ${YELLOW}⚠ [%s] #%d %s${NC} — %s\n → %s:%s\n %s\n" \ + "$RULE_SEVERITY" "$rule_num" "$RULE_NAME" "$RULE_DESC" \ + "$relpath" "$linenum" "$content")" + medium_count=$((medium_count + 1)) + fi + done <<< "$matches" + done + done <<< "$files" + + # 결과 출력 + local total=$((violations + medium_count)) + if [[ $total -gt 0 ]]; then + echo "" + echo -e "${BOLD}[SECURITY] Full scan: ${total} issue(s) found:${NC}" + echo -e "$results" + echo "" + + if [[ $medium_count -gt $MEDIUM_THRESHOLD ]]; then + echo -e "${RED}[SECURITY] MEDIUM violations ($medium_count) exceed threshold ($MEDIUM_THRESHOLD)${NC}" + violations=$((violations + 1)) + fi + if [[ "${STRICT_MODE:-false}" == "true" && $medium_count -gt 0 ]]; then + echo -e "${RED}[SECURITY] --strict mode: MEDIUM violations also count${NC}" + violations=$((violations + 1)) + fi + + if [[ $violations -gt 0 ]]; then + echo -e "${RED}${violations} blocking violation(s) found.${NC}" + return 1 + else + echo -e "${YELLOW}Warnings only (${medium_count} MEDIUM).${NC}" + return 0 + fi + else + echo -e "${GREEN}[SECURITY] Full scan: 0 violations found.${NC}" + return 0 + fi +} + +# --- 메인 --- +main() { + local mode="staged" + local old_rev="" new_rev="" + STRICT_MODE="false" + + while [[ $# -gt 0 ]]; do + case "$1" in + --staged) mode="staged"; shift ;; + --all) mode="all"; shift ;; + --diff) mode="diff"; old_rev="$2"; new_rev="$3"; shift 3 ;; + --strict) STRICT_MODE="true"; shift ;; + -h|--help) + echo "Usage: security-scan.sh [--staged|--all|--diff OLD NEW] [--strict]" + echo " --staged Check staged files (default, for pre-commit)" + echo " --all Scan entire project" + echo " --diff Check changes between two commits (for pre-receive)" + echo " --strict Block MEDIUM violations too" + exit 0 + ;; + *) echo "Unknown option: $1"; exit 1 ;; + esac + done + + load_ignore_list + + case "$mode" in + staged) + local diff_output + diff_output=$(git diff --cached -U0 --diff-filter=ACMRT 2>/dev/null || true) + if [[ -z "$diff_output" ]]; then + echo -e "${GREEN}[SECURITY] No staged changes to scan.${NC}" + exit 0 + fi + scan_diff "$diff_output" + ;; + all) + scan_all + ;; + diff) + if [[ -z "$old_rev" || -z "$new_rev" ]]; then + echo "Error: --diff requires OLD and NEW revisions" + exit 1 + fi + local diff_output + diff_output=$(git diff -U0 --diff-filter=ACMRT "$old_rev" "$new_rev" 2>/dev/null || true) + if [[ -z "$diff_output" ]]; then + echo -e "${GREEN}[SECURITY] No changes to scan.${NC}" + exit 0 + fi + scan_diff "$diff_output" + ;; + esac +} + +main "$@" diff --git a/system1-factory/api/db/migrations/20260313001000_create_push_subscriptions.sql b/system1-factory/api/db/migrations/20260313001000_create_push_subscriptions.sql index 8122b4d..797bcaa 100644 --- a/system1-factory/api/db/migrations/20260313001000_create_push_subscriptions.sql +++ b/system1-factory/api/db/migrations/20260313001000_create_push_subscriptions.sql @@ -1,5 +1,5 @@ -- Push 구독 테이블 생성 --- 실행: docker exec -i tk-mariadb mysql -uhyungi_user -p'hyung-ddfdf3-D341@' hyungi < db/migrations/20260313001000_create_push_subscriptions.sql +-- 실행: docker exec -i tk-mariadb mysql -uhyungi_user -p"$MYSQL_PASSWORD" hyungi < db/migrations/20260313001000_create_push_subscriptions.sql CREATE TABLE IF NOT EXISTS push_subscriptions ( id INT AUTO_INCREMENT PRIMARY KEY, diff --git a/system1-factory/web/logo 2.png b/system1-factory/web/logo 2.png deleted file mode 100644 index 5bbd962e3dedce356d28528ee4d3414d2279fdb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14999 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 - + diff --git a/system3-nonconformance/web/issues-archive.html b/system3-nonconformance/web/public/issues-archive.html similarity index 100% rename from system3-nonconformance/web/issues-archive.html rename to system3-nonconformance/web/public/issues-archive.html diff --git a/system3-nonconformance/web/issues-dashboard.html b/system3-nonconformance/web/public/issues-dashboard.html similarity index 100% rename from system3-nonconformance/web/issues-dashboard.html rename to system3-nonconformance/web/public/issues-dashboard.html diff --git a/system3-nonconformance/web/issues-inbox.html b/system3-nonconformance/web/public/issues-inbox.html similarity index 100% rename from system3-nonconformance/web/issues-inbox.html rename to system3-nonconformance/web/public/issues-inbox.html diff --git a/system3-nonconformance/web/issues-management.html b/system3-nonconformance/web/public/issues-management.html similarity index 100% rename from system3-nonconformance/web/issues-management.html rename to system3-nonconformance/web/public/issues-management.html diff --git a/system3-nonconformance/web/m/dashboard.html b/system3-nonconformance/web/public/m/dashboard.html similarity index 100% rename from system3-nonconformance/web/m/dashboard.html rename to system3-nonconformance/web/public/m/dashboard.html diff --git a/system3-nonconformance/web/m/inbox.html b/system3-nonconformance/web/public/m/inbox.html similarity index 100% rename from system3-nonconformance/web/m/inbox.html rename to system3-nonconformance/web/public/m/inbox.html diff --git a/system3-nonconformance/web/m/management.html b/system3-nonconformance/web/public/m/management.html similarity index 100% rename from system3-nonconformance/web/m/management.html rename to system3-nonconformance/web/public/m/management.html diff --git a/system3-nonconformance/web/push-sw.js b/system3-nonconformance/web/public/push-sw.js similarity index 100% rename from system3-nonconformance/web/push-sw.js rename to system3-nonconformance/web/public/push-sw.js diff --git a/system3-nonconformance/web/reports-daily.html b/system3-nonconformance/web/public/reports-daily.html similarity index 100% rename from system3-nonconformance/web/reports-daily.html rename to system3-nonconformance/web/public/reports-daily.html diff --git a/system3-nonconformance/web/reports-monthly.html b/system3-nonconformance/web/public/reports-monthly.html similarity index 100% rename from system3-nonconformance/web/reports-monthly.html rename to system3-nonconformance/web/public/reports-monthly.html diff --git a/system3-nonconformance/web/reports-weekly.html b/system3-nonconformance/web/public/reports-weekly.html similarity index 100% rename from system3-nonconformance/web/reports-weekly.html rename to system3-nonconformance/web/public/reports-weekly.html diff --git a/system3-nonconformance/web/reports.html b/system3-nonconformance/web/public/reports.html similarity index 100% rename from system3-nonconformance/web/reports.html rename to system3-nonconformance/web/public/reports.html diff --git a/system3-nonconformance/web/static/css/ai-assistant.css b/system3-nonconformance/web/public/static/css/ai-assistant.css similarity index 100% rename from system3-nonconformance/web/static/css/ai-assistant.css rename to system3-nonconformance/web/public/static/css/ai-assistant.css diff --git a/system3-nonconformance/web/static/css/issue-view.css b/system3-nonconformance/web/public/static/css/issue-view.css similarity index 100% rename from system3-nonconformance/web/static/css/issue-view.css rename to system3-nonconformance/web/public/static/css/issue-view.css diff --git a/system3-nonconformance/web/static/css/issues-archive.css b/system3-nonconformance/web/public/static/css/issues-archive.css similarity index 100% rename from system3-nonconformance/web/static/css/issues-archive.css rename to system3-nonconformance/web/public/static/css/issues-archive.css diff --git a/system3-nonconformance/web/static/css/issues-dashboard.css b/system3-nonconformance/web/public/static/css/issues-dashboard.css similarity index 100% rename from system3-nonconformance/web/static/css/issues-dashboard.css rename to system3-nonconformance/web/public/static/css/issues-dashboard.css diff --git a/system3-nonconformance/web/static/css/issues-inbox.css b/system3-nonconformance/web/public/static/css/issues-inbox.css similarity index 100% rename from system3-nonconformance/web/static/css/issues-inbox.css rename to system3-nonconformance/web/public/static/css/issues-inbox.css diff --git a/system3-nonconformance/web/static/css/issues-management.css b/system3-nonconformance/web/public/static/css/issues-management.css similarity index 100% rename from system3-nonconformance/web/static/css/issues-management.css rename to system3-nonconformance/web/public/static/css/issues-management.css diff --git a/system3-nonconformance/web/static/css/m-common.css b/system3-nonconformance/web/public/static/css/m-common.css similarity index 100% rename from system3-nonconformance/web/static/css/m-common.css rename to system3-nonconformance/web/public/static/css/m-common.css diff --git a/system3-nonconformance/web/static/css/mobile-calendar.css b/system3-nonconformance/web/public/static/css/mobile-calendar.css similarity index 100% rename from system3-nonconformance/web/static/css/mobile-calendar.css rename to system3-nonconformance/web/public/static/css/mobile-calendar.css diff --git a/system3-nonconformance/web/static/css/tkqc-common.css b/system3-nonconformance/web/public/static/css/tkqc-common.css similarity index 100% rename from system3-nonconformance/web/static/css/tkqc-common.css rename to system3-nonconformance/web/public/static/css/tkqc-common.css diff --git a/system3-nonconformance/web/static/js/api.js b/system3-nonconformance/web/public/static/js/api.js similarity index 100% rename from system3-nonconformance/web/static/js/api.js rename to system3-nonconformance/web/public/static/js/api.js diff --git a/system3-nonconformance/web/static/js/app.js b/system3-nonconformance/web/public/static/js/app.js similarity index 100% rename from system3-nonconformance/web/static/js/app.js rename to system3-nonconformance/web/public/static/js/app.js diff --git a/system3-nonconformance/web/static/js/components/common-header.js b/system3-nonconformance/web/public/static/js/components/common-header.js similarity index 100% rename from system3-nonconformance/web/static/js/components/common-header.js rename to system3-nonconformance/web/public/static/js/components/common-header.js diff --git a/system3-nonconformance/web/static/js/components/mobile-bottom-nav.js b/system3-nonconformance/web/public/static/js/components/mobile-bottom-nav.js similarity index 100% rename from system3-nonconformance/web/static/js/components/mobile-bottom-nav.js rename to system3-nonconformance/web/public/static/js/components/mobile-bottom-nav.js diff --git a/system3-nonconformance/web/static/js/components/mobile-calendar.js b/system3-nonconformance/web/public/static/js/components/mobile-calendar.js similarity index 100% rename from system3-nonconformance/web/static/js/components/mobile-calendar.js rename to system3-nonconformance/web/public/static/js/components/mobile-calendar.js diff --git a/system3-nonconformance/web/static/js/core/auth-manager.js b/system3-nonconformance/web/public/static/js/core/auth-manager.js similarity index 100% rename from system3-nonconformance/web/static/js/core/auth-manager.js rename to system3-nonconformance/web/public/static/js/core/auth-manager.js diff --git a/system3-nonconformance/web/static/js/core/keyboard-shortcuts.js b/system3-nonconformance/web/public/static/js/core/keyboard-shortcuts.js similarity index 100% rename from system3-nonconformance/web/static/js/core/keyboard-shortcuts.js rename to system3-nonconformance/web/public/static/js/core/keyboard-shortcuts.js diff --git a/system3-nonconformance/web/static/js/core/page-manager.js b/system3-nonconformance/web/public/static/js/core/page-manager.js similarity index 100% rename from system3-nonconformance/web/static/js/core/page-manager.js rename to system3-nonconformance/web/public/static/js/core/page-manager.js diff --git a/system3-nonconformance/web/static/js/core/page-preloader.js b/system3-nonconformance/web/public/static/js/core/page-preloader.js similarity index 100% rename from system3-nonconformance/web/static/js/core/page-preloader.js rename to system3-nonconformance/web/public/static/js/core/page-preloader.js diff --git a/system3-nonconformance/web/static/js/core/permissions.js b/system3-nonconformance/web/public/static/js/core/permissions.js similarity index 100% rename from system3-nonconformance/web/static/js/core/permissions.js rename to system3-nonconformance/web/public/static/js/core/permissions.js diff --git a/system3-nonconformance/web/static/js/date-utils.js b/system3-nonconformance/web/public/static/js/date-utils.js similarity index 100% rename from system3-nonconformance/web/static/js/date-utils.js rename to system3-nonconformance/web/public/static/js/date-utils.js diff --git a/system3-nonconformance/web/static/js/image-utils.js b/system3-nonconformance/web/public/static/js/image-utils.js similarity index 100% rename from system3-nonconformance/web/static/js/image-utils.js rename to system3-nonconformance/web/public/static/js/image-utils.js diff --git a/system3-nonconformance/web/static/js/lib/purify.min.js b/system3-nonconformance/web/public/static/js/lib/purify.min.js similarity index 100% rename from system3-nonconformance/web/static/js/lib/purify.min.js rename to system3-nonconformance/web/public/static/js/lib/purify.min.js diff --git a/system3-nonconformance/web/static/js/m/m-common.js b/system3-nonconformance/web/public/static/js/m/m-common.js similarity index 100% rename from system3-nonconformance/web/static/js/m/m-common.js rename to system3-nonconformance/web/public/static/js/m/m-common.js diff --git a/system3-nonconformance/web/static/js/m/m-dashboard.js b/system3-nonconformance/web/public/static/js/m/m-dashboard.js similarity index 100% rename from system3-nonconformance/web/static/js/m/m-dashboard.js rename to system3-nonconformance/web/public/static/js/m/m-dashboard.js diff --git a/system3-nonconformance/web/static/js/m/m-inbox.js b/system3-nonconformance/web/public/static/js/m/m-inbox.js similarity index 100% rename from system3-nonconformance/web/static/js/m/m-inbox.js rename to system3-nonconformance/web/public/static/js/m/m-inbox.js diff --git a/system3-nonconformance/web/static/js/m/m-management.js b/system3-nonconformance/web/public/static/js/m/m-management.js similarity index 100% rename from system3-nonconformance/web/static/js/m/m-management.js rename to system3-nonconformance/web/public/static/js/m/m-management.js diff --git a/system3-nonconformance/web/static/js/pages/ai-assistant.js b/system3-nonconformance/web/public/static/js/pages/ai-assistant.js similarity index 100% rename from system3-nonconformance/web/static/js/pages/ai-assistant.js rename to system3-nonconformance/web/public/static/js/pages/ai-assistant.js diff --git a/system3-nonconformance/web/static/js/pages/issue-view.js b/system3-nonconformance/web/public/static/js/pages/issue-view.js similarity index 98% rename from system3-nonconformance/web/static/js/pages/issue-view.js rename to system3-nonconformance/web/public/static/js/pages/issue-view.js index 9a26b90..0fc0954 100644 --- a/system3-nonconformance/web/static/js/pages/issue-view.js +++ b/system3-nonconformance/web/public/static/js/pages/issue-view.js @@ -673,13 +673,20 @@ async function handlePasswordChange(e) { // 현재 비밀번호 확인 (localStorage 기반) let users = JSON.parse(localStorage.getItem('work-report-users') || '[]'); + // 임시 비밀번호 생성 (클라이언트에 하드코딩하지 않음) + function generateTempPassword() { + const arr = new Uint8Array(12); + crypto.getRandomValues(arr); + return Array.from(arr, b => b.toString(36).padStart(2, '0')).join('').slice(0, 16); + } + // 기본 사용자가 없으면 생성 if (users.length === 0) { users = [ { username: 'hyungi', full_name: '관리자', - password: 'djg3-jj34-X3Q3', + password: generateTempPassword(), role: 'admin' } ]; @@ -694,7 +701,7 @@ async function handlePasswordChange(e) { user = { username: username, full_name: username === 'hyungi' ? '관리자' : username, - password: 'djg3-jj34-X3Q3', + password: generateTempPassword(), role: username === 'hyungi' ? 'admin' : 'user' }; users.push(user); diff --git a/system3-nonconformance/web/static/js/pages/issues-archive.js b/system3-nonconformance/web/public/static/js/pages/issues-archive.js similarity index 100% rename from system3-nonconformance/web/static/js/pages/issues-archive.js rename to system3-nonconformance/web/public/static/js/pages/issues-archive.js diff --git a/system3-nonconformance/web/static/js/pages/issues-dashboard.js b/system3-nonconformance/web/public/static/js/pages/issues-dashboard.js similarity index 100% rename from system3-nonconformance/web/static/js/pages/issues-dashboard.js rename to system3-nonconformance/web/public/static/js/pages/issues-dashboard.js diff --git a/system3-nonconformance/web/static/js/pages/issues-inbox.js b/system3-nonconformance/web/public/static/js/pages/issues-inbox.js similarity index 100% rename from system3-nonconformance/web/static/js/pages/issues-inbox.js rename to system3-nonconformance/web/public/static/js/pages/issues-inbox.js diff --git a/system3-nonconformance/web/static/js/pages/issues-management.js b/system3-nonconformance/web/public/static/js/pages/issues-management.js similarity index 100% rename from system3-nonconformance/web/static/js/pages/issues-management.js rename to system3-nonconformance/web/public/static/js/pages/issues-management.js diff --git a/system3-nonconformance/web/static/js/sso-relay.js b/system3-nonconformance/web/public/static/js/sso-relay.js similarity index 100% rename from system3-nonconformance/web/static/js/sso-relay.js rename to system3-nonconformance/web/public/static/js/sso-relay.js diff --git a/system3-nonconformance/web/static/js/utils/issue-helpers.js b/system3-nonconformance/web/public/static/js/utils/issue-helpers.js similarity index 100% rename from system3-nonconformance/web/static/js/utils/issue-helpers.js rename to system3-nonconformance/web/public/static/js/utils/issue-helpers.js diff --git a/system3-nonconformance/web/static/js/utils/photo-modal.js b/system3-nonconformance/web/public/static/js/utils/photo-modal.js similarity index 100% rename from system3-nonconformance/web/static/js/utils/photo-modal.js rename to system3-nonconformance/web/public/static/js/utils/photo-modal.js diff --git a/system3-nonconformance/web/static/js/utils/toast.js b/system3-nonconformance/web/public/static/js/utils/toast.js similarity index 100% rename from system3-nonconformance/web/static/js/utils/toast.js rename to system3-nonconformance/web/public/static/js/utils/toast.js diff --git a/system3-nonconformance/web/sw.js b/system3-nonconformance/web/public/sw.js similarity index 100% rename from system3-nonconformance/web/sw.js rename to system3-nonconformance/web/public/sw.js diff --git a/user-management/api/controllers/pushSubscriptionController.js b/user-management/api/controllers/pushSubscriptionController.js index 8bb3328..3a61479 100644 --- a/user-management/api/controllers/pushSubscriptionController.js +++ b/user-management/api/controllers/pushSubscriptionController.js @@ -61,7 +61,7 @@ const pushSubscriptionController = { topic, serverUrl: process.env.NTFY_EXTERNAL_URL || 'https://ntfy.technicalkorea.net', username: 'subscriber', - password: 'tkfactory-sub-2026' + password: process.env.NTFY_SUB_PASSWORD || '' } }); } catch (error) {