From df7d2edec91ac81c2a1ad44567eefc9a056e78c6 Mon Sep 17 00:00:00 2001 From: SalkCoding Date: Thu, 10 Sep 2026 20:17:03 +0900 Subject: [PATCH] feat: prepare 1.0.5.1 release --- .claude/launch.json | 11 - .claude/settings.json | 7 - .codex/config.toml | 2 + .cursorrules | 92 - .dockerignore | 16 + .github/copilot-instructions.md | 144 - .github/prompts/qa.prompt.md | 293 - .github/scripts/prepare-pages-site.sh | 1 + .github/scripts/prepare-wiki.py | 111 + .github/scripts/validate-release.py | 76 + .github/workflows/ci-cd.yml | 109 +- .github/workflows/wiki-sync.yml | 21 +- .gitignore | 27 +- .kimi/AGENTS.md | 92 - .release-please-manifest.json | 3 - CHANGELOG.md | 20 + CLAUDE.md | 92 - README.ja.md | 6 +- README.ko.md | 7 +- README.md | 47 +- THIRD_PARTY_LICENSES.md | 308 +- build.gradle | 234 +- deploy/README.md | 60 + .env.example => deploy/docker/.env.example | 0 .../docker/.env.prod.example | 10 +- Dockerfile => deploy/docker/Dockerfile | 0 deploy/docker/compose.prod.yml | 63 + .../docker/compose.yml | 11 +- .../observability/grafana/oswl-dashboard.json | 788 + docker-compose.prod.yml | 40 - docs/Embedded-AI.md | 174 - docs/Getting-Started.md | 153 - docs/Production-Deployment-Checklist.md | 215 - docs/README.md | 31 + docs/_Sidebar.md | 30 - docs/{ => en}/API-Reference.md | 11 +- docs/{ => en}/Administration.md | 74 +- docs/en/Analysis-Coverage.md | 15 + docs/{ => en}/Authorization-Layers.md | 0 docs/en/Backup-And-Restore.md | 69 + docs/en/Browser-Security-Alerts.md | 13 + docs/{ => en}/CLI-Integration.md | 22 +- docs/en/Container-Image-Inspection.md | 18 + docs/en/Custom-Scan-Rules.md | 19 + docs/{ => en}/Database-Schema.md | 10 +- docs/en/Embedded-AI.md | 103 + docs/en/Getting-Started.md | 122 + docs/{ => en}/Glossary.md | 0 docs/{ => en}/Home.md | 8 +- docs/en/Inline-Team-Access.md | 7 + docs/{ => en}/License-Analysis.md | 0 docs/en/Offline-CocoaPods-Specs.md | 17 + docs/en/Organization-Risk-Briefing.md | 9 + docs/en/Production-Deployment-Checklist.md | 235 + docs/{ => en}/Project-Access-Control.md | 0 docs/{ => en}/Quick-Import.md | 0 docs/{ => en}/Risk-Trend.md | 0 docs/{ => en}/Scan-Api-Security.md | 2 +- docs/{ => en}/Scan-History.md | 0 docs/{ => en}/Security-Center.md | 2 +- docs/{ => en}/User-Guide.md | 4 +- docs/{ => en}/Version-Diff.md | 0 docs/{ => en}/Whats-New-v1.0.4.md | 2 +- docs/en/Whats-New-v1.0.5.1.md | 24 + docs/en/_Sidebar.md | 35 + docs/ja/API-Reference.md | 11 +- docs/ja/Administration.md | 68 +- docs/ja/Backup-And-Restore.md | 69 + docs/ja/CLI-Integration.md | 22 +- docs/ja/Database-Schema.md | 10 +- docs/ja/Embedded-AI.md | 201 +- docs/ja/Getting-Started.md | 73 +- docs/ja/Home.md | 4 +- docs/ja/Production-Deployment-Checklist.md | 114 +- docs/ja/Scan-Api-Security.md | 2 +- docs/ja/Security-Center.md | 2 +- docs/ja/User-Guide.md | 4 +- docs/ja/Whats-New-v1.0.4.md | 8 +- docs/ja/Whats-New-v1.0.5.1.md | 24 + docs/ko/API-Reference.md | 15 +- docs/ko/Administration.md | 68 +- docs/ko/Backup-And-Restore.md | 69 + docs/ko/CLI-Integration.md | 22 +- docs/ko/Database-Schema.md | 14 +- docs/ko/Embedded-AI.md | 207 +- docs/ko/Getting-Started.md | 81 +- docs/ko/Glossary.md | 2 +- docs/ko/Home.md | 11 +- docs/ko/Production-Deployment-Checklist.md | 121 +- docs/ko/Quick-Import.md | 2 +- docs/ko/Scan-Api-Security.md | 2 +- docs/ko/Security-Center.md | 2 +- docs/ko/User-Guide.md | 4 +- docs/ko/Whats-New-v1.0.4.md | 2 +- docs/ko/Whats-New-v1.0.5.1.md | 24 + landing/i18n/en.json | 87 +- landing/i18n/ja.json | 91 +- landing/i18n/ko.json | 87 +- landing/index.html | 273 +- landing/landing-i18n.js | 67 +- release-please-config.json | 13 - scripts/README.md | 27 + scripts/{ => dev}/check-java.ps1 | 0 scripts/{ => dev}/check-java.sh | 0 scripts/dev/measure-agent-context.py | 58 + scripts/{ => dev}/oswl-bootRun.command | 6 +- scripts/oci/scan-image.py | 346 + scripts/oci/test_scan_image.py | 117 + scripts/ops/verify-restore.sh | 121 + scripts/oswl-vdb/oswl-vdb.sh | 2 +- scripts/vdb/build-cocoapods-specs.py | 52 + scripts/verification/cluster_assertions.py | 75 + scripts/verification/performance.gradle | 11 + .../verification/test_cluster_assertions.py | 36 + scripts/verification/test_push_worker.cjs | 14 + scripts/verification/verify-h2-cluster.py | 98 + scripts/verification/verify-multi-instance.sh | 194 + .../oswl/config/TmpObjectMapperConfig.java | 20 - .../oswl/controller/TestDataController.java | 12 +- .../demo/SupplyChainHeuristicsScratch.java | 6 +- .../local/CppManifestParserSanityCheck.java | 73 + .../local/CvssV4CalculatorSanityCheck.java | 65 + .../oswl/local/LockParserSanityCheck.java | 167 +- .../oswl/local/LogoResizeSanityCheck.java | 88 + .../oswl/auth/cli/AuditLogIntegrityCli.java | 43 + .../AdminAuditLogIntegrityController.java | 28 + .../auth/controller/AuthViewController.java | 2 +- .../auth/controller/OnboardingController.java | 40 + .../auth/controller/OtpVerifyController.java | 2 +- .../auth/controller/UserThemeController.java | 47 + .../AdminAuditLogIntegrityControllerSpec.java | 33 + .../spec/UserThemeControllerSpec.java | 45 + .../salkcoding/oswl/auth/dto/AuditLogDto.java | 4 + .../auth/dto/AuditLogIntegrityReport.java | 38 + .../oswl/auth/dto/CreateUserRequest.java | 2 + .../oswl/auth/dto/UserThemeRequest.java | 12 + .../salkcoding/oswl/auth/entity/AuditLog.java | 8 + .../oswl/auth/entity/OnboardingProgress.java | 39 + .../com/salkcoding/oswl/auth/entity/User.java | 6 + .../oswl/auth/enums/Permission.java | 11 +- .../oswl/auth/enums/UserThemeMode.java | 7 + .../auth/repository/AuditLogRepository.java | 15 + .../OnboardingProgressRepository.java | 7 + .../repository/RoleTemplateRepository.java | 3 + .../UserVcsConnectionRepository.java | 2 + .../oswl/auth/security/JdbcSessionConfig.java | 39 + .../security/OidcLoginSuccessHandler.java | 2 +- .../OswlSecurityHeadersProperties.java | 2 +- .../oswl/auth/security/OswlUserPrincipal.java | 22 + .../security/Saml2LoginSuccessHandler.java | 147 + .../oswl/auth/security/SecurityConfig.java | 50 +- .../TwoFaAuthenticationSuccessHandler.java | 4 +- .../auth/security/UserDetailsServiceImpl.java | 3 +- .../auth/service/AccountDeletionService.java | 5 +- .../service/AuditLogIntegrityService.java | 142 + .../oswl/auth/service/AuditLogService.java | 76 +- .../service/AuditLogSiemExportService.java | 2 + .../auth/service/LoginCompletionService.java | 19 + .../oswl/auth/service/OnboardingService.java | 64 + .../auth/service/RoleTemplateService.java | 56 +- .../auth/service/SecuritySettingService.java | 46 +- .../auth/service/UserManagementService.java | 2 + .../oswl/auth/service/UserThemeService.java | 39 + .../oswl/auth/web/SettingsTabAccess.java | 36 +- .../oswl/client/CocoaPodsSpecsClient.java | 173 + .../oswl/client/CpeMatchService.java | 17 + .../salkcoding/oswl/client/CpeNameMapper.java | 79 + .../salkcoding/oswl/client/DepsDevClient.java | 64 +- .../salkcoding/oswl/client/EpssClient.java | 20 +- .../oswl/client/GitHubAdvisoryClient.java | 331 + .../oswl/client/KevCatalogService.java | 20 +- .../com/salkcoding/oswl/client/NvdClient.java | 293 + .../com/salkcoding/oswl/client/OsvClient.java | 236 +- .../oswl/config/AirgappedClientConfig.java | 75 +- .../oswl/config/OswlStartupWarnings.java | 31 +- .../controller/AdminProjectController.java | 28 + .../controller/ConfigTransferController.java | 29 + .../controller/DiagnosticsController.java | 28 + .../oswl/controller/LocalDevController.java | 12 +- .../oswl/controller/MobileController.java | 38 + .../controller/ScanArchivingController.java | 36 + .../oswl/controller/SearchController.java | 23 + .../{ => ai}/AiSettingController.java | 17 +- .../{ => apikey}/AdminCliKeyController.java | 29 +- .../{ => apikey}/ApiKeyController.java | 14 +- .../AdminAuditExportController.java | 2 +- .../{ => ingest}/QuickImportController.java | 4 +- .../{ => ingest}/ScanController.java | 20 +- .../controller/{ => jira}/JiraController.java | 8 +- .../{ => license}/LicenseController.java | 8 +- .../LicensePolicyController.java | 4 +- .../{ => license}/OssNoticesController.java | 8 +- .../notification/WebPushController.java | 17 + .../notification/WebPushPageController.java | 9 + .../WebhookSettingController.java | 124 + .../org/ExecutiveSummaryController.java | 16 + .../{ => org}/OrgDashboardController.java | 4 +- .../oswl/controller/org/TeamController.java | 128 + .../controller/policy/PolicyController.java | 109 + .../ProjectContextController.java | 16 +- .../{ => project}/ProjectController.java | 8 +- .../reporting/ReportBrandingController.java | 37 + .../{ => reporting}/RiskTrendController.java | 6 +- .../{ => reporting}/SbomController.java | 14 +- .../{ => scan}/ComponentDetailController.java | 12 +- .../scan/CustomScanRuleController.java | 20 + .../{ => scan}/ScanHistoryController.java | 12 +- .../{ => scan}/VersionDiffController.java | 6 +- .../controller/scim/ScimGroupController.java | 106 + .../controller/scim/ScimUserController.java | 108 + .../SnapshotAdminController.java | 20 +- .../spec/AdminCliKeyControllerSpec.java | 3 + .../spec/AdminProjectControllerSpec.java | 29 + .../spec/ConfigTransferControllerSpec.java | 39 + .../spec/CustomScanRuleControllerSpec.java | 12 + .../spec/DiagnosticsControllerSpec.java | 34 + .../spec/ExecutiveSummaryControllerSpec.java | 7 + .../controller/spec/PolicyControllerSpec.java | 80 + .../spec/ProjectContextControllerSpec.java | 8 + .../spec/ProjectControllerSpec.java | 2 +- .../spec/QuickImportControllerSpec.java | 3 +- .../spec/ReportBrandingControllerSpec.java | 37 + .../spec/SavedViewControllerSpec.java | 60 + .../controller/spec/SbomControllerSpec.java | 2 +- .../spec/ScanArchivingControllerSpec.java | 62 + .../spec/ScimGroupControllerSpec.java | 79 + .../spec/ScimUserControllerSpec.java | 83 + .../controller/spec/SearchControllerSpec.java | 32 + .../spec/SecurityCenterControllerSpec.java | 51 + .../spec/SnapshotAdminControllerSpec.java | 16 +- .../controller/spec/TeamControllerSpec.java | 157 + .../spec/WebPushControllerSpec.java | 8 + .../spec/WebhookSettingControllerSpec.java | 71 + .../{ => vcs}/GitHubApiController.java | 8 +- .../{ => vcs}/VcsBranchController.java | 16 +- .../vulnerability/SavedViewController.java | 56 + .../SecurityCenterController.java | 89 +- .../domain/entity/{ => ai}/AiDailyUsage.java | 17 +- .../domain/entity/{ => ai}/AiPreferences.java | 2 +- .../domain/entity/{ => ai}/AiSetting.java | 2 +- .../domain/entity/{ => ai}/AiUsageEvent.java | 2 +- .../domain/entity/{ => apikey}/ApiKey.java | 15 +- .../entity/config/CacheInvalidation.java | 38 + .../domain/entity/{ => jira}/JiraSetting.java | 4 +- .../{ => license}/LicensePolicyEntry.java | 2 +- .../entity/notification/WebPushDelivery.java | 18 + .../notification/WebPushSubscription.java | 13 + .../entity/notification/WebhookDelivery.java | 66 + .../entity/notification/WebhookSetting.java | 81 + .../oswl/domain/entity/org/Organization.java | 39 + .../oswl/domain/entity/org/Team.java | 62 + .../oswl/domain/entity/org/TeamMember.java | 51 + .../oswl/domain/entity/policy/Policy.java | 118 + .../domain/entity/policy/PolicyException.java | 115 + .../domain/entity/{ => project}/Project.java | 52 +- .../entity/{ => project}/ProjectMember.java | 2 +- .../entity/{ => project}/ProjectVersion.java | 2 +- .../reporting/ReportBrandingSettings.java | 60 + .../entity/scan/CustomRuleConfiguration.java | 19 + .../entity/{ => scan}/DependencyPath.java | 2 +- .../domain/entity/scan/ImportCoordinator.java | 16 + .../oswl/domain/entity/scan/ImportJob.java | 27 + .../entity/{ => scan}/ScanComponent.java | 39 +- .../oswl/domain/entity/scan/ScanFinding.java | 70 + .../domain/entity/{ => scan}/ScanResult.java | 84 +- .../entity/{ => snapshot}/SnapshotEntry.java | 2 +- .../entity/{ => snapshot}/SnapshotMeta.java | 8 +- .../entity/system/DatabaseMutationLock.java | 13 + .../entity/{ => vulnerability}/Cve.java | 94 +- .../entity/{ => vulnerability}/CveAlert.java | 3 +- .../entity/{ => vulnerability}/Library.java | 81 +- .../entity/vulnerability/SavedView.java | 55 + .../oswl/domain/enums/ApiKeyScope.java | 11 + .../oswl/domain/enums/CveSource.java | 12 + .../oswl/domain/enums/MatchConfidence.java | 12 + .../domain/enums/PolicyExceptionStatus.java | 11 + .../enums/PolicyExceptionTargetType.java | 11 + .../oswl/domain/enums/PolicyScopeType.java | 11 + .../oswl/domain/enums/Reachability.java | 16 + .../oswl/domain/enums/ScanFindingType.java | 5 + .../oswl/domain/enums/ScimGroupMapping.java | 9 + .../oswl/domain/enums/TeamMemberRole.java | 15 + .../domain/enums/WebhookDeliveryStatus.java | 10 + .../oswl/domain/enums/WebhookEventType.java | 12 + .../oswl/domain/enums/WebhookProvider.java | 10 + .../oswl/dto/AdminProjectRefDto.java | 11 + .../salkcoding/oswl/dto/ComponentRowDto.java | 13 + .../java/com/salkcoding/oswl/dto/CveDto.java | 5 + .../oswl/dto/OrgProjectRiskDto.java | 6 + .../salkcoding/oswl/dto/OrgTeamRiskDto.java | 51 + .../oswl/dto/ProjectSummaryDto.java | 11 + .../oswl/dto/QuickImportJobStatus.java | 10 +- .../oswl/dto/ReportBrandingResponse.java | 11 + .../oswl/dto/ReportBrandingUpdateRequest.java | 20 + .../oswl/dto/SavedViewCreateRequest.java | 21 + .../com/salkcoding/oswl/dto/SavedViewDto.java | 19 + .../oswl/dto/ScanHistoryRowDto.java | 8 + .../dto/SecurityCenterRowFilterParams.java | 32 + .../dto/SnapshotImportFromPathRequest.java | 2 +- .../salkcoding/oswl/dto/TeamMemberDto.java | 25 + .../salkcoding/oswl/dto/TeamSummaryDto.java | 37 + .../oswl/dto/api/AiCacheSumsDto.java | 7 + .../oswl/dto/api/AiSettingUpdateRequest.java | 2 +- .../oswl/dto/api/AiUsageStatsResponse.java | 5 + .../oswl/dto/api/EmbeddedAiConfigRequest.java | 4 +- .../oswl/dto/api/WebPushAttempt.java | 2 + .../oswl/dto/api/WebPushStatus.java | 6 + .../dto/api/WebPushSubscriptionRequest.java | 4 + .../oswl/dto/api/WebhookDeliveryDto.java | 28 + .../oswl/dto/api/WebhookSettingResponse.java | 22 + .../dto/api/WebhookSettingUpdateRequest.java | 22 + .../oswl/dto/api/WebhookTestRequest.java | 14 + .../oswl/dto/config/ConfigBundle.java | 52 + .../oswl/dto/config/ConfigImportResult.java | 19 + .../diagnostics/DiagnosticCheckResult.java | 14 + .../salkcoding/oswl/dto/gate/GateRequest.java | 6 + .../oswl/dto/gate/GateResultDto.java | 12 +- .../oswl/dto/mobile/MobileAlertDto.java | 18 + .../dto/mobile/MobileProjectAlertsDto.java | 10 + .../oswl/dto/mobile/MobileWaiverDto.java | 17 + .../oswl/dto/policy/EffectivePolicyDto.java | 26 + .../salkcoding/oswl/dto/policy/PolicyDto.java | 30 + .../oswl/dto/policy/PolicyExceptionDto.java | 30 + .../dto/policy/PolicyExceptionRequest.java | 17 + .../oswl/dto/policy/PolicyGitOpsRequest.java | 11 + .../oswl/dto/policy/PolicyRequest.java | 22 + .../dto/policy/PolicyScopeOptionsDto.java | 12 + .../oswl/dto/scan/CustomRuleSet.java | 3 + .../oswl/dto/scan/CustomScanRule.java | 4 + .../oswl/dto/scan/ScanArchiveExportDto.java | 37 + .../oswl/dto/scan/ScanArchiveResult.java | 5 + .../oswl/dto/scan/ScanFindingCandidate.java | 19 + .../oswl/dto/scan/ScanFindingRowDto.java | 17 + .../salkcoding/oswl/dto/scan/ScanPayload.java | 14 + .../oswl/dto/scan/SourceAnalysisDetails.java | 19 + .../oswl/dto/scan/VersionDiffComponent.java | 18 + .../salkcoding/oswl/dto/scim/ScimEmail.java | 14 + .../salkcoding/oswl/dto/scim/ScimError.java | 18 + .../salkcoding/oswl/dto/scim/ScimGroup.java | 24 + .../oswl/dto/scim/ScimListResponse.java | 23 + .../salkcoding/oswl/dto/scim/ScimMember.java | 14 + .../salkcoding/oswl/dto/scim/ScimMeta.java | 18 + .../salkcoding/oswl/dto/scim/ScimName.java | 14 + .../oswl/dto/scim/ScimPatchOperation.java | 12 + .../oswl/dto/scim/ScimPatchRequest.java | 15 + .../salkcoding/oswl/dto/scim/ScimUser.java | 29 + .../oswl/dto/search/ComponentSearchHit.java | 11 + .../oswl/dto/search/CveSearchHit.java | 10 + .../oswl/dto/search/GlobalSearchResponse.java | 16 + .../oswl/dto/search/ProjectSearchHit.java | 8 + .../oswl/dto/search/SearchGroup.java | 21 + .../oswl/dto/snapshot/CocoaPodsSpec.java | 32 + .../exception/GlobalExceptionHandler.java | 12 +- .../health/AiProviderHealthIndicator.java | 40 + .../oswl/health/DbHealthIndicator.java | 41 + .../oswl/health/DiskSpaceHealthIndicator.java | 55 + .../EmbeddedSidecarHealthIndicator.java | 59 + .../SnapshotFreshnessHealthIndicator.java | 75 + .../oswl/logging/OswlFileEncoder.java | 81 + .../oswl/repository/CveRepository.java | 12 - .../DatabaseMutationLockRepository.java | 38 + .../repository/DependencyPathRepository.java | 12 - .../oswl/repository/LibraryRepository.java | 50 - .../oswl/repository/ProjectRepository.java | 29 - .../repository/ScanComponentRepository.java | 106 - .../oswl/repository/ScanResultRepository.java | 56 - .../{ => ai}/AiDailyUsageRepository.java | 19 +- .../{ => ai}/AiPreferencesRepository.java | 4 +- .../{ => ai}/AiSettingRepository.java | 4 +- .../{ => ai}/AiUsageEventRepository.java | 4 +- .../{ => apikey}/ApiKeyRepository.java | 4 +- .../config/CacheInvalidationRepository.java | 22 + .../{ => jira}/JiraSettingRepository.java | 4 +- .../LicensePolicyRepository.java | 4 +- .../WebPushDeliveryRepository.java | 12 + .../WebPushSubscriptionRepository.java | 10 + .../WebhookDeliveryRepository.java | 18 + .../WebhookSettingRepository.java | 12 + .../org/OrganizationRepository.java | 12 + .../repository/org/TeamMemberRepository.java | 36 + .../oswl/repository/org/TeamRepository.java | 19 + .../policy/PolicyExceptionRepository.java | 34 + .../repository/policy/PolicyRepository.java | 20 + .../ProjectMemberRepository.java | 4 +- .../repository/project/ProjectRepository.java | 83 + .../ProjectVersionRepository.java | 6 +- .../ReportBrandingSettingsRepository.java | 12 + .../CustomRuleConfigurationRepository.java | 4 + .../scan/DependencyPathRepository.java | 29 + .../scan/ImportCoordinatorRepository.java | 10 + .../repository/scan/ImportJobRepository.java | 36 + .../scan/ScanComponentRepository.java | 276 + .../scan/ScanFindingRepository.java | 41 + .../repository/scan/ScanResultRepository.java | 100 + .../SnapshotEntryRepository.java | 6 +- .../SnapshotMetaRepository.java | 4 +- .../CveAlertRepository.java | 16 +- .../vulnerability/CveRepository.java | 40 + .../LibraryCatalogRepository.java | 61 + .../vulnerability/LibraryRepository.java | 96 + .../vulnerability/SavedViewRepository.java | 11 + .../scheduler/CacheInvalidationPoller.java | 95 + .../ContinuousMonitoringScheduler.java | 11 +- .../oswl/scheduler/DeferExpiryScheduler.java | 56 +- .../ImportJobMaintenanceScheduler.java | 40 + .../PolicyExceptionExpiryScheduler.java | 94 + .../oswl/scheduler/SchedulerLockConfig.java | 29 + .../oswl/scheduler/TrashCleanupScheduler.java | 11 +- .../oswl/security/OutboundUrlValidator.java | 44 + .../oswl/service/GatePolicyService.java | 285 - .../oswl/service/MobileDashboardService.java | 88 + .../oswl/service/SearchService.java | 74 + .../oswl/service/SecurityCenterService.java | 338 - .../oswl/service/ai/AiAnalysisService.java | 35 +- .../oswl/service/ai/AiCallTrace.java | 4 +- .../service/ai/AiConnectionDiagnostics.java | 2 +- .../ai/AiEnrichmentContextBuilder.java | 6 +- .../oswl/service/ai/AiPreferencesService.java | 14 +- .../service/ai/AiPromptTemplateService.java | 16 +- .../service/ai/AiUsageLimiterService.java | 4 +- .../service/ai/AiUsageRecorderService.java | 44 +- .../oswl/service/ai/AiUsageStatsService.java | 29 +- .../oswl/service/ai/AnthropicClient.java | 14 +- .../ai/EmbeddedAiBootstrapService.java | 2 +- .../ai/EmbeddedAiProviderRegistrar.java | 4 +- .../oswl/service/ai/EmbeddedAiService.java | 118 +- .../oswl/service/ai/OpenAiClient.java | 12 +- .../service/{ => apikey}/ApiKeyService.java | 90 +- .../{ => apikey}/ApiKeyTokenSupport.java | 2 +- .../service/{ => apikey}/IssuedApiKey.java | 4 +- .../ProjectCliKeyPolicyService.java | 6 +- .../config/CacheInvalidationService.java | 74 + .../service/config/ConfigTransferService.java | 388 + .../container/BaseImageEcosystemMapper.java | 100 + .../service/container/DockerfileParser.java | 158 + .../oswl/service/cvss/CvssV3Calculator.java | 167 + .../oswl/service/cvss/CvssV3Vector.java | 78 + .../oswl/service/cvss/CvssV4Calculator.java | 409 + .../oswl/service/cvss/CvssV4Vector.java | 108 + .../oswl/service/cvss/CvssVectorVersion.java | 26 + .../cvss/EnvironmentalRequirementMapper.java | 61 + .../diagnostics/DiagnosticsService.java | 196 + .../oswl/service/gate/GatePolicyService.java | 452 + .../service/{ => gate}/PrGateService.java | 24 +- .../oswl/service/git/GitCloneExecutor.java | 7 +- .../oswl/service/iacscan/IacRule.java | 9 + .../oswl/service/iacscan/IacScanner.java | 161 + .../oswl/service/iacscan/IacTargetKind.java | 5 + .../{ => ingest}/CloneCleanupService.java | 2 +- .../ingest/CondaPypiMappingService.java | 72 + .../DependencyManifestParserService.java | 1276 +- .../DependencyManifestPatcher.java | 8 +- .../EnrichmentProgressContext.java | 10 +- .../EnrichmentProgressHolder.java | 32 +- .../oswl/service/ingest/ImportJobStore.java | 203 + .../{ => ingest}/ManifestArchiveService.java | 2 +- .../{ => ingest}/ManifestPatchInfo.java | 2 +- .../{ => ingest}/MavenBomVersionResolver.java | 66 +- .../{ => ingest}/QuickImportService.java | 226 +- .../{ => ingest}/SbomImportService.java | 16 +- .../ScanApiCredentialThrottleService.java | 2 +- .../{ => ingest}/ScanIngestService.java | 129 +- .../ingest/parser/CargoManifestParser.java | 105 + .../ingest/parser/CocoaPodsLockParser.java | 56 + .../ingest/parser/ComposerLockParser.java | 79 + .../ingest/parser/ConanLockParser.java | 80 + .../ingest/parser/CondaLockParser.java | 160 + .../ingest/parser/GoManifestParser.java | 67 + .../ingest/parser/ManifestDiscovery.java | 96 + .../service/ingest/parser/ManifestIndex.java | 5 + .../service/ingest/parser/MavenPomParser.java | 111 + .../ingest/parser/NativeManifestParser.java | 190 + .../ingest/parser/NpmManifestParser.java | 162 + .../ingest/parser/NugetManifestParser.java | 220 + .../ingest/parser/PythonManifestParser.java | 175 + .../service/ingest/parser/RubyLockParser.java | 54 + .../ingest/parser/UploadedLockFileParser.java | 75 + .../ingest/parser/VersionCatalogParser.java | 70 + .../oswl/service/{ => jira}/JiraService.java | 17 +- .../{ => license}/LicensePolicyService.java | 76 +- .../service/{ => license}/LicenseService.java | 14 +- .../manifest/ManifestCollectRules.java | 7 +- .../oswl/service/metrics/OswlMetrics.java | 118 + .../service/notification/WebPushService.java | 161 + .../notification/WebPushTransport.java | 60 + .../service/notification/WebPushWorker.java | 24 + .../service/notification/WebhookClient.java | 142 + .../notification/WebhookMessageBuilder.java | 135 + .../WebhookNotificationService.java | 189 + .../notification/WebhookSettingService.java | 135 + .../service/org/ExecutiveSummaryService.java | 30 + .../{ => org}/OrgDashboardService.java | 157 +- .../oswl/service/org/TeamBootstrapRunner.java | 27 + .../oswl/service/org/TeamService.java | 339 + .../oswl/service/policy/PolicyService.java | 661 + .../{ => project}/ProjectAccessService.java | 45 +- .../ProjectMemberBootstrapRunner.java | 6 +- .../service/{ => project}/ProjectService.java | 139 +- .../reachability/CallGraphAnalyzer.java | 462 + .../reachability/LibraryPackageMapper.java | 176 + .../reachability/ReachabilityService.java | 73 + .../reachability/SourceImportSyntax.java | 102 + .../SourceReachabilityService.java | 41 + .../reachability/SourceReachabilityStore.java | 35 + .../reachability/SourceReferenceAnalyzer.java | 200 + .../ComplianceReportService.java | 20 +- .../reporting/ReportBrandingService.java | 52 + .../{ => reporting}/RiskTrendService.java | 63 +- .../{ => reporting}/SarifExportService.java | 26 +- .../{ => reporting}/SbomExportService.java | 24 +- .../{ => scan}/ComponentDetailService.java | 107 +- .../service/scan/ScanArchivingService.java | 158 + .../service/{ => scan}/ScanFailureMarker.java | 14 +- .../{ => scan}/ScanHistoryService.java | 25 +- .../{ => scan}/ScanStatusEmitterRegistry.java | 2 +- .../oswl/service/scan/ScanSummaryReader.java | 48 + .../{ => scan}/ScanTimingRecorder.java | 2 +- .../{ => scan}/ScanVersionDiffAnalyzer.java | 50 +- .../{ => scan}/VersionDiffService.java | 10 +- .../service/scim/ScimProvisioningService.java | 511 + .../service/secretscan/CustomRuleScanner.java | 76 + .../secretscan/CustomScanRuleService.java | 83 + .../secretscan/SecretIacScanService.java | 90 + .../oswl/service/secretscan/SecretRule.java | 16 + .../service/secretscan/SecretScanner.java | 190 + .../secretscan/SourceFindingStore.java | 27 + .../AirgappedSnapshotService.java | 536 +- .../snapshot/SnapshotBundleStager.java | 106 + .../service/{ => vcs}/BitbucketService.java | 4 +- .../oswl/service/{ => vcs}/GitHubService.java | 4 +- .../oswl/service/{ => vcs}/GitLabService.java | 4 +- .../{ => vcs}/VcsAuthTokenService.java | 3 +- .../ContinuousMonitoringService.java | 64 +- .../vulnerability/SavedViewService.java | 67 + .../vulnerability/SecurityCenterService.java | 476 + .../SupplyChainHeuristicsService.java | 6 +- .../VulnerabilityEnrichmentService.java | 566 +- .../sources/AdvisoryFetchResult.java | 9 + .../sources/GitHubAdvisorySource.java | 29 + .../sources/NvdAdvisorySource.java | 40 + .../oswl/util/LogoImageNormalizer.java | 99 + .../salkcoding/oswl/util/VersionOrder.java | 2 +- .../oswl/vdb/ApkVersionComparator.java | 116 + .../salkcoding/oswl/vdb/DepsDevSource.java | 33 +- .../com/salkcoding/oswl/vdb/EpssSource.java | 2 +- .../oswl/vdb/GitHubAdvisorySource.java | 81 + .../com/salkcoding/oswl/vdb/HttpCache.java | 57 +- .../com/salkcoding/oswl/vdb/NvdSource.java | 96 + .../salkcoding/oswl/vdb/OsvBulkSource.java | 122 +- .../oswl/vdb/PreviousBundleReader.java | 4 +- .../oswl/vdb/SimpleVersionComparator.java | 9 +- .../salkcoding/oswl/vdb/VdbBuildOptions.java | 18 +- .../salkcoding/oswl/vdb/VdbBuilderCli.java | 27 +- .../salkcoding/oswl/vdb/VdbBundleWriter.java | 30 +- .../salkcoding/oswl/vdb/WantedComponent.java | 2 +- .../oswl/web/config/OpenApiConfig.java | 8 +- .../oswl/web/config/WebMvcConfig.java | 12 + .../filter/RequestContextLoggingFilter.java | 49 + .../interceptor/ApiKeyAuthInterceptor.java | 6 +- .../web/interceptor/ScimAuthInterceptor.java | 69 + .../META-INF/THIRD_PARTY_LICENSES.txt | 142 + .../licenses/conda-forge-bot-data-LICENSE.txt | 125 + src/main/resources/ai/prompts.properties | 10 +- src/main/resources/ai/prompts_ja.properties | 14 +- src/main/resources/ai/prompts_ko.properties | 8 +- src/main/resources/application-local.yaml | 19 +- src/main/resources/application-prod.yaml | 44 +- src/main/resources/application.yaml | 144 +- .../conda/grayskull-pypi-mapping.json | 71040 ++++++++++++++++ src/main/resources/cvss/cvss-v4-lookup.json | 272 + src/main/resources/db/ai_cache_hit_stats.sql | 4 + .../db/audit_log_integrity_hash_chain.sql | 8 + src/main/resources/db/cache_invalidation.sql | 8 + src/main/resources/db/ccpp_coverage.sql | 4 + src/main/resources/db/cve_sources.sql | 13 + .../resources/db/database_mutation_locks.sql | 3 + src/main/resources/db/durable_import_jobs.sql | 18 + src/main/resources/db/hot_path_indexes.sql | 25 + .../V10__spring_session_and_shedlock.sql | 43 + .../db/migration/V11__org_team_hierarchy.sql | 61 + .../db/migration/V12__hot_path_indexes.sql | 44 + .../resources/db/migration/V13__saml_scim.sql | 14 + .../db/migration/V14__webhook_settings.sql | 36 + .../db/migration/V15__cve_sources.sql | 17 + .../db/migration/V16__ccpp_coverage.sql | 7 + .../db/migration/V17__policy_as_code.sql | 62 + .../db/migration/V18__reachability.sql | 7 + .../V19__audit_log_integrity_hash_chain.sql | 10 + .../db/migration/V20__user_theme.sql | 2 + .../V21__secret_iac_scan_findings.sql | 17 + .../db/migration/V22__scan_archiving.sql | 16 + .../db/migration/V23__onboarding_progress.sql | 6 + .../db/migration/V24__saved_views.sql | 19 + .../V25__report_branding_settings.sql | 10 + .../db/migration/V26__ai_cache_hit_stats.sql | 3 + .../db/migration/V27__cache_invalidation.sql | 8 + .../V28__policy_gate_reachability_secrets.sql | 7 + .../migration/V29__reachability_evidence.sql | 6 + .../db/migration/V2__v104_features.sql | 2 +- .../V30__source_analysis_metadata.sql | 1 + .../db/migration/V31__durable_import_jobs.sql | 18 + .../V32__vulnerability_lookup_outcomes.sql | 2 + .../db/migration/V33__custom_scan_rules.sql | 5 + .../migration/V34__web_push_notifications.sql | 22 + .../V35__database_mutation_locks.sql | 3 + .../db/migration/V4__scan_ai_status.sql | 2 +- .../V5__library_version_meta_fetched_at.sql | 2 +- .../db/migration/V6__ai_context_hash.sql | 2 +- .../V7__snapshot_v2_and_license_raw.sql | 2 +- src/main/resources/db/onboarding_progress.sql | 6 + src/main/resources/db/org_team_hierarchy.sql | 52 + src/main/resources/db/policy_as_code.sql | 59 + .../db/policy_gate_reachability_secrets.sql | 5 + src/main/resources/db/project_members.sql | 2 +- src/main/resources/db/reachability.sql | 7 + .../resources/db/report_branding_settings.sql | 11 + src/main/resources/db/saml_scim.sql | 8 + src/main/resources/db/saved_views.sql | 15 + src/main/resources/db/scan_archiving.sql | 15 + .../resources/db/secret_iac_scan_findings.sql | 16 + .../resources/db/source_analysis_metadata.sql | 1 + .../db/spring_session_and_shedlock.sql | 36 + .../resources/db/supply_chain_heuristics.sql | 2 +- src/main/resources/db/user_theme.sql | 3 + src/main/resources/db/webhook_settings.sql | 35 + src/main/resources/logback-spring.xml | 40 + src/main/resources/messages.properties | 597 +- src/main/resources/messages_ja.properties | 615 +- src/main/resources/messages_ko.properties | 673 +- .../resources/secretscan/secret-rules.json | 68 + src/main/resources/static/css/globals.css | 70 +- src/main/resources/static/js/a11y.js | 122 + .../resources/static/js/onboarding-team.js | 37 + .../static/js/org-dashboard/org-dashboard.js | 153 +- src/main/resources/static/js/oswl-http.js | 35 + src/main/resources/static/js/oswl-theme.js | 26 + .../resources/static/js/projects/projects.js | 35 +- .../static/js/projects/quick-import.js | 262 +- .../static/js/risk-trend/risk-trend.js | 177 +- .../static/js/security-center/page.js | 450 + .../static/js/security-center/slideout.js | 36 + .../resources/static/js/settings/admin.js | 480 + src/main/resources/static/js/settings/ai.js | 700 + .../resources/static/js/settings/cache.js | 92 + src/main/resources/static/js/settings/cli.js | 86 + .../static/js/settings/config-transfer.js | 75 + .../static/js/settings/diagnostics.js | 128 + .../static/js/settings/license-policy.js | 178 + .../resources/static/js/settings/policy.js | 366 + .../resources/static/js/settings/reports.js | 85 + .../resources/static/js/settings/security.js | 147 + .../static/js/settings/settings-dirty.js | 84 + .../static/js/settings/settings-subtabs.js | 25 + src/main/resources/static/js/settings/vcs.js | 239 + .../resources/static/js/settings/webhooks.js | 145 + .../static/js/web-push-preferences.js | 67 + src/main/resources/static/oswl-push-sw.js | 16 + .../static/scripts/manifest-rules.json | 2 +- .../supply-chain/popular-packages.txt | 2 +- src/main/resources/templates/auth/login.html | 12 +- .../resources/templates/auth/onboarding.html | 262 + .../fragments/detail-content.html | 166 +- .../resources/templates/fragments/footer.html | 10 +- .../resources/templates/fragments/head.html | 18 +- .../templates/fragments/settings-subtabs.html | 30 + .../resources/templates/fragments/topbar.html | 311 +- .../license/components/context_bar.html | 10 +- .../resources/templates/mobile/index.html | 213 + .../templates/notification/preferences.html | 27 + .../templates/org-dashboard/index.html | 36 +- .../templates/org-dashboard/summary.html | 51 + .../templates/oss-notices/index.html | 29 +- .../templates/projects/cli-integration.html | 5 +- .../templates/projects/git-integration.html | 67 +- .../resources/templates/projects/index.html | 83 +- .../templates/projects/quick-import.html | 29 +- .../templates/reports/compliance-report.html | 33 +- .../risk-trend/components/risk-trend.html | 34 +- .../resources/templates/risk-trend/index.html | 10 +- .../templates/scan-history/index.html | 9 +- .../security-center/fragments/actions.html | 107 +- .../security-center/fragments/filters.html | 35 + .../security-center/fragments/findings.html | 50 + .../security-center/fragments/table.html | 417 +- .../templates/security-center/index.html | 443 +- .../templates/security-center/print.html | 4 +- .../resources/templates/settings/index.html | 67 +- .../templates/settings/tabs/admin.html | 649 +- .../resources/templates/settings/tabs/ai.html | 859 +- .../templates/settings/tabs/cache.html | 112 +- .../templates/settings/tabs/cli.html | 113 +- .../settings/tabs/config-transfer.html | 99 + .../templates/settings/tabs/diagnostics.html | 171 + .../settings/tabs/license-policy.html | 193 +- .../templates/settings/tabs/policy.html | 474 + .../templates/settings/tabs/reports.html | 132 + .../templates/settings/tabs/security.html | 182 +- .../templates/settings/tabs/vcs.html | 300 +- .../templates/settings/tabs/webhooks.html | 268 + .../templates/version-diff/index.html | 54 +- .../controller/OtpVerifyControllerTest.java | 3 + ...TwoFaAuthenticationSuccessHandlerTest.java | 5 + .../auth/service/RoleTemplateServiceTest.java | 2 + .../service/SecuritySettingServiceTest.java | 2 + .../oswl/auth/web/SettingsTabAccessTest.java | 4 +- .../oswl/client/NvdLookupOutcomeTest.java | 39 + .../salkcoding/oswl/client/OsvClientTest.java | 41 + .../oswl/client/OsvLookupOutcomeTest.java | 71 + .../controller/AdminCliKeyControllerTest.java | 13 +- .../AdminProjectControllerTest.java | 52 + .../controller/AiSettingControllerTest.java | 9 +- .../oswl/controller/ApiKeyControllerTest.java | 13 +- .../ComponentDetailControllerTest.java | 11 +- .../controller/LicenseControllerTest.java | 7 +- .../controller/ProjectControllerTest.java | 7 +- .../controller/QuickImportControllerTest.java | 3 +- .../controller/RiskTrendControllerTest.java | 5 +- .../oswl/controller/ScanControllerTest.java | 19 +- .../controller/ScanHistoryControllerTest.java | 9 +- .../SecurityCenterControllerTest.java | 9 +- .../controller/TestDataControllerTest.java | 8 +- .../controller/VcsBranchControllerTest.java | 15 +- .../controller/VersionDiffControllerTest.java | 5 +- .../{ => vcs}/GitHubApiControllerTest.java | 11 +- .../oswl/domain/entity/LibraryTest.java | 2 + .../exception/GlobalExceptionHandlerTest.java | 18 + .../repository/LibraryRepositoryTest.java | 3 +- .../LicensePolicyRepositoryTest.java | 3 +- .../repository/ProjectRepositoryTest.java | 3 +- .../repository/ScanResultRepositoryTest.java | 18 +- .../CacheInvalidationPollerTest.java | 105 + .../ImportJobMaintenanceSchedulerTest.java | 41 + .../scheduler/TrashCleanupSchedulerTest.java | 4 +- ...ncyManifestParserEcosystemMappingTest.java | 58 + ...sFrameworkGradleParseVerificationTest.java | 5 +- .../ExpressNpmLockVerificationTest.java | 14 +- .../MauiNuGetParseVerificationTest.java | 14 +- .../service/RailsGemfileVerificationTest.java | 14 +- .../oswl/service/SearchServiceTest.java | 150 + .../service/ai/AiAnalysisServiceTest.java | 8 +- .../ai/AiEnrichmentContextBuilderTest.java | 4 +- .../service/ai/AiPreferencesServiceTest.java | 17 +- .../ai/AiPromptTemplateServiceTest.java | 4 +- .../AiUsageLimiterServiceIntegrationTest.java | 4 +- .../service/ai/AiUsageLimiterServiceTest.java | 6 +- .../service/ai/AiUsageStatsServiceTest.java | 110 + .../oswl/service/ai/AnthropicClientTest.java | 6 +- .../service/ai/EmbeddedAiDownloadTest.java | 93 + ...mbeddedAiFreshInstallVerificationTest.java | 56 + .../ai/EmbeddedAiProviderRegistrarTest.java | 4 +- .../oswl/service/ai/OpenAiClientTest.java | 6 +- .../{ => apikey}/ApiKeyServiceTest.java | 22 +- .../ProjectCliKeyPolicyServiceTest.java | 9 +- .../config/ConfigTransferServiceTest.java | 218 + .../oswl/service/gate/GateCoverageTest.java | 95 + .../DependencyManifestPatcherTest.java | 3 +- .../EnrichmentProgressHolderTest.java | 3 +- .../service/ingest/ImportJobStoreTest.java | 135 + .../ingest/LibraryCatalogConcurrencyTest.java | 46 + .../ingest/ManifestParserBoundaryTest.java | 59 + .../MavenBomVersionResolverTest.java | 38 +- .../QuickImportServiceParserTest.java | 13 +- .../{ => ingest}/QuickImportServiceTest.java | 13 +- .../ScanApiCredentialThrottleServiceTest.java | 3 +- .../{ => ingest}/ScanIngestServiceTest.java | 98 +- .../LicensePolicyServiceTest.java | 10 +- .../{ => license}/LicenseServiceTest.java | 15 +- .../manifest/ManifestCollectParityTest.java | 18 +- .../oswl/service/metrics/OswlMetricsTest.java | 104 + .../notification/WebPushMigrationTest.java | 28 + .../notification/WebPushServiceTest.java | 131 + .../notification/WebPushTransportTest.java | 53 + .../OrgDashboardServiceQueryCountTest.java | 156 + .../ProjectAccessServiceTest.java | 11 +- .../project/ProjectServiceQueryCountTest.java | 148 + .../{ => project}/ProjectServiceTest.java | 98 +- .../SourceReferenceAnalyzerTest.java | 47 + .../{ => reporting}/RiskTrendServiceTest.java | 26 +- .../ComponentDetailServiceTest.java | 17 +- .../scan/ScanArchivingServiceTest.java | 162 + .../{ => scan}/ScanHistoryServiceTest.java | 15 +- .../ScanStatusEmitterRegistryTest.java | 3 +- .../service/scan/ScanSummaryReaderTest.java | 78 + .../scan/ScanVersionDiffProjectionTest.java | 57 + .../{ => scan}/VersionDiffServiceTest.java | 12 +- .../secretscan/CustomRulePublicationTest.java | 112 + .../secretscan/CustomScanRuleTest.java | 60 + .../snapshot/CocoaPodsSnapshotTest.java | 123 + .../SnapshotImportTransactionTest.java | 113 + .../snapshot/SnapshotStagingBoundaryTest.java | 41 + .../{ => vcs}/BitbucketServiceTest.java | 3 +- .../service/{ => vcs}/GitHubServiceTest.java | 3 +- .../service/{ => vcs}/GitLabServiceTest.java | 3 +- .../ContinuousMonitoringServiceTest.java | 46 + .../SecurityCenterServiceTest.java | 33 +- .../VulnerabilityEnrichmentServiceTest.java | 100 +- .../support/ExternalVerificationFixture.java | 40 + .../ExternalVerificationFixtureTest.java | 32 + .../ApiKeyAuthInterceptorTest.java | 8 +- src/test/resources/application.yaml | 3 + .../oswl/uitest/AuthExpiryUiTest.java | 87 + .../oswl/uitest/AxeAuditUiTest.java | 176 + .../oswl/uitest/DetectionCoverageUiTest.java | 100 + .../oswl/uitest/EmbeddedAiFailureUiTest.java | 27 + .../oswl/uitest/JdbcBudgetProbe.java | 104 + .../oswl/uitest/KeyboardFlowUiTest.java | 133 + .../oswl/uitest/LocalSmartHttpGitServer.java | 271 + .../oswl/uitest/OnboardingFailureUiTest.java | 43 + .../uitest/OrganizationWorkflowUiTest.java | 82 + .../oswl/uitest/PerformanceBudgetUiTest.java | 291 + .../oswl/uitest/QuickImportLoadUiTest.java | 175 + .../uitest/QuickImportQueueCapUiTest.java | 48 + .../QuickImportRealPipelineLoadUiTest.java | 459 + .../QuickImportSourceReachabilityUiTest.java | 170 + .../oswl/uitest/RequestLifecycleUiTest.java | 389 + .../oswl/uitest/ScaleMeasurementUiTest.java | 142 + .../oswl/uitest/SettingsFailureUiTest.java | 89 + .../oswl/uitest/SettingsModulesUiTest.java | 50 + .../salkcoding/oswl/uitest/SmokeUiTest.java | 73 + .../oswl/uitest/UiStateMatrixUiTest.java | 102 + .../salkcoding/oswl/uitest/UiTestBase.java | 130 + src/uiTest/resources/application-uitest.yaml | 32 + tailwind/tailwind.config.js | 1 + 823 files changed, 115394 insertions(+), 8819 deletions(-) delete mode 100644 .claude/launch.json delete mode 100644 .claude/settings.json create mode 100644 .codex/config.toml delete mode 100644 .cursorrules delete mode 100644 .github/copilot-instructions.md delete mode 100644 .github/prompts/qa.prompt.md create mode 100644 .github/scripts/prepare-wiki.py create mode 100644 .github/scripts/validate-release.py delete mode 100644 .kimi/AGENTS.md delete mode 100644 .release-please-manifest.json delete mode 100644 CLAUDE.md create mode 100644 deploy/README.md rename .env.example => deploy/docker/.env.example (100%) rename .env.prod.example => deploy/docker/.env.prod.example (71%) rename Dockerfile => deploy/docker/Dockerfile (100%) create mode 100644 deploy/docker/compose.prod.yml rename docker-compose.yml => deploy/docker/compose.yml (87%) create mode 100644 deploy/observability/grafana/oswl-dashboard.json delete mode 100644 docker-compose.prod.yml delete mode 100644 docs/Embedded-AI.md delete mode 100644 docs/Getting-Started.md delete mode 100644 docs/Production-Deployment-Checklist.md create mode 100644 docs/README.md delete mode 100644 docs/_Sidebar.md rename docs/{ => en}/API-Reference.md (97%) rename docs/{ => en}/Administration.md (79%) create mode 100644 docs/en/Analysis-Coverage.md rename docs/{ => en}/Authorization-Layers.md (100%) create mode 100644 docs/en/Backup-And-Restore.md create mode 100644 docs/en/Browser-Security-Alerts.md rename docs/{ => en}/CLI-Integration.md (75%) create mode 100644 docs/en/Container-Image-Inspection.md create mode 100644 docs/en/Custom-Scan-Rules.md rename docs/{ => en}/Database-Schema.md (79%) create mode 100644 docs/en/Embedded-AI.md create mode 100644 docs/en/Getting-Started.md rename docs/{ => en}/Glossary.md (100%) rename docs/{ => en}/Home.md (92%) create mode 100644 docs/en/Inline-Team-Access.md rename docs/{ => en}/License-Analysis.md (100%) create mode 100644 docs/en/Offline-CocoaPods-Specs.md create mode 100644 docs/en/Organization-Risk-Briefing.md create mode 100644 docs/en/Production-Deployment-Checklist.md rename docs/{ => en}/Project-Access-Control.md (100%) rename docs/{ => en}/Quick-Import.md (100%) rename docs/{ => en}/Risk-Trend.md (100%) rename docs/{ => en}/Scan-Api-Security.md (96%) rename docs/{ => en}/Scan-History.md (100%) rename docs/{ => en}/Security-Center.md (98%) rename docs/{ => en}/User-Guide.md (89%) rename docs/{ => en}/Version-Diff.md (100%) rename docs/{ => en}/Whats-New-v1.0.4.md (96%) create mode 100644 docs/en/Whats-New-v1.0.5.1.md create mode 100644 docs/en/_Sidebar.md create mode 100644 docs/ja/Backup-And-Restore.md create mode 100644 docs/ja/Whats-New-v1.0.5.1.md create mode 100644 docs/ko/Backup-And-Restore.md create mode 100644 docs/ko/Whats-New-v1.0.5.1.md delete mode 100644 release-please-config.json create mode 100644 scripts/README.md rename scripts/{ => dev}/check-java.ps1 (100%) rename scripts/{ => dev}/check-java.sh (100%) create mode 100644 scripts/dev/measure-agent-context.py rename scripts/{ => dev}/oswl-bootRun.command (66%) create mode 100644 scripts/oci/scan-image.py create mode 100644 scripts/oci/test_scan_image.py create mode 100644 scripts/ops/verify-restore.sh create mode 100644 scripts/vdb/build-cocoapods-specs.py create mode 100644 scripts/verification/cluster_assertions.py create mode 100644 scripts/verification/performance.gradle create mode 100644 scripts/verification/test_cluster_assertions.py create mode 100644 scripts/verification/test_push_worker.cjs create mode 100644 scripts/verification/verify-h2-cluster.py create mode 100644 scripts/verification/verify-multi-instance.sh delete mode 100644 src/local/java/com/salkcoding/oswl/config/TmpObjectMapperConfig.java create mode 100644 src/local/java/com/salkcoding/oswl/local/CppManifestParserSanityCheck.java create mode 100644 src/local/java/com/salkcoding/oswl/local/CvssV4CalculatorSanityCheck.java create mode 100644 src/local/java/com/salkcoding/oswl/local/LogoResizeSanityCheck.java create mode 100644 src/main/java/com/salkcoding/oswl/auth/cli/AuditLogIntegrityCli.java create mode 100644 src/main/java/com/salkcoding/oswl/auth/controller/AdminAuditLogIntegrityController.java create mode 100644 src/main/java/com/salkcoding/oswl/auth/controller/OnboardingController.java create mode 100644 src/main/java/com/salkcoding/oswl/auth/controller/UserThemeController.java create mode 100644 src/main/java/com/salkcoding/oswl/auth/controller/spec/AdminAuditLogIntegrityControllerSpec.java create mode 100644 src/main/java/com/salkcoding/oswl/auth/controller/spec/UserThemeControllerSpec.java create mode 100644 src/main/java/com/salkcoding/oswl/auth/dto/AuditLogIntegrityReport.java create mode 100644 src/main/java/com/salkcoding/oswl/auth/dto/UserThemeRequest.java create mode 100644 src/main/java/com/salkcoding/oswl/auth/entity/OnboardingProgress.java create mode 100644 src/main/java/com/salkcoding/oswl/auth/enums/UserThemeMode.java create mode 100644 src/main/java/com/salkcoding/oswl/auth/repository/OnboardingProgressRepository.java create mode 100644 src/main/java/com/salkcoding/oswl/auth/security/JdbcSessionConfig.java create mode 100644 src/main/java/com/salkcoding/oswl/auth/security/Saml2LoginSuccessHandler.java create mode 100644 src/main/java/com/salkcoding/oswl/auth/service/AuditLogIntegrityService.java create mode 100644 src/main/java/com/salkcoding/oswl/auth/service/OnboardingService.java create mode 100644 src/main/java/com/salkcoding/oswl/auth/service/UserThemeService.java create mode 100644 src/main/java/com/salkcoding/oswl/client/CocoaPodsSpecsClient.java create mode 100644 src/main/java/com/salkcoding/oswl/client/CpeMatchService.java create mode 100644 src/main/java/com/salkcoding/oswl/client/CpeNameMapper.java create mode 100644 src/main/java/com/salkcoding/oswl/client/GitHubAdvisoryClient.java create mode 100644 src/main/java/com/salkcoding/oswl/client/NvdClient.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/AdminProjectController.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/ConfigTransferController.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/DiagnosticsController.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/MobileController.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/ScanArchivingController.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/SearchController.java rename src/main/java/com/salkcoding/oswl/controller/{ => ai}/AiSettingController.java (98%) rename src/main/java/com/salkcoding/oswl/controller/{ => apikey}/AdminCliKeyController.java (79%) rename src/main/java/com/salkcoding/oswl/controller/{ => apikey}/ApiKeyController.java (89%) rename src/main/java/com/salkcoding/oswl/controller/{ => audit}/AdminAuditExportController.java (98%) rename src/main/java/com/salkcoding/oswl/controller/{ => ingest}/QuickImportController.java (98%) rename src/main/java/com/salkcoding/oswl/controller/{ => ingest}/ScanController.java (93%) rename src/main/java/com/salkcoding/oswl/controller/{ => jira}/JiraController.java (89%) rename src/main/java/com/salkcoding/oswl/controller/{ => license}/LicenseController.java (95%) rename src/main/java/com/salkcoding/oswl/controller/{ => license}/LicensePolicyController.java (92%) rename src/main/java/com/salkcoding/oswl/controller/{ => license}/OssNoticesController.java (85%) create mode 100644 src/main/java/com/salkcoding/oswl/controller/notification/WebPushController.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/notification/WebPushPageController.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/notification/WebhookSettingController.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/org/ExecutiveSummaryController.java rename src/main/java/com/salkcoding/oswl/controller/{ => org}/OrgDashboardController.java (90%) create mode 100644 src/main/java/com/salkcoding/oswl/controller/org/TeamController.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/policy/PolicyController.java rename src/main/java/com/salkcoding/oswl/controller/{ => project}/ProjectContextController.java (63%) rename src/main/java/com/salkcoding/oswl/controller/{ => project}/ProjectController.java (96%) create mode 100644 src/main/java/com/salkcoding/oswl/controller/reporting/ReportBrandingController.java rename src/main/java/com/salkcoding/oswl/controller/{ => reporting}/RiskTrendController.java (86%) rename src/main/java/com/salkcoding/oswl/controller/{ => reporting}/SbomController.java (91%) rename src/main/java/com/salkcoding/oswl/controller/{ => scan}/ComponentDetailController.java (92%) create mode 100644 src/main/java/com/salkcoding/oswl/controller/scan/CustomScanRuleController.java rename src/main/java/com/salkcoding/oswl/controller/{ => scan}/ScanHistoryController.java (87%) rename src/main/java/com/salkcoding/oswl/controller/{ => scan}/VersionDiffController.java (85%) create mode 100644 src/main/java/com/salkcoding/oswl/controller/scim/ScimGroupController.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/scim/ScimUserController.java rename src/main/java/com/salkcoding/oswl/controller/{ => snapshot}/SnapshotAdminController.java (91%) create mode 100644 src/main/java/com/salkcoding/oswl/controller/spec/AdminProjectControllerSpec.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/spec/ConfigTransferControllerSpec.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/spec/CustomScanRuleControllerSpec.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/spec/DiagnosticsControllerSpec.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/spec/ExecutiveSummaryControllerSpec.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/spec/PolicyControllerSpec.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/spec/ReportBrandingControllerSpec.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/spec/SavedViewControllerSpec.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/spec/ScanArchivingControllerSpec.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/spec/ScimGroupControllerSpec.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/spec/ScimUserControllerSpec.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/spec/SearchControllerSpec.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/spec/TeamControllerSpec.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/spec/WebPushControllerSpec.java create mode 100644 src/main/java/com/salkcoding/oswl/controller/spec/WebhookSettingControllerSpec.java rename src/main/java/com/salkcoding/oswl/controller/{ => vcs}/GitHubApiController.java (98%) rename src/main/java/com/salkcoding/oswl/controller/{ => vcs}/VcsBranchController.java (91%) create mode 100644 src/main/java/com/salkcoding/oswl/controller/vulnerability/SavedViewController.java rename src/main/java/com/salkcoding/oswl/controller/{ => vulnerability}/SecurityCenterController.java (62%) rename src/main/java/com/salkcoding/oswl/domain/entity/{ => ai}/AiDailyUsage.java (75%) rename src/main/java/com/salkcoding/oswl/domain/entity/{ => ai}/AiPreferences.java (99%) rename src/main/java/com/salkcoding/oswl/domain/entity/{ => ai}/AiSetting.java (98%) rename src/main/java/com/salkcoding/oswl/domain/entity/{ => ai}/AiUsageEvent.java (97%) rename src/main/java/com/salkcoding/oswl/domain/entity/{ => apikey}/ApiKey.java (78%) create mode 100644 src/main/java/com/salkcoding/oswl/domain/entity/config/CacheInvalidation.java rename src/main/java/com/salkcoding/oswl/domain/entity/{ => jira}/JiraSetting.java (95%) rename src/main/java/com/salkcoding/oswl/domain/entity/{ => license}/LicensePolicyEntry.java (97%) create mode 100644 src/main/java/com/salkcoding/oswl/domain/entity/notification/WebPushDelivery.java create mode 100644 src/main/java/com/salkcoding/oswl/domain/entity/notification/WebPushSubscription.java create mode 100644 src/main/java/com/salkcoding/oswl/domain/entity/notification/WebhookDelivery.java create mode 100644 src/main/java/com/salkcoding/oswl/domain/entity/notification/WebhookSetting.java create mode 100644 src/main/java/com/salkcoding/oswl/domain/entity/org/Organization.java create mode 100644 src/main/java/com/salkcoding/oswl/domain/entity/org/Team.java create mode 100644 src/main/java/com/salkcoding/oswl/domain/entity/org/TeamMember.java create mode 100644 src/main/java/com/salkcoding/oswl/domain/entity/policy/Policy.java create mode 100644 src/main/java/com/salkcoding/oswl/domain/entity/policy/PolicyException.java rename src/main/java/com/salkcoding/oswl/domain/entity/{ => project}/Project.java (71%) rename src/main/java/com/salkcoding/oswl/domain/entity/{ => project}/ProjectMember.java (95%) rename src/main/java/com/salkcoding/oswl/domain/entity/{ => project}/ProjectVersion.java (96%) create mode 100644 src/main/java/com/salkcoding/oswl/domain/entity/reporting/ReportBrandingSettings.java create mode 100644 src/main/java/com/salkcoding/oswl/domain/entity/scan/CustomRuleConfiguration.java rename src/main/java/com/salkcoding/oswl/domain/entity/{ => scan}/DependencyPath.java (98%) create mode 100644 src/main/java/com/salkcoding/oswl/domain/entity/scan/ImportCoordinator.java create mode 100644 src/main/java/com/salkcoding/oswl/domain/entity/scan/ImportJob.java rename src/main/java/com/salkcoding/oswl/domain/entity/{ => scan}/ScanComponent.java (79%) create mode 100644 src/main/java/com/salkcoding/oswl/domain/entity/scan/ScanFinding.java rename src/main/java/com/salkcoding/oswl/domain/entity/{ => scan}/ScanResult.java (61%) rename src/main/java/com/salkcoding/oswl/domain/entity/{ => snapshot}/SnapshotEntry.java (96%) rename src/main/java/com/salkcoding/oswl/domain/entity/{ => snapshot}/SnapshotMeta.java (88%) create mode 100644 src/main/java/com/salkcoding/oswl/domain/entity/system/DatabaseMutationLock.java rename src/main/java/com/salkcoding/oswl/domain/entity/{ => vulnerability}/Cve.java (58%) rename src/main/java/com/salkcoding/oswl/domain/entity/{ => vulnerability}/CveAlert.java (96%) rename src/main/java/com/salkcoding/oswl/domain/entity/{ => vulnerability}/Library.java (75%) create mode 100644 src/main/java/com/salkcoding/oswl/domain/entity/vulnerability/SavedView.java create mode 100644 src/main/java/com/salkcoding/oswl/domain/enums/ApiKeyScope.java create mode 100644 src/main/java/com/salkcoding/oswl/domain/enums/CveSource.java create mode 100644 src/main/java/com/salkcoding/oswl/domain/enums/MatchConfidence.java create mode 100644 src/main/java/com/salkcoding/oswl/domain/enums/PolicyExceptionStatus.java create mode 100644 src/main/java/com/salkcoding/oswl/domain/enums/PolicyExceptionTargetType.java create mode 100644 src/main/java/com/salkcoding/oswl/domain/enums/PolicyScopeType.java create mode 100644 src/main/java/com/salkcoding/oswl/domain/enums/Reachability.java create mode 100644 src/main/java/com/salkcoding/oswl/domain/enums/ScanFindingType.java create mode 100644 src/main/java/com/salkcoding/oswl/domain/enums/ScimGroupMapping.java create mode 100644 src/main/java/com/salkcoding/oswl/domain/enums/TeamMemberRole.java create mode 100644 src/main/java/com/salkcoding/oswl/domain/enums/WebhookDeliveryStatus.java create mode 100644 src/main/java/com/salkcoding/oswl/domain/enums/WebhookEventType.java create mode 100644 src/main/java/com/salkcoding/oswl/domain/enums/WebhookProvider.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/AdminProjectRefDto.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/OrgTeamRiskDto.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/ReportBrandingResponse.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/ReportBrandingUpdateRequest.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/SavedViewCreateRequest.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/SavedViewDto.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/SecurityCenterRowFilterParams.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/TeamMemberDto.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/TeamSummaryDto.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/api/AiCacheSumsDto.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/api/WebPushAttempt.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/api/WebPushStatus.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/api/WebPushSubscriptionRequest.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/api/WebhookDeliveryDto.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/api/WebhookSettingResponse.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/api/WebhookSettingUpdateRequest.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/api/WebhookTestRequest.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/config/ConfigBundle.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/config/ConfigImportResult.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/diagnostics/DiagnosticCheckResult.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/mobile/MobileAlertDto.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/mobile/MobileProjectAlertsDto.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/mobile/MobileWaiverDto.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/policy/EffectivePolicyDto.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/policy/PolicyDto.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/policy/PolicyExceptionDto.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/policy/PolicyExceptionRequest.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/policy/PolicyGitOpsRequest.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/policy/PolicyRequest.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/policy/PolicyScopeOptionsDto.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/scan/CustomRuleSet.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/scan/CustomScanRule.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/scan/ScanArchiveExportDto.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/scan/ScanArchiveResult.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/scan/ScanFindingCandidate.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/scan/ScanFindingRowDto.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/scan/SourceAnalysisDetails.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/scan/VersionDiffComponent.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/scim/ScimEmail.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/scim/ScimError.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/scim/ScimGroup.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/scim/ScimListResponse.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/scim/ScimMember.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/scim/ScimMeta.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/scim/ScimName.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/scim/ScimPatchOperation.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/scim/ScimPatchRequest.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/scim/ScimUser.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/search/ComponentSearchHit.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/search/CveSearchHit.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/search/GlobalSearchResponse.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/search/ProjectSearchHit.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/search/SearchGroup.java create mode 100644 src/main/java/com/salkcoding/oswl/dto/snapshot/CocoaPodsSpec.java create mode 100644 src/main/java/com/salkcoding/oswl/health/AiProviderHealthIndicator.java create mode 100644 src/main/java/com/salkcoding/oswl/health/DbHealthIndicator.java create mode 100644 src/main/java/com/salkcoding/oswl/health/DiskSpaceHealthIndicator.java create mode 100644 src/main/java/com/salkcoding/oswl/health/EmbeddedSidecarHealthIndicator.java create mode 100644 src/main/java/com/salkcoding/oswl/health/SnapshotFreshnessHealthIndicator.java create mode 100644 src/main/java/com/salkcoding/oswl/logging/OswlFileEncoder.java delete mode 100644 src/main/java/com/salkcoding/oswl/repository/CveRepository.java create mode 100644 src/main/java/com/salkcoding/oswl/repository/DatabaseMutationLockRepository.java delete mode 100644 src/main/java/com/salkcoding/oswl/repository/DependencyPathRepository.java delete mode 100644 src/main/java/com/salkcoding/oswl/repository/LibraryRepository.java delete mode 100644 src/main/java/com/salkcoding/oswl/repository/ProjectRepository.java delete mode 100644 src/main/java/com/salkcoding/oswl/repository/ScanComponentRepository.java delete mode 100644 src/main/java/com/salkcoding/oswl/repository/ScanResultRepository.java rename src/main/java/com/salkcoding/oswl/repository/{ => ai}/AiDailyUsageRepository.java (73%) rename src/main/java/com/salkcoding/oswl/repository/{ => ai}/AiPreferencesRepository.java (59%) rename src/main/java/com/salkcoding/oswl/repository/{ => ai}/AiSettingRepository.java (90%) rename src/main/java/com/salkcoding/oswl/repository/{ => ai}/AiUsageEventRepository.java (86%) rename src/main/java/com/salkcoding/oswl/repository/{ => apikey}/ApiKeyRepository.java (85%) create mode 100644 src/main/java/com/salkcoding/oswl/repository/config/CacheInvalidationRepository.java rename src/main/java/com/salkcoding/oswl/repository/{ => jira}/JiraSettingRepository.java (73%) rename src/main/java/com/salkcoding/oswl/repository/{ => license}/LicensePolicyRepository.java (67%) create mode 100644 src/main/java/com/salkcoding/oswl/repository/notification/WebPushDeliveryRepository.java create mode 100644 src/main/java/com/salkcoding/oswl/repository/notification/WebPushSubscriptionRepository.java create mode 100644 src/main/java/com/salkcoding/oswl/repository/notification/WebhookDeliveryRepository.java create mode 100644 src/main/java/com/salkcoding/oswl/repository/notification/WebhookSettingRepository.java create mode 100644 src/main/java/com/salkcoding/oswl/repository/org/OrganizationRepository.java create mode 100644 src/main/java/com/salkcoding/oswl/repository/org/TeamMemberRepository.java create mode 100644 src/main/java/com/salkcoding/oswl/repository/org/TeamRepository.java create mode 100644 src/main/java/com/salkcoding/oswl/repository/policy/PolicyExceptionRepository.java create mode 100644 src/main/java/com/salkcoding/oswl/repository/policy/PolicyRepository.java rename src/main/java/com/salkcoding/oswl/repository/{ => project}/ProjectMemberRepository.java (92%) create mode 100644 src/main/java/com/salkcoding/oswl/repository/project/ProjectRepository.java rename src/main/java/com/salkcoding/oswl/repository/{ => project}/ProjectVersionRepository.java (85%) create mode 100644 src/main/java/com/salkcoding/oswl/repository/reporting/ReportBrandingSettingsRepository.java create mode 100644 src/main/java/com/salkcoding/oswl/repository/scan/CustomRuleConfigurationRepository.java create mode 100644 src/main/java/com/salkcoding/oswl/repository/scan/DependencyPathRepository.java create mode 100644 src/main/java/com/salkcoding/oswl/repository/scan/ImportCoordinatorRepository.java create mode 100644 src/main/java/com/salkcoding/oswl/repository/scan/ImportJobRepository.java create mode 100644 src/main/java/com/salkcoding/oswl/repository/scan/ScanComponentRepository.java create mode 100644 src/main/java/com/salkcoding/oswl/repository/scan/ScanFindingRepository.java create mode 100644 src/main/java/com/salkcoding/oswl/repository/scan/ScanResultRepository.java rename src/main/java/com/salkcoding/oswl/repository/{ => snapshot}/SnapshotEntryRepository.java (85%) rename src/main/java/com/salkcoding/oswl/repository/{ => snapshot}/SnapshotMetaRepository.java (57%) rename src/main/java/com/salkcoding/oswl/repository/{ => vulnerability}/CveAlertRepository.java (69%) create mode 100644 src/main/java/com/salkcoding/oswl/repository/vulnerability/CveRepository.java create mode 100644 src/main/java/com/salkcoding/oswl/repository/vulnerability/LibraryCatalogRepository.java create mode 100644 src/main/java/com/salkcoding/oswl/repository/vulnerability/LibraryRepository.java create mode 100644 src/main/java/com/salkcoding/oswl/repository/vulnerability/SavedViewRepository.java create mode 100644 src/main/java/com/salkcoding/oswl/scheduler/CacheInvalidationPoller.java create mode 100644 src/main/java/com/salkcoding/oswl/scheduler/ImportJobMaintenanceScheduler.java create mode 100644 src/main/java/com/salkcoding/oswl/scheduler/PolicyExceptionExpiryScheduler.java create mode 100644 src/main/java/com/salkcoding/oswl/scheduler/SchedulerLockConfig.java delete mode 100644 src/main/java/com/salkcoding/oswl/service/GatePolicyService.java create mode 100644 src/main/java/com/salkcoding/oswl/service/MobileDashboardService.java create mode 100644 src/main/java/com/salkcoding/oswl/service/SearchService.java delete mode 100644 src/main/java/com/salkcoding/oswl/service/SecurityCenterService.java rename src/main/java/com/salkcoding/oswl/service/{ => apikey}/ApiKeyService.java (58%) rename src/main/java/com/salkcoding/oswl/service/{ => apikey}/ApiKeyTokenSupport.java (94%) rename src/main/java/com/salkcoding/oswl/service/{ => apikey}/IssuedApiKey.java (60%) rename src/main/java/com/salkcoding/oswl/service/{ => apikey}/ProjectCliKeyPolicyService.java (93%) create mode 100644 src/main/java/com/salkcoding/oswl/service/config/CacheInvalidationService.java create mode 100644 src/main/java/com/salkcoding/oswl/service/config/ConfigTransferService.java create mode 100644 src/main/java/com/salkcoding/oswl/service/container/BaseImageEcosystemMapper.java create mode 100644 src/main/java/com/salkcoding/oswl/service/container/DockerfileParser.java create mode 100644 src/main/java/com/salkcoding/oswl/service/cvss/CvssV3Calculator.java create mode 100644 src/main/java/com/salkcoding/oswl/service/cvss/CvssV3Vector.java create mode 100644 src/main/java/com/salkcoding/oswl/service/cvss/CvssV4Calculator.java create mode 100644 src/main/java/com/salkcoding/oswl/service/cvss/CvssV4Vector.java create mode 100644 src/main/java/com/salkcoding/oswl/service/cvss/CvssVectorVersion.java create mode 100644 src/main/java/com/salkcoding/oswl/service/cvss/EnvironmentalRequirementMapper.java create mode 100644 src/main/java/com/salkcoding/oswl/service/diagnostics/DiagnosticsService.java create mode 100644 src/main/java/com/salkcoding/oswl/service/gate/GatePolicyService.java rename src/main/java/com/salkcoding/oswl/service/{ => gate}/PrGateService.java (81%) create mode 100644 src/main/java/com/salkcoding/oswl/service/iacscan/IacRule.java create mode 100644 src/main/java/com/salkcoding/oswl/service/iacscan/IacScanner.java create mode 100644 src/main/java/com/salkcoding/oswl/service/iacscan/IacTargetKind.java rename src/main/java/com/salkcoding/oswl/service/{ => ingest}/CloneCleanupService.java (99%) create mode 100644 src/main/java/com/salkcoding/oswl/service/ingest/CondaPypiMappingService.java rename src/main/java/com/salkcoding/oswl/service/{ => ingest}/DependencyManifestParserService.java (51%) rename src/main/java/com/salkcoding/oswl/service/{ => ingest}/DependencyManifestPatcher.java (97%) rename src/main/java/com/salkcoding/oswl/service/{ => ingest}/EnrichmentProgressContext.java (92%) rename src/main/java/com/salkcoding/oswl/service/{ => ingest}/EnrichmentProgressHolder.java (86%) create mode 100644 src/main/java/com/salkcoding/oswl/service/ingest/ImportJobStore.java rename src/main/java/com/salkcoding/oswl/service/{ => ingest}/ManifestArchiveService.java (99%) rename src/main/java/com/salkcoding/oswl/service/{ => ingest}/ManifestPatchInfo.java (89%) rename src/main/java/com/salkcoding/oswl/service/{ => ingest}/MavenBomVersionResolver.java (89%) rename src/main/java/com/salkcoding/oswl/service/{ => ingest}/QuickImportService.java (84%) rename src/main/java/com/salkcoding/oswl/service/{ => ingest}/SbomImportService.java (93%) rename src/main/java/com/salkcoding/oswl/service/{ => ingest}/ScanApiCredentialThrottleService.java (99%) rename src/main/java/com/salkcoding/oswl/service/{ => ingest}/ScanIngestService.java (66%) create mode 100644 src/main/java/com/salkcoding/oswl/service/ingest/parser/CargoManifestParser.java create mode 100644 src/main/java/com/salkcoding/oswl/service/ingest/parser/CocoaPodsLockParser.java create mode 100644 src/main/java/com/salkcoding/oswl/service/ingest/parser/ComposerLockParser.java create mode 100644 src/main/java/com/salkcoding/oswl/service/ingest/parser/ConanLockParser.java create mode 100644 src/main/java/com/salkcoding/oswl/service/ingest/parser/CondaLockParser.java create mode 100644 src/main/java/com/salkcoding/oswl/service/ingest/parser/GoManifestParser.java create mode 100644 src/main/java/com/salkcoding/oswl/service/ingest/parser/ManifestDiscovery.java create mode 100644 src/main/java/com/salkcoding/oswl/service/ingest/parser/ManifestIndex.java create mode 100644 src/main/java/com/salkcoding/oswl/service/ingest/parser/MavenPomParser.java create mode 100644 src/main/java/com/salkcoding/oswl/service/ingest/parser/NativeManifestParser.java create mode 100644 src/main/java/com/salkcoding/oswl/service/ingest/parser/NpmManifestParser.java create mode 100644 src/main/java/com/salkcoding/oswl/service/ingest/parser/NugetManifestParser.java create mode 100644 src/main/java/com/salkcoding/oswl/service/ingest/parser/PythonManifestParser.java create mode 100644 src/main/java/com/salkcoding/oswl/service/ingest/parser/RubyLockParser.java create mode 100644 src/main/java/com/salkcoding/oswl/service/ingest/parser/UploadedLockFileParser.java create mode 100644 src/main/java/com/salkcoding/oswl/service/ingest/parser/VersionCatalogParser.java rename src/main/java/com/salkcoding/oswl/service/{ => jira}/JiraService.java (94%) rename src/main/java/com/salkcoding/oswl/service/{ => license}/LicensePolicyService.java (84%) rename src/main/java/com/salkcoding/oswl/service/{ => license}/LicenseService.java (98%) create mode 100644 src/main/java/com/salkcoding/oswl/service/metrics/OswlMetrics.java create mode 100644 src/main/java/com/salkcoding/oswl/service/notification/WebPushService.java create mode 100644 src/main/java/com/salkcoding/oswl/service/notification/WebPushTransport.java create mode 100644 src/main/java/com/salkcoding/oswl/service/notification/WebPushWorker.java create mode 100644 src/main/java/com/salkcoding/oswl/service/notification/WebhookClient.java create mode 100644 src/main/java/com/salkcoding/oswl/service/notification/WebhookMessageBuilder.java create mode 100644 src/main/java/com/salkcoding/oswl/service/notification/WebhookNotificationService.java create mode 100644 src/main/java/com/salkcoding/oswl/service/notification/WebhookSettingService.java create mode 100644 src/main/java/com/salkcoding/oswl/service/org/ExecutiveSummaryService.java rename src/main/java/com/salkcoding/oswl/service/{ => org}/OrgDashboardService.java (67%) create mode 100644 src/main/java/com/salkcoding/oswl/service/org/TeamBootstrapRunner.java create mode 100644 src/main/java/com/salkcoding/oswl/service/org/TeamService.java create mode 100644 src/main/java/com/salkcoding/oswl/service/policy/PolicyService.java rename src/main/java/com/salkcoding/oswl/service/{ => project}/ProjectAccessService.java (69%) rename src/main/java/com/salkcoding/oswl/service/{ => project}/ProjectMemberBootstrapRunner.java (88%) rename src/main/java/com/salkcoding/oswl/service/{ => project}/ProjectService.java (77%) create mode 100644 src/main/java/com/salkcoding/oswl/service/reachability/CallGraphAnalyzer.java create mode 100644 src/main/java/com/salkcoding/oswl/service/reachability/LibraryPackageMapper.java create mode 100644 src/main/java/com/salkcoding/oswl/service/reachability/ReachabilityService.java create mode 100644 src/main/java/com/salkcoding/oswl/service/reachability/SourceImportSyntax.java create mode 100644 src/main/java/com/salkcoding/oswl/service/reachability/SourceReachabilityService.java create mode 100644 src/main/java/com/salkcoding/oswl/service/reachability/SourceReachabilityStore.java create mode 100644 src/main/java/com/salkcoding/oswl/service/reachability/SourceReferenceAnalyzer.java rename src/main/java/com/salkcoding/oswl/service/{ => reporting}/ComplianceReportService.java (92%) create mode 100644 src/main/java/com/salkcoding/oswl/service/reporting/ReportBrandingService.java rename src/main/java/com/salkcoding/oswl/service/{ => reporting}/RiskTrendService.java (80%) rename src/main/java/com/salkcoding/oswl/service/{ => reporting}/SarifExportService.java (91%) rename src/main/java/com/salkcoding/oswl/service/{ => reporting}/SbomExportService.java (96%) rename src/main/java/com/salkcoding/oswl/service/{ => scan}/ComponentDetailService.java (85%) create mode 100644 src/main/java/com/salkcoding/oswl/service/scan/ScanArchivingService.java rename src/main/java/com/salkcoding/oswl/service/{ => scan}/ScanFailureMarker.java (70%) rename src/main/java/com/salkcoding/oswl/service/{ => scan}/ScanHistoryService.java (80%) rename src/main/java/com/salkcoding/oswl/service/{ => scan}/ScanStatusEmitterRegistry.java (98%) create mode 100644 src/main/java/com/salkcoding/oswl/service/scan/ScanSummaryReader.java rename src/main/java/com/salkcoding/oswl/service/{ => scan}/ScanTimingRecorder.java (98%) rename src/main/java/com/salkcoding/oswl/service/{ => scan}/ScanVersionDiffAnalyzer.java (74%) rename src/main/java/com/salkcoding/oswl/service/{ => scan}/VersionDiffService.java (95%) create mode 100644 src/main/java/com/salkcoding/oswl/service/scim/ScimProvisioningService.java create mode 100644 src/main/java/com/salkcoding/oswl/service/secretscan/CustomRuleScanner.java create mode 100644 src/main/java/com/salkcoding/oswl/service/secretscan/CustomScanRuleService.java create mode 100644 src/main/java/com/salkcoding/oswl/service/secretscan/SecretIacScanService.java create mode 100644 src/main/java/com/salkcoding/oswl/service/secretscan/SecretRule.java create mode 100644 src/main/java/com/salkcoding/oswl/service/secretscan/SecretScanner.java create mode 100644 src/main/java/com/salkcoding/oswl/service/secretscan/SourceFindingStore.java rename src/main/java/com/salkcoding/oswl/service/{ => snapshot}/AirgappedSnapshotService.java (65%) create mode 100644 src/main/java/com/salkcoding/oswl/service/snapshot/SnapshotBundleStager.java rename src/main/java/com/salkcoding/oswl/service/{ => vcs}/BitbucketService.java (99%) rename src/main/java/com/salkcoding/oswl/service/{ => vcs}/GitHubService.java (99%) rename src/main/java/com/salkcoding/oswl/service/{ => vcs}/GitLabService.java (99%) rename src/main/java/com/salkcoding/oswl/service/{ => vcs}/VcsAuthTokenService.java (96%) rename src/main/java/com/salkcoding/oswl/service/{ => vulnerability}/ContinuousMonitoringService.java (81%) create mode 100644 src/main/java/com/salkcoding/oswl/service/vulnerability/SavedViewService.java create mode 100644 src/main/java/com/salkcoding/oswl/service/vulnerability/SecurityCenterService.java rename src/main/java/com/salkcoding/oswl/service/{ => vulnerability}/SupplyChainHeuristicsService.java (98%) rename src/main/java/com/salkcoding/oswl/service/{ => vulnerability}/VulnerabilityEnrichmentService.java (70%) create mode 100644 src/main/java/com/salkcoding/oswl/service/vulnerability/sources/AdvisoryFetchResult.java create mode 100644 src/main/java/com/salkcoding/oswl/service/vulnerability/sources/GitHubAdvisorySource.java create mode 100644 src/main/java/com/salkcoding/oswl/service/vulnerability/sources/NvdAdvisorySource.java create mode 100644 src/main/java/com/salkcoding/oswl/util/LogoImageNormalizer.java create mode 100644 src/main/java/com/salkcoding/oswl/vdb/ApkVersionComparator.java create mode 100644 src/main/java/com/salkcoding/oswl/vdb/GitHubAdvisorySource.java create mode 100644 src/main/java/com/salkcoding/oswl/vdb/NvdSource.java create mode 100644 src/main/java/com/salkcoding/oswl/web/filter/RequestContextLoggingFilter.java create mode 100644 src/main/java/com/salkcoding/oswl/web/interceptor/ScimAuthInterceptor.java create mode 100644 src/main/resources/META-INF/licenses/conda-forge-bot-data-LICENSE.txt create mode 100644 src/main/resources/conda/grayskull-pypi-mapping.json create mode 100644 src/main/resources/cvss/cvss-v4-lookup.json create mode 100644 src/main/resources/db/ai_cache_hit_stats.sql create mode 100644 src/main/resources/db/audit_log_integrity_hash_chain.sql create mode 100644 src/main/resources/db/cache_invalidation.sql create mode 100644 src/main/resources/db/ccpp_coverage.sql create mode 100644 src/main/resources/db/cve_sources.sql create mode 100644 src/main/resources/db/database_mutation_locks.sql create mode 100644 src/main/resources/db/durable_import_jobs.sql create mode 100644 src/main/resources/db/hot_path_indexes.sql create mode 100644 src/main/resources/db/migration/V10__spring_session_and_shedlock.sql create mode 100644 src/main/resources/db/migration/V11__org_team_hierarchy.sql create mode 100644 src/main/resources/db/migration/V12__hot_path_indexes.sql create mode 100644 src/main/resources/db/migration/V13__saml_scim.sql create mode 100644 src/main/resources/db/migration/V14__webhook_settings.sql create mode 100644 src/main/resources/db/migration/V15__cve_sources.sql create mode 100644 src/main/resources/db/migration/V16__ccpp_coverage.sql create mode 100644 src/main/resources/db/migration/V17__policy_as_code.sql create mode 100644 src/main/resources/db/migration/V18__reachability.sql create mode 100644 src/main/resources/db/migration/V19__audit_log_integrity_hash_chain.sql create mode 100644 src/main/resources/db/migration/V20__user_theme.sql create mode 100644 src/main/resources/db/migration/V21__secret_iac_scan_findings.sql create mode 100644 src/main/resources/db/migration/V22__scan_archiving.sql create mode 100644 src/main/resources/db/migration/V23__onboarding_progress.sql create mode 100644 src/main/resources/db/migration/V24__saved_views.sql create mode 100644 src/main/resources/db/migration/V25__report_branding_settings.sql create mode 100644 src/main/resources/db/migration/V26__ai_cache_hit_stats.sql create mode 100644 src/main/resources/db/migration/V27__cache_invalidation.sql create mode 100644 src/main/resources/db/migration/V28__policy_gate_reachability_secrets.sql create mode 100644 src/main/resources/db/migration/V29__reachability_evidence.sql create mode 100644 src/main/resources/db/migration/V30__source_analysis_metadata.sql create mode 100644 src/main/resources/db/migration/V31__durable_import_jobs.sql create mode 100644 src/main/resources/db/migration/V32__vulnerability_lookup_outcomes.sql create mode 100644 src/main/resources/db/migration/V33__custom_scan_rules.sql create mode 100644 src/main/resources/db/migration/V34__web_push_notifications.sql create mode 100644 src/main/resources/db/migration/V35__database_mutation_locks.sql create mode 100644 src/main/resources/db/onboarding_progress.sql create mode 100644 src/main/resources/db/org_team_hierarchy.sql create mode 100644 src/main/resources/db/policy_as_code.sql create mode 100644 src/main/resources/db/policy_gate_reachability_secrets.sql create mode 100644 src/main/resources/db/reachability.sql create mode 100644 src/main/resources/db/report_branding_settings.sql create mode 100644 src/main/resources/db/saml_scim.sql create mode 100644 src/main/resources/db/saved_views.sql create mode 100644 src/main/resources/db/scan_archiving.sql create mode 100644 src/main/resources/db/secret_iac_scan_findings.sql create mode 100644 src/main/resources/db/source_analysis_metadata.sql create mode 100644 src/main/resources/db/spring_session_and_shedlock.sql create mode 100644 src/main/resources/db/user_theme.sql create mode 100644 src/main/resources/db/webhook_settings.sql create mode 100644 src/main/resources/logback-spring.xml create mode 100644 src/main/resources/secretscan/secret-rules.json create mode 100644 src/main/resources/static/js/a11y.js create mode 100644 src/main/resources/static/js/onboarding-team.js create mode 100644 src/main/resources/static/js/oswl-http.js create mode 100644 src/main/resources/static/js/oswl-theme.js create mode 100644 src/main/resources/static/js/security-center/page.js create mode 100644 src/main/resources/static/js/security-center/slideout.js create mode 100644 src/main/resources/static/js/settings/admin.js create mode 100644 src/main/resources/static/js/settings/ai.js create mode 100644 src/main/resources/static/js/settings/cache.js create mode 100644 src/main/resources/static/js/settings/cli.js create mode 100644 src/main/resources/static/js/settings/config-transfer.js create mode 100644 src/main/resources/static/js/settings/diagnostics.js create mode 100644 src/main/resources/static/js/settings/license-policy.js create mode 100644 src/main/resources/static/js/settings/policy.js create mode 100644 src/main/resources/static/js/settings/reports.js create mode 100644 src/main/resources/static/js/settings/security.js create mode 100644 src/main/resources/static/js/settings/settings-dirty.js create mode 100644 src/main/resources/static/js/settings/settings-subtabs.js create mode 100644 src/main/resources/static/js/settings/vcs.js create mode 100644 src/main/resources/static/js/settings/webhooks.js create mode 100644 src/main/resources/static/js/web-push-preferences.js create mode 100644 src/main/resources/static/oswl-push-sw.js create mode 100644 src/main/resources/templates/auth/onboarding.html create mode 100644 src/main/resources/templates/fragments/settings-subtabs.html create mode 100644 src/main/resources/templates/mobile/index.html create mode 100644 src/main/resources/templates/notification/preferences.html create mode 100644 src/main/resources/templates/org-dashboard/summary.html create mode 100644 src/main/resources/templates/security-center/fragments/findings.html create mode 100644 src/main/resources/templates/settings/tabs/config-transfer.html create mode 100644 src/main/resources/templates/settings/tabs/diagnostics.html create mode 100644 src/main/resources/templates/settings/tabs/policy.html create mode 100644 src/main/resources/templates/settings/tabs/reports.html create mode 100644 src/main/resources/templates/settings/tabs/webhooks.html create mode 100644 src/test/java/com/salkcoding/oswl/client/NvdLookupOutcomeTest.java create mode 100644 src/test/java/com/salkcoding/oswl/client/OsvLookupOutcomeTest.java create mode 100644 src/test/java/com/salkcoding/oswl/controller/AdminProjectControllerTest.java rename src/test/java/com/salkcoding/oswl/controller/{ => vcs}/GitHubApiControllerTest.java (98%) create mode 100644 src/test/java/com/salkcoding/oswl/scheduler/CacheInvalidationPollerTest.java create mode 100644 src/test/java/com/salkcoding/oswl/scheduler/ImportJobMaintenanceSchedulerTest.java create mode 100644 src/test/java/com/salkcoding/oswl/service/DependencyManifestParserEcosystemMappingTest.java create mode 100644 src/test/java/com/salkcoding/oswl/service/SearchServiceTest.java create mode 100644 src/test/java/com/salkcoding/oswl/service/ai/AiUsageStatsServiceTest.java create mode 100644 src/test/java/com/salkcoding/oswl/service/ai/EmbeddedAiDownloadTest.java create mode 100644 src/test/java/com/salkcoding/oswl/service/ai/EmbeddedAiFreshInstallVerificationTest.java rename src/test/java/com/salkcoding/oswl/service/{ => apikey}/ApiKeyServiceTest.java (82%) rename src/test/java/com/salkcoding/oswl/service/{ => apikey}/ProjectCliKeyPolicyServiceTest.java (88%) create mode 100644 src/test/java/com/salkcoding/oswl/service/config/ConfigTransferServiceTest.java create mode 100644 src/test/java/com/salkcoding/oswl/service/gate/GateCoverageTest.java rename src/test/java/com/salkcoding/oswl/service/{ => ingest}/DependencyManifestPatcherTest.java (97%) rename src/test/java/com/salkcoding/oswl/service/{ => ingest}/EnrichmentProgressHolderTest.java (94%) create mode 100644 src/test/java/com/salkcoding/oswl/service/ingest/ImportJobStoreTest.java create mode 100644 src/test/java/com/salkcoding/oswl/service/ingest/LibraryCatalogConcurrencyTest.java create mode 100644 src/test/java/com/salkcoding/oswl/service/ingest/ManifestParserBoundaryTest.java rename src/test/java/com/salkcoding/oswl/service/{ => ingest}/MavenBomVersionResolverTest.java (78%) rename src/test/java/com/salkcoding/oswl/service/{ => ingest}/QuickImportServiceParserTest.java (97%) rename src/test/java/com/salkcoding/oswl/service/{ => ingest}/QuickImportServiceTest.java (94%) rename src/test/java/com/salkcoding/oswl/service/{ => ingest}/ScanApiCredentialThrottleServiceTest.java (94%) rename src/test/java/com/salkcoding/oswl/service/{ => ingest}/ScanIngestServiceTest.java (66%) rename src/test/java/com/salkcoding/oswl/service/{ => license}/LicensePolicyServiceTest.java (96%) rename src/test/java/com/salkcoding/oswl/service/{ => license}/LicenseServiceTest.java (97%) create mode 100644 src/test/java/com/salkcoding/oswl/service/metrics/OswlMetricsTest.java create mode 100644 src/test/java/com/salkcoding/oswl/service/notification/WebPushMigrationTest.java create mode 100644 src/test/java/com/salkcoding/oswl/service/notification/WebPushServiceTest.java create mode 100644 src/test/java/com/salkcoding/oswl/service/notification/WebPushTransportTest.java create mode 100644 src/test/java/com/salkcoding/oswl/service/org/OrgDashboardServiceQueryCountTest.java rename src/test/java/com/salkcoding/oswl/service/{ => project}/ProjectAccessServiceTest.java (89%) create mode 100644 src/test/java/com/salkcoding/oswl/service/project/ProjectServiceQueryCountTest.java rename src/test/java/com/salkcoding/oswl/service/{ => project}/ProjectServiceTest.java (83%) create mode 100644 src/test/java/com/salkcoding/oswl/service/reachability/SourceReferenceAnalyzerTest.java rename src/test/java/com/salkcoding/oswl/service/{ => reporting}/RiskTrendServiceTest.java (84%) rename src/test/java/com/salkcoding/oswl/service/{ => scan}/ComponentDetailServiceTest.java (98%) create mode 100644 src/test/java/com/salkcoding/oswl/service/scan/ScanArchivingServiceTest.java rename src/test/java/com/salkcoding/oswl/service/{ => scan}/ScanHistoryServiceTest.java (90%) rename src/test/java/com/salkcoding/oswl/service/{ => scan}/ScanStatusEmitterRegistryTest.java (96%) create mode 100644 src/test/java/com/salkcoding/oswl/service/scan/ScanSummaryReaderTest.java create mode 100644 src/test/java/com/salkcoding/oswl/service/scan/ScanVersionDiffProjectionTest.java rename src/test/java/com/salkcoding/oswl/service/{ => scan}/VersionDiffServiceTest.java (93%) create mode 100644 src/test/java/com/salkcoding/oswl/service/secretscan/CustomRulePublicationTest.java create mode 100644 src/test/java/com/salkcoding/oswl/service/secretscan/CustomScanRuleTest.java create mode 100644 src/test/java/com/salkcoding/oswl/service/snapshot/CocoaPodsSnapshotTest.java create mode 100644 src/test/java/com/salkcoding/oswl/service/snapshot/SnapshotImportTransactionTest.java create mode 100644 src/test/java/com/salkcoding/oswl/service/snapshot/SnapshotStagingBoundaryTest.java rename src/test/java/com/salkcoding/oswl/service/{ => vcs}/BitbucketServiceTest.java (96%) rename src/test/java/com/salkcoding/oswl/service/{ => vcs}/GitHubServiceTest.java (95%) rename src/test/java/com/salkcoding/oswl/service/{ => vcs}/GitLabServiceTest.java (94%) create mode 100644 src/test/java/com/salkcoding/oswl/service/vulnerability/ContinuousMonitoringServiceTest.java rename src/test/java/com/salkcoding/oswl/service/{ => vulnerability}/SecurityCenterServiceTest.java (92%) rename src/test/java/com/salkcoding/oswl/service/{ => vulnerability}/VulnerabilityEnrichmentServiceTest.java (90%) create mode 100644 src/test/java/com/salkcoding/oswl/support/ExternalVerificationFixture.java create mode 100644 src/test/java/com/salkcoding/oswl/support/ExternalVerificationFixtureTest.java create mode 100644 src/uiTest/java/com/salkcoding/oswl/uitest/AuthExpiryUiTest.java create mode 100644 src/uiTest/java/com/salkcoding/oswl/uitest/AxeAuditUiTest.java create mode 100644 src/uiTest/java/com/salkcoding/oswl/uitest/DetectionCoverageUiTest.java create mode 100644 src/uiTest/java/com/salkcoding/oswl/uitest/EmbeddedAiFailureUiTest.java create mode 100644 src/uiTest/java/com/salkcoding/oswl/uitest/JdbcBudgetProbe.java create mode 100644 src/uiTest/java/com/salkcoding/oswl/uitest/KeyboardFlowUiTest.java create mode 100644 src/uiTest/java/com/salkcoding/oswl/uitest/LocalSmartHttpGitServer.java create mode 100644 src/uiTest/java/com/salkcoding/oswl/uitest/OnboardingFailureUiTest.java create mode 100644 src/uiTest/java/com/salkcoding/oswl/uitest/OrganizationWorkflowUiTest.java create mode 100644 src/uiTest/java/com/salkcoding/oswl/uitest/PerformanceBudgetUiTest.java create mode 100644 src/uiTest/java/com/salkcoding/oswl/uitest/QuickImportLoadUiTest.java create mode 100644 src/uiTest/java/com/salkcoding/oswl/uitest/QuickImportQueueCapUiTest.java create mode 100644 src/uiTest/java/com/salkcoding/oswl/uitest/QuickImportRealPipelineLoadUiTest.java create mode 100644 src/uiTest/java/com/salkcoding/oswl/uitest/QuickImportSourceReachabilityUiTest.java create mode 100644 src/uiTest/java/com/salkcoding/oswl/uitest/RequestLifecycleUiTest.java create mode 100644 src/uiTest/java/com/salkcoding/oswl/uitest/ScaleMeasurementUiTest.java create mode 100644 src/uiTest/java/com/salkcoding/oswl/uitest/SettingsFailureUiTest.java create mode 100644 src/uiTest/java/com/salkcoding/oswl/uitest/SettingsModulesUiTest.java create mode 100644 src/uiTest/java/com/salkcoding/oswl/uitest/SmokeUiTest.java create mode 100644 src/uiTest/java/com/salkcoding/oswl/uitest/UiStateMatrixUiTest.java create mode 100644 src/uiTest/java/com/salkcoding/oswl/uitest/UiTestBase.java create mode 100644 src/uiTest/resources/application-uitest.yaml diff --git a/.claude/launch.json b/.claude/launch.json deleted file mode 100644 index e72e8eff..00000000 --- a/.claude/launch.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": "0.0.1", - "configurations": [ - { - "name": "oswl", - "runtimeExecutable": "cmd", - "runtimeArgs": ["/c", "E:\\Programming\\Visual Studio Code\\Oswl\\gradlew.bat", "bootRun"], - "port": 8080 - } - ] -} diff --git a/.claude/settings.json b/.claude/settings.json deleted file mode 100644 index c161cd84..00000000 --- a/.claude/settings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "includeCoAuthoredBy": false, - "enabledPlugins": { - "frontend-design@claude-plugins-official": true, - "claude-md-management@claude-plugins-official": true - } -} diff --git a/.codex/config.toml b/.codex/config.toml new file mode 100644 index 00000000..0e5c6736 --- /dev/null +++ b/.codex/config.toml @@ -0,0 +1,2 @@ +[mcp_servers.figma] +url = "http://127.0.0.1:3845/mcp" diff --git a/.cursorrules b/.cursorrules deleted file mode 100644 index 12ffa548..00000000 --- a/.cursorrules +++ /dev/null @@ -1,92 +0,0 @@ -# OsWL — Project Guidelines - -OsWL is an in-house SCA (Software Composition Analysis) platform for tracking and managing CVE security vulnerabilities and license risks in OSS components. - ---- - -## Test Account -- Email: test@test.com | Password: 1q2w3e4r | Name: test -- OTP: `000000` (bypass in dev) | Test issuance OTP: `123456` - ---- - -## Tech Stack -- **Backend:** Spring Boot 4.0.5 (Java 25) — WebMVC, Data JPA, Security, Mail, Validation, AspectJ -- **View:** Thymeleaf + thymeleaf-extras-springsecurity6 -- **Frontend:** Vanilla HTML/CSS/JS · Tailwind CSS 3.4 (precompiled standalone CLI, **NO CDN**) · Chart.js · HTMX · Alpine.js -- **DB:** PostgreSQL (prod) / H2 file-mode (local profile, PostgreSQL-compatible) -- **Auth:** Spring Security + Email OTP (2FA) + Trusted Device + Single-Session Enforcement -- **API Docs:** springdoc-openapi — OpenAPI annotations live in `controller/spec/*Spec.java` interfaces only -- **Local SMTP:** GreenMail embedded (port 3025) — OTP code printed as `*** OTP CODE: NNNNNN ***` in server logs -- **Build:** Gradle Wrapper (`./gradlew`) - ---- - -## Project Structure (Key Packages) - - src/main/java/com/salkcoding/oswl/ - ├── aop/ # Cross-cutting concerns (audit logging) - ├── auth/ # Auth module — config, controller, dto, entity, enums, repo, security, service, web - ├── client/ # External API clients (OSV, deps.dev, GitHub…) - ├── controller/ # Business controllers - │ └── spec/ # Controller spec interfaces (all springdoc annotations here) - ├── domain/entity/ # JPA entities - ├── domain/enums/ - ├── dto/ exception/ repository/ scheduler/ service/ web/ - resources/ - ├── application.yaml / application-local.yaml / application-prod.yaml - ├── static/{css,js,img,icon,graphic,scripts}/ - └── templates/{auth, projects, security-center, component-detail, license, - risk-trend, scan-history, settings, error, fragments}/ - ---- - -## Architecture & Code Style - -**Backend** -- **Layered:** Controller → Service → Repository. Keep controllers thin. -- **Controller Spec:** All OpenAPI annotations go in `controller/spec/*ControllerSpec.java` interfaces. Implementations contain business logic only. -- **DI:** Lombok `@RequiredArgsConstructor` constructor injection only. `@Autowired` is forbidden. -- **Authorization:** Method-level `@PreAuthorize("hasPermission(null, 'PROJECT_VIEW') or hasRole('SYSTEM_ADMIN')")`. -- **Audit Logging:** Use `AuditLogService.log…()` for user actions and system events. -- **Profile Gating:** Local-only beans use `@Profile("local")` (e.g. `TestDataController`, `LocalSmtpConfig`). -- **JPA Cascade:** Project → ProjectVersion / ScanResult / ScanComponent / DependencyPath; Library → Cve (cascade ALL). - -**Frontend** -- **Vanilla first.** Add HTMX (partial updates) or Alpine.js (local state) only when needed. -- **Tailwind CSS:** `/css/tailwind.css` (Gradle build output) only — CDN is forbidden. -- **HTMX pattern:** Return fragment-only response when `HX-Request: true` header is present. -- **Assets:** JS in `static/js/`, organized by page subdirectory. - ---- - -## DB & Environments -- **Local DB:** H2 file (`./oswl-db.mv.db`). Console: `http://localhost:8080/h2-console` (JDBC `jdbc:h2:file:./oswl-db`, user `sa`). -- **DB Reset:** Stop server → delete `oswl-db.*` files → restart → lands on Setup screen. -- **Test Data Seed:** `GET /data/test` (auth required, local only) — wipes and re-seeds all data. -- **Encryption Key (`OSWL_ENCRYPTION_KEY`):** Dummy key hardcoded for local; production requires 32-byte key via `openssl rand -base64 32`. - ---- - -## Build & Run - - ./gradlew bootRun # Local run (profile=local, port 8080) - ./gradlew build # Full build (includes Tailwind) - ./gradlew test # JUnit tests - ./gradlew buildTailwindCss # Tailwind rebuild only - ---- - -## Conventions -- **DB naming:** snake_case tables/columns; entity PK is `Long id`. -- **DTOs:** `record` or Lombok `@Value` in `dto/` package. -- **Dependencies:** Add to `build.gradle` (backend) or use CDN/webjars (frontend). -- **Error pages:** `error/{401,403,404,500,503}.html` — use `_owl-error.html` fragment (shared owl illustration). -- **Log tone:** Business events → INFO/WARN/ERROR; details → `log.debug`. - -## Test Code Policy - -- Do NOT modify, create, or delete any test files (e.g., *Test.java, *.test.ts, *.spec.ts) unless I explicitly ask you to. -- When fixing bugs or refactoring production code, leave all existing test files exactly as they are. -- If a test needs to be updated as a direct result of your change, ask me first before touching it. -- Never auto-generate test stubs or test scaffolding without being explicitly instructed to do so. \ No newline at end of file diff --git a/.dockerignore b/.dockerignore index e7b64ee6..a653daa3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -10,3 +10,19 @@ tmp .mvn .vscode .DS_Store +.env +.env.* +**/.env +**/.env.* +*.db +**/*.db +.venv +**/__pycache__ +bin +embedded-ai +.agents +.codex +.mcp.json +docs +landing +ROADMAP*.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index cef5450d..00000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,144 +0,0 @@ -# OsWL — Project Guidelines - -OsWL는 OSS 컴포넌트의 보안 취약점(CVE)과 라이선스 리스크를 추적·관리하는 사내 SCA(Software Composition Analysis) 플랫폼입니다. - -# Test account rule -테스트 계정은 무조건 -이메일: test@test.com -비밀번호: 1q2w3e4r -로 진행하며 이름은 test로 통일한다. -OTP는 개발용이라 000000으로 통과 가능하며, 테스트용으로 발급할 때는 123456을 사용한다. - -## Tech Stack - -- **Backend:** Spring Boot 4.0.5 (Java 25) — WebMVC, Data JPA, Security, Mail, Validation, AspectJ -- **View:** Thymeleaf + `thymeleaf-extras-springsecurity6` -- **Frontend:** Vanilla HTML/CSS/JS, Tailwind CSS 3.4 (precompiled standalone CLI), Chart.js, HTMX·Alpine.js (필요 시) -- **DB:** PostgreSQL (prod) / H2 file-mode (`local` profile, PostgreSQL 호환) -- **Auth:** Spring Security + Email OTP(2FA) + Trusted Device + Single-Session Enforcement -- **API Docs:** `springdoc-openapi` (Controller 스펙은 `controller/spec/*Spec.java` 인터페이스로 분리) -- **Local SMTP:** GreenMail 임베디드 (포트 3025, OTP 코드는 서버 로그에 `*** OTP CODE: NNNNNN ***` 로 출력) -- **Build:** Gradle Wrapper (`./gradlew`) - -## Project Structure - -```text -OsWL/ -├── src/main/ -│ ├── java/com/salkcoding/oswl/ -│ │ ├── OswlApplication.java -│ │ ├── aop/ # 공통 관점 (감사 로깅 등) -│ │ ├── auth/ # 인증·인가 모듈 (자체 패키지로 분리) -│ │ │ ├── config/ # SecurityConfig, LocalSmtpConfig 등 -│ │ │ ├── controller/ # /login, /setup, /api/admin/*, /api/settings/{security,vcs,cache} -│ │ │ ├── dto/ entity/ enums/ repository/ security/ service/ web/ -│ │ ├── client/ # 외부 API 클라이언트 (OSV, deps.dev, GitHub …) -│ │ ├── controller/ # 비즈니스 컨트롤러 (Project, SecurityCenter, License, RiskTrend …) -│ │ │ └── spec/ # 컨트롤러 스펙 인터페이스 (springdoc 어노테이션 집중) -│ │ ├── domain/entity/ # JPA 엔티티 (Project, ScanResult, Library, Cve …) -│ │ ├── domain/enums/ # ImportSource, LicenseStatus, Severity … -│ │ ├── dto/ exception/ repository/ scheduler/ service/ web/ -│ └── resources/ -│ ├── application.yaml # 공통 + profile 스위치 -│ ├── application-local.yaml # H2 file (./oswl-db), devtools, 자세한 로깅 -│ ├── application-prod.yaml # PostgreSQL -│ ├── static/{css,js,img,icon,graphic,scripts}/ -│ └── templates/ -│ ├── auth/{login,otp-verify,setup}.html -│ ├── projects/{index,quick-import,cli-integration,git-integration}.html -│ ├── security-center/{index,fragments/} -│ ├── component-detail/{index,fragments/} -│ ├── license/ risk-trend/ scan-history/ version-diff/ glossary/ -│ ├── settings/{index, tabs/{admin,security,ai,vcs,cli,cache}.html} -│ ├── error/{401,403,404,500,503,_owl-error}.html -│ └── fragments/{head,topbar,risk-graph,tags}.html -├── tailwind/{input.css, tailwind.config.js} # Tailwind 빌드 입력 -├── build.gradle / settings.gradle / gradlew -└── README.md -``` - -## 핵심 도메인 (참고용) - -- **Repository / Project / ProjectVersion** — 리포지토리(연결된 VCS) 단위로 `Project`를 생성하고, 브랜치/버전별로 `ProjectVersion`을 관리합니다. -- **Scan / ScanResult / ScanComponent / DependencyPath / DependencyGraph** — 각 스캔(수집·분석) 결과와 컴포넌트·의존성 경로, 그래프를 나타냅니다. -- **SBOM / BillOfMaterials** — 스캔 결과나 외부 연동으로부터 생성되는 SBOM(Software Bill Of Materials)을 저장·조회합니다 -- **Library / Artifact** — 공유 라이브러리(예: `group:artifact@version`)와 메타데이터를 중심으로 관리합니다. -- **Vulnerability (CVE/OSV/OSV-like)** — 취약점 엔티티(심각도, 설명, 출처, 패치 정보) 및 연관 라이브러리 매핑. -- **License / LicensePolicy / PolicyViolation** — 라이선스 정보와 정책, 정책 위반 기록 및 정책 기반 알림/차단 로직. -- **Finding / Evidence / Enrichment** — 스캔·분석에서 도출된 소결과(발견사항), 증거자료, 외부 데이터 보강(Enrichment). -- **AiSetting / ApiKey / CacheSetting / Webhook** — AI 설정, 프로젝트 CLI 키, 라이브러리 보강 캐시(`cache_settings`), 웹훅 등록·관리. -- **Auth entities:** User / Role / RoleTemplate / AuditLog / SecuritySetting / VcsConnection / TrustedDevice / OtpChallenge - -설계 원칙: 도메인은 공유 가능한 `Library` 중심, 스캔 결과는 불변(이력)으로 저장, 정책·알림은 별도 엔티티로 분리하여 재사용성을 높입니다. - -## 주요 URL 맵 (요약) - -아래는 주요 엔드포인트의 요약(REST/HTTP 기반). 세부 스펙은 `controller/spec/*Spec.java` 인터페이스에 명세합니다. - -- **Auth / Account**: `GET/POST /login`, `GET/POST /login/otp-verify`, `POST /login/otp-resend`, `GET/POST /setup`, `POST /api/auth` (토큰/CLI 인증) -- **Projects / Repositories**: `GET /projects`, `GET /projects/list`, `POST /projects` (수동 생성), `GET/PUT/DELETE /projects/{id}`, `POST /projects/{id}/restore`, `DELETE /projects/{id}/permanent`, `DELETE /projects/trash/all|selected`, `POST /projects/trash/restore-selected` -- **Project Versions / Branches**: `GET /projects/{id}/versions`, `POST /projects/{id}/versions`, `GET /projects/{projectId}/versions/{versionId}` -- **Quick Import / VCS Connections**: `GET /projects/quick-import`, `GET /api/quick-import/connections`, `POST /api/quick-import/start`, `GET /api/quick-import/job/{jobId}`, `POST /api/import/webhook` (VCS push hook) -- **VCS Providers (GitHub/GitLab/Bitbucket)**: `POST/DELETE /api/github/connect|disconnect|accounts/{login}`, `GET /api/github/{status,accounts,repos,branches,branch-updated-at}`, 유사한 경로로 GitLab/Bitbucket 지원 -- **Scans / Scan Jobs**: `POST /api/scan` (새 스캔 시작), `GET /api/scan/{scanId}/status`, `GET /api/scan/{scanId}/result`, `GET /projects/{id}/scan-history`, `DELETE /projects/{id}/scan-history/{scanId}`, `POST /api/scan/schedule` (예약 스캔), `GET /api/scan/jobs` -- **Security Center / Findings**: `GET /projects/{id}/security-center`, `PATCH /projects/{id}/security-center/bulk-status`, `GET /projects/{id}/findings`, `PATCH /projects/{id}/findings/{findingId}` -- **Component Detail / SBOM / Dependency Graph**: `GET /projects/{id}/components/{compId}` (HX-Request fragment), `GET /projects/{id}/sbom/{sbomId}`, `GET /projects/{id}/dependency-graph` -- **License / Policy**: `GET /projects/{id}/license`, `GET/PUT /api/policies/license`, `POST /api/policies/evaluate`, `GET /api/policies/violations` -- **Risk Trend / Analytics**: `GET /projects/{id}/risk-trend` (최대 `oswl.risk-trend.limit` 스캔), `GET /api/analytics/risk`, `GET /api/metrics` (애플리케이션 지표) -- **Version Diff / History**: `GET /projects/{id}/version-diff`, `GET /projects/{id}/history` -- **Glossary / Docs**: `GET /glossary`, `GET /docs/**` -- **Settings / Admin**: `GET /settings` (탭: admin/security/ai/vcs/cli/cache), `GET/PUT /api/settings/security`, `POST /api/settings/security/mail/test`, `GET/PUT /api/settings/ai`, `PUT /api/settings/ai/deactivate`, `PUT /api/settings/ai/activate/{provider}`, `GET/POST /api/settings/vcs`, `DELETE /api/settings/vcs/{id}`, `GET/PUT /api/settings/cache`, `POST /api/settings/cache/clear` -- **Admin Users / Role Templates / Audit**: `GET/POST /api/admin/users`, `PUT /api/admin/users/{id}/{roles|display-name|activate|deactivate}`, `DELETE /api/admin/users/{id}`, `GET/POST /api/admin/role-templates`, `GET /api/admin/role-templates/permissions`, `PUT/DELETE /api/admin/role-templates/{id}`, `GET /api/admin/audit-logs`, `GET /api/admin/audit-logs/export.csv` -- **AI / Enrichment / External Data**: `POST /api/ai/assist`, `GET/PUT /api/settings/ai`, `POST /api/external/enrich/{libraryId}` -- **Webhooks & Integrations**: `POST/GET/DELETE /api/webhooks`, `POST /api/webhooks/test`, `POST /api/external/push` (integration callbacks) -- **CLI Keys / API Keys**: `GET/POST /api/projects/{id}/api-keys`, `DELETE /api/projects/{id}/api-keys/{keyId}`, `GET/POST /api/admin/cli-keys`, `PATCH /api/admin/cli-keys/{keyId}/toggle` -- **CLI Public API / Scan API**: `POST /api/auth` (CLI token), `GET /api/scan/ping`, `POST /api/scan`, `GET /api/scan/{scanId}/status`, `GET /api/scan/{scanId}/result` -- **SBOM / Exports**: `GET /api/projects/{id}/exports/sbom`, `GET /api/projects/{id}/exports/findings.csv`, `POST /api/projects/{id}/exports/start` -- **Health / Observability**: `GET /actuator/health`, `GET /actuator/metrics`, `GET /actuator/prometheus` (optional) -- **Test Data (local only)**: `GET /data/test`, `GET /data/test-api-key` (local-profile only) - -참고: 컨트롤러 스펙은 `controller/spec/*Spec.java`에 집중시킵니다. - -## Code Style & Architecture - -**Backend** -- **Layered:** `Controller` → `Service` → `Repository`. Controller는 가능한 얇게. -- **Controller Spec 분리:** OpenAPI 어노테이션은 `controller/spec/*ControllerSpec.java` 인터페이스에 모은다. 구현체는 비즈니스 로직만 깔끔히. -- **DI:** Lombok `@RequiredArgsConstructor` 기반 생성자 주입. `@Autowired` 금지. -- **Authorization:** 메서드 레벨 `@PreAuthorize("hasPermission(null, 'PROJECT_VIEW') or hasRole('SYSTEM_ADMIN')")` 패턴. -- **Audit Logging:** 사용자 액션·시스템 이벤트는 `AuditLogService.log…()` 호출로 기록 (Setup, Security Setting 변경 등). -- **Profile gating:** 로컬 전용 빈은 `@Profile("local")` (예: `TestDataController`, `LocalSmtpConfig`). -- **JPA cascade:** Project → ProjectVersion/ScanResult/ScanComponent/DependencyPath, Library → Cve 가 cascade ALL. - -**Frontend** -- **Vanilla First.** 필요할 때만 HTMX(부분 갱신), Alpine.js(소규모 상태) 추가. 서버 사이드 렌더링은 Thymeleaf로. -- **Tailwind CSS:** **CDN 금지**. `/css/tailwind.css` (Gradle 빌드 산출물) 만 사용. 새 클래스를 쓰면 빌드 시 자동 포함. -- **HTMX 패턴:** `Component Detail`처럼 `HX-Request: true` 헤더 시 fragment만 반환하는 컨트롤러 분기 활용. -- **Chart.js / D3.js**: Risk Trend 등 시각화에 사용 가능. CDN 또는 webjar. -- **자산 위치:** JS는 `static/js/`, 페이지별로 하위 폴더 분리 (`projects/`, `risk-trend/`). - -## DB / 환경 - -- **로컬 DB:** H2 file (`./oswl-db.mv.db`). 콘솔: `http://localhost:8080/h2-console` (JDBC URL `jdbc:h2:file:./oswl-db`, user `sa`). `local`은 `ddl-auto: update`. -- **운영 DB:** PostgreSQL, `ddl-auto: validate`. 업그레이드 시 `src/main/resources/db/*.sql` 수동 실행 (`schema_cleanup.sql` 등). 문서: `docs/Database-Schema.md`. -- **DB 초기화:** 서버 정지 후 `oswl-db.*` 파일 삭제 → 재기동 시 빈 상태에서 시작 (Setup 화면으로 진입). -- **테스트 데이터 시드:** 로컬에서 `GET /data/test` 호출 (인증 필요). 모든 기존 데이터를 지우고 풍부한 케이스로 재시드. -- **암호화 키 (`OSWL_ENCRYPTION_KEY`):** 로컬은 더미 키가 박혀 있고, 운영은 `openssl rand -base64 32` 로 생성한 32바이트 키 필수. -- **OTP 메일:** 로컬 프로파일은 GreenMail이 자동 기동되어 모든 메일 가로챔. **OTP 코드는 서버 로그의 `*** OTP CODE: NNNNNN ***` 라인에서 확인** (실제 메일 발송 X). - -## Build & Run - -```powershell -./gradlew bootRun # 로컬 기동 (기본 profile=local, 포트 8080) -./gradlew build # 풀 빌드 (Tailwind 포함) -./gradlew test # JUnit Platform 테스트 -./gradlew buildTailwindCss # Tailwind만 재빌드 -``` - -## Conventions - -- **DB 명명:** snake_case 테이블/컬럼, 엔티티 PK는 `Long id`. -- **DTO:** `dto/` 패키지에 record 또는 Lombok `@Value` 형태 권장. -- **의존성 추가:** 빌드 시 자동 다운로드 가능한 라이브러리는 `build.gradle` 추가, 프론트는 CDN 또는 webjars 우선. -- **에러 페이지:** `error/{401,403,404,500,503}.html` — 공통 부엉이 일러스트 (`_owl-error.html` fragment) 사용. -- **로그 톤:** 비즈니스 INFO/WARN/ERROR · 디테일은 `log.debug` (예: `client`, `VulnerabilityEnrichmentService`). diff --git a/.github/prompts/qa.prompt.md b/.github/prompts/qa.prompt.md deleted file mode 100644 index c3656dba..00000000 --- a/.github/prompts/qa.prompt.md +++ /dev/null @@ -1,293 +0,0 @@ ---- -agent: agent -description: OsWL 풀 BVT(Build Verification Test) 자동 실행 — DB 초기화부터 모든 기능 체크까지 브라우저 자동화로 검증. ---- - -# OsWL Full BVT — QA Runner - -사용자가 **"QA 진행"**, **"BVT 돌려"**, **"풀 테스트"** 또는 `/qa` 슬래시 커맨드로 이 프롬프트를 호출하면, 아래 절차를 **그대로** 자동 수행한다. 사용자에게 추가 확인을 묻지 않고 바로 시작한다. - -> 호출되는 도구: `run_in_terminal`, `open_browser_page`, `navigate_page`, `click_element`, `type_in_page`, `screenshot_page`, `get_terminal_output`, `read_page`, `run_playwright_code`. 필요 시 `tool_search`로 미리 로드한다. - ---- - -## 0. 사전 준비 (Setup) - -1. **서버 상태 확인** - - `Invoke-WebRequest http://localhost:8080/login -UseBasicParsing -TimeoutSec 3` 으로 핑 → 200/302면 **이미 켜진 상태**. - - 켜져 있으면 → **기존 서버 종료 필요**. 사용자에게 1회 확인 후 종료 (`Stop-Process` 로 `bootRun` 프로세스 kill, 또는 `gradlew --stop`). -2. **DB 완전 초기화** (워크스페이스 루트에서) - ```powershell - if (Test-Path "oswl-db.mv.db") { Remove-Item "oswl-db.mv.db" -Force } - if (Test-Path "oswl-db.trace.db") { Remove-Item "oswl-db.trace.db" -Force } - if (Test-Path "oswl-db.lock.db") { Remove-Item "oswl-db.lock.db" -Force } - ``` -3. **서버 기동** (async 모드, 시작 로그 대기) - ```powershell - ./gradlew bootRun - ``` - `Started OswlApplication` 로그가 보일 때까지 `get_terminal_output`으로 폴링 (최대 3분). -4. **브라우저 오픈**: `http://localhost:8080` → 자동으로 `/setup`로 리다이렉트 되어야 함 (DB 비어 있으니 최초 어드민 설정 화면). - -### QA 자격 증명 (테스트 전용 — 매 QA마다 동일) - -| 항목 | 값 | -|------|---| -| Email | `qa@oswl.local` | -| Password | `Qa!Test1234` | -| Display Name | `QA Tester` | - -### OTP 코드 추출 방법 -- 로컬 SMTP는 GreenMail. 메일은 발송되지 않고 **서버 로그에 `*** OTP CODE: NNNNNN ***` 형식으로 출력**됨. -- `get_terminal_output`(서버 터미널 ID)에서 가장 최근 OTP 라인을 정규식 `\*\*\* OTP CODE: (\d{6})` 로 파싱한다. - -### 테스트 데이터 시드 -- 로그인 후 `GET http://localhost:8080/data/test` 호출 → 모든 데이터 리셋 후 풍부한 테스트 셋 생성. **반드시 P-01 진입 직전에 1회 실행**. -- CLI 토큰이 필요하면 `GET /data/test-api-key` 로 발급받은 `oswl_xxx` 토큰을 그대로 사용. - -### 시드 후 추가 QA 계정 (권한·프로젝트 ACL 검증용, 비밀번호 모두 `Qa!Test1234`) -| Email | 역할 템플릿 | 용도 | -|-------|-------------|------| -| `viewer@oswl.local` | Viewer | 읽기 전용, `frontend-dashboard`·`ml-pipeline` 멤버 | -| `developer@oswl.local` | Developer | `backend-api` ADMIN + B/E 멤버, SC 상태 변경·CLI 권한 | -| `noproject@oswl.local` | Viewer | 프로젝트 멤버십 없음 → 빈 프로젝트 목록 | -| `disabled@oswl.local` | Viewer | 비활성 계정 (SA-03) | -| `custom@oswl.local` | QA Custom | SC 상태 변경 불가, `ml-pipeline`만 멤버 | - ---- - -## 1. 실행 규칙 - -- **순차 실행**: 각 케이스를 표 순서대로 진행. 케이스 사이에 페이지 이동 후 DOM 안정 대기. -- **결과 기록**: 케이스마다 `PASS / FAIL / SKIP(사유)` 표시. FAIL시 스크린샷 1장 + 콘솔/네트워크 에러 짧게 캡처. -- **스킵 허용 케이스 (외부 의존):** - - **P-03 Quick Import 실제 임포트** — GitHub PAT 필요, **모달 UI까지만** 검증하고 PAT 입력 단계는 SKIP(REASON: PAT 미주입). - - **QI-01~11 실제 클론·스캔** — PAT·VCS 연결이 없으면 아래 표의 **첫 1~2개 소형 레포만** 시도하거나 전부 SKIP(REASON: PAT 미주입). - -### Quick Import QA 레포 (에코시스템별 1개 — **소형·얕은 클론용**) - -> **절대 사용 금지** (클론 실패·Windows 경로 길이·수십 분 소요): -> `dotnet/maui`, `rails/rails`, `kubernetes/kubernetes`, `dotnet/runtime`, `microsoft/vscode` 등 대형 모노레포. - -| 에코시스템 | 매니페스트 | 레포 URL | 비고 | -|---|---|---|---| -| Maven | `pom.xml` | https://github.com/spring-projects/spring-petclinic | ~23 컴포넌트 | -| Gradle | `gradlew` | https://github.com/Netflix/dgs-framework | Gradle + lock | -| npm v2 | `package-lock.json` | https://github.com/expressjs/express | lock v2 | -| npm v3 | `package-lock.json` | https://github.com/koajs/koa | lock v3 | -| Python | `poetry.lock` | https://github.com/Textualize/rich | | -| Python | `requirements.txt` | https://github.com/pallets/flask | | -| Python | `Pipfile.lock` | https://github.com/pypa/pipenv | | -| Cargo | `Cargo.lock` | https://github.com/BurntSushi/ripgrep | | -| Go | `go.sum` | https://github.com/gin-gonic/gin | | -| NuGet | `.csproj` | https://github.com/ardalis/CleanArchitecture | **MAUI 대신** — 소형 샘플 (~3MB) | -| Ruby | `Gemfile.lock` | https://github.com/sinatra/sinatra | **rails 대신** — 소형 | - -- 한 번에 **전부** 돌리지 말 것. PAT QA 시 **에코시스템당 1개** 또는 **Maven + npm 1개**만 스모크로 충분. -- NuGet 검증은 `ardalis/CleanArchitecture` 또는 `jasontaylordev/CleanArchitecture`만 사용. `dotnet/maui`는 테스트 불가. - - **SS-03 메일 테스트** — GreenMail로 송신 자체는 가능. 응답 200만 확인. - - **AI-06 Test Connection** — 실제 API 키 없으니 버튼 활성/비활성 토글만 확인, 호출은 SKIP. -- **DB 검증**이 필요한 케이스(예: A-07 단일 세션)는 새 incognito context를 열어 동일 계정으로 로그인 후, 첫 세션 페이지 새로고침 시 `/login?displaced=true` 로 튕기는지 확인. -- 테스트 종료 후 **최종 리포트**를 마크다운 표로 출력. 합계: `PASS X / FAIL Y / SKIP Z (총 N)`. - ---- - -## 2. BVT 체크리스트 (총 ~95) - -### 🔐 인증 (Auth) -| # | 케이스 | 절차 / 확인 | -|---|--------|-----| -| A-00 | Setup 화면 | DB 빈 상태에서 `/` → `/setup` 리다이렉트, 폼 표시 | -| A-01 | 최초 어드민 생성 | QA 자격증명으로 폼 제출 → `/login?setup` | -| A-02 | 로그인 폼 | `/login` GET — 이메일/비밀번호 입력 필드 | -| A-03 | 로그인 실패 | 잘못된 비번 → 에러 메시지, 폼 재표시 | -| A-04 | 로그인 성공 (1차) | 올바른 자격증명 → `/login/otp-verify` 리다이렉트 | -| A-05 | OTP 페이지 | 6자리 입력 폼, 마스킹된 이메일(`qa@…local`), 만료 카운트다운 표시 | -| A-06 | OTP 타이머 확인 | 타이머가 정상작동하는지 시작 시, 그리고 재전송 이후도 5분으로 초기화 되는지 확인 | -| A-07 | OTP 실패 | 잘못된 코드 → 에러 메시지, 재입력 가능 | -| A-08 | OTP 재발송 | "Resend" 클릭 → 200, 새 OTP 로그 출력 | -| A-09 | OTP 성공 | 서버 로그에서 코드 추출 → 입력 → `/projects` | -| A-10 | 단일 세션 | 새 브라우저 컨텍스트로 동일 계정 로그인 → 첫 세션이 `/login?displaced=true&from=…` 로 튕기는지, 앰버 알림 표시 | -| A-11 | 로그아웃 | 우상단 메뉴 → Logout → `/login?logout` | - -### 📁 Projects (시드 후 진행) -| # | 케이스 | 확인 | -|---|--------|------| -| P-00 | 테스트 데이터 시드 | `/data/test` 호출 → redirect, Active 5개 (`backend-api`, `frontend-dashboard`, `ml-pipeline`, `new-service`, `payment-gateway`), Trash 1개 (`legacy-monolith`) | -| P-01 | 목록 렌더링 | Active/Trash 탭 표시. 카드: 이름/마지막 스캔/Security Risk(C/H/M/L/U)/License Risk(R/C/P/U) | -| P-02 | CLI Integration 모달 | "CLI Integration" 버튼 → 모달 오픈, install/auth/ping/scan 명령 표시, `--username/--password/--project` 인자 표시 | -| P-03 | Quick Import 모달 (UI) | "Quick Import" → 슬라이드아웃 진입, VCS 연결·URL 입력·진행 카드 UI (실제 클론은 QI-* 또는 SKIP) | -| QI-01 | Quick Import 스모크 (선택) | PAT 있을 때 위 표에서 **spring-petclinic 1건만** 임포트 → `DONE` 또는 `FAILED`까지 (5분 이내) | -| P-04 | 프로젝트 액션 메뉴 | 카드 우측 ⋯ 버튼 → Rename / Move to Trash / Delete | -| P-05 | Move to Trash | 한 프로젝트 → Trash → Active에서 사라지고 Trash 탭에 등장 | -| P-06 | Restore | Trash 탭에서 Restore → 다시 Active | -| P-07 | Permanent Delete | Trash → Permanently Delete → DB에서 완전 제거 (목록에서 사라짐) | -| P-08 | Bulk Restore | Trash에 여러 개 두고 다중 선택 → restore-selected | -| P-09 | Bulk Permanent Delete | trash/selected 다중 영구삭제 | -| P-10 | Trash 비우기 | trash/all → 모든 휴지통 비움 | -| P-11 | JSON list 엔드포인트 | `GET /projects/list` (Accept: json) → 200, 배열 응답 | - -### 🛡️ Security Center -| # | 케이스 | 확인 | -|---|--------|------| -| SC-01 | 페이지 로드 | `/projects/{id}/security-center` — 상단 요약(C/H/M/L/U), 컴포넌트 테이블 | -| SC-02 | 버전 드롭다운 | Topbar 버전 버튼 → 드롭다운, 5개↑ 시 스크롤, 선택 시 해당 scan 전환 | -| SC-03 | 검색 | 컴포넌트명 부분 일치 검색 (필터 미적용 상태에서도 동작) | -| SC-04 | 리스크 필터 | C/H/M/L/U 체크박스 토글 → 행 수 변동 | -| SC-05 | Status 필터 | Reviewed / Not Reviewed / Ignored 체크박스 | -| SC-06 | 컴포넌트 클릭 | 행 클릭 → `/components/{compId}` 이동 | -| SC-07 | Bulk Actions | 다중 체크 → 버튼 활성 → Mark Reviewed / Unreviewed / Ignore / Unignore (PATCH bulk-status) | -| SC-08 | Export PDF/CSV | 드롭다운 → 두 옵션 표시 (다운로드 트리거) | -| SC-09 | Defer (Exception) | Defer 버튼 → 모달: 사유/만료일/검토자/PR 설명 | -| SC-10 | Review 토글 | 단일 행 체크박스 → Reviewed 토글 | -| SC-11 | Version Diff 링크 | 버전 드롭다운 하단 "Version Diff..." 링크 동작 | - -### 📦 Component Detail -| # | 케이스 | 확인 | -|---|--------|------| -| CD-01 | 페이지 로드 | 라이브러리명/버전/생태계 표시 | -| CD-02 | CVE 목록 | CVE ID, 심각도, CVSS, 설명, 패치 버전 | -| CD-03 | 라이선스 정보 | 라이선스명/리스크/의무사항 | -| CD-04 | AI 요약 영역 | AI 미설정 시 "Configure AI" 안내 또는 영역 비활성 | -| CD-05 | 사용 프로젝트 수 | "N Projects" 라벨 | -| CD-06 | HTMX fragment | `curl -H "HX-Request: true"` → fragment HTML만 반환 | - -### 📄 License -| # | 케이스 | 확인 | -|---|--------|------| -| L-01 | 페이지 로드 | `/projects/{id}/license` — 라이선스 그룹별 카드, R/C/P/U 표시 | -| L-02 | 버전 선택 | scanId 쿼리로 다른 스캔 라이선스 조회 | -| L-03 | 의무사항 | 라이선스별 의무사항 카운트 | - -### 📈 Risk Trend -| # | 케이스 | 확인 | -|---|--------|------| -| RT-01 | 페이지 로드 | `/projects/{id}/risk-trend` — 최근 N개 스캔(`oswl.risk-trend.limit=10`) | -| RT-02 | Security 차트 | Chart.js 라인 차트 렌더 | -| RT-03 | License 차트 | 동일 | -| RT-04 | 델타 표시 | 직전 스캔 대비 증감 뱃지 | - -### 🔀 Version Diff -| # | 케이스 | 확인 | -|---|--------|------| -| VD-01 | From/To 선택 | `/projects/{id}/version-diff` 드롭다운 | -| VD-02 | 요약 카드 | Added / Removed / Updated / New Threats 카운트 | -| VD-03 | New Threats(추가) | 새 컴포넌트 + Risk 있음 → New Threat | -| VD-04 | New Threats(격상) | 기존 컴포넌트 Risk 격상 → New Threat | -| VD-05 | New Threats 탭 | 해당 컴포넌트만 표시 | -| VD-06 | Updated 탭 | 버전 변경 컴포넌트 | -| VD-07 | Added 탭 | 새 추가(Risk 없음) | -| VD-08 | Removed 탭 | 제거된 컴포넌트 | - -### 🕐 Scan History -| # | 케이스 | 확인 | -|---|--------|------| -| SH-01 | 목록 | 버전/날짜/상태(SUCCESS/FAILED/PENDING)/컴포넌트 수 | -| SH-02 | 개별 스캔 삭제 | DELETE → 204 | - -### 📖 Glossary -| # | 케이스 | 확인 | -|---|--------|------| -| G-01 | 페이지 로드 | `/glossary` 용어 목록 | - -### ⚙️ Settings — Administration -| # | 케이스 | 확인 | -|---|--------|------| -| SA-01 | 사용자 목록 | 이름/이메일/역할/상태/마지막 로그인 | -| SA-02 | Invite User | 모달 → 신규 사용자 생성 | -| SA-03 | Activate / Deactivate | 상태 토글 | -| SA-04 | Edit (display name / roles) | PUT 호출 성공 | -| SA-05 | Delete User | DELETE 후 목록에서 제거 | -| SA-06 | Permission Templates 탭 | 역할 템플릿/권한 목록 | -| SA-07 | Template CRUD | 생성/수정/삭제 | -| SA-08 | Audit Log 탭 | 시간/Actor/IP/Action/Target/Detail 컬럼 | -| SA-09 | Audit Log 필터 | Last 7/30/90/All, All Users, All Actions | -| SA-10 | Audit Log Export CSV | `/api/admin/audit-logs/export.csv` 다운로드 | - -### ⚙️ Settings — Security -| # | 케이스 | 확인 | -|---|--------|------| -| SS-01 | 탭 로드 | OTP/세션 정책/비밀번호 정책 표시 | -| SS-02 | 저장 | PUT → 200, Audit Log에 `SECURITY_SETTING.UPDATE` | -| SS-03 | Test Mail | "Test Mail" 클릭 → 200, GreenMail 로그에 메일 도착 | - -### ⚙️ Settings — AI -| # | 케이스 | 확인 | -|---|--------|------| -| AI-01 | 탭 로드 | 5옵션: Do not use / OpenAI / Anthropic / Google / Local | -| AI-02 | OpenAI 입력 | API Key / Endpoint(opt) / Model 드롭다운 | -| AI-03 | Anthropic 입력 | API Key / Model | -| AI-04 | Google 입력 | API Key / Model | -| AI-05 | Local/Ollama | Base URL / Model (API Key 없음) | -| AI-06 | Test Connection | 버튼 활성 토글 (실제 호출 SKIP) | -| AI-07 | Save (PUT /api/settings/ai) | 200 | -| AI-08 | Activate Provider | `PUT /api/settings/ai/activate/{provider}` | -| AI-09 | Deactivate | `PUT /api/settings/ai/deactivate` | - -### ⚙️ Settings — VCS -| # | 케이스 | 확인 | -|---|--------|------| -| VC-01 | 탭 로드 | 연결 목록 | -| VC-02 | 연결 추가 | POST 200 (잘못된 토큰은 PAT 검증 단계에서 실패 허용 — UI 흐름만) | -| VC-03 | 연결 삭제 | DELETE 204 | - -### ⚙️ Settings — CLI Keys -| # | 케이스 | 확인 | -|---|--------|------| -| CK-01 | 글로벌 키 목록 | 모든 프로젝트의 키 노출 | -| CK-02 | 키 발급 | POST → 응답 1회만 `oswl_xxx` 평문 표시 | -| CK-03 | 키 토글 | PATCH /toggle → 활성/비활성 변경 | - -### ⚙️ Settings — Cache (라이브러리 보강 캐시 — deps.dev + OSV) -| # | 케이스 | 확인 | -|---|--------|------| -| CC-01 | 탭 로드 | Custom TTL / Always Refresh / Permanent 옵션 | -| CC-02 | Custom TTL 입력 | 숫자 + hour(s)/day(s) 단위 | -| CC-03 | Save | `PUT /api/settings/cache` → 200 | -| CC-04 | Cache Clear | `POST /api/settings/cache/clear` → 200 | - -> `/api/settings/external` 및 `external_api_settings`는 제거됨. 캐시는 위 Cache 탭/API만 사용. - -### 🔌 CLI 공개 API (oswl_xxx 토큰 사용) -> `GET /data/test-api-key` 로 발급받은 토큰을 `Authorization: Bearer oswl_xxx` 헤더로 호출. - -| # | 케이스 | 확인 | -|---|--------|------| -| CLI-01 | Ping | `GET /api/scan/ping` → `{ status, projectId }` | -| CLI-02 | Scan 제출 | `POST /api/scan` (의존성 페이로드) → `{ scanId, version, status }` | -| CLI-03 | 스캔 상태 | `GET /api/scan/{scanId}/status` → `{ scanId, status, componentCount }` | -| CLI-04 | CLI 인증 | `POST /api/auth` (이메일/비번) → `{ token }` | - -### ❌ 에러 페이지 -| # | 케이스 | 확인 | -|---|--------|------| -| E-01 | 404 | 존재하지 않는 URL → 부엉이 일러스트 + "Go to Home" | -| E-02 | 403 | 비권한 사용자로 admin 페이지 접근 (또는 `/error/403`) | -| E-03 | 401 | 비로그인 상태 보호 페이지 접근 | -| E-04 | 500 | `/error/500` 직접 → 페이지 렌더 | -| E-05 | 503 | `/error/503` 직접 → 페이지 렌더 | - ---- - -## 3. 최종 리포트 형식 - -``` -## OsWL BVT Report — - -| Section | PASS | FAIL | SKIP | -|---------|------|------|------| -| Auth (A) | x/11 | … | … | -| Projects (P) | … | … | … | -| Security Center (SC) | … | … | … | -| ... 이하 동일 ... | - -### FAILURES -- **<케이스 ID>**: 한 줄 원인 요약. 스크린샷: <경로> - -### SKIPPED -- **<케이스 ID>**: 사유 - -**TOTAL: PASS X / FAIL Y / SKIP Z (총 N)** -``` - -리포트 출력 후 서버는 **켜진 상태로 유지** (다음 디버깅을 위해). 사용자가 종료를 요청하면 `gradlew --stop` 으로 정리. diff --git a/.github/scripts/prepare-pages-site.sh b/.github/scripts/prepare-pages-site.sh index 33e644c6..dc3c546e 100644 --- a/.github/scripts/prepare-pages-site.sh +++ b/.github/scripts/prepare-pages-site.sh @@ -39,6 +39,7 @@ test -f _site/index.html test -f _site/landing-i18n.js test -f _site/i18n/en.json test -f _site/i18n/ko.json +test -f _site/i18n/ja.json test -f _site/css/tailwind.css test -f _site/icon/icon-logo.svg test -f _site/graphic/symbol_w.svg diff --git a/.github/scripts/prepare-wiki.py b/.github/scripts/prepare-wiki.py new file mode 100644 index 00000000..7b0dadf2 --- /dev/null +++ b/.github/scripts/prepare-wiki.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +"""Prepare English Markdown for the Wiki without changing repository sources. + +Uses only the Python standard library. Run from any directory: + python .github/scripts/prepare-wiki.py owner/repo [--ref main] +""" + +import argparse +from pathlib import Path +import re +import subprocess +from urllib.parse import quote, unquote, urlsplit, urlunsplit + + +ROOT = Path(__file__).resolve().parents[2] +SOURCE = ROOT / 'docs/en' +OUTPUT = ROOT / 'build/wiki' +# Match code spans first so literal Markdown examples remain unchanged. +INLINE = re.compile( + r'(?P(?P`+).*?(?P=ticks))' + r'|(?P!?\[[^\n]*?\]\()(?P<[^>]+>|[^\s)]+)' +) +REFERENCE = re.compile(r'^(?P {0,3}\[[^]]+\]:\s*)(?P<[^>]+>|\S+)') +FENCE = re.compile(r'^ {0,3}(`{3,}|~{3,})') + + +def rewrite_url(url, source, repository, ref): + angled = url.startswith('<') and url.endswith('>') + value = url[1:-1] if angled else url + parts = urlsplit(value) + if parts.scheme or parts.netloc or not parts.path: + return url + base = ROOT if parts.path.startswith('/') else source.parent + target = (base / unquote(parts.path).lstrip('/')).resolve() + if not target.is_relative_to(ROOT): + raise ValueError(f'{source.name}: link escapes repository: {url}') + # Support the traditional extensionless Wiki links in a sidebar as well. + if not target.exists() and target.with_suffix('.md').is_file(): + target = target.with_suffix('.md') + if not target.exists(): + raise ValueError(f'{source.name}: missing link target: {url}') + if target.parent == SOURCE and target.suffix == '.md': + path = quote(target.stem) + else: + kind = 'tree' if target.is_dir() else 'blob' + relative = quote(target.relative_to(ROOT).as_posix(), safe='/') + path = f'https://github.com/{repository}/{kind}/{quote(ref, safe="")}/{relative}' + rewritten = urlunsplit(('', '', path, parts.query, parts.fragment)) + return f'<{rewritten}>' if angled else rewritten + + +def prepare(text, source, repository, ref): + fence = None + output = [] + + def replace(match): + if match.re is INLINE and match['code'] is not None: + return match[0] + return match['prefix'] + rewrite_url(match['url'], source, repository, ref) + + for line in text.splitlines(keepends=True): + marker = FENCE.match(line) + if fence: + if marker and marker[1][0] == fence[0] and len(marker[1]) >= len(fence) and not line[marker.end():].strip(): + fence = None + output.append(line) + continue + if marker: + fence = marker[1] + output.append(line) + continue + if line.startswith((' ', '\t')): + output.append(line) + continue + output.append(REFERENCE.sub(replace, line) if REFERENCE.match(line) else INLINE.sub(replace, line)) + return ''.join(output) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('repository', help='GitHub owner/repository') + parser.add_argument('--ref', default='main', help='Repository ref used for non-Wiki links') + args = parser.parse_args() + if not re.fullmatch(r'[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+', args.repository): + parser.error('repository must be owner/repository') + # Exclude local-only documents retained outside version control. + tracked = subprocess.check_output(['git', 'ls-files', '--cached', '--others', '--exclude-standard', + 'docs/en/*.md'], cwd=ROOT, text=True).splitlines() + sources = sorted({ROOT / name for name in tracked if (ROOT / name).is_file()}) + names = {source.name for source in sources} + if not {'Home.md', '_Sidebar.md'} <= names or 'README.md' in names: + parser.error('English docs must include Home.md and _Sidebar.md, without a competing README.md') + # Resolve all links before writing any output, so a broken source fails publishing. + prepared = {source.name: prepare(source.read_text(encoding='utf-8'), source, args.repository, args.ref) + for source in sources} + if OUTPUT.is_symlink() or OUTPUT.resolve() != ROOT / 'build/wiki': + parser.error('Wiki output must be the repository build/wiki directory') + OUTPUT.mkdir(parents=True, exist_ok=True) + for existing in OUTPUT.iterdir(): + if existing.is_symlink() or not existing.is_file() or existing.suffix != '.md': + parser.error(f'unexpected file in Wiki output: {existing.name}') + for existing in OUTPUT.glob('*.md'): + if existing.name not in prepared: + existing.unlink() + for name, text in prepared.items(): + (OUTPUT / name).write_text(text, encoding='utf-8', newline='\n') + print(f'Prepared {len(prepared)} English Wiki pages in {OUTPUT}') + + +if __name__ == '__main__': + main() diff --git a/.github/scripts/validate-release.py b/.github/scripts/validate-release.py new file mode 100644 index 00000000..c0840a8a --- /dev/null +++ b/.github/scripts/validate-release.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +"""Validate release inputs without publishing or changing Git references.""" +import argparse +import json +from pathlib import Path +import re + +ROOT = Path(__file__).resolve().parents[2] + +def keys(value, prefix=''): + return {prefix + k for k, v in value.items() if not isinstance(v, dict)} | set().union( + *(keys(v, prefix + k + '.') for k, v in value.items() if isinstance(v, dict)), set()) + +def properties(path): + result = set() + for line in path.read_text(encoding='utf-8').splitlines(): + if '=' not in line or line.lstrip().startswith(('#', '!')): + continue + key = line.split('=', 1)[0].strip() + if key in result: + raise ValueError(f'{path.name}: duplicate key {key}') + result.add(key) + return result + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--tag') + parser.add_argument('--notes-output', type=Path) + args = parser.parse_args() + match = re.search(r"^version = '([0-9]+(?:\.[0-9]+){2,3})'$", (ROOT / 'build.gradle').read_text(encoding='utf-8'), re.M) + if not match: + parser.error('build.gradle must declare a three- or four-part numeric release version') + version = match.group(1) + if args.tag and args.tag != 'v' + version: + parser.error(f'tag {args.tag!r} does not match build version v{version}') + templates = ROOT / 'src/main/resources/templates' + for template in templates.rglob('*.html'): + for asset_version in re.findall(r"\(v='([^']+)'\)", template.read_text(encoding='utf-8')): + if asset_version != version: + parser.error(f'{template.relative_to(ROOT)}: asset cache version differs') + landing = (ROOT / 'landing/index.html').read_text(encoding='utf-8') + if f'data-i18n="hero.badge">OsWL {version}' not in landing: + parser.error('landing fallback release version differs') + sarif = (ROOT / 'src/main/java/com/salkcoding/oswl/service/reporting/SarifExportService.java').read_text(encoding='utf-8') + if f'driver.put("version", "{version}")' not in sarif: + parser.error('SARIF tool version does not match build.gradle') + changelog = (ROOT / 'CHANGELOG.md').read_text(encoding='utf-8') + match = re.search(r'^## \[' + re.escape(version) + r'\][^\n]*\n(.*?)(?=^## |\Z)', changelog, re.M | re.S) + if not match: + parser.error(f'CHANGELOG.md has no entry for {version}') + notes = match.group(1).strip() + # Release bodies are read on GitHub, not relative to the repository root. + notes = re.sub(r'\]\((docs/[^)]+)\)', lambda m: f'](https://github.com/SalkCoding/Oswl/blob/v{version}/{m.group(1)})', notes) + base_keys = None + ui_keys = None + for lang, suffix in [('en', ''), ('ko', '_ko'), ('ja', '_ja')]: + data = json.loads((ROOT / f'landing/i18n/{lang}.json').read_text(encoding='utf-8')) + current = keys(data) + if base_keys is not None and current != base_keys: + parser.error(f'landing {lang} translation keys differ') + base_keys = current + if data['hero']['badge'] != 'OsWL ' + version: + parser.error(f'landing {lang} release version differs') + current = properties(ROOT / f'src/main/resources/messages{suffix}.properties') + if ui_keys is not None and current != ui_keys: + parser.error(f'application {lang} translation keys differ') + ui_keys = current + if not (ROOT / f'docs/{lang}/Whats-New-v{version}.md').is_file(): + parser.error(f'missing {lang} release notes') + if args.notes_output: + args.notes_output.parent.mkdir(parents=True, exist_ok=True) + args.notes_output.write_text(notes + '\n', encoding='utf-8') + print(f'Release inputs validated: v{version}; English/Korean/Japanese keys match') + +if __name__ == '__main__': + main() diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index b65ffbb4..e7f2164f 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -1,8 +1,11 @@ name: CI/CD on: + pull_request: + branches: [main] push: branches: [main] + tags: ['v[0-9]*'] workflow_dispatch: concurrency: @@ -10,9 +13,7 @@ concurrency: cancel-in-progress: true permissions: - contents: write - pull-requests: write - issues: write + contents: read env: GRADLE_OPTS: -Dorg.gradle.daemon=false -Djava.net.preferIPv4Stack=true @@ -36,82 +37,76 @@ jobs: - name: Validate Gradle wrapper uses: gradle/actions/wrapper-validation@v6 + - name: Validate release version, notes, and translation keys + run: python3 .github/scripts/validate-release.py + - name: Build, test, and verify production JAR run: ./gradlew --no-daemon clean test bootJar verifyProdJar - - name: Verify THIRD_PARTY_LICENSES.md version sync (H3) - # Catches a build.gradle dependency bump whose THIRD_PARTY_LICENSES.md **Version:** - # lines were never re-synced (./gradlew syncThirdPartyLicenseVersions). This workflow - # is push-triggered on main only (no PR trigger configured), so this catches drift - # that already landed on main rather than blocking it pre-merge — still useful as a - # release-please gate: a broken doc sync fails the build before release-please runs. + - name: Check cluster result predicates without starting servers + run: | + python3 -m unittest discover -s scripts/verification -p test_cluster_assertions.py + bash -n scripts/verification/verify-multi-instance.sh + + - name: Verify THIRD_PARTY_LICENSES.md version sync run: | ./gradlew --no-daemon syncThirdPartyLicenseVersions git diff --exit-code THIRD_PARTY_LICENSES.md - release-please: - name: Version & Release - needs: build + ui: + name: Browser smoke and accessibility runs-on: ubuntu-latest - outputs: - release_created: ${{ steps.release.outputs.release_created }} - tag_name: ${{ steps.release.outputs.tag_name }} - version: ${{ steps.release.outputs.version }} steps: - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 with: - fetch-depth: 0 - - - name: Release Please - id: release - uses: googleapis/release-please-action@v4 + distribution: temurin + java-version: '25' + cache: gradle + - name: Run browser checks + run: ./gradlew --no-daemon uiTest --tests '*SmokeUiTest' --tests '*KeyboardFlowUiTest' --tests '*AxeAuditUiTest' --tests '*RequestLifecycleUiTest' --tests '*QuickImportSourceReachabilityUiTest' --tests '*SettingsModulesUiTest' --tests '*SettingsFailureUiTest' --tests '*UiStateMatrixUiTest' --tests '*OnboardingFailureUiTest' --tests '*DetectionCoverageUiTest' --tests '*EmbeddedAiFailureUiTest' --tests '*OrganizationWorkflowUiTest' --tests '*AuthExpiryUiTest' + - name: Upload browser reports + if: always() + uses: actions/upload-artifact@v4 with: - token: ${{ secrets.GITHUB_TOKEN }} - config-file: release-please-config.json - manifest-file: .release-please-manifest.json + name: ui-reports + path: build/reports/tests/uiTest publish: - name: Publish JAR - needs: [build, release-please] - if: needs.release-please.outputs.release_created == 'true' + name: Publish tagged release + needs: [build, ui] + if: startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest permissions: contents: write steps: - - name: Checkout release tag - uses: actions/checkout@v4 - with: - ref: ${{ needs.release-please.outputs.tag_name }} - - - name: Set up JDK 25 - uses: actions/setup-java@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 with: distribution: temurin java-version: '25' cache: gradle - + - name: Validate exact tag and prepare release notes + run: python3 .github/scripts/validate-release.py --tag "$GITHUB_REF_NAME" --notes-output build/release-notes.md - name: Build production JAR run: ./gradlew --no-daemon bootJar verifyProdJar - - - name: Resolve JAR path - id: jar - run: | - VERSION=$(./gradlew -q properties | awk '/^version:/ {print $2}') - JAR_PATH="build/libs/oswl-${VERSION}.jar" - if [ ! -f "$JAR_PATH" ]; then - echo "::error::JAR not found at ${JAR_PATH}" - ls -la build/libs/ || true - exit 1 - fi - echo "path=${JAR_PATH}" >> "$GITHUB_OUTPUT" - echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - - - name: Upload JAR to GitHub Release + - name: Publish JAR and checksum env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - gh release upload "${{ needs.release-please.outputs.tag_name }}" \ - "${{ steps.jar.outputs.path }}" \ - --clobber - echo "Uploaded oswl-${{ steps.jar.outputs.version }}.jar to ${{ needs.release-please.outputs.tag_name }}" + set -euo pipefail + VERSION="${GITHUB_REF_NAME#v}" + JAR="build/libs/oswl-${VERSION}.jar" + test -f "$JAR" + (cd build/libs && sha256sum "oswl-${VERSION}.jar" > SHA256SUMS) + if gh release view "$GITHUB_REF_NAME" --json isDraft --jq '.isDraft' > build/release-is-draft; then + if [ "$(cat build/release-is-draft)" != "true" ]; then + echo "Release is already published; existing assets will not be overwritten." + exit 0 + fi + else + gh release create "$GITHUB_REF_NAME" --verify-tag --draft \ + --title "OsWL ${VERSION}" --notes-file build/release-notes.md + fi + gh release upload "$GITHUB_REF_NAME" "$JAR" build/libs/SHA256SUMS --clobber + gh release edit "$GITHUB_REF_NAME" --draft=false --latest diff --git a/.github/workflows/wiki-sync.yml b/.github/workflows/wiki-sync.yml index ba6dd822..5a49e7e2 100644 --- a/.github/workflows/wiki-sync.yml +++ b/.github/workflows/wiki-sync.yml @@ -4,10 +4,15 @@ on: push: branches: [main] paths: - - 'docs/**' - - '!docs/ko/**' + - 'docs/en/**' + - '.github/scripts/prepare-wiki.py' - '.github/workflows/wiki-sync.yml' workflow_dispatch: + inputs: + dry_run: + description: 'Preview the Wiki commit without publishing' + type: boolean + default: true concurrency: group: wiki-sync-${{ github.ref }} @@ -27,13 +32,17 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Prepare English Wiki pages + env: + WIKI_REPOSITORY: ${{ github.repository }} + run: python3 .github/scripts/prepare-wiki.py "$WIKI_REPOSITORY" + - name: Sync to GitHub Wiki uses: Andrew-Chen-Wang/github-wiki-action@v5.0.4 with: - path: docs/ + path: build/wiki/ strategy: clone - preprocess: true + preprocess: false + dry-run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }} disable-empty-commits: true commit-message: "wiki: sync from ${{ github.sha }}" - ignore: | - ko/** diff --git a/.gitignore b/.gitignore index b6c24e95..02981011 100644 --- a/.gitignore +++ b/.gitignore @@ -45,10 +45,19 @@ oswl-db.mv.db ### Tailwind compiled output (generated by buildTailwindCss) ### src/main/resources/static/css/tailwind.css +### Minified custom JS (generated by minifyJs; vendor/*.min.js stays tracked) ### +src/main/resources/static/js/**/*.min.js +!src/main/resources/static/js/vendor/** + ### GitHub Pages build output (generated by prepare-pages-site.sh) ### _site/ ### Etc ### +__pycache__/ +.env +.env.* +!.env.example +!.env.prod.example cookies.txt server.log *.log @@ -61,5 +70,21 @@ htmlReport .claude # Local-only planning/verification artifacts -/ROADMAP.md +/ROADMAP*.md /e6verify/ + +# Local agent guidance and development reports are not release inputs. +/AGENTS.md +/CLAUDE.md +/.cursorrules +/.agents/ +/docs/**/Architecture-Optimization.md +/docs/**/Developer-Onboarding.md +/docs/**/Performance-Verification.md +/docs/**/Roadmap-Final-Audit.md +/docs/**/Ui-Operations-Verification.md +/docs/**/Ui-Ux-Regression-Audit.md +/docs/**/Ui-States-Checklist.md +/docs/**/Local-Cluster-Rehearsal.md +/docs/**/performance-evidence/ +/docs/**/ui-operations-evidence/ diff --git a/.kimi/AGENTS.md b/.kimi/AGENTS.md deleted file mode 100644 index 52a5f653..00000000 --- a/.kimi/AGENTS.md +++ /dev/null @@ -1,92 +0,0 @@ -# OsWL — Project Guidelines - -OsWL is an in-house SCA (Software Composition Analysis) platform for tracking and managing CVE security vulnerabilities and license risks in OSS components. - ---- - -## Test Account -- Email: test@test.com | Password: 1q2w3e4r | Name: test -- OTP: `000000` (bypass in dev) | Test issuance OTP: `123456` - ---- - -## Tech Stack -- **Backend:** Spring Boot 4.0.5 (Java 25) — WebMVC, Data JPA, Security, Mail, Validation, AspectJ -- **View:** Thymeleaf + thymeleaf-extras-springsecurity6 -- **Frontend:** Vanilla HTML/CSS/JS · Tailwind CSS 3.4 (precompiled standalone CLI, **NO CDN**) · Chart.js · HTMX · Alpine.js -- **DB:** PostgreSQL (prod) / H2 file-mode (local profile, PostgreSQL-compatible) -- **Auth:** Spring Security + Email OTP (2FA) + Trusted Device + Single-Session Enforcement -- **API Docs:** springdoc-openapi — OpenAPI annotations live in `controller/spec/*Spec.java` interfaces only -- **Local SMTP:** GreenMail embedded (port 3025) — OTP code printed as `*** OTP CODE: NNNNNN ***` in server logs -- **Build:** Gradle Wrapper (`./gradlew`) - ---- - -## Project Structure (Key Packages) - - src/main/java/com/salkcoding/oswl/ - ├── aop/ # Cross-cutting concerns (audit logging) - ├── auth/ # Auth module — config, controller, dto, entity, enums, repo, security, service, web - ├── client/ # External API clients (OSV, deps.dev, GitHub…) - ├── controller/ # Business controllers - │ └── spec/ # Controller spec interfaces (all springdoc annotations here) - ├── domain/entity/ # JPA entities - ├── domain/enums/ - ├── dto/ exception/ repository/ scheduler/ service/ web/ - resources/ - ├── application.yaml / application-local.yaml / application-prod.yaml - ├── static/{css,js,img,icon,graphic,scripts}/ - └── templates/{auth, projects, security-center, component-detail, license, - risk-trend, scan-history, settings, error, fragments}/ - ---- - -## Architecture & Code Style - -**Backend** -- **Layered:** Controller → Service → Repository. Keep controllers thin. -- **Controller Spec:** All OpenAPI annotations go in `controller/spec/*ControllerSpec.java` interfaces. Implementations contain business logic only. -- **DI:** Lombok `@RequiredArgsConstructor` constructor injection only. `@Autowired` is forbidden. -- **Authorization:** Method-level `@PreAuthorize("hasPermission(null, 'PROJECT_VIEW') or hasRole('SYSTEM_ADMIN')")`. -- **Audit Logging:** Use `AuditLogService.log…()` for user actions and system events. -- **Profile Gating:** Local-only beans use `@Profile("local")` (e.g. `TestDataController`, `LocalSmtpConfig`). -- **JPA Cascade:** Project → ProjectVersion / ScanResult / ScanComponent / DependencyPath; Library → Cve (cascade ALL). - -**Frontend** -- **Vanilla first.** Add HTMX (partial updates) or Alpine.js (local state) only when needed. -- **Tailwind CSS:** `/css/tailwind.css` (Gradle build output) only — CDN is forbidden. -- **HTMX pattern:** Return fragment-only response when `HX-Request: true` header is present. -- **Assets:** JS in `static/js/`, organized by page subdirectory. - ---- - -## DB & Environments -- **Local DB:** H2 file (`./oswl-db.mv.db`). Console: `http://localhost:8080/h2-console` (JDBC `jdbc:h2:file:./oswl-db`, user `sa`). -- **DB Reset:** Stop server → delete `oswl-db.*` files → restart → lands on Setup screen. -- **Test Data Seed:** `GET /data/test` (auth required, local only) — wipes and re-seeds all data. -- **Encryption Key (`OSWL_ENCRYPTION_KEY`):** Dummy key hardcoded for local; production requires 32-byte key via `openssl rand -base64 32`. - ---- - -## Build & Run - - ./gradlew bootRun # Local run (profile=local, port 8080) - ./gradlew build # Full build (includes Tailwind) - ./gradlew test # JUnit tests - ./gradlew buildTailwindCss # Tailwind rebuild only - ---- - -## Conventions -- **DB naming:** snake_case tables/columns; entity PK is `Long id`. -- **DTOs:** `record` or Lombok `@Value` in `dto/` package. -- **Dependencies:** Add to `build.gradle` (backend) or use CDN/webjars (frontend). -- **Error pages:** `error/{401,403,404,500,503}.html` — use `_owl-error.html` fragment (shared owl illustration). -- **Log tone:** Business events → INFO/WARN/ERROR; details → `log.debug`. - -## Test Code Policy - -- Do NOT modify, create, or delete any test files (e.g., *Test.java, *.test.ts, *.spec.ts) unless explicitly asked to. -- When fixing bugs or refactoring production code, leave all existing test files exactly as they are. -- If a test needs to be updated as a direct result of a change, ask first before touching it. -- Never auto-generate test stubs or test scaffolding without being explicitly instructed to do so. diff --git a/.release-please-manifest.json b/.release-please-manifest.json deleted file mode 100644 index 0db4c771..00000000 --- a/.release-please-manifest.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - ".": "1.0.3" -} diff --git a/CHANGELOG.md b/CHANGELOG.md index 77000498..518e128e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## [1.0.5.1] - 2026-09-08 + +### Improvements + +- Restore the original light appearance across login, search and settings. Clarify input validation, save results and unsaved changes in English, Korean and Japanese. +- Restore CLI API key deletion, correct trash visibility and restoration, and provide direct NVD links beside CVE identifiers. +- Select known OSV fixes for the installed numeric release interval; resolve inherited Maven properties and supported Gradle rich version constraints. Preserve and refresh available license metadata. +- Improve scan retry isolation, incomplete-analysis reporting, request cancellation and duplicate-action handling. +- Add opt-in browser security notifications, custom scan rules, organization risk briefings and scoped offline CocoaPods data. Add bounded OCI image inspection tooling. +- Improve diagnostic status reporting and disk-space readability. Clarify offline snapshot import, configuration transfer and scan archive exports, including empty exports. + +### Upgrading + +- Java 25 is required. Back up the database and persistent files before upgrading; follow the deployment and migration instructions. Migrated databases require the migrations through V35. +- Browser notifications require web push/VAPID configuration and browser permission. SMTP, VCS and AI connections must be configured for the relevant integrations. +- Existing scan records are not rewritten by a code upgrade. Rescan to apply parsing and enrichment corrections. +- A missing fix version does not prove that no patch exists. Complex ranges, ecosystem-specific ordering, incomplete source data and offline parity still require review. + +See [English](docs/en/Whats-New-v1.0.5.1.md), [한국어](docs/ko/Whats-New-v1.0.5.1.md) or [日本語](docs/ja/Whats-New-v1.0.5.1.md) for release guidance. + ## [1.0.3](https://github.com/SalkCoding/Oswl/releases/tag/v1.0.3) (2026-07-19) User-feedback release — applies the remaining findings from the 5-person usability test diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 52a5f653..00000000 --- a/CLAUDE.md +++ /dev/null @@ -1,92 +0,0 @@ -# OsWL — Project Guidelines - -OsWL is an in-house SCA (Software Composition Analysis) platform for tracking and managing CVE security vulnerabilities and license risks in OSS components. - ---- - -## Test Account -- Email: test@test.com | Password: 1q2w3e4r | Name: test -- OTP: `000000` (bypass in dev) | Test issuance OTP: `123456` - ---- - -## Tech Stack -- **Backend:** Spring Boot 4.0.5 (Java 25) — WebMVC, Data JPA, Security, Mail, Validation, AspectJ -- **View:** Thymeleaf + thymeleaf-extras-springsecurity6 -- **Frontend:** Vanilla HTML/CSS/JS · Tailwind CSS 3.4 (precompiled standalone CLI, **NO CDN**) · Chart.js · HTMX · Alpine.js -- **DB:** PostgreSQL (prod) / H2 file-mode (local profile, PostgreSQL-compatible) -- **Auth:** Spring Security + Email OTP (2FA) + Trusted Device + Single-Session Enforcement -- **API Docs:** springdoc-openapi — OpenAPI annotations live in `controller/spec/*Spec.java` interfaces only -- **Local SMTP:** GreenMail embedded (port 3025) — OTP code printed as `*** OTP CODE: NNNNNN ***` in server logs -- **Build:** Gradle Wrapper (`./gradlew`) - ---- - -## Project Structure (Key Packages) - - src/main/java/com/salkcoding/oswl/ - ├── aop/ # Cross-cutting concerns (audit logging) - ├── auth/ # Auth module — config, controller, dto, entity, enums, repo, security, service, web - ├── client/ # External API clients (OSV, deps.dev, GitHub…) - ├── controller/ # Business controllers - │ └── spec/ # Controller spec interfaces (all springdoc annotations here) - ├── domain/entity/ # JPA entities - ├── domain/enums/ - ├── dto/ exception/ repository/ scheduler/ service/ web/ - resources/ - ├── application.yaml / application-local.yaml / application-prod.yaml - ├── static/{css,js,img,icon,graphic,scripts}/ - └── templates/{auth, projects, security-center, component-detail, license, - risk-trend, scan-history, settings, error, fragments}/ - ---- - -## Architecture & Code Style - -**Backend** -- **Layered:** Controller → Service → Repository. Keep controllers thin. -- **Controller Spec:** All OpenAPI annotations go in `controller/spec/*ControllerSpec.java` interfaces. Implementations contain business logic only. -- **DI:** Lombok `@RequiredArgsConstructor` constructor injection only. `@Autowired` is forbidden. -- **Authorization:** Method-level `@PreAuthorize("hasPermission(null, 'PROJECT_VIEW') or hasRole('SYSTEM_ADMIN')")`. -- **Audit Logging:** Use `AuditLogService.log…()` for user actions and system events. -- **Profile Gating:** Local-only beans use `@Profile("local")` (e.g. `TestDataController`, `LocalSmtpConfig`). -- **JPA Cascade:** Project → ProjectVersion / ScanResult / ScanComponent / DependencyPath; Library → Cve (cascade ALL). - -**Frontend** -- **Vanilla first.** Add HTMX (partial updates) or Alpine.js (local state) only when needed. -- **Tailwind CSS:** `/css/tailwind.css` (Gradle build output) only — CDN is forbidden. -- **HTMX pattern:** Return fragment-only response when `HX-Request: true` header is present. -- **Assets:** JS in `static/js/`, organized by page subdirectory. - ---- - -## DB & Environments -- **Local DB:** H2 file (`./oswl-db.mv.db`). Console: `http://localhost:8080/h2-console` (JDBC `jdbc:h2:file:./oswl-db`, user `sa`). -- **DB Reset:** Stop server → delete `oswl-db.*` files → restart → lands on Setup screen. -- **Test Data Seed:** `GET /data/test` (auth required, local only) — wipes and re-seeds all data. -- **Encryption Key (`OSWL_ENCRYPTION_KEY`):** Dummy key hardcoded for local; production requires 32-byte key via `openssl rand -base64 32`. - ---- - -## Build & Run - - ./gradlew bootRun # Local run (profile=local, port 8080) - ./gradlew build # Full build (includes Tailwind) - ./gradlew test # JUnit tests - ./gradlew buildTailwindCss # Tailwind rebuild only - ---- - -## Conventions -- **DB naming:** snake_case tables/columns; entity PK is `Long id`. -- **DTOs:** `record` or Lombok `@Value` in `dto/` package. -- **Dependencies:** Add to `build.gradle` (backend) or use CDN/webjars (frontend). -- **Error pages:** `error/{401,403,404,500,503}.html` — use `_owl-error.html` fragment (shared owl illustration). -- **Log tone:** Business events → INFO/WARN/ERROR; details → `log.debug`. - -## Test Code Policy - -- Do NOT modify, create, or delete any test files (e.g., *Test.java, *.test.ts, *.spec.ts) unless explicitly asked to. -- When fixing bugs or refactoring production code, leave all existing test files exactly as they are. -- If a test needs to be updated as a direct result of a change, ask first before touching it. -- Never auto-generate test stubs or test scaffolding without being explicitly instructed to do so. diff --git a/README.ja.md b/README.ja.md index c5155840..6d255ea4 100644 --- a/README.ja.md +++ b/README.ja.md @@ -1,5 +1,7 @@
+**OsWL 1.0.5.1** · [1.0.5.1 の変更点](docs/ja/Whats-New-v1.0.5.1.md) + # 🦉 OsWL **オープンソース・ソフトウェア・ウォッチリスト — SCA プラットフォーム** @@ -199,7 +201,9 @@ Library (プロジェクト間で共有 — group:artifact@version) ## ドキュメント -日本語ドキュメントは [`docs/ja/`](docs/ja/) フォルダで確認できます。英語ドキュメントは [`docs/`](docs/) フォルダ、韓国語ドキュメントは [`docs/ko/`](docs/ko/) フォルダ、および [GitHub Wiki](https://github.com/SalkCoding/Oswl/wiki)(`main` への push 時に `docs/` から自動同期)で提供されています。 +日本語ドキュメントは [`docs/ja/`](docs/ja/Home.md)、英語は [`docs/en/`](docs/en/Home.md)、韓国語は [`docs/ko/`](docs/ko/Home.md) にあります。[ドキュメント索引](docs/README.md)と [GitHub Wiki](https://github.com/SalkCoding/Oswl/wiki) は英語を既定の入口とし、Wiki は `main` への push 時に `docs/en/` から自動同期されます。 + +フォルダの役割: [`deploy/`](deploy/README.md) は Docker のビルド・実行設定と Grafana アセット、[`scripts/`](scripts/README.md) は開発・運用ツール、[`docs/`](docs/README.md) はガイド、[`landing/`](landing/index.html) は Pages の紹介サイトです。アプリと同梱設定は `src/`、GitHub 自動化は `.github/` にあります。 | ページ | 説明 | |---|---| diff --git a/README.ko.md b/README.ko.md index 5959aff1..ef5f5ef9 100644 --- a/README.ko.md +++ b/README.ko.md @@ -1,5 +1,7 @@
+**OsWL 1.0.5.1** · [1.0.5.1 변경 사항](docs/ko/Whats-New-v1.0.5.1.md) + # 🦉 OsWL **오픈소스 소프트웨어 감시 목록 — SCA 플랫폼** @@ -199,7 +201,10 @@ Library (프로젝트 간 공유 — group:artifact@version) ## 문서 -한국어 문서는 [`docs/ko/`](docs/ko/) 폴더에서 확인할 수 있습니다. 영문 문서는 [`docs/`](docs/), 일본어 문서는 [`docs/ja/`](docs/ja/)에서, 그리고 [GitHub Wiki](https://github.com/SalkCoding/Oswl/wiki) (`main` push 시 `docs/`에서 자동 동기화)에서도 제공됩니다. + +한국어 문서는 [`docs/ko/`](docs/ko/Home.md), 영어 문서는 [`docs/en/`](docs/en/Home.md), 일본어 문서는 [`docs/ja/`](docs/ja/Home.md)에 있습니다. [문서 인덱스](docs/README.md)와 [GitHub Wiki](https://github.com/SalkCoding/Oswl/wiki)는 영어를 기본으로 안내하며, Wiki는 `main` push 시 `docs/en/`에서 자동 동기화됩니다. + +폴더 역할: [`deploy/`](deploy/README.md)는 Docker 빌드·실행 설정과 Grafana 자산, [`scripts/`](scripts/README.md)는 개발·운영 도구, [`docs/`](docs/README.md)는 안내 문서, [`landing/`](landing/index.html)은 Pages 소개 사이트입니다. 앱 코드와 패키징되는 설정은 `src/`, GitHub 자동화는 `.github/`에 있습니다. | 페이지 | 설명 | |---|---| diff --git a/README.md b/README.md index 668463f8..e42b5f17 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@
+**OsWL 1.0.5.1** · [What's new in 1.0.5.1](docs/en/Whats-New-v1.0.5.1.md) + # 🦉 OsWL **Open-source Software Watchlist — SCA Platform** @@ -199,30 +201,33 @@ Interactive Swagger UI is available in the **`local` profile** at `http://localh ## Documentation -Full documentation is available in the [`docs/`](docs/) folder and on the [GitHub Wiki](https://github.com/SalkCoding/Oswl/wiki) (auto-synced from `docs/` on push to `main`). Korean docs live in [`docs/ko/`](docs/ko/), Japanese docs in [`docs/ja/`](docs/ja/). + +English documentation lives in [`docs/en/`](docs/en/Home.md) and on the [GitHub Wiki](https://github.com/SalkCoding/Oswl/wiki), auto-synced from `docs/en/` on push to `main`. Choose a language through the [documentation index](docs/README.md): [English](docs/en/Home.md), [한국어](docs/ko/Home.md), [日本語](docs/ja/Home.md). + +Repository layout: [`deploy/`](deploy/README.md) contains Docker build/run configuration and Grafana assets; [`scripts/`](scripts/README.md) contains development and operations tools; [`docs/`](docs/README.md) contains guides; [`landing/`](landing/index.html) is the Pages site. Application code and packaged configuration stay in `src/`, while GitHub automation stays in `.github/`. | Page | Description | |---|---| -| [Home](docs/Home.md) | Platform overview and navigation guide | -| [What's New in v1.0.4](docs/Whats-New-v1.0.4.md) | Release highlights — SBOM/VEX/SARIF exports, CI/CD gate, continuous monitoring, org dashboard, supply-chain heuristics, air-gapped mode | -| [Getting Started](docs/Getting-Started.md) | Installation, setup wizard, first project | -| [User Guide](docs/User-Guide.md) | Day-to-day usage of the dashboard | -| [Quick Import](docs/Quick-Import.md) | Importing projects from GitHub / GitLab / Bitbucket | -| [CLI Integration](docs/CLI-Integration.md) | Submitting scans from build pipelines | -| [Embedded AI](docs/Embedded-AI.md) | Running CVE triage and license insights on a local LLM — no cloud account or API key | -| [Security Center](docs/Security-Center.md) | Managing vulnerabilities (CVEs) | -| [License Analysis](docs/License-Analysis.md) | License compliance and policy management | -| [Risk Trend](docs/Risk-Trend.md) | Interpreting historical risk charts | -| [Version Diff](docs/Version-Diff.md) | Comparing two scan results | -| [Scan History](docs/Scan-History.md) | Reviewing every scan submitted for a project, in order | -| [Administration](docs/Administration.md) | Users, roles, audit logs, security settings | -| [Authorization layers](docs/Authorization-Layers.md) | Role templates vs project membership | -| [Project access control](docs/Project-Access-Control.md) | Technical reference for how project-level permissions are resolved | -| [Production deployment](docs/Production-Deployment-Checklist.md) | Production checklist | -| [Database schema](docs/Database-Schema.md) | `ddl-auto` strategy and SQL migrations | -| [Scan API security](docs/Scan-Api-Security.md) | CLI scan auth and audit logging | -| [API Reference](docs/API-Reference.md) | REST API endpoint summary | -| [Glossary](docs/Glossary.md) | Terms and definitions | +| [Home](docs/en/Home.md) | Platform overview and navigation guide | +| [What's New in v1.0.4](docs/en/Whats-New-v1.0.4.md) | Release highlights — SBOM/VEX/SARIF exports, CI/CD gate, continuous monitoring, org dashboard, supply-chain heuristics, air-gapped mode | +| [Getting Started](docs/en/Getting-Started.md) | Installation, setup wizard, first project | +| [User Guide](docs/en/User-Guide.md) | Day-to-day usage of the dashboard | +| [Quick Import](docs/en/Quick-Import.md) | Importing projects from GitHub / GitLab / Bitbucket | +| [CLI Integration](docs/en/CLI-Integration.md) | Submitting scans from build pipelines | +| [Embedded AI](docs/en/Embedded-AI.md) | Running CVE triage and license insights on a local LLM — no cloud account or API key | +| [Security Center](docs/en/Security-Center.md) | Managing vulnerabilities (CVEs) | +| [License Analysis](docs/en/License-Analysis.md) | License compliance and policy management | +| [Risk Trend](docs/en/Risk-Trend.md) | Interpreting historical risk charts | +| [Version Diff](docs/en/Version-Diff.md) | Comparing two scan results | +| [Scan History](docs/en/Scan-History.md) | Reviewing every scan submitted for a project, in order | +| [Administration](docs/en/Administration.md) | Users, roles, audit logs, security settings | +| [Authorization layers](docs/en/Authorization-Layers.md) | Role templates vs project membership | +| [Project access control](docs/en/Project-Access-Control.md) | Technical reference for how project-level permissions are resolved | +| [Production deployment](docs/en/Production-Deployment-Checklist.md) | Production checklist | +| [Database schema](docs/en/Database-Schema.md) | `ddl-auto` strategy and SQL migrations | +| [Scan API security](docs/en/Scan-Api-Security.md) | CLI scan auth and audit logging | +| [API Reference](docs/en/API-Reference.md) | REST API endpoint summary | +| [Glossary](docs/en/Glossary.md) | Terms and definitions | --- diff --git a/THIRD_PARTY_LICENSES.md b/THIRD_PARTY_LICENSES.md index 8c5ac3da..c5d3b2e9 100644 --- a/THIRD_PARTY_LICENSES.md +++ b/THIRD_PARTY_LICENSES.md @@ -22,6 +22,7 @@ OsWL uses the following third-party libraries. This document lists each library, | [H2 Database](#h2-database) | EPL 2.0 / MPL 2.0 | | [PostgreSQL JDBC Driver](#postgresql-jdbc-driver) | BSD 2-Clause | | [Project Lombok](#project-lombok) | MIT | +| [Caffeine](#caffeine) | Apache 2.0 | | [Alpine.js](#alpinejs) | MIT | | [@alpinejs/collapse](#alpinejscollapse) | MIT | | [Chart.js](#chartjs) | MIT | @@ -32,11 +33,20 @@ OsWL uses the following third-party libraries. This document lists each library, | [Micrometer Prometheus Registry](#micrometer-prometheus-registry) | Apache 2.0 | | [Flyway](#flyway) | Apache 2.0 | | [Spring Security OAuth2 Client](#spring-security-oauth2-client) | Apache 2.0 | -| [Qwen3-1.7B (GGUF)](#qwen3-17b-gguf) | Apache 2.0 | -| [OSV (Open Source Vulnerabilities)](#osv-open-source-vulnerabilities) | CC-BY 4.0 / CC0 1.0 (varies) | +| [Spring Session JDBC](#spring-session-jdbc) | Apache 2.0 | +| [ShedLock](#shedlock) | Apache 2.0 | +| [Qwen3.5-2B (GGUF)](#qwen35-2b-gguf) | Apache 2.0 | +| [Gemma 4 E2B (GGUF)](#gemma-4-e2b-gguf) | Apache 2.0 | +| [llama.cpp](#llamacpp) | MIT | +| [OSV (Open Source Vulnerabilities)](#osv-open-source-vulnerabilities) | Source-specific, including CC-BY-SA 4.0 | +| [GitHub Advisory Database](#github-advisory-database) | CC-BY 4.0 | +| [NIST NVD](#nist-national-vulnerability-database-nvd) | NIST / CVE source terms; redistribution review pending | | [FIRST.org EPSS](#firstorg-epss-exploit-prediction-scoring-system) | Free access, attribution requested | | [CISA KEV](#cisa-kev-known-exploited-vulnerabilities-catalog) | CC0 1.0 | -| [deps.dev](#depsdev) | CC-BY 4.0 (generated data) / Apache 2.0 (client repo) | +| [deps.dev](#depsdev) | CC-BY 4.0 (generated data); upstream terms for aggregated data | +| [CocoaPods specifications](#cocoapods-specifications) | MIT for specifications; pod licenses separate | +| [Bundled Conda mapping](#bundled-conda-to-pypi-name-mapping) | Combined BSD-style / BSD 3-Clause / MIT notices | +| [CVSS v4.0 Lookup Table (cvss-v4-calculator)](#cvss-v40-lookup-table-cvss-v4-calculator) | BSD 2-Clause | --- @@ -231,6 +241,36 @@ Licensed under the Apache License, Version 2.0 (the "License"). --- +### Spring Session JDBC + +- **Version:** Managed by Spring Boot 4.1.0 +- **Website:** https://spring.io/projects/spring-session +- **License:** Apache License, Version 2.0 +- **Used for:** Cluster-wide HTTP session storage in PostgreSQL, so a multi-instance deployment behind a load balancer keeps users logged in across instances and survives a single instance restarting. + +``` +Copyright 2014-2024 the original author or authors. + +Licensed under the Apache License, Version 2.0 (the "License"). +``` + +--- + +### ShedLock + +- **Version:** 7.7.0 (shedlock-spring, shedlock-provider-jdbc-template) +- **Website:** https://github.com/lukas-krecan/ShedLock +- **License:** Apache License, Version 2.0 +- **Used for:** Cluster-wide lock ensuring each `@Scheduled` job (nightly monitoring, deferral expiry, trash cleanup) runs on exactly one instance even when OsWL is deployed with multiple instances. + +``` +Copyright 2009-2024 the original author(s) + +Licensed under the Apache License, Version 2.0 (the "License"). +``` + +--- + ### Jackson - **Version:** Managed by Spring Boot 4.1.0 @@ -381,9 +421,28 @@ all copies or substantial portions of the Software. --- -## Frontend Dependencies (CDN) +### Caffeine + +- **Version:** 3.2.4 +- **Website:** https://github.com/ben-manes/caffeine +- **License:** Apache License, Version 2.0 +- **Used for:** High-performance in-memory query cache for read-heavy configuration data (license policy, role templates, settings). + +``` +Copyright 2015 Ben Manes. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 +``` + +--- + +## Vendored Frontend Dependencies -These libraries are loaded at runtime from public CDNs and are not bundled inside the JAR artifact. +Gradle downloads pinned versions of these libraries into `src/main/resources/static/js/vendor/`. They are packaged in the JAR and served locally from `/js/vendor/`; application pages do not load them from a runtime CDN. ### Alpine.js @@ -485,50 +544,128 @@ all copies or substantial portions of the Software. --- -## Bundled AI Models +## Embedded AI Runtime and Models + +Runtime binaries and model weights are optional downloads, excluded from the source repository, +Docker build context and released JAR. The JAR includes this notice and the Apache 2.0 text. +Inference runs locally. Installation from upstream is a separate network operation. + +### Qwen3.5-2B (GGUF) + +- **Role:** Default CPU model; replaces the previous Qwen3-1.7B automatic download. +- **Publisher:** Alibaba Cloud / Qwen team. +- **Upstream:** https://huggingface.co/Qwen/Qwen3.5-2B +- **License:** Apache License, Version 2.0; full text below. +- **Quantization:** Unsloth AI GGUF Q4_K_M. OsWL does not fine-tune or modify the downloaded bytes. +- **Source revision:** `unsloth/Qwen3.5-2B-GGUF@f6d5376be1edb4d416d56da11e5397a961aca8ae` +- **File:** `model/Qwen/Qwen3.5-2B-Q4_K_M.gguf`, 1280835840 bytes. +- **SHA256:** `aaf42c8b7c3cab2bf3d69c355048d4a0ee9973d48f16c731c0520ee914699223` +- **Pinned download:** https://huggingface.co/unsloth/Qwen3.5-2B-GGUF/resolve/f6d5376be1edb4d416d56da11e5397a961aca8ae/Qwen3.5-2B-Q4_K_M.gguf + +### Gemma 4 E2B (GGUF) -Unlike the libraries above, model weights are not source code and are not covered by a -uniform "open source" regime — each model below is listed with its actual license, which -may impose obligations beyond a copyright notice. +- **Role:** Optional CPU model, installed manually; replaces Gemma 3 1B in the refreshed local installation. +- **Publisher:** Google DeepMind. +- **Upstream:** https://huggingface.co/google/gemma-4-E2B-it +- **License:** Apache License, Version 2.0, as stated in the Gemma 4 model card and https://ai.google.dev/gemma/apache_2 . + This entry is specifically for Gemma 4, not the distinct terms of earlier Gemma releases. +- **Quantization:** Unsloth AI GGUF Q4_K_M. OsWL does not fine-tune or modify the downloaded bytes. +- **Source revision:** `unsloth/gemma-4-E2B-it-GGUF@0314792d7f1f7e229411f620751375812bb9faf2` +- **File:** `model/Gemma/gemma-4-E2B-it-Q4_K_M.gguf`, 3106738272 bytes. +- **SHA256:** `740185b21d22ceb83a11c3aa62ad5842ef32c70f6096d756bbee85a1e4ec34b8` +- **Pinned download:** https://huggingface.co/unsloth/gemma-4-E2B-it-GGUF/resolve/0314792d7f1f7e229411f620751375812bb9faf2/gemma-4-E2B-it-Q4_K_M.gguf -### Qwen3-1.7B (GGUF) +Apache 2.0 permits redistribution subject to its conditions, including supplying the license, +retaining applicable copyright/attribution notices and carrying forward any upstream NOTICE +content when provided. Preserve the quantization provenance above. Do not represent these weights +as OsWL-authored or imply endorsement by Qwen, Google or Unsloth. -- **Version:** Qwen3-1.7B, quantized to GGUF (Q4_K_M) -- **Publisher:** Alibaba Cloud (Qwen team) -- **Website:** https://github.com/QwenLM/Qwen3 -- **License:** Apache License, Version 2.0 — full text below under [Apache License 2.0 — Full Text](#apache-license-20--full-text) -- **Distribution:** Not bundled in the git repository or build artifacts. OsWL downloads the official `ggml-org/Qwen3-1.7B-GGUF` file `Qwen3-1.7B-Q4_K_M.gguf` directly from Hugging Face (over plain HTTPS, verifying its SHA256 against the value below) the first time a user clicks **Start** on Embedded AI with no model present, or in the background shortly after boot (see [Embedded AI](docs/Embedded-AI.md)), storing it at `embedded-ai/qwen3-1.7b-q4_k_m.gguf`. Nothing is downloaded in air-gapped mode — those hosts place the file themselves. Operators may point `OSWL_EMBEDDED_DEFAULT_MODEL_URL` at a self-hosted byte-identical mirror instead; Apache 2.0 permits that redistribution, no modifications are made to the model weights beyond the upstream GGUF quantization already noted above, and this notice plus the included license text and upstream attribution satisfy its requirements. Permissively licensed, so mirroring and auto-fetching it carries no obligation beyond this notice. -- **SHA256:** `d2387ca2dbfee2ffabce7120d3770dadca0b293052bc2f0e138fdc940d9bc7b5` — must always match `oswl.ai.embedded.default-model-sha256` (`EmbeddedAiService`'s default); if one changes without the other, downloads fail integrity verification. +The old GitHub `models-v1` asset is Qwen3-1.7B and is not the new default. +A new mirror must be a verified byte-identical copy of the relevant pinned file and accompany +the license and attribution notices. This change does not publish new model assets. +Custom/older weights retain their own licenses; these entries do not relicense them. + +### llama.cpp + +- **Publisher:** The ggml authors. +- **Source:** https://github.com/ggml-org/llama.cpp +- **License:** MIT. +- **Validated local runtime:** b10068, commit `571d0d540`; operators supply an OS/architecture-compatible build in `embedded-ai/llama/`. +- **Distribution:** Not bundled in the repository or JAR. If packaging runtime binaries separately, + preserve this full license and the licenses/notices of all included libraries (for example, + an OpenMP runtime); this MIT notice alone does not cover every binary in a vendor archive. ``` -Copyright Alibaba Cloud. Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at https://www.apache.org/licenses/LICENSE-2.0 +MIT License + +Copyright (c) 2023-2026 The ggml authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. ``` --- ## External Data Sources (Vulnerability / Threat Intelligence Feeds) -Unlike the libraries and models above, the entries below are **data, not code** — consumed live -by `OsvClient`/`DepsDevClient`/`EpssClient`/`KevCatalogService` when air-gapped mode is off, and -by the `oswl-vdb` builder CLI (E5, `com.salkcoding.oswl.vdb`) when constructing an offline -snapshot bundle for air-gapped instances. None of this data is bundled in the git repository or -build artifacts; it is fetched over HTTPS at scan time or at bundle-build time. +The notices below distinguish remotely acquired data from bundled reference resources. +Online clients and the `oswl-vdb` builder acquire data under the relevant provider's terms. +Conda name mappings and the CVSS lookup table are already included in build resources. +This notice does not grant rights in upstream material or establish that every existing +snapshot/export preserves the attribution required for redistribution. Preserve record-level +origin, license links, supplied copyright notices and modification history with redistributed +data. Review date: 2026-09-07; unresolved permissions are identified explicitly below. ### OSV (Open Source Vulnerabilities) - **Website:** https://osv.dev/ · bulk dumps: `https://storage.googleapis.com/osv-vulnerabilities//all.zip` -- **License:** Varies by upstream advisory source, documented per-ecosystem at - https://google.github.io/osv.dev/data/. For the ecosystems OsWL supports: **npm, Maven, - RubyGems, NuGet** entries originate from the **GitHub Advisory Database (CC-BY 4.0)**; **PyPI** - additionally draws from the PyPI Advisory Database and the Python Software Foundation Database - (both **CC-BY 4.0**); **Go** from the Go Vulnerability Database (**CC-BY 4.0**); **crates.io** - from the RustSec Advisory Database (**CC0 1.0**, public domain). +- **License:** Varies by original source, not merely by ecosystem. Consult the + [OSV source catalog](https://google.github.io/osv.dev/data/) and the source's own license. + GitHub Advisory Database, PyPA advisory data and Go `/data/` use CC-BY 4.0. + [RustSec](https://github.com/rustsec/advisory-db/blob/main/LICENSE.txt) defaults to CC0, + but imported GHSA records use CC-BY 4.0 with record-specific attribution. + [Ubuntu Security Notices](https://github.com/canonical/ubuntu-security-notices/blob/main/LICENSE) + use CC-BY-SA 4.0. No blanket redistribution clearance for Debian/Alpine-derived data + was established in this review. OSV access does not replace upstream permissions. - **Used for:** Live per-component vulnerability lookups (`OsvClient`) and, in `oswl-vdb build`, bulk re-indexing of the ecosystem `all.zip` dumps into `osv.jsonl` snapshot entries. -- **Attribution:** CC-BY 4.0 requires attribution to the original source; this notice plus OSV's - own `id`/`aliases` fields preserved verbatim in every re-indexed entry satisfy that. +- **Attribution:** Credit OSV and the original advisory publisher; retain supplied notices, + source and license links, and indicate transformations. IDs alone do not establish compliance. + Apply CC-BY-SA obligations to qualifying adaptations; do not relabel all data as OsWL's MIT code. + +### GitHub Advisory Database + +- **Source / license:** [GitHub Advisory Database — CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md). +- **Used for:** Direct `GitHubAdvisoryClient` queries as well as advisories surfaced through OSV. +- **Attribution:** GitHub Advisory Database and the contributors identified by the source; + preserve advisory URL, license and supplied attribution when sharing transformed records. + +### NIST National Vulnerability Database (NVD) + +- **Source:** https://nvd.nist.gov/ · API: https://nvd.nist.gov/developers/vulnerabilities +- **Used for:** CVE details and CPE-based advisory lookup (`NvdClient`). +- **Terms:** Consult [NIST copyright and licensing statements](https://www.nist.gov/open/copyright-fair-use-and-licensing-statements-srd-data-software-and-technical-series-publications) + and the applicable NVD/CVE source terms. A U.S. government publisher does not establish a + CC0 license for every incorporated third-party record or referenced publication. The NVD-specific + terms pages did not expose sufficient content during this review; blanket redistribution + permission for every incorporated field remains unverified. +- **Attribution / non-endorsement:** Data source: NIST NVD. This product uses data from the + NVD API but is not endorsed or certified by the NVD. ### FIRST.org EPSS (Exploit Prediction Scoring System) @@ -539,14 +676,17 @@ build artifacts; it is fetched over HTTPS at scan time or at bundle-build time. **not** shared per that FAQ, only the published per-CVE scores OsWL consumes. - **Used for:** Live per-CVE probability-of-exploitation scores (`EpssClient`) and, in `oswl-vdb build`, the full bulk CSV. -- **Attribution:** This notice + preserving FIRST.org as the named source satisfies the - attribution request. +- **Attribution:** FIRST.org EPSS; preserve score date and model version when supplied. + Public access and requested attribution do not establish unrestricted dataset redistribution. + No explicit SPDX data license or blanket commercial redistribution grant was verified in + the published FAQ/data pages; confirm terms before distributing a score database to others. ### CISA KEV (Known Exploited Vulnerabilities Catalog) - **Website:** https://www.cisa.gov/known-exploited-vulnerabilities-catalog -- **License:** **CC0 1.0** (public domain) — a work of the U.S. federal government, mirrored - under CC0 at https://github.com/cisagov/kev-data. +- **License:** **CC0 1.0**, expressly published in the + [CISA KEV data repository license](https://github.com/cisagov/kev-data/blob/develop/LICENSE). + Linked third-party material and CISA/DHS marks are not licensed by that dedication. - **Used for:** Live KEV-listed flagging (`KevCatalogService`) and, in `oswl-vdb build`, the full bulk JSON feed. @@ -554,30 +694,83 @@ build artifacts; it is fetched over HTTPS at scan time or at bundle-build time. - **Website:** https://deps.dev/ · API: https://docs.deps.dev/api/v3/ · source: https://github.com/google/deps.dev -- **License:** The deps.dev README states: *"deps.dev generates additional data, including - resolved dependencies, advisory statistics, associations between entities, etc. This generated - data is available under a **CC-BY 4.0** license."* This covers the derived fields OsWL consumes - (`licenses`, `advisoryKeys`, resolved version/dependency data). Advisory content itself - (GHSA title/CVSS surfaced via `GetAdvisory`) originates from OSV/GHSA and is independently - CC-BY 4.0 per the OSV entry above. The raw registry fields deps.dev merely aggregates (not - generates) have no independently stated license and inherit whatever terms the origin registry - applies. Access to the API itself is governed by the - [Google APIs Terms of Service](https://developers.google.com/terms), which explicitly permits - caching: *"Clients are expressly permitted to cache data served by the API."* The deps.dev - **client repository's own code** (not the data) is Apache 2.0. +- **License:** The [deps.dev README](https://github.com/google/deps.dev#readme) licenses generated + data, including resolved dependencies and advisory statistics, under **CC-BY 4.0**. + Aggregated registry/advisory content remains subject to its original terms: do not infer that + every `licenses` or `advisoryKeys` field and referenced record has been relicensed. + The README permits API caching; API use is also subject to the + [Google APIs Terms of Service](https://developers.google.com/terms). + The repository's code is Apache 2.0; this does not license the hosted backend or all its data. - **Used for:** Live per-version license/advisory-key lookups and Scorecard scores (`DepsDevClient`) and, in `oswl-vdb build`, targeted `GetVersion`/`GetAdvisory` calls against a - wanted-list (E6) — deps.dev has no bulk dump, so this is the only viable ingestion path (E5.2). -- **Attribution:** This notice + the OSV/GHSA attribution above satisfies CC-BY 4.0 for the - generated and advisory data. deps.dev-derived fields are used to power OsWL's own analysis - output, not redistributed as a standalone dataset. + wanted-list. The provider also documents a BigQuery dataset in its [FAQ](https://docs.deps.dev/faq/); + availability is not a grant to redistribute every underlying source. +- **Attribution:** deps.dev / Google and the original data publishers, source and license URLs, + and an indication of transformations. Existing offline snapshots require the same review as + other exported data. This notice alone does not certify their attribution completeness. + +### CocoaPods specifications + +- **Source / license:** [CocoaPods Specs](https://github.com/CocoaPods/Specs#readme) states that + the specifications are under the [CocoaPods MIT license](https://github.com/CocoaPods/CocoaPods/blob/master/LICENSE). +- **Used for:** Repository and package-license metadata (`CocoaPodsSpecsClient`). Retain the + applicable MIT copyright and permission notice when redistributing substantial specification + content. Each actual pod's source code retains its own license; a podspec is not permission + to redistribute the package under MIT. + +### Bundled Conda-to-PyPI name mapping + +- **Resource:** `src/main/resources/conda/grayskull-pypi-mapping.json`. +- **Origin:** `mappings/pypi/grayskull_pypi_mapping.json` from `regro/cf-graph-countyfair`, now + [conda-forge/conda-forge-bot-data](https://github.com/conda-forge/conda-forge-bot-data). + This is not the separate Grayskull program's Apache-licensed source. +- **License:** The upstream `License` contains a Columbia University BSD-style notice, + Tick-my-feedstocks and Rever BSD 3-Clause notices, and a Doctr MIT notice. Preserve the + [complete upstream text shipped with OsWL](src/main/resources/META-INF/licenses/conda-forge-bot-data-LICENSE.txt). + Do not replace the combined/custom text with an assumed single SPDX identifier. +- **Copyright:** © 2018 Board of Trustees of Columbia University in the city of New York; + © 2017 Peter M. Landwehr; © 2017 Anthony Scopatz; © 2016 Aaron Meurer, Gil Forsyth. +- **Provenance:** License text retrieved from commit + `fd0dfed0d43fbb084f683d2c00473a887b645d69` on 2026-09-07; this is the license revision, + **not a verified revision of the bundled mapping**. The mapping's original commit and any + additional upstream-data terms remain to be established before claiming complete provenance. + Existing mapping SHA-256: `345402a491deabb648ddbb60a5c6708e0d99c69a99d11e5d8cd64a1d495ea2bd`. + +### CVSS v4.0 Lookup Table (cvss-v4-calculator) + +- **Source:** https://github.com/FIRSTdotorg/cvss-v4-calculator (`cvss_lookup.js`, `max_composed.js`, + `max_severity.js`) · publisher: FIRST.org, Inc., Red Hat, and contributors +- **License:** BSD 2-Clause +- **Note:** Unlike the other entries in this section, this is not a live-queried feed — the + 270-entry MacroVector→score table and its supporting per-equivalence-class data are vendored + verbatim as `src/main/resources/cvss/cvss-v4-lookup.json`, converted from the source `.js` object + literals to JSON with no values changed. `service/cvss/CvssV4Calculator.java` reimplements the + surrounding scoring algorithm (MacroVector derivation, severity-distance interpolation) in Java + from the same reference source, since the algorithm itself is not data that can be vendored as a + file. +- **Used for:** CVSS v4.0 Base/Environmental scoring (`CvssV4Calculator`) when a CVE supplies a + `CVSS:4.0/...` vector. + +``` +Copyright (c) 2023 FIRST.ORG, Inc., Red Hat, and contributors + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +``` --- ## Apache License 2.0 — Full Text The following libraries and models are licensed under the Apache License, Version 2.0: -Spring Boot, Spring Framework, Spring Security, Spring Data JPA, Thymeleaf, thymeleaf-extras-springsecurity6, springdoc-openapi, Jackson, GreenMail, Qwen3-1.7B. +Spring Boot, Spring Framework, Spring Security, Spring Data JPA, Thymeleaf, thymeleaf-extras-springsecurity6, springdoc-openapi, Jackson, GreenMail, Spring Session JDBC, ShedLock, Qwen3.5-2B, Gemma 4 E2B. ``` Apache License @@ -693,3 +886,14 @@ Spring Boot, Spring Framework, Spring Security, Spring Data JPA, Thymeleaf, thym END OF TERMS AND CONDITIONS ``` + +## Additional rule and browser notification libraries + +Versions are resolved in the generated OSS manifest and displayed on `/oss-notices`. + +- **RE2/J**: BSD 3-Clause. [Upstream license/project](https://github.com/google/re2j/blob/re2j-1.8/LICENSE). +- **web-push**: MIT. [Upstream license/project](https://github.com/web-push-libs/webpush-java). +- **Bouncy Castle**: MIT. [Upstream license/project](https://www.bouncycastle.org/licence.html). +- **jose4j**: Apache 2.0. [Upstream license/project](https://bitbucket.org/b_c/jose4j). + +RE2/J derives from the Go RE2 implementation, copyright 2009 The Go Authors. Bouncy Castle is copyright 2000–2026 The Legion of the Bouncy Castle Inc.; its license is interpreted as MIT by the publisher. The distributed dependency JARs retain their upstream notices. diff --git a/build.gradle b/build.gradle index 2c0e9ac3..bbe33bf9 100644 --- a/build.gradle +++ b/build.gradle @@ -8,9 +8,8 @@ plugins { } group = 'com.salkcoding' -// x-release-please-start-version -version = '1.0.4' -// x-release-please-end +// Release tags and the production JAR use this exact version, including hotfix revisions. +version = '1.0.5.1' java { toolchain { @@ -31,13 +30,42 @@ sourceSets { compileClasspath += sourceSets.local.output runtimeClasspath += sourceSets.local.output } + // Playwright-driven UI/accessibility tests against a live app instance. + // Deliberately separate from `test`: needs a browser download (hundreds of MB) and a real + // Spring Boot boot, so it must never run as part of the default `./gradlew test`/`check`. + uiTest { + java { + srcDir 'src/uiTest/java' + } + // resources default to src/uiTest/resources by convention — no extra srcDir needed. + compileClasspath += sourceSets.main.compileClasspath + sourceSets.main.output + sourceSets.local.output + runtimeClasspath += sourceSets.main.runtimeClasspath + sourceSets.main.output + sourceSets.local.output + } +} + +configurations { + uiTestImplementation.extendsFrom testImplementation + uiTestRuntimeOnly.extendsFrom testRuntimeOnly } repositories { mavenCentral() + // Spring Security SAML 7+ pulls OpenSAML 5.x from the Shibboleth repository. + maven { url 'https://build.shibboleth.net/maven/releases/' } } dependencies { + implementation 'com.google.re2j:re2j:1.8' + implementation('nl.martijndwars:web-push:5.1.2') { + exclude group: 'org.bouncycastle', module: 'bcprov-jdk15on' + exclude group: 'org.apache.httpcomponents', module: 'httpasyncclient' + exclude group: 'org.asynchttpclient', module: 'async-http-client' + } + implementation 'org.bouncycastle:bcprov-jdk18on' + implementation 'org.bitbucket.b_c:jose4j:0.9.6' + // Bytecode call-graph analysis + implementation 'org.ow2.asm:asm:9.7' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-aspectj' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' @@ -45,6 +73,9 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-mail' + + // In-memory query cache for read-heavy configuration data + implementation 'com.github.ben-manes.caffeine:caffeine' implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.3' @@ -54,13 +85,22 @@ dependencies { // CycloneDX SBOM export/import (spec 1.6) implementation 'org.cyclonedx:cyclonedx-core-java:13.0.0' implementation 'org.springframework.boot:spring-boot-starter-actuator' - // Prometheus scrape endpoint (/actuator/prometheus) — roadmap #13 ops foundation + // Prometheus scrape endpoint (/actuator/prometheus) implementation 'io.micrometer:micrometer-registry-prometheus' // OIDC SSO (Okta / Entra) — optional, activated only when a provider is configured implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' - // Flyway DB migrations (prod-profile gated) — roadmap #13 + // SAML 2.0 SSO (Okta / Entra ID / AD FS) — optional, activated only when IdP metadata is configured + implementation 'org.springframework.security:spring-security-saml2-service-provider' + // Flyway DB migrations (prod-profile gated) implementation 'org.flywaydb:flyway-core' implementation 'org.flywaydb:flyway-database-postgresql' + // Horizontal scaling / HA: cluster-wide HTTP sessions so a multi-instance + // deployment behind a load balancer doesn't drop auth on failover. + implementation 'org.springframework.session:spring-session-jdbc' + // Cluster-wide dedup for @Scheduled jobs — without this, 2 instances would + // each run the nightly monitoring/defer-expiry/trash-cleanup jobs independently. + implementation 'net.javacrumbs.shedlock:shedlock-spring:7.7.0' + implementation 'net.javacrumbs.shedlock:shedlock-provider-jdbc-template:7.7.0' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' @@ -75,6 +115,14 @@ dependencies { testCompileOnly 'org.projectlombok:lombok' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testAnnotationProcessor 'org.projectlombok:lombok' + + // Headless-browser UI/accessibility verification. Java-native: no + // package.json/npm needed, same "standalone binary, self-managed" spirit as the Tailwind CLI + // and esbuild downloads below — Playwright's own CLI fetches its browser binaries on demand. + uiTestImplementation 'com.microsoft.playwright:playwright:1.52.0' + uiTestImplementation 'com.deque.html.axe-core:playwright:4.10.1' + uiTestCompileOnly 'org.projectlombok:lombok' + uiTestAnnotationProcessor 'org.projectlombok:lombok' } tasks.withType(JavaCompile).configureEach { @@ -99,7 +147,7 @@ tasks.named('bootRun') { dependsOn compileLocalJava doFirst { def os = org.gradle.internal.os.OperatingSystem.current() - def script = os.isWindows() ? file('scripts/check-java.ps1') : file('scripts/check-java.sh') + def script = os.isWindows() ? file('scripts/dev/check-java.ps1') : file('scripts/dev/check-java.sh') if (script.exists()) { def cmd = os.isWindows() ? ['powershell', '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', script.absolutePath] @@ -135,15 +183,24 @@ tasks.named('bootJar') { tasks.register('verifyProdJar') { group = 'verification' description = 'Fails if local-only classes (e.g. TestDataController) are packaged in bootJar.' - dependsOn bootJar + dependsOn bootJar, compileLocalJava doLast { - def jarFile = tasks.named('bootJar').get().archiveFile.get().asFile - def forbidden = ['com/salkcoding/oswl/controller/TestDataController.class'] + def jarFile = providers.gradleProperty('verifyJar').isPresent() + ? file(providers.gradleProperty('verifyJar').get()) + : tasks.named('bootJar').get().archiveFile.get().asFile + def forbidden = new HashSet() + sourceSets.local.output.classesDirs.files.each { root -> + fileTree(root).matching { include '**/*.class' }.files.each { compiled -> + forbidden.add(root.toPath().relativize(compiled.toPath()).toString().replace('\\', '/')) + } + } + if (forbidden.isEmpty()) throw new GradleException('No compiled local classes to verify.') jarFile.withInputStream { input -> def zip = new ZipInputStream(input) def entry while ((entry = zip.nextEntry) != null) { - if (forbidden.any { entry.name == it }) { + def classPath = entry.name.replaceFirst('^BOOT-INF/classes/', '') + if (forbidden.contains(classPath)) { throw new GradleException("Production JAR must not contain ${entry.name}. Use src/local/java and bootRun classpath only.") } } @@ -160,6 +217,38 @@ tasks.named('test') { dependsOn compileLocalJava } +tasks.named('compileUiTestJava') { + dependsOn compileLocalJava +} + +// ───────────────────────────────────────────────────────────────────────────── +// Playwright + axe-core UI verification +// +// `./gradlew uiTest` boots the real app (@SpringBootTest RANDOM_PORT) and drives it with a +// headless Chromium via Playwright. Kept out of `test`/`check` on purpose — see sourceSets.uiTest +// above. In an offline/air-gapped build environment, pre-populate the browser cache and point +// PLAYWRIGHT_BROWSERS_PATH at it instead of relying on installPlaywrightBrowsers' download. +// ───────────────────────────────────────────────────────────────────────────── +tasks.register('installPlaywrightBrowsers', JavaExec) { + description = 'Downloads the Playwright Chromium browser binary into the local Playwright cache (cached; run once or after a Playwright version bump).' + group = 'verification' + classpath = configurations.uiTestRuntimeClasspath + mainClass = 'com.microsoft.playwright.CLI' + args = ['install', 'chromium'] +} + +tasks.register('uiTest', Test) { + description = 'Runs Playwright-driven UI and axe-core accessibility tests against a live app instance. Not part of test/check — run explicitly.' + group = 'verification' + dependsOn 'installPlaywrightBrowsers' + testClassesDirs = sourceSets.uiTest.output.classesDirs + classpath = sourceSets.uiTest.runtimeClasspath + useJUnitPlatform() + shouldRunAfter test + // Isolated URL rewrites let pipeline fixtures use their loopback Git server. + environment 'GIT_CONFIG_GLOBAL', layout.buildDirectory.file('uitest/git-config').get().asFile.absolutePath +} + jacoco { toolVersion = '0.8.15' } @@ -185,6 +274,7 @@ jacocoTestReport { '**/web/interceptor/**', '**/aop/**', '**/scheduler/**', + '**/logging/**', '**/domain/**', '**/spec/**', ]) @@ -252,7 +342,9 @@ tasks.register('buildTailwindCss', Exec) { inputs.file(inputCss) inputs.file(configJs) inputs.dir('src/main/resources/templates') - inputs.dir('src/main/resources/static/js') + // Sources only — the generated .min.js files (minifyJs) mirror them and would + // otherwise overlap with this task's input directory. + inputs.files(fileTree('src/main/resources/static/js') { exclude '**/*.min.js' }) outputs.file(outCss) doFirst { @@ -310,7 +402,107 @@ tasks.register('downloadVendorJs') { processResources.dependsOn 'downloadVendorJs' // ───────────────────────────────────────────────────────────────────────────── -// H2/H3: OSS notices / THIRD_PARTY_LICENSES.md version single-sourcing. +// Custom JS minification +// +// Minifies src/main/resources/static/js/**/*.js (vendor/ excluded — vendor files +// already ship minified from their CDNs) to .min.js next to each source. +// Templates reference the .min.js files; the unminified sources stay the editable +// originals. The generated .min.js files are gitignored (build output, same as +// tailwind.css). +// +// Uses the esbuild standalone binary (no Node.js required), downloaded from the +// npm registry to build/tools/ on first build — same pattern as the Tailwind CLI. +// ───────────────────────────────────────────────────────────────────────────── +ext { + esbuildVersion = '0.28.1' + esbuildToolDir = layout.buildDirectory.dir("tools/esbuild-${esbuildVersion}").get().asFile +} + +static def esbuildPackageName() { + def os = System.getProperty('os.name').toLowerCase() + def arch = System.getProperty('os.arch').toLowerCase() + def aarch = (arch == 'aarch64' || arch == 'arm64') + if (os.contains('win')) { + return aarch ? 'win32-arm64' : 'win32-x64' + } else if (os.contains('mac')) { + return aarch ? 'darwin-arm64' : 'darwin-x64' + } else { + return aarch ? 'linux-arm64' : 'linux-x64' + } +} + +static def esbuildBinaryFile(File toolDir) { + // The npm tgz extracts under package/; the binary is package/esbuild.exe on + // Windows and package/bin/esbuild elsewhere. + return System.getProperty('os.name').toLowerCase().contains('win') + ? new File(toolDir, 'unpacked/package/esbuild.exe') + : new File(toolDir, 'unpacked/package/bin/esbuild') +} + +tasks.register('downloadEsbuild') { + description = 'Downloads the esbuild standalone binary from the npm registry (cached).' + group = 'frontend' + def pkg = esbuildPackageName() + def tgz = new File(esbuildToolDir, "${pkg}.tgz") + def marker = esbuildBinaryFile(esbuildToolDir) + outputs.file(marker) + doLast { + if (!marker.exists()) { + esbuildToolDir.mkdirs() + def url = "https://registry.npmjs.org/@esbuild/${pkg}/-/${pkg}-${esbuildVersion}.tgz" + logger.lifecycle("[esbuild] downloading ${url}") + URI.create(url).toURL().withInputStream { i -> tgz.withOutputStream { o -> o << i } } + def unpacked = new File(esbuildToolDir, 'unpacked') + unpacked.mkdirs() + project.copy { + from(tarTree(resources.gzip(tgz))) + into(unpacked) + } + if (!System.getProperty('os.name').toLowerCase().contains('win')) { + marker.setExecutable(true, false) + } + logger.lifecycle("[esbuild] extracted to ${marker}") + } + } +} + +tasks.register('minifyJs') { + description = 'Minifies custom static JS (vendor excluded) to .min.js next to each source.' + group = 'frontend' + dependsOn 'downloadEsbuild' + + def jsDir = file('src/main/resources/static/js') + def sources = fileTree(jsDir) { + include '**/*.js' + exclude 'vendor/**' + exclude '**/*.min.js' + } + def minified = sources.files.collect { f -> new File(f.parent, f.name.replaceAll(/\.js$/, '.min.js')) } + + inputs.files(sources) + outputs.files(minified) + + doLast { + def bin = esbuildBinaryFile(esbuildToolDir) + sources.files.each { src -> + def out = new File(src.parent, src.name.replaceAll(/\.js$/, '.min.js')) + // ProcessBuilder rather than an Exec task so one task handles all files; + // project.exec was removed in Gradle 9. + def proc = new ProcessBuilder(bin.absolutePath, src.absolutePath, '--minify', + "--outfile=${out.absolutePath}".toString()) + .redirectErrorStream(true).start() + def output = proc.inputStream.text + if (proc.waitFor() != 0) { + throw new GradleException("esbuild failed for ${src}: ${output}") + } + } + } +} + +processResources.dependsOn 'minifyJs' + +// ───────────────────────────────────────────────────────────────────────────── +// OSS notices / THIRD_PARTY_LICENSES.md version single-sourcing. // This is version-SYNC automation, not dependency-DISCOVERY — a newly added // library must be added to both maps below, or it is simply not tracked. // ───────────────────────────────────────────────────────────────────────────── @@ -333,14 +525,23 @@ ext.trackedOssCoordinates = [ h2 : 'com.h2database:h2', postgresqlJdbc : 'org.postgresql:postgresql', lombok : 'org.projectlombok:lombok', + caffeine : 'com.github.ben-manes.caffeine:caffeine', cyclonedx : 'org.cyclonedx:cyclonedx-core-java', packageurl : 'com.github.package-url:packageurl-java', micrometer : 'io.micrometer:micrometer-registry-prometheus', flyway : 'org.flywaydb:flyway-core', springOauth2 : 'org.springframework.security:spring-security-oauth2-client', + springSaml2Provider : 'org.springframework.security:spring-security-saml2-service-provider', + springSessionJdbc : 'org.springframework.session:spring-session-jdbc', + shedlock : 'net.javacrumbs.shedlock:shedlock-spring', + re2j : 'com.google.re2j:re2j', + webPush : 'nl.martijndwars:web-push', + bouncyCastle : 'org.bouncycastle:bcprov-jdk18on', + jose4j : 'org.bitbucket.b_c:jose4j', ] tasks.register('generateOssManifest') { + inputs.file('build.gradle') description = 'Resolves runtime dependency versions and writes the single OSS version manifest ' + 'consumed by the /oss-notices page and the THIRD_PARTY_LICENSES.md sync task.' group = 'documentation' @@ -393,7 +594,7 @@ tasks.named('processResources') { // line, and whether that line is a literal version or a "Managed by X.Y.Z" phrase // (only the trailing version token is rewritten in the phrase case — see // syncThirdPartyLicenseVersions below). Sections not listed here (e.g. Qwen3-1.7B, which has -// its own G3 redistribution-notice lifecycle) are left completely untouched. +// its own redistribution-notice lifecycle) are left completely untouched. ext.thirdPartyVersionSections = [ 'Spring Boot / Spring Framework' : [key: 'springBootBomVersion', mode: 'bomPhrase'], 'Spring Security' : [key: 'springBootBomVersion', mode: 'bomPhrase'], @@ -406,6 +607,8 @@ ext.thirdPartyVersionSections = [ 'Micrometer Prometheus Registry' : [key: 'springBootBomVersion', mode: 'bomPhrase'], 'Flyway' : [key: 'springBootBomVersion', mode: 'bomPhrase'], 'Spring Security OAuth2 Client' : [key: 'springBootBomVersion', mode: 'bomPhrase'], + 'Spring Session JDBC' : [key: 'springBootBomVersion', mode: 'bomPhrase'], + 'ShedLock' : [key: 'shedlock', mode: 'literal'], 'Jackson' : [key: 'springBootBomVersion', mode: 'bomPhrase'], 'Logback Classic' : [key: 'springBootBomVersion', mode: 'bomPhrase'], 'SLF4J API' : [key: 'springBootBomVersion', mode: 'bomPhrase'], @@ -413,6 +616,7 @@ ext.thirdPartyVersionSections = [ 'H2 Database' : [key: 'springBootBomVersion', mode: 'bomPhrase'], 'PostgreSQL JDBC Driver' : [key: 'springBootBomVersion', mode: 'bomPhrase'], 'Project Lombok' : [key: 'springBootBomVersion', mode: 'bomPhrase'], + 'Caffeine' : [key: 'caffeine', mode: 'literal'], 'Alpine.js' : [key: 'alpine', mode: 'literal'], '@alpinejs/collapse' : [key: 'alpineCollapse', mode: 'literal'], 'Chart.js' : [key: 'chartjs', mode: 'literal'], @@ -422,7 +626,7 @@ ext.thirdPartyVersionSections = [ tasks.register('syncThirdPartyLicenseVersions') { description = 'Rewrites only the **Version:** lines in THIRD_PARTY_LICENSES.md from the OSS ' + - 'manifest (H2/H3) — no other prose (license text, distribution policy) is touched.' + 'manifest — no other prose (license text, distribution policy) is touched.' group = 'documentation' dependsOn 'generateOssManifest' doLast { @@ -482,7 +686,7 @@ tasks.register('syncThirdPartyLicenseVersions') { } tasks.register('vdbBuild', JavaExec) { - description = 'E5: runs the oswl-vdb CLI (build/verify/inspect offline vulnerability-DB bundles) — ' + + description = 'Runs the oswl-vdb CLI (build/verify/inspect offline vulnerability-DB bundles) — ' + 'see scripts/oswl-vdb/oswl-vdb.{sh,ps1} for thin wrapper scripts. ' + 'Example: ./gradlew vdbBuild --args="build --wanted wanted-list.jsonl --out bundle.zip"' group = 'application' diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 00000000..706ed317 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,60 @@ +# Deployment files + +Run the commands below from the repository root. + +- [`docker/Dockerfile`](docker/Dockerfile) builds the application from source and packages its JAR in a JRE image. Its build context is the repository root. +- [`docker/compose.yml`](docker/compose.yml) runs the source-built application and PostgreSQL 18. The application profile defaults to `prod` in Compose; the supplied `.env.example` selects `local` explicitly. +- [`docker/compose.prod.yml`](docker/compose.prod.yml) is a separate, complete production configuration with PostgreSQL 15, loopback host-port publishing and log volumes. Use it by itself, not as an override layered over the other file. +- [`observability/grafana/oswl-dashboard.json`](observability/grafana/oswl-dashboard.json) is an importable dashboard, not a documentation page. + +## Build an image + +```sh +docker build -f deploy/docker/Dockerfile -t oswl:local . +``` + +The root `.dockerignore` controls the build context. Application profiles and database migrations stay under `src/main/resources/` because they are packaged in the JAR. + +## Run with Compose + +Keep actual `.env` and `.env.prod` files at the repository root. Only the templates moved. If you already have an environment file, keep it instead of copying over it. + +```sh +# First-time setup only: copy the template, then edit its values. +cp deploy/docker/.env.example .env +docker compose --env-file .env -f deploy/docker/compose.yml up -d --build +``` + +```sh +# First-time production setup only: copy the template, then fill every required value. +cp deploy/docker/.env.prod.example .env.prod +docker compose --env-file .env.prod -f deploy/docker/compose.prod.yml up -d --build +``` + +For production, the sample sets `SERVER_ADDRESS=0.0.0.0` inside the container while the host port remains bound to `127.0.0.1`. Prepare the database schema before startup: a new empty database can use `OSWL_FLYWAY_ENABLED=true` to run the supplied baseline and later migrations. Review existing schema/history before enabling Flyway on an existing installation. + +`--env-file` supplies Compose interpolation; each service also reads the same root file through its `env_file` setting. Shell variables can override interpolated values. Review the [production checklist](../docs/en/Production-Deployment-Checklist.md) before running an instance. + +## Existing installations + +Both files use `name: oswl` to retain the default project name of the original `Oswl` checkout instead of deriving `docker` from their new directory. Service names, database versions, volume keys, ports and profiles are unchanged. + +If your installation used another checkout directory name, `-p`, or `COMPOSE_PROJECT_NAME`, keep that original project name. Inspect `docker compose ls` and `docker volume ls` before switching commands; use `-p YOUR_EXISTING_PROJECT` with the new file path. A different project name can create new empty volumes instead of reconnecting the existing database. Do not remove existing volumes during this path migration. + +The two configurations retain different PostgreSQL major versions. Changing between them is not a database upgrade procedure. See [backup and restore](../docs/en/Backup-And-Restore.md). + +## Release and site publishing + +The [CI workflow](../.github/workflows/ci-cd.yml) publishes the production JAR to GitHub Releases. It does not publish a Docker image or deploy an application server. [Pages](../.github/workflows/pages.yml) publishes the landing site, and [Wiki sync](../.github/workflows/wiki-sync.yml) publishes English documentation. + +[English deployment guide](../docs/en/Production-Deployment-Checklist.md) | [한국어 배포 가이드](../docs/ko/Production-Deployment-Checklist.md) | [日本語デプロイガイド](../docs/ja/Production-Deployment-Checklist.md) + +## Release artifacts + +The release version is declared in `build.gradle`. OsWL uses exact numeric versions, including four-part hotfix versions such as `1.0.5.1`; release tags use the `v` prefix. + +Before tagging a reviewed commit, run `python .github/scripts/validate-release.py --tag v1.0.5.1`, the CI checks, and `./gradlew bootJar verifyProdJar`. The production artifact is `build/libs/oswl-1.0.5.1.jar`. The validator checks the changelog, SARIF version, translated release notes and landing/application translation keys. + +To publish the reviewed commit, create an annotated `v1.0.5.1` tag and push that tag to GitHub. The CI/CD workflow runs backend and browser checks for the tag, requires its version to match `build.gradle`, then creates a draft release, attaches the JAR and `SHA256SUMS`, and publishes it. A failed build or validation does not publish a release. Reruns can resume a draft; published assets are never overwritten automatically. Branch pushes and manual CI dispatches do not publish releases. + +Pages and Wiki remain separate workflows on `main`; publishing a binary release does not deploy a server or update those sites. See [release notes](../docs/en/Whats-New-v1.0.5.1.md) for upgrade requirements and data limitations. diff --git a/.env.example b/deploy/docker/.env.example similarity index 100% rename from .env.example rename to deploy/docker/.env.example diff --git a/.env.prod.example b/deploy/docker/.env.prod.example similarity index 71% rename from .env.prod.example rename to deploy/docker/.env.prod.example index 87e1c8e6..107fa5ef 100644 --- a/.env.prod.example +++ b/deploy/docker/.env.prod.example @@ -1,5 +1,5 @@ # Copy to .env.prod and fill real production secrets. Do NOT commit real secrets. -# See docs/Production-Deployment-Checklist.md +# See docs/en/Production-Deployment-Checklist.md # Postgres POSTGRES_DB=oswl @@ -17,11 +17,15 @@ OSWL_ENCRYPTION_KEY= # Optional: separate HMAC key for trusted-device cookie (recommended) # OSWL_TRUSTED_DEVICE_HMAC_KEY= +# Empty DB: enable Flyway to apply the supplied baseline and later migrations. +# Existing DB: review schema/history and back up before enabling. +OSWL_FLYWAY_ENABLED=false + # JVM profile SPRING_PROFILES_ACTIVE=prod -# Listen address inside the container (default 127.0.0.1; map host port in compose) -SERVER_ADDRESS=127.0.0.1 +# Listen on the container network; Compose separately publishes only host 127.0.0.1:8080. +SERVER_ADDRESS=0.0.0.0 # SMTP (use your production SMTP provider credentials) SMTP_HOST=smtp.example.com diff --git a/Dockerfile b/deploy/docker/Dockerfile similarity index 100% rename from Dockerfile rename to deploy/docker/Dockerfile diff --git a/deploy/docker/compose.prod.yml b/deploy/docker/compose.prod.yml new file mode 100644 index 00000000..c7ed474e --- /dev/null +++ b/deploy/docker/compose.prod.yml @@ -0,0 +1,63 @@ +# Preserve the original Oswl checkout project name; -p / COMPOSE_PROJECT_NAME can override it. +name: oswl +services: + db: + image: postgres:15 + restart: unless-stopped + env_file: + - ../../.env.prod + environment: + POSTGRES_DB: "${POSTGRES_DB}" + POSTGRES_USER: "${POSTGRES_USER}" + POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}" + volumes: + - db-data-prod:/var/lib/postgresql/data + # Cap container log growth (docker itself, not the app's own file log below). + logging: + driver: json-file + options: + max-size: "100m" + max-file: "10" + + app: + build: + context: ../.. + dockerfile: deploy/docker/Dockerfile + env_file: + - ../../.env.prod + environment: + DB_URL: "${DB_URL}" + DB_USERNAME: "${DB_USERNAME}" + DB_PASSWORD: "${DB_PASSWORD}" + OSWL_ENCRYPTION_KEY: "${OSWL_ENCRYPTION_KEY}" + SPRING_PROFILES_ACTIVE: "prod" + SERVER_ADDRESS: "${SERVER_ADDRESS:-127.0.0.1}" + JAVA_OPTS: "-Xms512m -Xmx1g" + SPRING_MAIL_HOST: "${SMTP_HOST}" + SPRING_MAIL_PORT: "${SMTP_PORT}" + SPRING_MAIL_USERNAME: "${SMTP_USER}" + SPRING_MAIL_PASSWORD: "${SMTP_PASS}" + SPRING_MAIL_PROPERTIES_MAIL_SMTP_AUTH: "true" + SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_ENABLE: "true" + # Rotating file log inside the container, mounted to a named volume below so + # it survives container recreation. OSWL_LOG_JSON opt-in via .env.prod (default false). + OSWL_LOG_DIR: "/var/log/oswl" + OSWL_LOG_JSON: "${OSWL_LOG_JSON:-false}" + volumes: + - oswl-logs-prod:/var/log/oswl + ports: + - "127.0.0.1:8080:8080" + depends_on: + - db + restart: unless-stopped + # Cap container stdout/stderr log growth (separate from the OSWL_LOG_DIR file + # log above — this is docker's own capture of the process's console output). + logging: + driver: json-file + options: + max-size: "100m" + max-file: "10" + +volumes: + db-data-prod: + oswl-logs-prod: diff --git a/docker-compose.yml b/deploy/docker/compose.yml similarity index 87% rename from docker-compose.yml rename to deploy/docker/compose.yml index 0c64dbc3..09dcf0c6 100644 --- a/docker-compose.yml +++ b/deploy/docker/compose.yml @@ -1,10 +1,11 @@ -version: '3.8' +# Preserve the original Oswl checkout project name; -p / COMPOSE_PROJECT_NAME can override it. +name: oswl services: db: image: postgres:18 restart: unless-stopped env_file: - - .env + - ../../.env environment: POSTGRES_DB: "${POSTGRES_DB:-oswl}" POSTGRES_USER: "${POSTGRES_USER:-oswl}" @@ -18,11 +19,13 @@ services: retries: 5 app: - build: . + build: + context: ../.. + dockerfile: deploy/docker/Dockerfile ports: - "8080:8080" env_file: - - .env + - ../../.env extra_hosts: - "host.docker.internal:host-gateway" environment: diff --git a/deploy/observability/grafana/oswl-dashboard.json b/deploy/observability/grafana/oswl-dashboard.json new file mode 100644 index 00000000..45f2e7d2 --- /dev/null +++ b/deploy/observability/grafana/oswl-dashboard.json @@ -0,0 +1,788 @@ +{ + "annotations": { + "list": [] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "links": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum by (le) (rate(oswl_scan_duration_seconds_bucket[$__rate_interval])))", + "legendFormat": "p95", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(rate(oswl_scan_duration_seconds_sum[$__rate_interval])) / sum(rate(oswl_scan_duration_seconds_count[$__rate_interval]))", + "legendFormat": "mean", + "range": true, + "refId": "B" + } + ], + "title": "Scan duration (p95 / mean)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "stepAfter", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "oswl_quickimport_queue_depth", + "legendFormat": "queue depth", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "oswl_quickimport_running", + "legendFormat": "running imports", + "range": true, + "refId": "B" + } + ], + "title": "Quick Import queue depth / running imports", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 3, + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum by (ecosystem) (increase(oswl_components_ingested_total[$__rate_interval]))", + "legendFormat": "{{ecosystem}}", + "range": true, + "refId": "A" + } + ], + "title": "Components ingested by ecosystem", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 4, + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum by (provider) (rate(oswl_ai_calls_total[$__rate_interval]))", + "legendFormat": "calls {{provider}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum by (provider, direction) (rate(oswl_ai_tokens_total[$__rate_interval]))", + "legendFormat": "tokens {{provider}} {{direction}}", + "range": true, + "refId": "B" + } + ], + "title": "AI calls / tokens", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 16 + }, + "id": 5, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(increase(oswl_ai_cost_usd_total[$__range]))", + "legendFormat": "estimated cost", + "range": true, + "refId": "A" + } + ], + "title": "AI estimated cost (dashboard range)", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 0.5 + }, + { + "color": "green", + "value": 0.9 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 6, + "y": 16 + }, + "id": 6, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(increase(oswl_gate_evaluations_total{outcome=\"pass\"}[$__range])) / sum(increase(oswl_gate_evaluations_total[$__range]))", + "legendFormat": "pass ratio", + "range": true, + "refId": "A" + } + ], + "title": "Gate pass ratio (dashboard range)", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 7, + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum by (outcome) (rate(oswl_gate_evaluations_total[$__rate_interval]))", + "legendFormat": "{{outcome}}", + "range": true, + "refId": "A" + } + ], + "title": "Gate evaluations (pass / fail)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 0.1 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 24 + }, + "id": 8, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum by (source) (rate(oswl_external_api_calls_total{outcome=~\"failure|ratelimited\"}[$__rate_interval])) / sum by (source) (rate(oswl_external_api_calls_total[$__rate_interval]))", + "legendFormat": "{{source}} failure+ratelimited ratio", + "range": true, + "refId": "A" + } + ], + "title": "External API failure / rate-limit ratio by source", + "type": "timeseries" + } + ], + "refresh": "30s", + "schemaVersion": 39, + "tags": [ + "oswl" + ], + "templating": { + "list": [ + { + "current": {}, + "includeAll": false, + "label": "Prometheus datasource", + "multi": false, + "name": "DS_PROMETHEUS", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "type": "datasource" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "OsWL Business Metrics", + "uid": "oswl-business-metrics", + "version": 1, + "weekStart": "" +} diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml deleted file mode 100644 index 369bf3d4..00000000 --- a/docker-compose.prod.yml +++ /dev/null @@ -1,40 +0,0 @@ -version: '3.8' -services: - db: - image: postgres:15 - restart: unless-stopped - env_file: - - .env.prod - environment: - POSTGRES_DB: "${POSTGRES_DB}" - POSTGRES_USER: "${POSTGRES_USER}" - POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}" - volumes: - - db-data-prod:/var/lib/postgresql/data - - app: - build: . - env_file: - - .env.prod - environment: - DB_URL: "${DB_URL}" - DB_USERNAME: "${DB_USERNAME}" - DB_PASSWORD: "${DB_PASSWORD}" - OSWL_ENCRYPTION_KEY: "${OSWL_ENCRYPTION_KEY}" - SPRING_PROFILES_ACTIVE: "prod" - SERVER_ADDRESS: "${SERVER_ADDRESS:-127.0.0.1}" - JAVA_OPTS: "-Xms512m -Xmx1g" - SPRING_MAIL_HOST: "${SMTP_HOST}" - SPRING_MAIL_PORT: "${SMTP_PORT}" - SPRING_MAIL_USERNAME: "${SMTP_USER}" - SPRING_MAIL_PASSWORD: "${SMTP_PASS}" - SPRING_MAIL_PROPERTIES_MAIL_SMTP_AUTH: "true" - SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_ENABLE: "true" - ports: - - "127.0.0.1:8080:8080" - depends_on: - - db - restart: unless-stopped - -volumes: - db-data-prod: diff --git a/docs/Embedded-AI.md b/docs/Embedded-AI.md deleted file mode 100644 index bb43f0b8..00000000 --- a/docs/Embedded-AI.md +++ /dev/null @@ -1,174 +0,0 @@ -# Embedded AI - -Embedded AI lets OsWL run a local LLM out of the box — no cloud account, no API key, and **nothing leaves the machine**. It starts a bundled [llama.cpp](https://github.com/ggml-org/llama.cpp) `llama-server` as a sidecar process and exposes an **OpenAI-compatible** endpoint (`http://127.0.0.1:/v1`), which is registered as the **LOCAL** AI provider. CVE triage summaries and license insights then run entirely on this machine. - ---- - -## How It Works - -* OsWL launches `llama-server` from a **model directory** (default `./embedded-ai`) and waits until it answers `/health`. If no `.gguf` is present yet, OsWL starts downloading the default Qwen3 model in the background shortly after boot (one-time, ~1.2 GB) — so by the time you visit Settings and click **Start**, the model is often already there or partway through downloading, instead of the download starting only once you click. Set `oswl.ai.embedded.auto-download-on-boot=false` to go back to download-on-click-Start only. On an **air-gapped** machine (`oswl.airgapped.enabled=true`), this background download never runs — place a `.gguf` file there yourself beforehand instead (see [Requirements & Directory Layout](#requirements--directory-layout)). -* The server binds **localhost only** (`127.0.0.1`) — it is never reachable from other machines. -* On a successful start, OsWL saves the endpoint as the **LOCAL** provider and activates it (any other active provider is deactivated, as only one provider is active at a time). -* Stopping Embedded AI also deactivates the LOCAL provider so AI calls do not fail against a dead endpoint. -* The server launches with reasoning disabled (`--reasoning-budget` 0) and no web UI (`--no-webui`). By default it also tries to offload model layers to the GPU (`-ngl 999`) — if the GPU launch fails (no GPU build, not enough VRAM), OsWL automatically retries the same model CPU-only before giving up on it. -* Concurrent AI calls are served in parallel instead of queuing one by one: the server starts with `--parallel 4 --cont-batching`, flash attention (`-fa`) and prefix-cache reuse (`--cache-reuse 256`) by default, and OsWL itself runs its independent AI calls concurrently (up to `oswl.ai.enrichment.max-parallel-calls`, default `3`). -* Batch prompts sent to the LOCAL provider use a simplified JSON schema variant (`oswl.ai.enrichment.local-simple-schema`, default `true`) — a small local model follows a simple output schema much more reliably than the full one meant for larger cloud models. -* CVE and license summaries are cached by a hash of the input context, so unchanged components are not re-asked on the next scan. -* Posture, security-trend, license-trend, and version-diff insights are generated in a single combined call (`insights.combined`) instead of four separate calls, reducing fixed overhead. - -> Required permission: `SETTINGS_AI_MANAGE` or System Admin — same as the other AI settings. - ---- - -## Requirements & Directory Layout - -The model directory needs the server binary; a `.gguf` model is fetched automatically the -first time you click **Start** if none is present yet: - -``` -embedded-ai/ - llama-server(.exe) — llama.cpp server binary (you provide this) - qwen3-1.7b-q4_k_m.gguf — default model (Apache 2.0) — auto-downloaded on first Start -``` - -With the directory empty, OsWL downloads Qwen3-1.7B (~1.2 GB) straight into it — starting -automatically shortly after boot, or immediately on clicking **Start** if it hasn't finished -yet — verifies the SHA256 checksum, and only then launches the sidecar. The card shows live -download progress, and the whole thing runs from just `java -jar app.jar`, no separate -script or build step needed. This is safe because Qwen3 is Apache 2.0 licensed (see -[THIRD_PARTY_LICENSES.md](../THIRD_PARTY_LICENSES.md#qwen3-17b-gguf)) — bundling/fetching it -on the user's behalf carries no extra redistribution obligation. The model is downloaded from -the upstream [Hugging Face repository](https://huggingface.co/ggml-org/Qwen3-1.7B-GGUF) by -default; point `OSWL_EMBEDDED_DEFAULT_MODEL_URL` at a byte-identical self-hosted mirror if you -would rather not depend on a third-party host. - -Treat the default-model URL, SHA256, and size settings as a matched set: if you override the -URL, you must also update the SHA256 and size to match, or every download will fail checksum -verification. - -You can drop in any other `.gguf` model yourself — OsWL picks up every `.gguf` file placed -directly in this directory, not just the default Qwen3 one. Check the model's own license -before redistributing or sharing it further; only Qwen3 is bundled/auto-fetched by OsWL. - -| Item | Where OsWL looks | -|---|---| -| Server binary | `/llama-server(.exe)`, then `/bin/`, then the system `PATH` ([llama.cpp releases](https://github.com/ggml-org/llama.cpp/releases)) — **not** auto-downloaded, place it yourself | -| Models | Every `.gguf` file directly inside the directory | - -Configuration defaults (a folder saved in the UI takes precedence over `dir`): - -| Config key | Env var | Default | Description | -|---|---|---|---| -| `oswl.ai.embedded.dir` | `OSWL_EMBEDDED_AI_DIR` | `embedded-ai` | Model directory (relative to the working directory) | -| `oswl.ai.embedded.port` | `OSWL_EMBEDDED_AI_PORT` | `11435` | localhost port for the sidecar | -| `oswl.ai.embedded.context-size` | `OSWL_EMBEDDED_AI_CONTEXT` | `8192` | Context window passed to `llama-server -c` — the **total** context, divided across the parallel slots | -| `oswl.ai.embedded.gpu-layers` | `OSWL_EMBEDDED_AI_GPU_LAYERS` | `-1` | GPU layers to offload (`-ngl`): `-1` = as many as the build supports, `0` = CPU only, a positive number pins an explicit layer count. A failed GPU start auto-retries the same model CPU-only | -| `oswl.ai.embedded.threads` | `OSWL_EMBEDDED_AI_THREADS` | `0` | Thread count (`-t`); `0` = let llama.cpp auto-detect | -| `oswl.ai.embedded.parallel-slots` | `OSWL_EMBEDDED_AI_PARALLEL` | `4` | `>1` adds `--parallel N --cont-batching` so concurrent AI calls aren't serialized on the server. Each slot gets `context-size / N` — a warning is logged when that drops below 2048 | -| `oswl.ai.embedded.flash-attn` | `OSWL_EMBEDDED_AI_FLASH_ATTN` | `true` | Flash attention (`-fa`) | -| `oswl.ai.embedded.cache-reuse` | `OSWL_EMBEDDED_AI_CACHE_REUSE` | `256` | `--cache-reuse N` for prefix-cache reuse across calls; `<=0` disables | -| `oswl.ai.embedded.extra-args` | `OSWL_EMBEDDED_AI_EXTRA_ARGS` | (empty) | Extra llama-server CLI args, appended verbatim — server config only, never taken from request input | -| `oswl.ai.embedded.startup-timeout-seconds` | `OSWL_EMBEDDED_AI_STARTUP_TIMEOUT_SEC` | `120` | How long each model candidate gets per launch attempt to become healthy | -| `oswl.ai.embedded.default-model-url` | `OSWL_EMBEDDED_DEFAULT_MODEL_URL` | Upstream Hugging Face `ggml-org/Qwen3-1.7B-GGUF` asset | Primary download source for the default Qwen3 model | -| `oswl.ai.embedded.default-model-sha256` | `OSWL_EMBEDDED_DEFAULT_MODEL_SHA256` | (see THIRD_PARTY_LICENSES.md) | Expected SHA256 — always change together with the URL | -| `oswl.ai.embedded.default-model-size-bytes` | `OSWL_EMBEDDED_DEFAULT_MODEL_SIZE_BYTES` | `1282439264` | Expected size, used to pre-fill the download progress bar — part of the same matched set as URL/SHA256 | -| `oswl.ai.embedded.fallback-model-url` | `OSWL_EMBEDDED_FALLBACK_MODEL_URL` | (empty) | Retried once if the primary URL fails; set this to the upstream URL when you override the primary with a self-hosted mirror | -| `oswl.ai.embedded.auto-download-on-boot` | `OSWL_EMBEDDED_AUTO_DOWNLOAD` | `true` | Prefetch the default model in the background on boot; never runs when `oswl.airgapped.enabled=true` | - ---- - -## Starting and Stopping - -Open **Settings → AI** and use the **Embedded AI (built-in local model)** card: - -1. Check the status line — **Running** / **Stopped**, plus a note when the binary is missing. -2. Click **Start**. If the directory has no model yet, a progress bar shows the Qwen3 - download (~1.2 GB — can take a few minutes depending on connection speed) before the - server launches; otherwise model loading alone can take up to a minute. -3. Once running, the card shows the **Active** model and the endpoint is live as the LOCAL provider. -4. Click **Stop** to shut the sidecar down. (This does not delete the downloaded model — - the next Start reuses it instantly.) - -The card also shows the folder in use, all detected `.gguf` files, and the last start error (if any) in red. - ---- - -## Switching Models - -You are not limited to the default model — any llama.cpp-compatible `.gguf` works: - -1. **Download** a quantized GGUF model (e.g. from [Hugging Face](https://huggingface.co/models?library=gguf)). -2. **Place** the `.gguf` file in the model folder shown on the card. -3. **Pick it** in the **Model** dropdown (the list is refreshed from the folder) and click **Save**. -4. Click **Start** — the selected model is tried first. The model choice applies on the next start; switching models while running is not possible (the dropdown is disabled). - -The **Auto (preference order)** option tries, in order: the model saved in the dropdown → `qwen3…` → the first remaining `.gguf` file (alphabetical). - -> For CPU-only inference, small quantized models in the **1B–4B parameter** range (Q4_K_M or similar) are recommended. Larger models need more RAM and may fail the start timeout on slow machines. The context window is `OSWL_EMBEDDED_AI_CONTEXT` (default `8192`) and is split across the parallel slots — with the default 4 slots each call gets 2048 tokens of context, so raise the context or lower `OSWL_EMBEDDED_AI_PARALLEL` if you need longer per-call contexts. - ---- - -## Changing the Model Folder - -Two ways to point OsWL at a different directory: - -| Method | Scope | -|---|---| -| **Folder** input + **Save** on the Embedded AI card | Persisted in the database (`ai_preferences.embedded_dir` / `embedded_model`); wins over the default | -| `OSWL_EMBEDDED_AI_DIR` env / `oswl.ai.embedded.dir` yaml | Default used when no folder is saved in the UI | - -Rules enforced by `PUT /api/settings/ai/embedded/config`: - -* The folder must **already exist** — otherwise the save is rejected (`400`, "Folder not found or not a directory"). -* Changing the folder **while the sidecar is running** stops it first (a running `llama-server` holds file locks on the old directory) and deactivates the LOCAL provider. Start it again from the new folder. -* Clearing the folder input (blank) removes the override and falls back to the configured default. - ---- - -## Automatic Fallback - -A single start click can try several models. Candidates are attempted in order — the requested/saved model first, then the built-in preference order, then any remaining `.gguf` — and each candidate gets up to **120 seconds** per launch attempt (`oswl.ai.embedded.startup-timeout-seconds`) to become healthy. With GPU offload enabled (the default), a candidate that fails its GPU launch is first retried CPU-only; a model that still crashes or times out is skipped in favor of the next one. - -When OsWL ends up running a model that is **not** the first choice, the card shows an amber **"Started with fallback model"** badge next to the model selector. This usually means the preferred model failed to load (too large, corrupted download) — the Active model line tells you what is actually running. - ---- - -## Status and Logs - -`GET /api/settings/ai/embedded` reports `running`, `external`, `binaryFound`, `activeModel`, `fallbackUsed`, `lastError`, `availableModels`, `modelsDir`, `baseUrl`, and (while a default-model download triggered by Start is in flight) `downloading`, `downloadedBytes`, `downloadTotalBytes`. - -`external` is `true` when something already answers `/health` on the configured port that OsWL did not start itself (a manually launched `llama-server`, or one orphaned by a previous OsWL process/crash). `running` stays `true` in this case — the endpoint is genuinely usable as the LOCAL provider — but clicking **Stop** cannot kill a process OsWL doesn't own; it leaves it running and the status keeps reporting `external: true`. - -`POST /api/settings/ai/embedded/start` returns immediately (`success: true`, `downloading: true`) when it kicks off the Qwen3 download instead of waiting for it — the settings page polls status for progress and the eventual `running`/`lastError` outcome. The download itself runs on the server independent of any browser session, so refreshing the page (or closing it) doesn't cancel it; reopening the page resumes showing progress. - -`llama-server` writes its own stdout/stderr to **`/llama-server.log`**. When a start fails, the last lines of that log are included in `lastError` and shown in red on the card — check the full file for details. - ---- - -## Troubleshooting - -| Symptom | Likely cause / fix | -|---|---| -| "llama-server binary not found" | Put `llama-server(.exe)` in the folder shown on the card (or its `bin/` subfolder, or on `PATH`) — this is never auto-downloaded | -| No model yet, and Start doesn't seem to do anything | Check for internet access — the default-model download needs it once. On an air-gapped machine, place a `.gguf` file directly inside the model folder yourself instead | -| "Model download failed" / checksum mismatch | Network interrupted mid-download or a corrupted transfer — the partial file is deleted automatically; click **Start** again to retry | -| `failed to open GGUF file` in the log | The folder in settings does not match where the model actually is — check the **Folder** field and that the file name matches the dropdown entry | -| "did not become healthy within 120s" | Slow machine or oversized model — try a smaller quantization (e.g. a Q4_K_M `.gguf` in the 1B–2B parameter range), or raise `OSWL_EMBEDDED_AI_STARTUP_TIMEOUT_SEC`. A `(GPU)` failure is retried CPU-only automatically, so this only persists when the CPU retry fails too | -| `slotContext … is below 2048` warning in the log | Too many parallel slots for the context size — raise `OSWL_EMBEDDED_AI_CONTEXT` or lower `OSWL_EMBEDDED_AI_PARALLEL` | -| Port already in use | Another process (or a manually started `llama-server`) occupies the port — stop it or set `OSWL_EMBEDDED_AI_PORT`. A healthy server already listening on the port counts as "running" and is flagged `external` in the status response | -| Clicking **Stop** doesn't turn the card off | The running server is `external` (not started by this OsWL instance) — stop the process yourself (or restart the machine/container it runs in), OsWL cannot terminate it | -| "Folder not found or not a directory" on Save | Create the directory first; the save only accepts existing folders | - ---- - -## Security & Privacy - -* The sidecar binds to `127.0.0.1` only and requires **no API key** — nothing is exposed to the network. -* The llama.cpp **CORS warning** in `llama-server.log` is expected and harmless: the server only listens on localhost. -* No telemetry: prompts, code snippets, and CVE data sent to the model never leave the machine. - ---- - -## REST API summary - -See [API Reference — AI](API-Reference.md#ai). Interactive schemas: Swagger UI (`local` profile). diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md deleted file mode 100644 index a83fef48..00000000 --- a/docs/Getting-Started.md +++ /dev/null @@ -1,153 +0,0 @@ -# Getting Started - -This guide walks you through installing OsWL, running the setup wizard, and completing your first project scan. - ---- - -## System Requirements - -| Component | Requirement | -|---|---| -| **JDK** | 25 or later | -| **Build tool** | Gradle Wrapper (bundled — `./gradlew`) | -| **Database** | H2 file-mode (local / dev) or PostgreSQL 15+ (production) | -| **OS** | Linux, macOS, or Windows | -| **Memory** | 512 MB minimum, 1 GB+ recommended | - -> No Node.js or npm is required — the Tailwind CSS standalone binary is downloaded automatically by Gradle on the first build. - ---- - -## Installation - -### 1. Clone the Repository - -```bash -git clone https://github.com/SalkCoding/Oswl.git -cd Oswl -``` - -### 2. Choose a Profile - -OsWL ships with two Spring profiles: - -| Profile | Database | Use case | -|---|---|---| -| `local` *(default)* | H2 file (`./oswl-db.mv.db`) | Development and evaluation | -| `prod` | PostgreSQL | Production deployment | - -### 3. Start the Application - -**Local (H2, zero config):** - -```bash -./gradlew bootRun -``` - -**Production (PostgreSQL):** - -```bash -export SPRING_PROFILES_ACTIVE=prod -export DB_URL=jdbc:postgresql://localhost:5432/oswl -export DB_USERNAME=oswl -export DB_PASSWORD=changeme -export OSWL_ENCRYPTION_KEY=$(openssl rand -base64 32) - -./gradlew bootRun -``` - -> **`OSWL_ENCRYPTION_KEY`** — protects stored secrets such as VCS tokens. In `local`, a development key may be generated automatically. In **`prod`**, you **must** set a stable value before startup; the application will not start without it. Losing the key makes previously stored VCS credentials unusable. - -The application starts on port **8080** by default. - ---- - -## Embedded AI Model (first run) - -OsWL can run its AI features fully on-premise through an embedded llama.cpp sidecar. On first boot, if no `.gguf` model exists in `embedded-ai/`, OsWL starts a background download of the default **Qwen3-1.7B** model (~1.2 GB): - -* Downloaded from the upstream Hugging Face repository (`ggml-org/Qwen3-1.7B-GGUF`), with SHA-256 integrity verification; point `OSWL_EMBEDDED_DEFAULT_MODEL_URL` at a self-hosted mirror to avoid depending on a third-party host. -* Download-only — it never starts the sidecar or changes the active AI provider on its own. Progress appears in **Settings → AI**; click **Start** there once the file is ready. -* Opt out with `OSWL_EMBEDDED_AUTO_DOWNLOAD=false`. In air-gapped mode the download is never attempted (see below). - -The `llama-server(.exe)` binary itself is the one manual step — download it from the [llama.cpp releases](https://github.com/ggml-org/llama.cpp/releases) and place it in `embedded-ai/` (or on `PATH`). See [Embedded AI](Embedded-AI.md) for details. - ---- - -## Air-Gapped (Offline) Startup - -For hosts without outbound internet access: - -1. Set `OSWL_AIRGAPPED_ENABLED=true` before startup. Vulnerability / threat-intel lookups (OSV, deps.dev, EPSS, KEV) are then served from an imported offline snapshot — no outbound HTTP is attempted, and the embedded-model auto-download is skipped. -2. On an internet-connected machine, build a snapshot bundle: - - ```bash - scripts/oswl-vdb/oswl-vdb.sh build --wanted wanted-list.jsonl --out bundle.zip - ``` - - (Windows: `scripts/oswl-vdb/oswl-vdb.ps1`.) To target the bundle at your actual dependencies, first export a wanted-list from a connected OsWL instance: `GET /api/admin/snapshot/wanted-list`. -3. Transfer `bundle.zip` to the air-gapped host and import it as a System Admin via `POST /api/admin/snapshot/import` (multipart upload), or whitelist a directory with `OSWL_AIRGAPPED_IMPORT_DIR` and use `POST /api/admin/snapshot/import-from-path`. See [Administration — Offline snapshot bundles](Administration.md). -4. For embedded AI, place a `.gguf` model you obtained yourself into `embedded-ai/` before clicking **Start**. - ---- - -## Setup Wizard - -On the very first startup (empty database), OsWL redirects every request to `http://localhost:8080/setup`. - -The wizard collects: - -| Field | Description | -|---|---| -| **Admin Email** | Used as the login credential for the System Admin account | -| **Password** | Must meet the minimum length policy (default: 8 characters) | -| **Display Name** | Shown in the UI and audit log | - -After you submit, OsWL creates the admin account and redirects to the login page. - -> If you need to restart from a clean state in local mode, stop the server and delete `oswl-db.mv.db` (and `oswl-db.trace.db` if present), then restart. - ---- - -## First Login - -1. Navigate to `http://localhost:8080/login`. -2. Enter the email and password you created in the setup wizard. -3. If **Two-Factor Authentication** is enabled (admin-configurable), you will be prompted for a 6-digit OTP sent to your email. - * In `local` mode the OTP appears in the server log: `*** OTP CODE: NNNNNN ***` - * Development shortcut: `000000` is accepted when using the test profile. -4. On first login with a temporary password, OsWL forces an immediate password change. - ---- - -## Seeding Test Data (local only) - -After logging in, call: - -``` -GET http://localhost:8080/data/test -``` - -This endpoint (available **only** in the `local` profile): - -* Deletes all existing projects, scans, libraries, and CVEs. -* Re-populates the database with a rich realistic dataset: multiple projects across Maven and npm ecosystems, dozens of CVEs at various severities, mixed license statuses, and multiple historical scans for trend visualization. - -A test API key is also available at: - -``` -GET http://localhost:8080/data/test-api-key -``` - ---- - -## Access control (recommended reading) - -* [Authorization layers](Authorization-Layers.md) — role templates (Admin / Developer / Viewer) vs project membership -* [Production deployment checklist](Production-Deployment-Checklist.md) — before going live with `prod` - -## Next Steps - -* [Connect your first VCS repository](Quick-Import.md) -* [Submit a scan via the CLI](CLI-Integration.md) -* [Explore the Security Center](Security-Center.md) diff --git a/docs/Production-Deployment-Checklist.md b/docs/Production-Deployment-Checklist.md deleted file mode 100644 index 50c22c92..00000000 --- a/docs/Production-Deployment-Checklist.md +++ /dev/null @@ -1,215 +0,0 @@ -# Production deployment checklist - -Use this one-page list before exposing OsWL on the internet. **Do not run `prod` with `local` defaults** (H2, Swagger, `/data/**`, or committed encryption keys). - -## 1. Profile and build - -| Check | Action | -|-------|--------| -| Profile | Set `SPRING_PROFILES_ACTIVE=prod` | -| JAR | Build with `./gradlew bootJar verifyProdJar` — `TestDataController` must **not** appear in the JAR | -| Local-only code | `src/local/java` is for `bootRun` / dev only, not packaged in `bootJar` | - -## 2. Required environment variables - -| Variable | Purpose | -|----------|---------| -| `DB_URL` | JDBC URL (e.g. `jdbc:postgresql://db:5432/oswl`) | -| `DB_USERNAME` | Database user | -| `DB_PASSWORD` | Database password | -| `OSWL_ENCRYPTION_KEY` | Instance encryption key (generate with `openssl rand -base64 32`) | - -Copy `.env.prod.example` → `.env.prod` and fill every value. **No defaults** for DB or encryption in `application-prod.yaml`. - -On startup, missing variables and other config issues are printed in **one `OSWL STARTUP WARNINGS` block** in the log (after the application is ready). In **`prod`**, if `OSWL_ENCRYPTION_KEY` is missing, the application **fails to start** — set a stable key before go-live. (The `local` profile may use a temporary key for development only.) - -## 3. Network binding - -| Check | Action | -|-------|--------| -| Default bind | `SERVER_ADDRESS=127.0.0.1` (see `application-prod.yaml`) | -| Public access | Put **nginx / Caddy / Traefik** (or cloud LB) in front; terminate TLS there | -| Direct `0.0.0.0` | Only if you accept exposing the JVM HTTP stack; document the risk and firewall | - -`docker-compose.prod.yml` maps **`127.0.0.1:8080:8080`** so the container is not published on all interfaces by default. - -Set `server.forward-headers-strategy=framework` (default in `application.yaml`) when the proxy sends `X-Forwarded-Proto` for HSTS and secure cookies. - -## 4. Docker Compose (production) - -```bash -cp .env.prod.example .env.prod -# Edit DB_*, OSWL_ENCRYPTION_KEY, SMTP_* -docker compose -f docker-compose.prod.yml up -d --build -``` - -Verify logs: no missing-env banner, PostgreSQL connected, no H2 or Swagger URLs. - -## 5. Logging and observability - -| Check | Action | -|-------|--------| -| Log levels | `prod` profile: `com.salkcoding.oswl` at **INFO** only; no DEBUG on AI/clients | -| AI excerpts | `oswl.ai.debug.log-prompt-excerpt` / `log-response-excerpt` default **false** in prod | -| Actuator | **`health`, `info`, `prometheus`** exposed (v1.0.4); everything else disabled (`enabled-by-default: false`) | -| Metrics scrape | Point Prometheus at `/actuator/prometheus` — the scraper must present admin credentials | -| Actuator auth | Requires **SYSTEM_ADMIN** session (not public) | - -## 6. Security features enabled in prod - -- Springdoc / Swagger UI: **off** -- H2 console and `/data/**`: **not in prod JAR** (local profile + `src/local/java` only) -- Security headers + HSTS (behind HTTPS): see `application-prod.yaml` `oswl.security.headers` -- Trusted-device cookie: `Secure` in prod - -## 7. Optional secrets - -| Variable | Purpose | -|----------|---------| -| `OSWL_TRUSTED_DEVICE_HMAC_KEY` | Dedicated HMAC key for `OSWL_TD` cookie (recommended; separate from `OSWL_ENCRYPTION_KEY`) | -| `OSWL_OIDC_CLIENT_ID` / `OSWL_OIDC_CLIENT_SECRET` / `OSWL_OIDC_ISSUER_URI` | **v1.0.4** — OIDC single sign-on. Also uncomment the `spring.security.oauth2.client` block in `application-prod.yaml`; the login page shows the SSO button only when a provider is registered. | - -### v1.0.4 opt-in features - -All default to **off** — enable deliberately. - -| Variable | Default | Effect when enabled | -|---|---|---| -| `OSWL_FLYWAY_ENABLED` | `false` | Versioned migrations with `baseline-on-migrate`; generate a full baseline first | -| `OSWL_AIRGAPPED_ENABLED` | `false` | All vulnerability / threat-intel lookups served from an imported offline snapshot; no outbound HTTP — full offline procedure in §9 | -| `OSWL_GATE_*` | see [What's New](Whats-New-v1.0.4.md) | Default thresholds for `POST /api/scan/gate` | - -Continuous monitoring is the exception: `OSWL_MONITORING_ENABLED` defaults to **`true`** (nightly OSV re-query at 03:00, `OSWL_MONITORING_CRON`). It sends e-mail to project members, so confirm SMTP is configured before first launch — or set it to `false`. - -### Performance tunables (v1.0.4) - -Defaults are production-safe — override only when you have a reason. - -| Variable | Default | Purpose | -|----------|---------|---------| -| `OSWL_DEPSDEV_CONNECT_TIMEOUT_MS` / `OSWL_DEPSDEV_READ_TIMEOUT_MS` | `5000` / `10000` | deps.dev HTTP timeouts (previously a stalled call could hang a whole scan) | -| `OSWL_DEPSDEV_MAX_CONCURRENT` | `24` | Max simultaneous deps.dev requests; on HTTP 429 the client backs off and retries once | -| `OSWL_OSV_CONNECT_TIMEOUT_MS` / `OSWL_OSV_READ_TIMEOUT_MS` | `5000` / `30000` | OSV HTTP timeouts (read is generous — a 1,000-item batch query can legitimately take a while) | -| `OSWL_VERSION_META_TTL_SEC` | `86400` | TTL for deps.dev version metadata on cache-hit libraries | -| `OSWL_CLONE_SPARSE_ENABLED` | `true` | Quick Import clones are blobless + sparse-checked-out; git servers without partial-clone support fall back to a full shallow clone automatically | -| `OSWL_AI_STREAMING_ENABLED` | `true` | Stream free-form AI calls (posture/trend/version diff) over SSE for live previews; endpoints that reject streaming fall back automatically | -| `OSWL_AI_MAX_PARALLEL_CALLS` | `3` | Max concurrent AI enrichment calls; a local llama-server benefits from `--parallel` up to this same count | -| `OSWL_ANTHROPIC_PROMPT_CACHING_ENABLED` | `true` | Mark the Anthropic system prompt as an ephemeral cache breakpoint for repeat calls | - -### 7.1 Air-gapped / offline snapshot (v1.0.4) - -Set `OSWL_AIRGAPPED_ENABLED=true` so vulnerability/threat-intel lookups (OSV, deps.dev, EPSS, CISA KEV) are served from an imported offline snapshot instead of live external APIs. No outbound HTTP is attempted for enrichment. - -| Step | Action | -|------|--------| -| 1. Build bundle | On an internet-connected machine, run the `oswl-vdb` builder. Wrapper scripts: `scripts/oswl-vdb/oswl-vdb.sh` (Linux/macOS) or `scripts/oswl-vdb/oswl-vdb.ps1` (Windows). Both invoke `./gradlew vdbBuild --args="..."`. | -| 2. Target the bundle | Export the components this instance actually scans with `GET /api/admin/snapshot/wanted-list` (SYSTEM_ADMIN), then pass it to `build --wanted wanted-list.jsonl`. The builder fetches only those ecosystem/name/version tuples instead of a full upstream mirror. | -| 3. Import bundle | `POST /api/admin/snapshot/import?mode=replace|merge` (multipart `.zip`). For large bundles, use `POST /api/admin/snapshot/import-from-path` with `{"path":"bundle.zip","mode":"merge"}` after setting `OSWL_AIRGAPPED_IMPORT_DIR` to a whitelist directory. | -| 4. Place model (if using Embedded AI) | Air-gapped hosts disable auto-download. Place the `.gguf` file in `embedded-ai/` manually or host it on an internal mirror (see §8). | - -`oswl-vdb build` options (see `VdbBuilderCli`): -- `--sources osv,epss,kev,depsdev` (default all). -- `--mode delta --since previous.zip` writes only added/changed keys plus `"_deleted":true` markers. -- `--offline-sources ` builds without any network from a cache directory populated by an earlier online run (covers `osv`/`epss`/`kev` only; deps.dev has no bulk dump and is skipped). -- `verify ` and `inspect ` check checksums and metadata. - -Import semantics: -- `replace` (default) clears each source in the store and writes the bundle. -- `merge` upserts by `(source, entry_key)` and honors `"_deleted":true` lines as deletes. -- v2 bundles are SHA-256-checksummed per file in `meta.json`; a mismatch rejects the whole bundle and leaves the existing store untouched. - -Definition freshness (E7): `OSWL_AIRGAPPED_STALENESS_WARN_DAYS` (default `7`) and `OSWL_AIRGAPPED_STALENESS_CRITICAL_DAYS` (default `30`) drive the admin UI badge, measured from the oldest per-source `sourceAsOf` date across imported snapshots. - -Snapshot uploads may need `OSWL_MULTIPART_MAX_FILE_SIZE` / `OSWL_MULTIPART_MAX_REQUEST_SIZE` (default `50MB` each) if your bundle is larger. - -## 8. Embedded AI model (optional, on-premise) - -Only relevant if you plan to use **Embedded AI** (Settings → AI → Local) instead of, or in -addition to, a cloud provider. - -| Check | Action | -|-------|--------| -| Server binary | Download `llama-server(.exe)` for your platform from the [llama.cpp releases](https://github.com/ggml-org/llama.cpp/releases) and place it in `embedded-ai/` (or `bin/` under it, or anywhere on `PATH`) — this is the only manual step | -| Model | Nothing to do — clicking **Start** on a fresh install downloads the Apache-2.0-licensed Qwen3-1.7B model automatically (~1.2 GB, verifies SHA256, shows progress in the UI) | -| Air-gapped hosts | The auto-download needs outbound internet access once. Without it, place a `.gguf` file you've obtained yourself into `embedded-ai/` before clicking Start | -| Custom models | OsWL only bundles/auto-fetches Qwen3-1.7B. Any other `.gguf` you want (e.g. a different size or license) — check its own license, then place it in `embedded-ai/` yourself; see [Embedded AI](Embedded-AI.md) | -| Directory | Defaults to `./embedded-ai` relative to the working directory the JVM starts in — set `OSWL_EMBEDDED_AI_DIR` for a different path | - -No Gradle task or separate script is involved — the download runs inside the application -itself the first time Start is clicked, so a plain `java -jar app.jar` deployment works. - -### Embedded AI tuning (B1 / v1.0.4) - -All default to production-safe values. Override only when you have measured a reason. - -| Variable | Default | Purpose | -|----------|---------|---------| -| `OSWL_EMBEDDED_AI_CONTEXT` | `8192` | Total context size (`-c`). With `--parallel`, this is divided across slots; a slot context below 2048 is logged as a warning. | -| `OSWL_EMBEDDED_AI_GPU_LAYERS` | `-1` | `-ngl`: `-1` offloads as many layers as the build supports (passed as `999`), `0` is CPU only, positive pins an explicit layer count | -| `OSWL_EMBEDDED_AI_THREADS` | `0` | `-t`: `0` lets llama.cpp auto-detect; positive values pin the thread count | -| `OSWL_EMBEDDED_AI_PARALLEL` | `4` | Enables `--parallel N --cont-batching` so concurrent AI calls are not serialized | -| `OSWL_EMBEDDED_AI_FLASH_ATTN` | `true` | Adds `-fa` (flash attention) | -| `OSWL_EMBEDDED_AI_CACHE_REUSE` | `256` | `--cache-reuse` token count; `<=0` disables | -| `OSWL_EMBEDDED_AI_EXTRA_ARGS` | (empty) | Space-separated extra `llama-server` CLI args appended verbatim (admin config only, never request input) | -| `OSWL_EMBEDDED_AI_STARTUP_TIMEOUT_SEC` | `120` | Seconds to wait for `/health` before falling back to CPU-only or the next model candidate | -| `OSWL_EMBEDDED_DEFAULT_MODEL_URL` / `SHA256` / `SIZE_BYTES` | Upstream Hugging Face `ggml-org/Qwen3-1.7B-GGUF` | Matched set for the default Qwen3-1.7B download; override all three for a self-hosted mirror (a byte-identical re-host needs only the URL changed) | -| `OSWL_EMBEDDED_FALLBACK_MODEL_URL` | Hugging Face | Retried once if the primary URL fails; set blank/equal to primary to disable | -| `OSWL_EMBEDDED_AUTO_DOWNLOAD` | `true` | Prefetch the default model on boot (download-only, never starts the sidecar). **Ignored when `OSWL_AIRGAPPED_ENABLED=true`**. | - -## 9. Database schema (upgrades) - -OsWL uses **Hibernate `ddl-auto=validate`** in `prod` — the app does not auto-alter PostgreSQL on startup. - -| Profile | Schema management | -|---------|-------------------| -| `local` | `ddl-auto: update` — H2 schema follows JPA entities automatically | -| `prod` | `ddl-auto: validate` — run SQL scripts manually when upgrading | - -Manual scripts live in `src/main/resources/db/`: - -| File | When to run | -|------|-------------| -| `project_members.sql` | First deploy of project ACL (if table missing) | -| `instance_setup_lock.sql` | First deploy after setup-lock feature | -| `ai_enhancement.sql` | Legacy installs predating AI preference columns / `ai_daily_usage` | -| `schema_cleanup.sql` | **Once** when upgrading to the release that removes unused tables/columns (`ai_feedback`, `external_api_settings`, denormalized `projects.version`, etc.) | - -After running migrations, restart the app and confirm `validate` passes. - -### Flyway (v1.0.4, opt-in) - -Set `OSWL_FLYWAY_ENABLED=true` to manage the schema with Flyway instead of hand-run scripts. `baseline-on-migrate` is enabled, so an existing populated database is baselined rather than rejected — but generate a full baseline migration that matches your current schema **before** turning it on. Left at the default `false`, nothing changes. - -### v1.0.4 columns - -This release adds `libraries.malicious` and `libraries.typosquat_risk`, both `NOT NULL DEFAULT false`. The defaults let the column be added to a populated table, so no manual script is required — but on `prod` (`ddl-auto: validate`) you still add them yourself. It also adds three nullable `libraries` columns (`description`, `homepage`, `source_repo_url`) for the upstream project metadata shown on Component Detail — `validate` checks that every mapped column exists regardless of nullability, so these need the same manual treatment: - -```sql -ALTER TABLE libraries ADD COLUMN IF NOT EXISTS malicious boolean NOT NULL DEFAULT false; -ALTER TABLE libraries ADD COLUMN IF NOT EXISTS typosquat_risk boolean NOT NULL DEFAULT false; -ALTER TABLE libraries ADD COLUMN IF NOT EXISTS description text; -ALTER TABLE libraries ADD COLUMN IF NOT EXISTS homepage varchar(500); -ALTER TABLE libraries ADD COLUMN IF NOT EXISTS source_repo_url varchar(500); -ALTER TABLE scan_results ADD COLUMN IF NOT EXISTS ai_locale varchar(16); -``` - -(Flyway users: `V3__component_metadata.sql` covers the three new `libraries` columns; see [Database Schema](Database-Schema.md).) - -## 10. Post-deploy smoke test - -1. Open UI via HTTPS reverse proxy only. -2. Complete setup / login and 2FA if enabled. -3. Create a project and VCS connection; restart app — token still decrypts (confirms stable `OSWL_ENCRYPTION_KEY`). -4. `POST /api/scan` with project API key (see [Scan API security](Scan-Api-Security.md)). -5. Open a project you are a member of — confirm another user’s project ID returns forbidden (project membership). -6. Review audit log for failed auth attempts. - -## 11. Operations - -- Back up PostgreSQL and store `OSWL_ENCRYPTION_KEY` in a secrets manager (loss = unreadable VCS tokens). -- Rotate API keys and SMTP credentials on compromise. -- Keep `SPRING_PROFILES_ACTIVE` out of images that should never run as `local`. - ---- - -**Local development:** `SPRING_PROFILES_ACTIVE=local`, copy `.env.example` → `.env`, set `OSWL_ENCRYPTION_KEY`, run `./gradlew bootRun`. H2 file DB, H2 console, Swagger, and `GET /data/test` are available only in this profile. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..10927873 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,31 @@ +# OsWL documentation + +**[English](en/Home.md)** | [한국어](ko/Home.md) | [日本語](ja/Home.md) + +English is the default documentation entry point. Start with the [English documentation home](en/Home.md), or choose a language above. + +- [Release notes for 1.0.5.1](en/Whats-New-v1.0.5.1.md) +- [Getting started](en/Getting-Started.md) +- [User guide](en/User-Guide.md) +- [Production deployment](en/Production-Deployment-Checklist.md) +- [Backup and restore](en/Backup-And-Restore.md) +- [API reference](en/API-Reference.md) + +- [Browser security alerts (English)](en/Browser-Security-Alerts.md) + +- [Container image inspection (English)](en/Container-Image-Inspection.md) + + +## Where files belong + +Document bodies live in `en/`, `ko/`, or `ja/` according to their actual language. Keep matching filenames for translations and prefer links to pages in the same language. Some developer references currently exist in only one language; link to that version with its language identified instead of creating empty translations. + +[Deployment files](../deploy/README.md) contain Docker build/run configuration and the Grafana dashboard. [Scripts](../scripts/README.md) contain executable tools. The [landing site](../landing/index.html) is published separately through GitHub Pages. + +## GitHub Wiki publishing + +The [English Wiki](https://github.com/SalkCoding/Oswl/wiki) is generated from `en/` on pushes to `main`. Edit the source documents here; the sync mirrors its generated input and removes pages no longer present in that input. + +The [preparation script](../.github/scripts/prepare-wiki.py) writes ignored output to `build/wiki/`. It converts links between English pages to Wiki links and links to other repository files to GitHub URLs. `Home.md` and `_Sidebar.md` retain their Wiki names. Korean and Japanese documents remain available through repository links from the Wiki. + +Run `python .github/scripts/prepare-wiki.py SalkCoding/Oswl` from the repository root to inspect the publishing output without publishing anything. The workflow's manual dispatch defaults to a dry run; publishing requires disabling its `dry_run` input. This does not change the landing site's Pages deployment. diff --git a/docs/_Sidebar.md b/docs/_Sidebar.md deleted file mode 100644 index 6ea4208b..00000000 --- a/docs/_Sidebar.md +++ /dev/null @@ -1,30 +0,0 @@ -**[Home](Home)** - -### Release -* [What's New in v1.0.4](Whats-New-v1.0.4) - -### Guides -* [Getting Started](Getting-Started) -* [User Guide](User-Guide) -* [Quick Import](Quick-Import) -* [CLI Integration](CLI-Integration) -* [Scan History](Scan-History) -* [Embedded AI](Embedded-AI) - -### Analysis -* [Security Center](Security-Center) -* [License Analysis](License-Analysis) -* [Risk Trend](Risk-Trend) -* [Version Diff](Version-Diff) - -### Administration -* [Administration](Administration) -* [Authorization layers](Authorization-Layers) -* [Project access control](Project-Access-Control) -* [Production deployment](Production-Deployment-Checklist) -* [Database schema](Database-Schema) -* [Scan API security](Scan-Api-Security) - -### Reference -* [API Reference](API-Reference) -* [Glossary](Glossary) diff --git a/docs/API-Reference.md b/docs/en/API-Reference.md similarity index 97% rename from docs/API-Reference.md rename to docs/en/API-Reference.md index d8ed1bd1..2ba43b76 100644 --- a/docs/API-Reference.md +++ b/docs/en/API-Reference.md @@ -84,7 +84,7 @@ The separate `aiStatus` field tracks background AI enrichment (`NOT_APPLICABLE`, | Method | Path | Description | |---|---|---| | `POST` | `/api/github/connect` | Connect a GitHub PAT | -| `DELETE` | `/api/github/disconnect` | Remove GitHub connection | +| `POST` | `/api/github/disconnect` | Remove GitHub connection | | `GET` | `/api/github/status` | Connection status | | `GET` | `/api/github/accounts` | List authenticated accounts | | `GET` | `/api/github/repos` | List accessible repositories | @@ -99,7 +99,6 @@ The separate `aiStatus` field tracks background AI enrichment (`NOT_APPLICABLE`, | Method | Path | Auth | Description | |---|---|---|---| -| `POST` | `/api/auth` | API key | Validate API key (legacy) | | `GET` | `/api/scan/ping` | API key | Connectivity and key validity check | | `GET` | `/api/scan/manifest-rules` | API key | Manifest file collection rules (same as `/scripts/manifest-rules.json`) | | `POST` | `/api/scan/parse` | API key | Parse a manifest zip archive (CLI step 1) | @@ -117,7 +116,7 @@ The separate `aiStatus` field tracks background AI enrichment (`NOT_APPLICABLE`, | `PATCH` | `/projects/{id}/security-center/bulk-status` | `SECURITY_CENTER_UPDATE_STATUS` | Bulk CVE status update | | `GET` | `/projects/{id}/security-center/export` | `SECURITY_CENTER_EXPORT` | Download the CVE list as CSV (`?scanId=`, `?format=csv`) | | `POST` | `/projects/{id}/security-center/batch-pr` | `SECURITY_CENTER_UPDATE_STATUS` | **v1.0.4** — Open one upgrade PR for all selected components | -| `GET` | `/security-center/compliance-report` | `SECURITY_CENTER_EXPORT` | **v1.0.4** — Print-ready compliance report | +| `GET` | `/projects/{projectId}/security-center/compliance-report` | `SECURITY_CENTER_EXPORT` | **v1.0.4** — Print-ready compliance report | ### SBOM / VEX / SARIF (v1.0.4) @@ -249,8 +248,8 @@ All endpoints require the `SYSTEM_ADMIN` role (or the `SETTINGS_SNAPSHOT_MANAGE` | Method | Path | Description | |---|---|---| -| `GET` | `/api/admin/cli-keys` | List global CLI keys | -| `POST` | `/api/admin/cli-keys` | Create global key | +| `GET` | `/api/admin/cli-keys` | List CLI keys across projects | +| `POST` | `/api/admin/cli-keys` | Issue a project-scoped key (`projectId` required) | | `PATCH` | `/api/admin/cli-keys/{keyId}/toggle` | Enable / disable key | --- @@ -279,7 +278,7 @@ All endpoints require the `SYSTEM_ADMIN` role (or the `SETTINGS_SNAPSHOT_MANAGE` | `GET` | `/api/settings/ai/usage` | `SETTINGS_AI_MANAGE` | AI usage stats — today's calls/tokens/estimated cost, daily cap, and the last 7 days, read from the daily aggregate table | | `GET` | `/api/settings/ai/usage/events` | `SETTINGS_AI_MANAGE` | Recent AI call events, newest first (`?page=`, `?size=`, default size `10`). Only the last **100** events are retained (FIFO), so at most 10 pages exist | | `GET` | `/api/settings/ai/embedded` | `SETTINGS_AI_MANAGE` | Embedded AI status (`running`, `external`, `binaryFound`, `activeModel`, `fallbackUsed`, `lastError`, `availableModels`, `modelsDir`, `baseUrl`, and while a default-model download is in flight: `downloading`, `downloadedBytes`, `downloadTotalBytes`) | -| `POST` | `/api/settings/ai/embedded/start?model=` | `SETTINGS_AI_MANAGE` | Start the llama.cpp sidecar (optional model file name; auto-fallback across candidates, 400 with reason on failure). On a fresh install with no `.gguf` present, downloads the Apache-2.0 Qwen3-1.7B model in the background instead and returns immediately (`downloading: true`) — poll `GET .../embedded` for progress | +| `POST` | `/api/settings/ai/embedded/start?model=` | `SETTINGS_AI_MANAGE` | Start the llama.cpp sidecar (optional model file name; auto-fallback across candidates, 400 with reason on failure). On a fresh install with no `.gguf` present, downloads the Apache-2.0 Qwen3.5-2B Q4_K_M model in the background instead and returns immediately (`downloading: true`) — poll `GET .../embedded` for progress | | `POST` | `/api/settings/ai/embedded/stop` | `SETTINGS_AI_MANAGE` | Stop the sidecar and deactivate the LOCAL provider | | `PUT` | `/api/settings/ai/embedded/config` | `SETTINGS_AI_MANAGE` | Save folder/model overrides `{ "dir", "model" }` (null keeps current, blank clears; 400 if dir missing) | diff --git a/docs/Administration.md b/docs/en/Administration.md similarity index 79% rename from docs/Administration.md rename to docs/en/Administration.md index d49659a7..3db4d788 100644 --- a/docs/Administration.md +++ b/docs/en/Administration.md @@ -1,5 +1,7 @@ # Administration +[What's new in 1.0.5.1](Whats-New-v1.0.5.1.md) + This page covers all admin-only features: user management, role templates, audit logs, security settings, and SMTP configuration. > All actions on this page require **System Admin** privileges unless noted otherwise. @@ -221,6 +223,24 @@ Requires `ORG_DASHBOARD_VIEW` (or `SYSTEM_ADMIN`). Once granted, the entry point All three are admin-gated. Prometheus scrape config lives in `application-prod.yaml` under `management`. +### Business metrics & Grafana + +In addition to the default JVM/HTTP meters, OsWL records these business metrics (all exposed via `/actuator/prometheus`; Prometheus names shown — dots become underscores): + +| Metric | Type | Tags | Description | +|---|---|---|---| +| `oswl_scan_duration_seconds` | Timer | `outcome` (`completed`\|`failed`) | End-to-end scan pipeline duration | +| `oswl_quickimport_queue_depth` | Gauge | — | Quick Import jobs waiting for a worker slot | +| `oswl_quickimport_running` | Gauge | — | Quick Import jobs currently running | +| `oswl_components_ingested_total` | Counter | `ecosystem` | Components persisted by scan ingest | +| `oswl_ai_calls_total` | Counter | `provider` | Recorded AI calls | +| `oswl_ai_tokens_total` | Counter | `provider`, `direction` (`in`\|`out`) | AI prompt/completion tokens | +| `oswl_ai_cost_usd_total` | Counter | `provider` | Estimated AI spend (USD) | +| `oswl_gate_evaluations_total` | Counter | `outcome` (`pass`\|`fail`) | Security-gate evaluations | +| `oswl_external_api_calls_total` | Counter | `source` (`depsdev`, `osv`, `epss`, `kev`, `github-advisory`, `nvd`), `outcome` (`success`\|`failure`\|`ratelimited`) | Outbound calls to external data sources | + +A ready-to-import Grafana dashboard covering these metrics ships at [`deploy/observability/grafana/oswl-dashboard.json`](../../deploy/observability/grafana/oswl-dashboard.json). Import it via **Dashboards → New → Import** — it prompts for a Prometheus datasource on import, so no JSON editing is needed. + --- ## Offline snapshot bundles (v1.0.4) @@ -296,7 +316,7 @@ Configure the LLM provider and enrichment behaviour for CVE/license summaries. Each provider's model field is a free-text combo box: the dropdown lists current models as suggestions, but any model ID your account has access to can be typed in directly. -The **Embedded AI (built-in local model)** card on the same tab runs a bundled llama.cpp `llama-server` sidecar (CPU-only, localhost-only, no API key) and registers it as the LOCAL provider. The default bundled model is **Qwen3 1.7B** (downloaded on first use); the card also offers a **model dropdown** for any `.gguf` you place in the folder, a **folder override** with Save (persisted; changing it while running stops the sidecar), and **automatic fallback** to the next available model when the first choice fails to start. See [Embedded AI](Embedded-AI.md). +Embedded AI runs a separately installed llama.cpp runtime with **Qwen3.5-2B Q4_K_M** as the default download. **Gemma 4 E2B** is optional and installed manually. The runtime belongs in `embedded-ai/llama/`, and models in `embedded-ai/model//`. Boot-time prefetch downloads only; it does not start the server or activate LOCAL. The default download uses a pinned Hugging Face revision with SHA-256 and size verification; no fallback mirror is configured by default. Air-gapped mode disables downloads. Use Settings to stop, select and save a model, then start again. See [Embedded AI](Embedded-AI.md) for current requirements and configuration. Only one provider is **active** at a time. The tab also exposes: @@ -310,9 +330,9 @@ Only one provider is **active** at a time. The tab also exposes: **Test Connection** does not spend any tokens: it lists the provider's available models (`GET {base}/models` for OpenAI/Gemini/Ollama, `GET /v1/models` for Anthropic) rather than sending a completion, so checking credentials and reachability is free and does not count against the daily call cap. If the configured model ID is not among the models the account can access, the test still succeeds but shows a warning so a typo or an unpulled local model surfaces immediately instead of on the next scan. -**API:** `GET|PUT /api/settings/ai`, `POST /api/settings/ai/test-connection`, `POST /api/settings/ai/golden-test`. -**Embedded AI:** `GET /api/settings/ai/embedded`, `POST .../embedded/start?model=`, `POST .../embedded/stop`, `PUT .../embedded/config` — see [API Reference — AI](API-Reference.md#ai). -**Per project:** `PATCH /api/projects/{id}/deployment-profile`. +**API:** `GET|PUT /api/settings/ai`, `POST /api/settings/ai/test-connection`, `POST /api/settings/ai/golden-test`.\ +**Embedded AI:** `GET /api/settings/ai/embedded`, `POST .../embedded/start?model=`, `POST .../embedded/stop`, `PUT .../embedded/config` — see [API Reference — AI](API-Reference.md#ai).\ +**Per project:** `PATCH /api/projects/{id}/deployment-profile`.\ **Component detail:** `POST .../cves/{cveDbId}/ai-summarize` to refresh a CVE AI summary (logged as `COMPONENT.CVE_AI_REGENERATE`). ### AI response caching (v1.0.4) @@ -355,3 +375,49 @@ Single control point for **library enrichment cache** (deps.dev + OSV). There is Changes are audited as `CACHE.UPDATE_TTL` and `CACHE.CLEAR`. + +--- + +## SAML 2.0 SSO and SCIM 2.0 Provisioning + +OsWL supports SAML 2.0 single sign-on for enterprises that use Okta, Entra ID, or on-premises AD FS. When a SAML IdP is configured, the **Sign in with SSO** option appears on `/login`. + +### SAML setup + +1. Generate an SP signing key pair (optional but recommended): + ```bash + openssl req -x509 -newkey rsa:2048 -keyout oswl-saml-sp.key -out oswl-saml-sp.crt -nodes -days 3650 -subj "/CN=oswl" + ``` +2. Uncomment the SAML block in `application-prod.yaml` and set the environment variables: + | Env var | Purpose | + |---|---| + | `OSWL_SAML_IDP_METADATA_URL` | IdP metadata URL (e.g. Okta/Entra app metadata) | + | `OSWL_SAML_IDP_CERTIFICATE` | Path to the IdP signing certificate file | + | `OSWL_SAML_SP_PRIVATE_KEY` | Path to the SP private key file | + | `OSWL_SAML_SP_CERTIFICATE` | Path to the SP certificate file | +3. Register the SP metadata with your IdP. The metadata endpoint is: + ``` + https:///saml2/service-provider-metadata/oswl + ``` +4. Ensure the IdP releases an email claim (NameID or `email`/`mail` attribute). + +> SAML logins skip the email OTP step because the IdP has already authenticated the user. If the email does not match an existing OsWL account, a disabled local account is created automatically so SCIM can activate and assign roles. + +### SCIM 2.0 provisioning + +SCIM keeps OsWL in sync with your identity provider's user lifecycle. + +| Resource | Endpoint | Notes | +|---|---|---| +| Users | `/scim/v2/Users` | GET/POST/PUT/PATCH/DELETE | +| Groups | `/scim/v2/Groups` | GET/POST/PUT/PATCH/DELETE | + +**Authentication:** every SCIM request must include `Authorization: Bearer `. Issue a dedicated SCIM token programmatically via `ApiKeyService#issueScimToken`. SCIM tokens are stored in the same `api_keys` table but have scope `SCIM`; they are rejected by the normal CLI scan API. + +**Group mapping:** configure `oswl.scim.group-mapping` (env: `OSWL_SCIM_GROUP_MAPPING`) to choose how SCIM groups are represented: +- `TEAM` (default) — each SCIM group becomes a Team; members become TeamMember rows. +- `ROLE_TEMPLATE` — each SCIM group becomes a RoleTemplate; members are assigned that role template. + +**User deactivation:** `DELETE /scim/v2/Users/{id}` sets `active=false` in OsWL. Users are never physically deleted via SCIM, preserving audit attribution. + +**Audit actions:** SCIM operations are recorded as `SCIM.USER_CREATE`, `SCIM.USER_UPDATE`, `SCIM.USER_DEACTIVATE`, `SCIM.GROUP_CREATE`, `SCIM.GROUP_UPDATE`, `SCIM.GROUP_DELETE`, `SCIM.GROUP_MEMBER_ADD`, `SCIM.GROUP_MEMBER_REMOVE`, `SCIM.AUTH_FAILURE`, and `SCIM_KEY.CREATE`. SAML login events are recorded as `SAML.LOGIN_SUCCESS` and `SAML.LOGIN_FAILURE`. diff --git a/docs/en/Analysis-Coverage.md b/docs/en/Analysis-Coverage.md new file mode 100644 index 00000000..021588c1 --- /dev/null +++ b/docs/en/Analysis-Coverage.md @@ -0,0 +1,15 @@ +# Analysis coverage + +Gate responses include `coverage`: total and unanalysed component counts, scan completion, availability of retained detail, and `complete`. An unfinished or archived scan, or any component without completed vulnerability lookup evidence, produces a `COVERAGE / INCOMPLETE_ANALYSIS` violation and exit code 1. Ignore/defer, reachability and newly introduced finding filters do not hide missing analysis. Quick Import stores a source-inspection pending marker in the fenced ingest transaction before asynchronous enrichment starts. It removes that marker after source work and successful finding persistence. Pending or failed scanners also make coverage incomplete, even if vulnerability enrichment already completed or secret gating is disabled. Interrupted imports retain this evidence; they must be retried instead of passing as a clean scan. + +Component rows and detail pages expose the latest lookup time and source outcomes: `RESOLVED`, `UNAVAILABLE`, `UNSUPPORTED`, or `NOT_CONFIGURED`. A successful empty response is resolved. OSV batch IDs are hydrated through the detail endpoint with per-call deduplication and a 256-request budget; after 30 seconds no new detail request starts. An in-flight request can last until its configured HTTP timeout. Unfetched details remain unavailable. A timeout, upstream error, malformed response, or incomplete pagination is unavailable. Any unavailable source keeps coverage incomplete, even when older CVEs or a fetched timestamp remain. Successful retry replaces the outcome. Legacy cached rows without outcome metadata retain their previous completion state until normal refresh. + +Coverage describes available analysis evidence, not a guarantee that every upstream database is current or that a package is safe. NVD and GitHub responses exceeding a single requested page currently fail coverage instead of silently accepting partial results. + +Offline exports preserve confirmed empty results and unresolved component markers separately. Imports retain severity, CVSS vectors/scores and match confidence. An unresolved marker wins over retained findings; missing entries do not count as completed queries. + +Same-version retries serialize ingestion per project. An active Quick Import source lease rejects an overlapping retry. Interrupted, failed, source-pending or AI-active results are retained under their old ID while the retry creates a new result; delayed workers therefore cannot overwrite the retry. Version lookup selects the latest attempt. Fully completed results without pending source or AI work retain the existing replacement behavior and clear previous findings. Expired canceled import workers retain their canceled job outcome while unfinished linked scans are marked failed. Java bytecode result writes finish before the scan publishes completion. + +OSV fixed-version hints are shown only for one unambiguous closed package range. Multiple or open-ended affected intervals require ecosystem-specific version comparison; their hint remains empty rather than recommending an older fix. + +Deployment adds the two nullable library columns in `V32__vulnerability_lookup_outcomes.sql`. Existing data is retained. PostgreSQL runtime validation still requires a dedicated test database; local integration verification uses H2. diff --git a/docs/Authorization-Layers.md b/docs/en/Authorization-Layers.md similarity index 100% rename from docs/Authorization-Layers.md rename to docs/en/Authorization-Layers.md diff --git a/docs/en/Backup-And-Restore.md b/docs/en/Backup-And-Restore.md new file mode 100644 index 00000000..59dde4b5 --- /dev/null +++ b/docs/en/Backup-And-Restore.md @@ -0,0 +1,69 @@ +# Backup and restore + +The most common real-world incident isn't losing PostgreSQL — it's losing `OSWL_ENCRYPTION_KEY` while the database backup is fine. Every VCS access token, AI provider API key, Jira API token, and SMTP mail password stored in the database is encrypted with that key. Lose the key and the database restores perfectly but every one of those secrets is permanently unreadable — every VCS connection, AI provider, and Jira integration has to be reconfigured from scratch. + +This page is the operator-facing counterpart to [Production deployment](Production-Deployment-Checklist.md) — read that first for how the app is deployed; this page is specifically about backing it up and proving a restore actually works. + +--- + +## What to back up + +| Item | Where | Why it matters | +|---|---|---| +| PostgreSQL database | `deploy/docker/compose.prod.yml` volume `db-data-prod`, or your managed PostgreSQL instance | All application data: projects, scans, findings, users, encrypted secrets. | +| `OSWL_ENCRYPTION_KEY` | Wherever you inject it (`.env.prod`, secrets manager) | Decrypts every VCS token / AI API key / Jira token / SMTP password in the database. **Without it, the database backup above is useless for anything requiring those secrets.** | +| Offline snapshot store | `OSWL_AIRGAPPED_IMPORT_DIR` (if air-gapped mode is used) | Re-importing after a restore is possible without this, but you lose your import history and have to re-fetch/re-verify bundles. | +| Embedded AI model directory | `OSWL_EMBEDDED_AI_DIR` (default `embedded-ai/`) | Re-downloadable (see [Embedded AI](Embedded-AI.md)) — back up only if you're air-gapped and can't re-fetch it. | +| Configuration files | `.env.prod`, `deploy/docker/compose.prod.yml`, any `application-prod.yaml` overrides | Without these, you know the *data* is fine but not how the instance was actually configured (SMTP host, HSTS settings, feature flags). | + +Everything else (`OSWL_LOG_DIR` file logs, Quick Import clone temp dirs) is disposable — do not back it up. + +--- + +## PostgreSQL backup + +```bash +# Schema + data, custom format (supports parallel restore, smaller than plain SQL) +pg_dump -Fc -h -U -d -f oswl-$(date +%Y%m%d).dump +``` + +**Recommended cadence:** nightly full dump, retained 30 days, plus PostgreSQL WAL archiving if you need point-in-time recovery between nightly dumps. Store dumps somewhere independent of the database host (object storage, a different availability zone) — a backup that lives next to the thing it backs up doesn't survive the incident that takes out the host. + +Back up `OSWL_ENCRYPTION_KEY` **in a separate secrets manager**, not alongside the `pg_dump` output — the whole point of encrypting these secrets at rest is defeated if the key sits next to the encrypted data in the same backup blob. + +--- + +## Restore procedure + +1. **Provision a fresh PostgreSQL instance** (or wipe the target) and restore the dump: + ```bash + pg_restore -h -U -d --clean --if-exists oswl-20260730.dump + ``` +2. **Inject the same `OSWL_ENCRYPTION_KEY`** the backed-up data was encrypted with — a different key (even a freshly generated valid-looking one) makes every stored secret undecryptable, indistinguishably from data corruption. +3. **Start the app** against the restored database (`SPRING_PROFILES_ACTIVE=prod`, `ddl-auto: validate` — the restored schema must already match the running version; apply any pending manual migration script from `src/main/resources/db/` *before* starting if you're restoring onto a newer app version than the backup was taken on). +4. **Run the verification script** below to confirm the restore is actually usable, not just "the process started." + +```bash +OSWL_VERIFY_EMAIL=you@example.com \ +OSWL_VERIFY_PASSWORD='...' \ +OSWL_VERIFY_PROJECT_ID=1 \ +./scripts/ops/verify-restore.sh https://your-instance.example.com +``` + +The script is interactive (it pauses for your email OTP code, same as any real login) and checks: + +| Check | What it proves | +|---|---| +| `GET /actuator/health` → 200 | The app started against the restored DB with the injected key. | +| Login + OTP | Auth and session infrastructure work against the restored `users` table. | +| `GET /api/settings/vcs` → 200 | **`OSWL_ENCRYPTION_KEY` is correct** — at least one stored VCS token decrypted without error. A wrong key surfaces here as a 500, not a subtle bug discovered weeks later. | +| `GET /projects/{id}/scan-history` → 200 | Scan history restored and queryable (needs `OSWL_VERIFY_PROJECT_ID` set to a project that exists in the restored data). | +| `GET /api/admin/audit-logs` → 200 | Audit log restored and queryable (needs a `SYSTEM_ADMIN` account). | + +Run this as an actual scheduled **rehearsal** (e.g. quarterly, into a scratch environment) — a restore procedure nobody has run since it was written is not a tested procedure. + +--- + +## Key rotation + +There is currently **no re-encryption batch job**. Rotating `OSWL_ENCRYPTION_KEY` today makes every existing encrypted value (VCS tokens, AI API keys, Jira tokens, SMTP passwords) undecryptable — the practical rotation procedure is: rotate the key, then re-enter every secret through its settings UI (VCS connections, AI provider keys, Jira integration, SMTP credentials) so they get re-encrypted under the new key. A proper rotation tool (decrypt-with-old-key → re-encrypt-with-new-key, in place, across all affected tables) is tracked separately as a backlog item — until it exists, treat `OSWL_ENCRYPTION_KEY` as effectively permanent once set, and protect it accordingly (secrets manager, not a `.env` file in a repo). diff --git a/docs/en/Browser-Security-Alerts.md b/docs/en/Browser-Security-Alerts.md new file mode 100644 index 00000000..dcd09a43 --- /dev/null +++ b/docs/en/Browser-Security-Alerts.md @@ -0,0 +1,13 @@ +# Browser security alerts + +The account menu opens **Browser notifications**. A user explicitly enables notifications and chooses new HIGH/CRITICAL vulnerabilities, failed gates, or both. The browser asks for permission only after that action. HTTPS (or localhost), Service Worker, Push API and Notification API support are required. This does not send invitation email or expose project names, CVEs, credentials or findings in notification text. + +Administrators supply `OSWL_WEB_PUSH_PUBLIC_KEY`, `OSWL_WEB_PUSH_PRIVATE_KEY` and `OSWL_WEB_PUSH_SUBJECT` (a contact `mailto:` or HTTPS URI). Use a P-256 VAPID key pair in unpadded base64url form: the public key is the 65-byte uncompressed point, the private key the 32-byte scalar. Keep the private key in the deployment secret store and retain the pair across restarts. Changing the pair requires affected browsers to unsubscribe and subscribe again. Keys are never returned by the API except for the public key. No keys means the feature is disabled; air-gapped mode also disables sending. + +Apply migrations V34 and V35 before enabling this on a migrated production database. Subscription endpoints and browser key material are encrypted with the existing application encryption key. Only the owner can update or remove a subscription. Registration requires Security Center access. Every queued delivery rechecks the current user's enabled/password-change state, project existence, membership and Security Center permission; administrators retain organization-wide access. + +Subscriptions last 30 days and can be renewed through the same form. Expired subscriptions and queued deliveries are cleaned up. Limits are 10 browsers per user and 1,000 per installation. A database row lock serializes subscription limits, event deduplication and delivery claims across workers. Events are deduplicated per subscription, retained for one day, and sent in batches of ten with at most three attempts separated by five minutes. Delivery uses a persisted queue and a shared scheduler lock when cluster locking is enabled. A crash after sending but before recording success can cause a duplicate notification; browser tags replace an existing notification for the same event. Delivery is not guaranteed when a browser is offline or the provider rejects a request. HTTP 404/410 expires the endpoint. Status/attempt metadata is audited without endpoint URLs or payloads. + +Outbound endpoints are restricted to supported Google, Mozilla and Apple push hosts, HTTPS and public network addresses; redirects are disabled. The service worker has no fetch handler and does not cache application pages. Notification clicks accept only a same-origin project Security Center path. Disable removes the server subscription before removing the browser subscription. + +Local verification covers persistence, encryption at rest, revoked access, ownership, deduplication, crash/retry limits, expired cleanup, actual VAPID/AES128GCM request construction, malicious notification navigation and browser permission denial. No live provider delivery or physical iOS/Android device was used. Provider credentials and an operator-owned subscribed device are required to verify those paths. diff --git a/docs/CLI-Integration.md b/docs/en/CLI-Integration.md similarity index 75% rename from docs/CLI-Integration.md rename to docs/en/CLI-Integration.md index ba0ba3b3..08066625 100644 --- a/docs/CLI-Integration.md +++ b/docs/en/CLI-Integration.md @@ -89,6 +89,8 @@ ScanIngestService → async CVE + license enrichment (OSV / deps.dev) --- +The manifest archive follows `/scripts/manifest-rules.json`. It can include build configuration, wrapper files, and `buildSrc` Java/Kotlin files as well as dependency manifests; review the collection rules before sending a repository’s files. + ## Prerequisites 1. A **project** registered in OsWL. @@ -108,12 +110,12 @@ ScanIngestService → async CVE + license enrichment (OSV / deps.dev) | `POST` | `/api/scan` | API key + user password | Submit scan for enrichment | | `GET` | `/api/scan/{scanId}/status` | Session | Poll scan status (UI) | | `POST` | `/api/scan/gate` | API key | **v1.0.4** — PR / CI security gate; returns a verdict with `exitCode` | -| `GET` | `/api/projects/{projectId}/sbom` | Session / key | **v1.0.4** — CycloneDX 1.6 SBOM | -| `GET` | `/api/projects/{projectId}/vex` | Session / key | **v1.0.4** — CycloneDX VEX | -| `GET` | `/api/projects/{projectId}/sarif` | Session / key | **v1.0.4** — SARIF 2.1.0 | +| `GET` | `/api/projects/{projectId}/sbom` | Session | **v1.0.4** — CycloneDX 1.6 SBOM | +| `GET` | `/api/projects/{projectId}/vex` | Session | **v1.0.4** — CycloneDX VEX | +| `GET` | `/api/projects/{projectId}/sarif` | Session | **v1.0.4** — SARIF 2.1.0 | | `POST` | `/api/sbom/import` | Session | **v1.0.4** — Import a third-party CycloneDX file | -> The CLI endpoints authenticate with the `Authorization: Bearer` header only — no session cookie or CSRF token is required. `POST /api/scan`, `POST /api/scan/parse`, and `GET /api/scan/ping` are exempt from the browser CSRF checks; every other route keeps normal CSRF protection. See [Scan API Security](Scan-Api-Security.md). +> CLI requests send a project API key in `Authorization: Bearer`; `POST /api/scan` additionally verifies the submitting user’s email, password, permission, and project access. `POST /api/scan`, `POST /api/scan/parse`, and `POST /api/scan/gate` do not require a browser session or CSRF token. `GET /api/scan/ping` verifies the key. See [Scan API Security](Scan-Api-Security.md). --- @@ -127,9 +129,9 @@ POST /api/projects/{projectId}/keys UI: project → **Settings (⚙)** → **CLI** → **Generate Key**. -### Admin global keys +### Administrator key management -**Settings → Admin → CLI Keys** — see [API Reference](API-Reference.md). +Administrators can list and revoke CLI keys across projects and issue a key for a specified `projectId`. Scan keys remain project-scoped; the separate SCIM token scope does not authorize scan submission. --- @@ -252,8 +254,14 @@ Server-side defaults (all overridable per request): | `failOnEpss` | `OSWL_GATE_FAIL_ON_EPSS` | `0.5` | | `failOnLicenseViolation` | `OSWL_GATE_FAIL_ON_LICENSE_VIOLATION` | `true` | | `onlyNew` | `OSWL_GATE_ONLY_NEW` | `true` | +| `onlyReachable` | `OSWL_GATE_ONLY_REACHABLE` | `false` | +| `failOnSecrets` | `OSWL_GATE_FAIL_ON_SECRETS` | `false` | + +`onlyNew` filters previously observed CVE and license findings using a baseline. It does not guarantee a passing gate: confirmed malware, secret findings when enabled, and other applicable rules can still fail it. `onlyReachable` filters CVE findings to components with a `REACHABLE` result from supported bytecode or source-reference analysis. `UNKNOWN` CVE findings are excluded when this option is enabled; absence of a reference is not proof that a vulnerability cannot be exploited. License and malware checks are independent of this filter. Review coverage before enabling it. A configured GitHub target can receive a Check Run and PR comment. + +A confirmed-malicious package (an OSV `MAL-` advisory) always blocks, regardless of every threshold above and regardless of `onlyNew`/`onlyReachable` — the only way to unblock one is an approved policy exception (waiver, **v1.0.5**, see `/api/policies/exceptions`). -`onlyNew` compares against the previous completed scan as a baseline, so pre-existing debt never blocks a merge. Supply a GitHub target in the request and the verdict is also posted as a Check Run and PR comment. +`failOnSecrets` (**v1.0.5**) blocks on any CRITICAL/HIGH-severity secret finding from the Quick Import clone scan (regex + entropy rules — AWS keys, GitHub/GitLab/Slack/npm tokens, embedded private key blocks, etc.). Like every other threshold above, it resolves through the request override, then the org/team/project policy hierarchy, then the instance default. --- diff --git a/docs/en/Container-Image-Inspection.md b/docs/en/Container-Image-Inspection.md new file mode 100644 index 00000000..66a7d555 --- /dev/null +++ b/docs/en/Container-Image-Inspection.md @@ -0,0 +1,18 @@ +# Container image inspection + +`python scripts/oci/scan-image.py` inspects an immutable OCI image without running it or requiring Docker. It verifies SHA-256 manifest, config and compressed layer contents, checks uncompressed `diff_id` values, and composes whiteouts before reading the final installed OS package database. It does not extract files into the host filesystem. Supported inventory databases are Alpine apk and Debian/Ubuntu dpkg; source package names and source versions are used for vulnerability matching. Language package inventories, RPM, Windows images and zstd layers are currently unsupported; missing/unsupported inventory fails explicitly. + +From the repository root, with Python 3.11 or later: + +```powershell +python scripts/oci/scan-image.py --image registry-1.docker.io/library/alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f271ab1acc53015037c --platform linux/amd64 --query-osv --output image-scan.json --report image-report.json +``` + +Outputs must be new paths. The scan JSON is the existing scan ingestion component payload; submit it using the deployment's authenticated scan workflow. The separate report records the selected digest, platform, installed package count and advisory query status. `--query-osv` queries exact package versions online and records advisory IDs and time. Without that flag, the report says `UNQUERIED`; package inventory alone does not mean a clean security result. Failed or truncated advisory responses fail the operation. Advisory IDs are point-in-time findings and can change as upstream records change. + +Offline OCI layouts use `--layout --digest sha256:` instead of `--image`. Supply the immutable digest explicitly. Private registries can use `--credentials-file ` containing `username` and `password`. Restrict access to that file. Credentials are sent only to the same registry authentication realm (or Docker Hub's designated authentication host), never to redirected blob/CDN hosts. Transport exceptions suppress URLs and credentials. Custom cross-host authentication realms require a separate reviewed integration and are rejected. + +Default budgets are 120 seconds, 512 MiB compressed content, 2 GiB expanded layer bytes, 256 layers, 250,000 entries per layer, 16 MiB per selected package DB and 50,000 packages. Unsupported compression, traversal, sparse selected entries, unresolved links, package DB removal and pending dpkg updates produce an incomplete-inspection error instead of an empty success. Temporary downloads are removed on normal success and failure; OS force termination can leave OS temporary files for the operator's normal temp retention policy. + + +The implementation follows the [OCI layer specification](https://github.com/opencontainers/image-spec/blob/main/layer.md) and [registry token authentication protocol](https://distribution.github.io/distribution/spec/auth/token/). Dockerfile analysis remains a separate heuristic path; it is not renamed as image inspection. diff --git a/docs/en/Custom-Scan-Rules.md b/docs/en/Custom-Scan-Rules.md new file mode 100644 index 00000000..d83fa74b --- /dev/null +++ b/docs/en/Custom-Scan-Rules.md @@ -0,0 +1,19 @@ +# Custom secret and IaC rules + +System administrators can read and publish the instance-wide rule set using `GET` and `PUT /api/settings/scan-rules`. Use the interactive API documentation or an authenticated, CSRF-protected request. Publication validates the whole set and uses its current `revision`; stale revisions receive HTTP 409. The initial empty set has revision -1. Publishing an empty list disables custom rules. Built-in rules remain active. + +Example rule set: + +```json +{"revision":-1,"rules":[{"id":"company-token","type":"SECRET","severity":"HIGH","description":"Company token detected","regex":"COMPANY_[A-Z0-9]{24}","fileSuffix":".env"}]} +``` + +`type` is `SECRET` or `IAC`. A suffix restricts matching to appropriate file names, for example `.tf`, `.yaml`, `Dockerfile` or `.env`. Rules inspect individual lines; IaC matches are text heuristics, not semantic infrastructure analysis. Descriptions are administrator-authored and must not contain secret values. + +Publication creates a new optimistic revision used by subsequent scans, with an audit event containing revision and rule count. Existing findings retain `custom-@` and their original description. Save the previous GET response if you need rollback: republish its rule list with the current revision. Each scan loads one coherent set from the database, so nodes do not depend on local cache invalidation. + +Limits: 32 rules, 256 regex characters, 2,048 compiled instructions, 400 description characters, 1 MB per file, 16 MB total input, 2,000 visited files, 8,192 characters per line, five seconds and 300 findings. Unsupported lookarounds/backreferences and empty matches are rejected. [RE2/J](https://github.com/google/re2j) provides linear-time matching; the pinned engine version is 1.8. Symlinks are not followed. A limit or unreadable selected input creates an explicit high-severity `custom-scan-incomplete@` IaC finding. Rule loading/compilation failure uses `custom-scan-incomplete` when the revision is unavailable. The gate treats these markers and built-in scanner failure markers as incomplete coverage regardless of only-new or secret-policy filters. Ordinary IaC findings are displayed for review; there is no separate IaC gate threshold. Such a scan must not be interpreted as clean coverage. + +Custom results contain location, rule id/version, severity and description. They contain neither matched text nor a secret fingerprint. Built-in scanning has its own existing fingerprint policy. + +Migration `V33__custom_scan_rules.sql` adds a small configuration table; it does not change source or finding retention. Local tests cover no-secret output, versioned results, invalid/expensive patterns, input limits and direct service authorization. PostgreSQL migration/runtime verification remains environment-dependent. diff --git a/docs/Database-Schema.md b/docs/en/Database-Schema.md similarity index 79% rename from docs/Database-Schema.md rename to docs/en/Database-Schema.md index d8443c1c..03a705c8 100644 --- a/docs/Database-Schema.md +++ b/docs/en/Database-Schema.md @@ -1,6 +1,6 @@ # Database schema and migrations -OsWL stores all application data in PostgreSQL (`prod`) or H2 file-mode (`local`). JPA entities under `domain/entity/` are the **source of truth** for the live schema. +OsWL stores all application data in PostgreSQL (`prod`) or H2 file-mode (`local`). JPA entities under `domain/entity/`, `auth/entity/` are the **source of truth** for the live schema. --- @@ -16,7 +16,9 @@ When upgrading a production database, apply SQL scripts from `src/main/resources ### Flyway (v1.0.4, opt-in) -`OSWL_FLYWAY_ENABLED=true` hands schema management to Flyway (`baseline-on-migrate` on, so an existing database is baselined rather than rejected). Generate a baseline matching your current schema before enabling it. The default is `false`, which keeps the `ddl-auto` behaviour above. +`OSWL_FLYWAY_ENABLED=true` enables the versioned migrations in `src/main/resources/db/migration/`; it defaults to `false`. The repository already provides `V1__baseline.sql` and subsequent migrations. On an empty PostgreSQL database, Flyway runs V1 and then the later versions before Hibernate validation. For an existing database without Flyway history, `baseline-on-migrate` records version 1 without executing V1, then runs V2 onward. Back up and compare the existing schema with these migrations before enabling it; manually applied changes can conflict with later migrations. Do not regenerate or edit a migration already applied to a shared database. If managing SQL manually, apply all required changes for the target version in order; the short list of legacy scripts below is not a complete fresh-install schema. + +The migration directory also covers the current schema beyond v1.0.4: organizations and teams (V11), SAML/SCIM (V13), webhooks (V14), CVE sources and C/C++ metadata (V15–V16), policy inheritance and exceptions (V17, V28), reachability and evidence (V18, V29–V30), audit integrity (V19), UI preferences and onboarding (V20, V23–V24), secret/IaC findings (V21), scan archiving (V22), report branding (V25), cache counters and invalidation (V26–V27), and durable import jobs (V31). Use the actual migration files as the complete ordered inventory. Some migrations are not idempotent; do not blindly rerun them. ### Columns added in v1.0.4 @@ -73,7 +75,7 @@ Run **once** when moving to a release that removed legacy schema: | `project_versions.imported_at`, `last_updated_at` | Unused timestamps | | `projects.updated_at`, `version`, `last_scanned_at` | Denormalized fields; UI reads latest `scan_results` instead | -See [Production deployment checklist](Production-Deployment-Checklist.md) §8. +See [Production deployment checklist](Production-Deployment-Checklist.md) §9. --- @@ -94,7 +96,7 @@ libraries (shared) airgapped_snapshot_entries ── airgapped_snapshot_meta (offline snapshot store) -users, role_templates, audit_logs, cache_settings, vcs_connections, … +users, role_templates, audit_logs, cache_settings, user_vcs_connections, … ``` - **Project card version / last scan** — derived from the latest `scan_results` row, not `projects.version`. diff --git a/docs/en/Embedded-AI.md b/docs/en/Embedded-AI.md new file mode 100644 index 00000000..95c9512f --- /dev/null +++ b/docs/en/Embedded-AI.md @@ -0,0 +1,103 @@ +# Embedded AI + +OsWL runs llama.cpp at `http://127.0.0.1:11435/v1`, registered as the LOCAL provider. Inference data stays on this server; model installation needs network access or offline file transfer. **Qwen3.5-2B Q4_K_M is the CPU default; Gemma 4 E2B Q4_K_M is optional.** Only one model runs at a time. The integration is text-only; no image/audio projector or GPU is required. + +## Requirements & Directory Layout + +| Model | Weight download | Whole-server planning minimum | Recommended | +|---|---:|---|---| +| Qwen3.5 2B Q4_K_M | 1.28 GB | 2 vCPU / 8 GB RAM | 4 vCPU / 8–16 GB RAM | +| Gemma 4 E2B Q4_K_M | 3.11 GB | 4 vCPU / 8 GB RAM | 4 vCPU / 16 GB RAM | + +These are **sizing estimates, not certified performance minima**: OsWL + PostgreSQL, small occasional scans, one AI generation, 4K–8K context. Large archives, concurrent scans and database growth require additional capacity. Weight size is not peak process memory. Reserve at least 6 GB disk for these two weights, plus runtime, DB, logs and older models. Application builds are outside this estimate. + +Qwen is the default because it has a smaller weight footprint for a shared CPU server, not because it always produces better answers. Validate English/Korean/Japanese quality on your own findings. Burstable vCPU counts do not guarantee sustained full-core capacity: check depleted CPU credits as well as warm-model performance. Continuous analysis may need sustained CPU capacity or a separate CPU worker, without requiring GPU hardware. + +Use a CPU runtime matching the server OS/architecture. Keep companion libraries from the same release together. Local validation uses llama.cpp **b10068 (571d0d540)**; this release or a compatible newer version must support both model architectures, `--chat-template-kwargs` and `-fa on`. + +```text +embedded-ai/ + llama/ + llama-server(.exe) + ... companion libraries ... + model/ + Qwen/Qwen3.5-2B-Q4_K_M.gguf + Gemma/gemma-4-E2B-it-Q4_K_M.gguf + llama-server.log +``` + +| Model | Bytes | SHA-256 | +|---|---:|---| +| Qwen3.5-2B Q4_K_M | 1280835840 | `aaf42c8b7c3cab2bf3d69c355048d4a0ee9973d48f16c731c0520ee914699223` | +| Gemma 4 E2B Q4_K_M | 3106738272 | `740185b21d22ceb83a11c3aa62ad5842ef32c70f6096d756bbee85a1e4ec34b8` | + +- [Qwen model card](https://huggingface.co/Qwen/Qwen3.5-2B) +- [Qwen GGUF, pinned revision](https://huggingface.co/unsloth/Qwen3.5-2B-GGUF/tree/f6d5376be1edb4d416d56da11e5397a961aca8ae) +- [Gemma model card](https://huggingface.co/google/gemma-4-E2B-it) +- [Gemma GGUF, pinned revision](https://huggingface.co/unsloth/gemma-4-E2B-it-GGUF/tree/0314792d7f1f7e229411f620751375812bb9faf2) +- [llama.cpp releases](https://github.com/ggml-org/llama.cpp/releases) +- [License notices](../../THIRD_PARTY_LICENSES.md#embedded-ai-runtime-and-models) + +The configured directory is the root, not a family folder. OsWL scans `model/` and one family subfolder. Legacy root-level GGUF and root/bin runtime locations remain supported. Use unique filenames: the managed tree wins over same-named legacy weights. Projectors (`mmproj*`), draft weights (`mtp-*`), importance matrices and files resolving outside the root are excluded. + +Existing models on other installations are not automatically deleted or replaced. Move retired files outside the discovered folders. If a saved model disappears, the UI returns to the installed default preference. + +Local smoke checks with the project's localized prompts produced English, Korean and Japanese answers from both models. Qwen also changed a supplied fix version and misreported counts in some samples. These checks establish runtime compatibility, not factual accuracy: verify AI recommendations against the original scan findings. The default is a resource choice, not a quality ranking. + +## Starting and Stopping + +1. Put the executable and libraries in `llama/`. +2. If no chat model exists, startup prefetch downloads **only Qwen3.5 2B** into `model/Qwen/`. Start also triggers download if necessary. Disable prefetch with `OSWL_EMBEDDED_AUTO_DOWNLOAD=false`. +3. Gemma is manual: download the pinned file above, verify both SHA-256 and byte count, then place it in `model/Gemma/`. +4. Open **Settings → AI → Provider → Embedded AI**. The dropdown lists installed files with model names and sizing guidance. +5. To switch: **Stop → select model → Save → Start**. Check Active; if a candidate fails, a fallback can start and the UI indicates this. +6. Under **Scope / Context**, choose English, Korean or Japanese and save. Embedded ownership and endpoint are preserved. Existing summaries only change after regeneration; language selection does not translate stored results. + +Existing AI-settings permissions apply. Start registers the actual model as LOCAL and deactivates another active provider. Stop deactivates LOCAL. Selecting another provider does not stop the embedded process; stop it separately to release memory. + +## Switching Models + +The default preference is Qwen3.5-2B Q4_K_M, then other Qwen3 weights, then Gemma 4 E2B, then remaining compatible GGUF files. Explicitly selected and valid saved models take priority. Stop before switching. Additional compatible chat models can be installed manually, subject to their own requirements and licenses. + +## Changing the Model Folder + +Save the root containing `llama/` and `model/`. A changed root stops the managed running server. Moving files while a server is running can fail due to file locks; stop first. On Linux, the executable also needs execute permission. + +## CPU defaults + +| Environment variable | Default | +|---|---| +| `OSWL_EMBEDDED_AI_DIR` | `embedded-ai` | +| `OSWL_EMBEDDED_AI_PORT` | `11435` | +| `OSWL_EMBEDDED_AI_GPU_LAYERS` | `0` | +| `OSWL_EMBEDDED_AI_THREADS` | `1` | +| `OSWL_EMBEDDED_AI_PARALLEL` | `1` | +| `OSWL_EMBEDDED_AI_CONTEXT` | `8192` | +| `OSWL_EMBEDDED_AI_FLASH_ATTN` | `true` | +| `OSWL_EMBEDDED_AI_CACHE_REUSE` | `256` | +| `OSWL_EMBEDDED_AI_STARTUP_TIMEOUT_SEC` | `120` | +| `OSWL_EMBEDDED_AI_EXTRA_ARGS` | empty | +| `OSWL_EMBEDDED_AUTO_DOWNLOAD` | `true` | + +CPU-only is explicit (`-ngl 0`), as is one generation slot. Non-thinking uses `LLAMA_ARG_CHAT_TEMPLATE_KWARGS={"enable_thinking":false}` plus a zero reasoning budget; budget alone can leave new models with empty answers. Consider two threads on a 4-vCPU server only after checking web/scan latency. Threads are not a CPU quota; apply OS/container limits if required. + +The context is total capacity divided across slots. `OSWL_AI_MAX_PARALLEL_CALLS` remains 3 and limits tasks **within each scan**, not globally. With one server slot, requests may queue past the current 90-second non-streaming read timeout. Small hosts should set this to 1 and avoid overlapping scans. It is not a global admission queue. + +## Integrity, offline use and GitHub distribution + +The default URL is pinned to the upstream revision above. Old OsWL GitHub `models-v1` contains Qwen3 1.7B and must not be reused for the new default. This change does not publish or assume a new GitHub model asset. + +`OSWL_EMBEDDED_DEFAULT_MODEL_URL`, `OSWL_EMBEDDED_DEFAULT_MODEL_SHA256` and `OSWL_EMBEDDED_DEFAULT_MODEL_SIZE_BYTES` must describe the same file. Both hash and size are checked before installing a partial download. `OSWL_EMBEDDED_FALLBACK_MODEL_URL` defaults to empty; configure only a verified byte-identical mirror. A mirror is installed under the default Qwen filename; different models belong under their real filenames. + +Air-gapped mode never downloads models: supply the matching runtime, verified weights and license notices offline. Docker does not bundle them: mount this root and supply a matching **Linux** runtime, with write access if downloads are enabled. Windows executables do not run inside Linux containers. + +`embedded-ai/` is excluded from Git and Docker context; weights/runtime are not in the published JAR. Qwen3.5 and Gemma 4 are Apache 2.0; llama.cpp is MIT. Preserve full licenses, attribution, upstream notices when supplied, and the Unsloth GGUF Q4_K_M quantization notice. Runtime redistribution also needs the notices of its companion libraries. + +## Troubleshooting + +- Missing runtime: check llama/, OS/architecture, libraries and execute permission. +- Empty answers: check compatible runtime and explicit non-thinking template parameters. +- Start failure: inspect llama-server.log for model support or RAM exhaustion. +- Wrong active model: stop, save the installed selection, start and check fallback status. +- Timeout under load: reduce concurrent scans/calls and inspect CPU credits. +- Download mismatch: check URL, size and SHA-256 as one set. diff --git a/docs/en/Getting-Started.md b/docs/en/Getting-Started.md new file mode 100644 index 00000000..4a395116 --- /dev/null +++ b/docs/en/Getting-Started.md @@ -0,0 +1,122 @@ +# Getting Started + +This guide walks you through installing OsWL, running the setup wizard, and completing your first project scan. + +--- + +## System Requirements + +| Component | Requirement | +|---|---| +| **JDK** | 25 | +| **Build tool** | Gradle Wrapper (bundled — `./gradlew`) | +| **Database** | H2 file-mode (local / dev) or PostgreSQL 15+ (production) | +| **OS** | Linux, macOS, or Windows | +| **Memory** | Depends on scan workload; embedded AI needs additional RAM | + +> No Node.js or npm is required — the Tailwind CSS standalone binary is downloaded automatically by Gradle on the first build. + +--- + +## Installation + +### 1. Clone the Repository + +```bash +git clone https://github.com/SalkCoding/Oswl.git +cd Oswl +``` + +### 2. Choose a Profile + +OsWL ships with two Spring profiles: + +| Profile | Database | Use case | +|---|---|---| +| `local` *(default)* | H2 file (`./oswl-db.mv.db`) | Development and evaluation | +| `prod` | PostgreSQL | Production deployment | + +### 3. Start the Application + +For local development, run `./gradlew bootRun` (PowerShell: `.\gradlew.bat bootRun`). The default `local` profile uses H2 and a fixed development encryption key. Use a separate, persistent key outside development; do not reuse the development key in production. + +For production, build the deployable artifact with `./gradlew bootJar verifyProdJar` and run the resulting JAR with the `prod` profile. Set `DB_URL`, `DB_USERNAME`, `DB_PASSWORD`, and a persistent `OSWL_ENCRYPTION_KEY` that decodes to 32 bytes. Generate the key once with `openssl rand -base64 32` and retain it across restarts and restores. Prepare the PostgreSQL schema before startup: `prod` validates the schema and does not create it automatically. + +See the [production checklist](Production-Deployment-Checklist.md) and [Docker commands](../../deploy/README.md) for deployment. A `.env` file alone is not loaded by `bootRun` or `java -jar`; export variables in the launching shell, supply Spring configuration, or use Compose with `--env-file`. + +The default application port is **8080**. Memory requirements depend on the workload; local AI needs additional memory for the model and context window. + +## Embedded AI Model (first run) + +The current default is **Qwen3.5-2B Q4_K_M** (1,280,835,840 bytes, about 1.28 GB), downloaded from a pinned revision of `unsloth/Qwen3.5-2B-GGUF` on Hugging Face. The file is stored under `embedded-ai/model/Qwen/` and verified against the configured SHA-256. The llama.cpp runtime is installed separately under `embedded-ai/llama/` or on `PATH`. + +When no model is available, `OSWL_EMBEDDED_AUTO_DOWNLOAD=true` permits background prefetch on startup. Prefetch does not start the sidecar or activate an AI provider. Start it from **Settings → AI** after installing the runtime. Set the variable to `false` to disable startup prefetch. Air-gapped mode disables automatic model downloads. See [Embedded AI](Embedded-AI.md) for model discovery, configuration and troubleshooting. + +## Air-Gapped (Offline) Startup + +Offline mode switches the supported vulnerability feeds to snapshot data; it is not a network firewall. Configure VCS, SMTP, webhooks and AI endpoints for your network separately. + +For hosts without outbound internet access: + +1. Set `OSWL_AIRGAPPED_ENABLED=true` before startup. Vulnerability / threat-intel lookups (OSV, deps.dev, EPSS, KEV) are then served from an imported offline snapshot — no outbound HTTP is attempted, and the embedded-model auto-download is skipped. +2. On an internet-connected machine, build a snapshot bundle: + + ```bash + scripts/oswl-vdb/oswl-vdb.sh build --wanted wanted-list.jsonl --out bundle.zip + ``` + + (Windows: `scripts/oswl-vdb/oswl-vdb.ps1`.) To target the bundle at your actual dependencies, first export a wanted-list from a connected OsWL instance: `GET /api/admin/snapshot/wanted-list`. +3. Transfer `bundle.zip` to the air-gapped host and import it as a System Admin via `POST /api/admin/snapshot/import` (multipart upload), or whitelist a directory with `OSWL_AIRGAPPED_IMPORT_DIR` and use `POST /api/admin/snapshot/import-from-path`. See [Administration — Offline snapshot bundles](Administration.md). +4. For embedded AI, place a `.gguf` model you obtained yourself into `embedded-ai/` before clicking **Start**. + +--- + +## Setup Wizard + +On the very first startup (empty database), OsWL redirects application-page requests to `http://localhost:8080/setup`. + +The wizard collects: + +| Field | Description | +|---|---| +| **Admin Email** | Used as the login credential for the System Admin account | +| **Password** | Must meet the minimum length policy (default: 8 characters) | +| **Display Name** | Shown in the UI and audit log | + +After you submit, OsWL creates the admin account and redirects to the login page. + +> If you need to restart from a clean state in local mode, stop the server and delete `oswl-db.mv.db` (and `oswl-db.trace.db` if present), then restart. + +--- + +## First Login + +1. Open `http://localhost:8080/login` and enter the account created in Setup. +2. If email two-factor authentication is required, enter the six-digit code sent for the current session. The code expires after three minutes, and resend has a 60-second cooldown. +3. With the default local GreenMail SMTP configuration, received codes are displayed in the server log as `*** OTP CODE: ... ***`. If external SMTP is configured, check the recipient mailbox instead. There is no fixed `000000` bypass. +4. Accounts marked for a password change must set a new password before continuing. + +## Seeding Test Data (local only) + +After completing Setup, `GET /data/test` deletes existing project, scan and library data and queues real Quick Import jobs for the public repositories in `DemoImportCatalog`. It redirects to Projects; imports run asynchronously and need network access. It does not create a fixed test account or a predetermined set of vulnerabilities. + +These development endpoints exist in the local source set under `local`/`test` profiles and are excluded from the production JAR. Under `local`, `/data/**` is permitted without authentication, so use this destructive reset endpoint only on an isolated development instance. + +`GET /data/test-api-key` issues a project-scoped key for the first available project. It returns 404 when no project exists; wait for the demo imports to create one. + +## UI/Accessibility Test Harness (developer-only) + +`./gradlew uiTest` runs the existing Playwright browser suite against a real application instance. The accessibility tests run axe-core on their selected pages; other tests cover interactions and request flows. The task downloads Chromium when needed and is separate from `test`/`check`. + +JUnit HTML reports are in `build/reports/tests/uiTest/`; axe reports are in `build/reports/axe/`. For offline execution, provision the browser binaries and build dependencies in advance and set `PLAYWRIGHT_BROWSERS_PATH` where required. Browser installation does not replace dependency provisioning. + +## Access control (recommended reading) + +* [Authorization layers](Authorization-Layers.md) — role templates (Admin / Developer / Viewer) vs project membership +* [Production deployment checklist](Production-Deployment-Checklist.md) — before going live with `prod` + +## Next Steps + +* [Connect your first VCS repository](Quick-Import.md) +* [Submit a scan via the CLI](CLI-Integration.md) +* [Explore the Security Center](Security-Center.md) diff --git a/docs/Glossary.md b/docs/en/Glossary.md similarity index 100% rename from docs/Glossary.md rename to docs/en/Glossary.md diff --git a/docs/Home.md b/docs/en/Home.md similarity index 92% rename from docs/Home.md rename to docs/en/Home.md index be8a06a9..011bddd5 100644 --- a/docs/Home.md +++ b/docs/en/Home.md @@ -1,13 +1,18 @@ # OsWL Documentation +[What's new in 1.0.5.1](Whats-New-v1.0.5.1.md) + Welcome to the **OsWL** (Open-source Software Watchlist) documentation hub. OsWL is an in-house **SCA (Software Composition Analysis)** platform that gives your team a single place to track CVE vulnerabilities and license compliance across all OSS dependencies — from a single microservice to an entire portfolio of products. --- +**English** | [한국어](../ko/Home.md) | [日本語](../ja/Home.md) + ## Navigation + | Page | What you'll find | |---|---| | [What's New in v1.0.4](Whats-New-v1.0.4.md) | SBOM / VEX / SARIF export, CI gate, continuous monitoring, org dashboard, air-gapped mode | @@ -25,12 +30,13 @@ OsWL is an in-house **SCA (Software Composition Analysis)** platform that gives | [Authorization layers](Authorization-Layers.md) | Role templates vs project membership (who can access what) | | [Project access control](Project-Access-Control.md) | Technical ACL reference for developers | | [Production deployment](Production-Deployment-Checklist.md) | Pre-launch checklist for `prod` profile | +| [Backup and restore](Backup-And-Restore.md) | Backup scope, restore procedure, verification | | [Database schema](Database-Schema.md) | `ddl-auto` strategy and `src/main/resources/db/` migrations | | [Scan API security](Scan-Api-Security.md) | How CLI scan submission is protected | | [API Reference](API-Reference.md) | Full REST endpoint catalogue | | [Glossary](Glossary.md) | Definitions of all OsWL terms | -> Korean documentation lives in [`docs/ko/`](ko/Home.md); Japanese documentation lives in [`docs/ja/`](ja/Home.md). +> Korean documentation lives in [`docs/ko/`](../ko/Home.md); Japanese documentation lives in [`docs/ja/`](../ja/Home.md). --- diff --git a/docs/en/Inline-Team-Access.md b/docs/en/Inline-Team-Access.md new file mode 100644 index 00000000..dc2b5458 --- /dev/null +++ b/docs/en/Inline-Team-Access.md @@ -0,0 +1,7 @@ +# Add a teammate during onboarding + +System administrators can create teammate access in the first onboarding step without leaving the wizard. Select a role template, enter name/email and a temporary password, then create the account. This reuses the existing user-management endpoint and audit path. It does not send an invitation email; share the temporary password through your organization's approved channel. The user must change it at first login. + +The form clears the password after success and updates the team-ready badge. Duplicate email, missing/deleted role and network failures leave a retryable form. Loading failure prevents submission; an empty role catalog directs the administrator to create a template in Settings. Non-administrators do not receive the form, and the existing API rejects their requests. + +Browser verification uses actual user creation and duplicate rejection, mandatory password-change state, and axe checks of the resulting success/error screen. Test accounts live only in the isolated H2 UI profile. diff --git a/docs/License-Analysis.md b/docs/en/License-Analysis.md similarity index 100% rename from docs/License-Analysis.md rename to docs/en/License-Analysis.md diff --git a/docs/en/Offline-CocoaPods-Specs.md b/docs/en/Offline-CocoaPods-Specs.md new file mode 100644 index 00000000..08a51b60 --- /dev/null +++ b/docs/en/Offline-CocoaPods-Specs.md @@ -0,0 +1,17 @@ +# Scoped offline CocoaPods Specs + +Air-gapped scans can resolve CocoaPods names to source repositories and license metadata from an imported `cocoapods-specs.jsonl` source. The existing Podfile.lock parser and vulnerability enrichment path consume this mapping. Missing or unsupported Specs remain unanalysed; importing Specs alone does not supply vulnerability data. + +Supply original podspec JSON files that your organization may use and a JSON manifest containing `{ "file": "FixturePod.podspec.json", "origin": "https://example.org/source/FixturePod.podspec.json" }` entries. Build a bundle with: + +```powershell +python scripts/vdb/build-cocoapods-specs.py manifest.json scoped-specs.zip +``` + +Import the ZIP through the existing offline snapshot settings. Use MERGE when adding mappings to an existing collection; REPLACE replaces the Specs source carried by the bundle. Other sources absent from the ZIP are unaffected. Import the corresponding OSV SwiftURL vulnerability records separately, using the repository name such as `github.com/Owner/Repo` and exact version. Explicit empty vulnerability records represent completed clean lookups; absent records remain unresolved. + +The bundle requires format version 2 and the normal SHA-256 file manifest. Each record contains name, version, HTTPS origin without credentials/query/fragment, SHA-256 of the original UTF-8 podspec, and the original podspec text. Identity must agree with its contents. Limits are 1,000 stored Specs and 64 KiB per original spec. Invalid checksums, identities, limits or legacy bundles are rejected transactionally. Export preserves original metadata and source dates rather than assigning fresh upstream dates. + +The builder never fetches or redistributes the upstream Specs index. Included regression fixtures are authored for this repository. A pod's declared license describes that pod and is not evidence of permission to redistribute the entire Specs index; operators supply metadata they are entitled to transfer. No third-party Specs dataset is shipped by this feature. + +Verification: scoped import/export and provenance preservation, tampering/legacy rejection, missing mapping, preserved unresolved/CVSS evidence, and Podfile → real ingest → offline enrichment of an owned vulnerable fixture. This does not certify current upstream advisory coverage. diff --git a/docs/en/Organization-Risk-Briefing.md b/docs/en/Organization-Risk-Briefing.md new file mode 100644 index 00000000..c0d082a1 --- /dev/null +++ b/docs/en/Organization-Risk-Briefing.md @@ -0,0 +1,9 @@ +# Organization risk briefing + +Open **Organization dashboard → Organization risk briefing** for a concise, printable summary of portfolio risks and follow-up actions. It uses each project's latest completed scan and lists up to five priority projects, critical/high findings, untriaged KEV findings and license-policy violations. The print button uses browser printing/PDF output. + +This is a decision-oriented view over the existing organization aggregates. It does not generate an AI opinion or infer that a reviewed/deferred item has been fixed. Counts represent finding occurrences rather than unique CVEs. Scan dates and analysis coverage must be checked before treating a result as current; the briefing states these limits explicitly. Projects without completed scans remain visible in the coverage denominator. + +Both controller and service require `ORG_DASHBOARD_VIEW` (system administrators also qualify). Links to individual projects still require normal project access. Access is audited as `ORG_SUMMARY.VIEW`. + +Chromium tests verify English/Korean/Japanese rendering, 390px layout, serious/critical axe violations, PDF rendering and denied access. These are browser tests, not physical-device certification. diff --git a/docs/en/Production-Deployment-Checklist.md b/docs/en/Production-Deployment-Checklist.md new file mode 100644 index 00000000..49098dc4 --- /dev/null +++ b/docs/en/Production-Deployment-Checklist.md @@ -0,0 +1,235 @@ +# Production deployment checklist + +Use this one-page list before exposing OsWL on the internet. **Do not run `prod` with `local` defaults** (H2, Swagger, `/data/**`, or committed encryption keys). + +## 1. Profile and build + +| Check | Action | +|-------|--------| +| Profile | Set `SPRING_PROFILES_ACTIVE=prod` | +| JAR | Build with `./gradlew bootJar verifyProdJar` — `TestDataController` must **not** appear in the JAR | +| Local-only code | `src/local/java` is for `bootRun` / dev only, not packaged in `bootJar` | + +## 2. Required environment variables + +| Variable | Purpose | +|----------|---------| +| `DB_URL` | JDBC URL (e.g. `jdbc:postgresql://db:5432/oswl`) | +| `DB_USERNAME` | Database user | +| `DB_PASSWORD` | Database password | +| `OSWL_ENCRYPTION_KEY` | Instance encryption key (generate with `openssl rand -base64 32`) | + +Copy `deploy/docker/.env.prod.example` → `.env.prod` and fill every value. **No defaults** for DB or encryption in `application-prod.yaml`. + +After startup, configuration warnings are grouped in the `OSWL STARTUP WARNINGS` log block. Some invalid settings, including a missing production encryption key or datasource configuration, can fail startup before this block appears. Keep a stable `OSWL_ENCRYPTION_KEY` in production. The `local` YAML supplies a stable development-only fallback; never use that key in production. + +## 3. Network binding + +For a JVM running directly on the host, `application-prod.yaml` defaults to `SERVER_ADDRESS=127.0.0.1`. A reverse proxy on the same host can connect to it. For **Docker Compose**, set `SERVER_ADDRESS=0.0.0.0` **inside the container** so Docker can forward traffic to the application. This does not publish every host interface: `deploy/docker/compose.prod.yml` separately binds the host port to **`127.0.0.1:8080:8080`**. The production sample uses this container setting. Existing `.env.prod` files must be checked when upgrading. + +Terminate TLS at your reverse proxy. With the supplied host-loopback port mapping, run the proxy on the Docker host; a proxy in another container should use the service address on a shared Docker network. Accept forwarded headers only from a trusted proxy. + +## 4. Docker Compose (production) + +Run from the repository root. Keep existing `.env.prod` values; copy the template only for a new installation. Both Compose files default to project name `oswl`. If the previous installation used another project name, keep it with `-p YOUR_EXISTING_PROJECT` or `COMPOSE_PROJECT_NAME` so it reconnects the existing volumes. See the [deployment file guide](../../deploy/README.md). + +```bash +cp deploy/docker/.env.prod.example .env.prod +# Edit DB_*, OSWL_ENCRYPTION_KEY, SMTP_* +docker compose --env-file .env.prod -f deploy/docker/compose.prod.yml up -d --build +``` + +Compose reads `.env.prod` through `--env-file`. A direct `java -jar` or `bootRun` launch does not automatically load this file: export the variables or configure them in the service manager. Prepare the database schema before first production startup (see §9). + +Verify logs: no missing-env banner, PostgreSQL connected, no H2 or Swagger URLs. + +`deploy/docker/compose.prod.yml` caps both the container's own stdout/stderr (docker `json-file` driver, 100MB × 10 files) and the app's own rotating file log (mounted to the `oswl-logs-prod` volume) — see §5 for the latter. + +## 5. Logging and observability + +| Check | Action | +|-------|--------| +| Log levels | `prod` profile: `com.salkcoding.oswl` at **INFO** only; no DEBUG on AI/clients | +| AI excerpts | `oswl.ai.debug.log-prompt-excerpt` / `log-response-excerpt` default **false** in prod | +| Actuator | **`health`, `info`, `prometheus`** exposed (v1.0.4); everything else disabled (`enabled-by-default: false`) | +| Metrics scrape | Point Prometheus at `/actuator/prometheus` — the scraper must present admin credentials | +| Actuator auth | Requires **SYSTEM_ADMIN** session (not public) | + +### Log rotation and request correlation + +`local`/`test` are console-only. In `prod`, `logback-spring.xml` additionally writes a rolling file log: + +| Variable | Default | Purpose | +|----------|---------|---------| +| `OSWL_LOG_DIR` | `./logs` (docker: `/var/log/oswl`, see §4) | Directory for `oswl.log`. Rotates at 100MB or daily, keeps 30 files, caps total at 5GB. | +| `OSWL_LOG_JSON` | `false` | `true` switches the file (not the console) to one JSON object per line — point a log shipper at it for SIEM ingestion. | + +Every request is stamped with a `requestId` (also returned as the `X-Request-Id` response header) and, once authenticated, `userId` — both appear in every log line for that request via MDC (`[req=...] [user=...]` in plain-text mode, top-level fields in JSON mode), so a support ticket referencing one request can be traced across the whole log without grepping by timestamp. + +## 6. Security features enabled in prod + +- Springdoc / Swagger UI: **off** +- H2 console and `/data/**`: **not in prod JAR** (local profile + `src/local/java` only) +- Security headers + HSTS (behind HTTPS): see `application-prod.yaml` `oswl.security.headers` +- Trusted-device cookie: `Secure` in prod + +## 7. Optional secrets + +| Variable | Purpose | +|----------|---------| +| `OSWL_TRUSTED_DEVICE_HMAC_KEY` | Dedicated HMAC key for `OSWL_TD` cookie (recommended; separate from `OSWL_ENCRYPTION_KEY`) | +| `OSWL_OIDC_CLIENT_ID` / `OSWL_OIDC_CLIENT_SECRET` / `OSWL_OIDC_ISSUER_URI` | **v1.0.4** — OIDC single sign-on. Also uncomment the `spring.security.oauth2.client` block in `application-prod.yaml`; the login page shows the SSO button only when a provider is registered. | + +### v1.0.4 opt-in features + +All default to **off** — enable deliberately. + +| Variable | Default | Effect when enabled | +|---|---|---| +| `OSWL_FLYWAY_ENABLED` | `false` | Run the supplied V1 baseline and later migrations; review existing schemas first | +| `OSWL_AIRGAPPED_ENABLED` | `false` | All vulnerability / threat-intel lookups served from an imported offline snapshot; no outbound HTTP — full offline procedure in §7.1 | +| `OSWL_GATE_*` | see [What's New](Whats-New-v1.0.4.md) | Default thresholds for `POST /api/scan/gate` | + +Continuous monitoring is the exception: `OSWL_MONITORING_ENABLED` defaults to **`true`** (nightly OSV re-query at 03:00, `OSWL_MONITORING_CRON`). It sends e-mail to project members, so confirm SMTP is configured before first launch — or set it to `false`. + +### Performance tunables (v1.0.4) + +Defaults are production-safe — override only when you have a reason. + +| Variable | Default | Purpose | +|----------|---------|---------| +| `OSWL_DEPSDEV_CONNECT_TIMEOUT_MS` / `OSWL_DEPSDEV_READ_TIMEOUT_MS` | `5000` / `10000` | deps.dev HTTP timeouts (previously a stalled call could hang a whole scan) | +| `OSWL_DEPSDEV_MAX_CONCURRENT` | `24` | Max simultaneous deps.dev requests; on HTTP 429 the client backs off and retries once | +| `OSWL_OSV_CONNECT_TIMEOUT_MS` / `OSWL_OSV_READ_TIMEOUT_MS` | `5000` / `30000` | OSV HTTP timeouts (read is generous — a 1,000-item batch query can legitimately take a while) | +| `OSWL_VERSION_META_TTL_SEC` | `86400` | TTL for deps.dev version metadata on cache-hit libraries | +| `OSWL_CLONE_SPARSE_ENABLED` | `true` | Quick Import clones are blobless + sparse-checked-out; git servers without partial-clone support fall back to a full shallow clone automatically | +| `OSWL_AI_STREAMING_ENABLED` | `true` | Stream free-form AI calls (posture/trend/version diff) over SSE for live previews; endpoints that reject streaming fall back automatically | +| `OSWL_AI_MAX_PARALLEL_CALLS` | `3` | Max concurrent AI enrichment calls; a local llama-server benefits from `--parallel` up to this same count | +| `OSWL_ANTHROPIC_PROMPT_CACHING_ENABLED` | `true` | Mark the Anthropic system prompt as an ephemeral cache breakpoint for repeat calls | + +### 7.1 Air-gapped / offline snapshot (v1.0.4) + +This mode redirects supported vulnerability and threat-intelligence feeds to the snapshot; it is not a network firewall. Configure VCS, SMTP, webhooks, and external AI providers separately for an isolated environment. + +Set `OSWL_AIRGAPPED_ENABLED=true` so vulnerability/threat-intel lookups (OSV, deps.dev, EPSS, CISA KEV) are served from an imported offline snapshot instead of live external APIs. No outbound HTTP is attempted for enrichment. + +| Step | Action | +|------|--------| +| 1. Build bundle | On an internet-connected machine, run the `oswl-vdb` builder. Wrapper scripts: `scripts/oswl-vdb/oswl-vdb.sh` (Linux/macOS) or `scripts/oswl-vdb/oswl-vdb.ps1` (Windows). Both invoke `./gradlew vdbBuild --args="..."`. | +| 2. Target the bundle | Export the components this instance actually scans with `GET /api/admin/snapshot/wanted-list` (SYSTEM_ADMIN), then pass it to `build --wanted wanted-list.jsonl`. The builder fetches only those ecosystem/name/version tuples instead of a full upstream mirror. | +| 3. Import bundle | `POST /api/admin/snapshot/import?mode=replace|merge` (multipart `.zip`). For large bundles, use `POST /api/admin/snapshot/import-from-path` with `{"path":"bundle.zip","mode":"merge"}` after setting `OSWL_AIRGAPPED_IMPORT_DIR` to a whitelist directory. | +| 4. Place model (if using Embedded AI) | Provision the runtime in `embedded-ai/llama/` and verified weights in `embedded-ai/model//` before starting the offline host. Downloads remain disabled even if an internal mirror is configured; see §8. | + +`oswl-vdb build` options (see `VdbBuilderCli`): +- `--sources osv,epss,kev,depsdev` (default all). +- `--mode delta --since previous.zip` writes only added/changed keys plus `"_deleted":true` markers. +- `--offline-sources ` builds without any network from a cache directory populated by an earlier online run (covers `osv`/`epss`/`kev` only; deps.dev has no bulk dump and is skipped). +- `verify ` and `inspect ` check checksums and metadata. + +Import semantics: +- `replace` (default) clears each source in the store and writes the bundle. +- `merge` upserts by `(source, entry_key)` and honors `"_deleted":true` lines as deletes. +- v2 bundles are SHA-256-checksummed per file in `meta.json`; a mismatch rejects the whole bundle and leaves the existing store untouched. + +Definition freshness: `OSWL_AIRGAPPED_STALENESS_WARN_DAYS` (default `7`) and `OSWL_AIRGAPPED_STALENESS_CRITICAL_DAYS` (default `30`) drive the admin UI badge, measured from the oldest per-source `sourceAsOf` date across imported snapshots. + +Snapshot uploads may need `OSWL_MULTIPART_MAX_FILE_SIZE` / `OSWL_MULTIPART_MAX_REQUEST_SIZE` (default `50MB` each) if your bundle is larger. + +## 8. Embedded AI (optional, CPU-only) + +Default: **Qwen3.5-2B Q4_K_M**; optional: **Gemma 4 E2B Q4_K_M**. Plan 2 vCPU / 8 GB RAM minimum for occasional Qwen use, or 4 vCPU / 16 GB recommended for Gemma, including OsWL and PostgreSQL. These are estimates, not validated performance guarantees; CPU credits and scan concurrency matter. + +Place the matching runtime in `embedded-ai/llama/`, Qwen weights in `model/Qwen/`, and Gemma weights in `model/Gemma/`. Only Qwen auto-downloads. Default GPU layers = 0, threads = 1, parallel slots = 1, context = 8192. Use the pinned URL/hash/size together; the old `models-v1` asset is not the new model. Docker requires a mounted root and a Linux runtime. + +See [Embedded AI](Embedded-AI.md) for full requirements, directory layout, checksums, mirror/offline installation, CPU tuning, model/language selection and distribution notices. + +## 9. Database schema (upgrades) + +OsWL uses **Hibernate `ddl-auto=validate`** in `prod` — the app does not auto-alter PostgreSQL on startup. + +| Profile | Schema management | +|---------|-------------------| +| `local` | `ddl-auto: update` — H2 schema follows JPA entities automatically | +| `prod` | `ddl-auto: validate` — run SQL scripts manually when upgrading | + +Manual scripts live in `src/main/resources/db/`: + +| File | When to run | +|------|-------------| +| `project_members.sql` | First deploy of project ACL (if table missing) | +| `instance_setup_lock.sql` | First deploy after setup-lock feature | +| `ai_enhancement.sql` | Legacy installs predating AI preference columns / `ai_daily_usage` | +| `schema_cleanup.sql` | **Once** when upgrading to the release that removes unused tables/columns (`ai_feedback`, `external_api_settings`, denormalized `projects.version`, etc.) | + +After running migrations, restart the app and confirm `validate` passes. + +### Flyway (v1.0.4, opt-in) + +`OSWL_FLYWAY_ENABLED=true` enables the versioned migrations in `src/main/resources/db/migration/`; it defaults to `false`. The repository already provides `V1__baseline.sql` and subsequent migrations. On an empty PostgreSQL database, Flyway runs V1 and then the later versions before Hibernate validation. For an existing database without Flyway history, `baseline-on-migrate` records version 1 without executing V1, then runs V2 onward. Back up and compare the existing schema with these migrations before enabling it; manually applied changes can conflict with later migrations. Do not regenerate or edit a migration already applied to a shared database. If managing SQL manually, apply all required changes for the target version in order; the short list of legacy scripts below is not a complete fresh-install schema. + +### v1.0.4 columns + +This release adds `libraries.malicious` and `libraries.typosquat_risk`, both `NOT NULL DEFAULT false`. The defaults let the column be added to a populated table, so no manual script is required — but on `prod` (`ddl-auto: validate`) you still add them yourself. It also adds three nullable `libraries` columns (`description`, `homepage`, `source_repo_url`) for the upstream project metadata shown on Component Detail — `validate` checks that every mapped column exists regardless of nullability, so these need the same manual treatment: + +```sql +ALTER TABLE libraries ADD COLUMN IF NOT EXISTS malicious boolean NOT NULL DEFAULT false; +ALTER TABLE libraries ADD COLUMN IF NOT EXISTS typosquat_risk boolean NOT NULL DEFAULT false; +ALTER TABLE libraries ADD COLUMN IF NOT EXISTS description text; +ALTER TABLE libraries ADD COLUMN IF NOT EXISTS homepage varchar(500); +ALTER TABLE libraries ADD COLUMN IF NOT EXISTS source_repo_url varchar(500); +ALTER TABLE scan_results ADD COLUMN IF NOT EXISTS ai_locale varchar(16); +``` + +(Flyway users: `V3__component_metadata.sql` covers the three new `libraries` columns; see [Database Schema](Database-Schema.md).) + +### v1.0.5: Spring Session / ShedLock tables (opt-in) + +Only needed if you're moving to a **multi-instance** deployment (see §12). Adds `spring_session`, `spring_session_attributes`, and `shedlock`. Flyway users get this from `db/migration/V10__spring_session_and_shedlock.sql`; manual-script users run `db/spring_session_and_shedlock.sql`. A single-instance deployment can skip this entirely — nothing reads these tables until you set `OSWL_SESSION_STORE_TYPE=jdbc` and/or `OSWL_SCHEDULER_LOCK_ENABLED=true`. + +## 10. Post-deploy smoke test + +1. Open UI via HTTPS reverse proxy only. +2. Complete setup / login and 2FA if enabled. +3. Create a project and VCS connection; restart app — token still decrypts (confirms stable `OSWL_ENCRYPTION_KEY`). +4. `POST /api/scan` with project API key (see [Scan API security](Scan-Api-Security.md)). +5. Open a project you are a member of — confirm another user’s project ID returns forbidden (project membership). +6. Review audit log for failed auth attempts. + +## 11. Operations + +- Back up PostgreSQL and store `OSWL_ENCRYPTION_KEY` in a secrets manager (loss = unreadable VCS tokens) — see [Backup and restore](Backup-And-Restore.md) for the full procedure and a restore-rehearsal script. +- Rotate API keys and SMTP credentials on compromise. +- Keep `SPRING_PROFILES_ACTIVE` out of images that should never run as `local`. + +## 12. Multi-instance deployment (horizontal scaling / HA) + +By default OsWL runs as a **single instance** — an in-memory HTTP session and per-instance `@Scheduled` jobs. That's correct for a single container/process, but it does not survive a second instance behind a load balancer: a user's session would be pinned to whichever instance served their login, and the nightly monitoring / defer-expiry / trash-cleanup jobs would each run once *per instance* instead of once per cluster. This section is only relevant once you deploy **2+ instances against the same PostgreSQL database**. + +**1. Run the schema first.** Before starting any instance with these features on, make sure `spring_session`, `spring_session_attributes`, and `shedlock` exist (§9, "v1.0.5: Spring Session / ShedLock tables"). Rolling out the env vars below before the tables exist will crash every instance on the first request/job tick. + +**2. Environment variables:** + +| Variable | Purpose | +|----------|---------| +| `OSWL_SESSION_STORE_TYPE=jdbc` | Moves HTTP sessions from in-memory Tomcat storage to PostgreSQL (`spring_session`). Login state and single-session enforcement (`maximumSessions(1)`) then work across the whole cluster instead of per-instance. | +| `OSWL_SCHEDULER_LOCK_ENABLED=true` | Wraps the 3 scheduled jobs (`ContinuousMonitoringScheduler`, `DeferExpiryScheduler`, `TrashCleanupScheduler`) in a cluster-wide lock (ShedLock, backed by the `shedlock` table) so only one instance runs each job per cycle. | + +Set both together for a real multi-instance deployment — enabling only one leaves the other gap open. + +**3. Load balancer:** any standard L7 LB (nginx, ALB, etc.) works — **no sticky sessions required** once `OSWL_SESSION_STORE_TYPE=jdbc` is set, since session state is centralized in PostgreSQL rather than instance memory. + +**4. Scan-progress polling is the one exception.** `EnrichmentProgressHolder` and `ScanStatusEmitterRegistry` (the live progress shown during Quick Import / scan enrichment) are still in-memory per instance, not backed by the DB. Recommended: keep the load balancer's routing **sticky for the duration of an active scan** (e.g. cookie-based affinity scoped to the session), so progress-polling requests land back on the instance that's actually running the scan. The alternative — moving scan progress into the DB and switching the UI to pure polling — is a larger change tracked separately; sticky routing is the pragmatic default for now. + +**5. Rolling deploy order:** + 1. Apply any pending DB migration first (old app code must tolerate the new schema — additive-only migrations, which is what `db/migration` follows). + 2. Roll instances one at a time (not all at once), waiting for each new instance to pass its readiness check before moving to the next. + 3. Because sessions live in PostgreSQL (not instance memory) once `OSWL_SESSION_STORE_TYPE=jdbc` is set, a rolling restart no longer logs users out. + +**6. Verifying it worked:** + - Log in against instance A, then send a subsequent request that the LB routes to instance B — it should stay authenticated (not redirected to `/login`). + - Stop instance A — the session (and single-session enforcement) should keep working from instance B. + - Check scheduler logs across both instances after a nightly job fires — the job's log lines should appear on exactly one instance, not both. + +--- + +**Local development:** Run `./gradlew bootRun` (PowerShell: `.\gradlew.bat bootRun`) for the `local` profile and H2. The local YAML supplies a development-only encryption key; override it through the process environment if needed. `.env` is only loaded when the launcher explicitly reads it. diff --git a/docs/Project-Access-Control.md b/docs/en/Project-Access-Control.md similarity index 100% rename from docs/Project-Access-Control.md rename to docs/en/Project-Access-Control.md diff --git a/docs/Quick-Import.md b/docs/en/Quick-Import.md similarity index 100% rename from docs/Quick-Import.md rename to docs/en/Quick-Import.md diff --git a/docs/Risk-Trend.md b/docs/en/Risk-Trend.md similarity index 100% rename from docs/Risk-Trend.md rename to docs/en/Risk-Trend.md diff --git a/docs/Scan-Api-Security.md b/docs/en/Scan-Api-Security.md similarity index 96% rename from docs/Scan-Api-Security.md rename to docs/en/Scan-Api-Security.md index 83471ed0..9f76a703 100644 --- a/docs/Scan-Api-Security.md +++ b/docs/en/Scan-Api-Security.md @@ -30,7 +30,7 @@ All other routes keep normal CSRF protection for the UI. |------|------------------| | 1 | **Project API key** in `Authorization: Bearer …` | | 2 | **Submitter** email and password in the JSON body | -| 3 | Submitter has **`SCAN_SUBMIT`** (role template permission) and is in **`project_members`** for that project | +| 3 | Submitter has **`SCAN_SUBMIT`** and project access through direct project membership or a team grant. | See [Authorization layers](Authorization-Layers.md) for how role templates differ from project membership. diff --git a/docs/Scan-History.md b/docs/en/Scan-History.md similarity index 100% rename from docs/Scan-History.md rename to docs/en/Scan-History.md diff --git a/docs/Security-Center.md b/docs/en/Security-Center.md similarity index 98% rename from docs/Security-Center.md rename to docs/en/Security-Center.md index cb67892b..38f02b05 100644 --- a/docs/Security-Center.md +++ b/docs/en/Security-Center.md @@ -89,7 +89,7 @@ The **Export** dropdown in the action toolbar produces standards-based artifacts | SBOM (CycloneDX) | `GET /api/projects/{projectId}/sbom` | CycloneDX 1.6 JSON | | VEX | `GET /api/projects/{projectId}/vex` | CycloneDX VEX — carries your triage decisions | | SARIF | `GET /api/projects/{projectId}/sarif` | SARIF 2.1.0, uploadable to GitHub code scanning | -| Compliance report | `GET /security-center/compliance-report` | Print-ready HTML (use *Print → Save as PDF*) | +| Compliance report | `GET /projects/{projectId}/security-center/compliance-report` | Print-ready HTML (use *Print → Save as PDF*) | | CVE / license CSV | — | Current filtered view | Bulk actions also include **Create upgrade PR**, which opens one pull request bumping every selected component to its fix version. diff --git a/docs/User-Guide.md b/docs/en/User-Guide.md similarity index 89% rename from docs/User-Guide.md rename to docs/en/User-Guide.md index dd40c51d..5512abcd 100644 --- a/docs/User-Guide.md +++ b/docs/en/User-Guide.md @@ -1,5 +1,7 @@ # User Guide +[What's new in 1.0.5.1](Whats-New-v1.0.5.1.md) + This page covers the day-to-day use of the OsWL web dashboard. You only see projects you are a **member** of (unless you are a **system administrator**). What you can do inside a project depends on your **role template** (Admin / Developer / Viewer). See [Authorization layers](Authorization-Layers.md). @@ -96,7 +98,7 @@ Use **Administration → Offline Snapshot** to import a bundle, export the curre ## Embedded AI Model -OsWL's built-in local AI runs a llama.cpp sidecar and defaults to the **Qwen3 1.7B** GGUF model. On first boot, OsWL prefetches the default model in the background (configurable with `oswl.ai.embedded.auto-download-on-boot`; disabled in air-gapped mode) so that enabling embedded AI in Settings is faster. The default download uses OsWL's own GitHub Release asset with a fallback to the original Hugging Face repository, and every download is verified against a SHA-256 checksum. You can also place any compatible `.gguf` file in the configured embedded-AI directory. See [Embedded AI](Embedded-AI.md) for installation and troubleshooting. +Embedded AI runs a separately installed llama.cpp runtime with **Qwen3.5-2B Q4_K_M** as the default download. **Gemma 4 E2B** is optional and installed manually. The runtime belongs in `embedded-ai/llama/`, and models in `embedded-ai/model//`. Boot-time prefetch downloads only; it does not start the server or activate LOCAL. The default download uses a pinned Hugging Face revision with SHA-256 and size verification; no fallback mirror is configured by default. Air-gapped mode disables downloads. Use Settings to stop, select and save a model, then start again. See [Embedded AI](Embedded-AI.md) for current requirements and configuration. --- diff --git a/docs/Version-Diff.md b/docs/en/Version-Diff.md similarity index 100% rename from docs/Version-Diff.md rename to docs/en/Version-Diff.md diff --git a/docs/Whats-New-v1.0.4.md b/docs/en/Whats-New-v1.0.4.md similarity index 96% rename from docs/Whats-New-v1.0.4.md rename to docs/en/Whats-New-v1.0.4.md index 6200b94b..19c6be97 100644 --- a/docs/Whats-New-v1.0.4.md +++ b/docs/en/Whats-New-v1.0.4.md @@ -64,7 +64,7 @@ Upload a third-party CycloneDX file to scan components you don't build yourself ### Compliance report pack -`GET /security-center/compliance-report` renders a print-ready report: component inventory, licence obligations, NOTICE text, and open findings by severity. Use your browser's *Print → Save as PDF*. The preview no longer opens the print dialog automatically, so you can review it before exporting. +`GET /projects/{projectId}/security-center/compliance-report` renders a print-ready report: component inventory, licence obligations, NOTICE text, and open findings by severity. Use your browser's *Print → Save as PDF*. The preview no longer opens the print dialog automatically, so you can review it before exporting. --- diff --git a/docs/en/Whats-New-v1.0.5.1.md b/docs/en/Whats-New-v1.0.5.1.md new file mode 100644 index 00000000..30e43a78 --- /dev/null +++ b/docs/en/Whats-New-v1.0.5.1.md @@ -0,0 +1,24 @@ +# What's new in OsWL 1.0.5.1 + +OsWL 1.0.5.1 improves dependency metadata, scan reliability and the light interface. + +## Using this release + +- Login, search and settings use light mode. Settings show unsaved changes separately from errors. AI parameter errors appear beside the field, and a successful save remains visible beside Save. +- AI response diversity accepts 0–2 (default 0.15); maximum tokens accepts integers 256–8192 (default 1200). Empty values use defaults. Provider and analysis-task limits can override their use; Anthropic does not use the temperature setting. +- Deleted projects appear in Trash and can be restored while retained. CLI API keys can be deleted from Settings; deleted keys stop working. +- CVE identifiers have a direct NVD link. Fixed versions are selected from supported numeric release intervals. Maven parent properties and supported Gradle version constraints improve dependency resolution. +- Diagnostics distinguish failures, warnings and skipped checks and format disk capacity. Empty archive exports explain why there is no file; eligible exports download JSON. +- Cache “refresh on every scan” applies when a scan requests data, not as continuous background refresh. + +## Upgrade + +Use Java 25 and back up the database and persistent files first. Read the [deployment checklist](Production-Deployment-Checklist.md) and [backup instructions](Backup-And-Restore.md). On databases using migrations, apply the migrations through V35. Do not assume a JAR replacement has migrated your database. + +Download `oswl-1.0.5.1.jar` and `SHA256SUMS` from the matching GitHub Release. Follow [Getting started](Getting-Started.md) for configuration. Browser security alerts require web push/VAPID configuration and browser permission; external AI, SMTP and VCS integrations require their own configuration. + +## Accuracy and scope + +Existing saved scans are not rewritten by upgrading. Rescan to apply parser and enrichment fixes. Manifest declarations are not always the dependencies selected by a build; unresolved versions require investigation. Missing license or fix information is incomplete evidence, not a clean bill of health or proof that no patch exists. Complex version ranges, ecosystem-specific ordering and online/offline parity are not fully resolved in this release. + +See [Analysis coverage](Analysis-Coverage.md) for supported evidence and limitations. diff --git a/docs/en/_Sidebar.md b/docs/en/_Sidebar.md new file mode 100644 index 00000000..eb33fbe3 --- /dev/null +++ b/docs/en/_Sidebar.md @@ -0,0 +1,35 @@ +* [What's new in 1.0.5.1](Whats-New-v1.0.5.1.md) +**[Home](Home.md)** + +[English](Home.md) | [한국어](../ko/Home.md) | [日本語](../ja/Home.md) + +### Release +* [What's New in v1.0.4](Whats-New-v1.0.4.md) + +### Guides + +* [Getting Started](Getting-Started.md) +* [User Guide](User-Guide.md) +* [Quick Import](Quick-Import.md) +* [CLI Integration](CLI-Integration.md) +* [Scan History](Scan-History.md) +* [Embedded AI](Embedded-AI.md) + +### Analysis +* [Security Center](Security-Center.md) +* [License Analysis](License-Analysis.md) +* [Risk Trend](Risk-Trend.md) +* [Version Diff](Version-Diff.md) + +### Administration +* [Administration](Administration.md) +* [Authorization layers](Authorization-Layers.md) +* [Project access control](Project-Access-Control.md) +* [Production deployment](Production-Deployment-Checklist.md) +* [Backup and restore](Backup-And-Restore.md) +* [Database schema](Database-Schema.md) +* [Scan API security](Scan-Api-Security.md) + +### Reference +* [API Reference](API-Reference.md) +* [Glossary](Glossary.md) diff --git a/docs/ja/API-Reference.md b/docs/ja/API-Reference.md index 13764f5d..3786c9d4 100644 --- a/docs/ja/API-Reference.md +++ b/docs/ja/API-Reference.md @@ -84,7 +84,7 @@ Authorization: Bearer oswl_ | Method | Path | 説明 | |---|---|---| | `POST` | `/api/github/connect` | GitHub PAT の接続 | -| `DELETE` | `/api/github/disconnect` | GitHub 接続の削除 | +| `POST` | `/api/github/disconnect` | GitHub 接続の削除 | | `GET` | `/api/github/status` | 接続状態 | | `GET` | `/api/github/accounts` | 認証済みアカウントの一覧 | | `GET` | `/api/github/repos` | アクセス可能なリポジトリの一覧 | @@ -99,7 +99,6 @@ Authorization: Bearer oswl_ | Method | Path | 認証 | 説明 | |---|---|---|---| -| `POST` | `/api/auth` | API key | API キーの検証(レガシー) | | `GET` | `/api/scan/ping` | API key | 疎通確認とキーの有効性確認 | | `GET` | `/api/scan/manifest-rules` | API key | マニフェストファイルの収集ルール(`/scripts/manifest-rules.json` と同じ) | | `POST` | `/api/scan/parse` | API key | マニフェスト zip アーカイブの解析(CLI ステップ 1) | @@ -117,7 +116,7 @@ Authorization: Bearer oswl_ | `PATCH` | `/projects/{id}/security-center/bulk-status` | `SECURITY_CENTER_UPDATE_STATUS` | CVE 状態の一括更新 | | `GET` | `/projects/{id}/security-center/export` | `SECURITY_CENTER_EXPORT` | CVE 一覧を CSV としてダウンロード(`?scanId=`、`?format=csv`) | | `POST` | `/projects/{id}/security-center/batch-pr` | `SECURITY_CENTER_UPDATE_STATUS` | **v1.0.4** — 選択したすべてのコンポーネントに対して 1 件のアップグレード PR を作成 | -| `GET` | `/security-center/compliance-report` | `SECURITY_CENTER_EXPORT` | **v1.0.4** — 印刷用のコンプライアンスレポート | +| `GET` | `/projects/{projectId}/security-center/compliance-report` | `SECURITY_CENTER_EXPORT` | **v1.0.4** — 印刷用のコンプライアンスレポート | ### SBOM / VEX / SARIF(v1.0.4) @@ -249,8 +248,8 @@ Authorization: Bearer oswl_ | Method | Path | 説明 | |---|---|---| -| `GET` | `/api/admin/cli-keys` | グローバル CLI キーの一覧 | -| `POST` | `/api/admin/cli-keys` | グローバルキーの作成 | +| `GET` | `/api/admin/cli-keys` | 各プロジェクトの CLI キーを一覧表示 | +| `POST` | `/api/admin/cli-keys` | プロジェクト単位のキーを発行(`projectId` 必須) | | `PATCH` | `/api/admin/cli-keys/{keyId}/toggle` | キーの有効/無効切り替え | --- @@ -279,7 +278,7 @@ Authorization: Bearer oswl_ | `GET` | `/api/settings/ai/usage` | `SETTINGS_AI_MANAGE` | AI 使用統計 — 本日の呼び出し/トークン/推定コスト、日次上限、直近 7 日間(日次集計テーブルから取得) | | `GET` | `/api/settings/ai/usage/events` | `SETTINGS_AI_MANAGE` | 最近の AI 呼び出しイベント、新しい順(`?page=`、`?size=`、既定サイズ `10`)。直近**100 件**のみ保持(FIFO)のため、最大でも 10 ページ | | `GET` | `/api/settings/ai/embedded` | `SETTINGS_AI_MANAGE` | 内蔵 AI の状態(`running`、`external`、`binaryFound`、`activeModel`、`fallbackUsed`、`lastError`、`availableModels`、`modelsDir`、`baseUrl`。既定モデルのダウンロード中は `downloading`、`downloadedBytes`、`downloadTotalBytes` も) | -| `POST` | `/api/settings/ai/embedded/start?model=` | `SETTINGS_AI_MANAGE` | llama.cpp サイドカーの起動(モデルファイル名は任意。候補間の自動フォールバック、失敗時は理由付きで 400)。新規インストールで `.gguf` がまだない場合、代わりに Apache-2.0 の Qwen3-1.7B モデルをバックグラウンドでダウンロードし即座に応答(`downloading: true`)— 進捗は `GET .../embedded` でポーリング | +| `POST` | `/api/settings/ai/embedded/start?model=` | `SETTINGS_AI_MANAGE` | llama.cpp サイドカーの起動(モデルファイル名は任意。候補間の自動フォールバック、失敗時は理由付きで 400)。新規インストールで `.gguf` がまだない場合、代わりに Apache-2.0 の Qwen3.5-2B Q4_K_M モデルをバックグラウンドでダウンロードし即座に応答(`downloading: true`)— 進捗は `GET .../embedded` でポーリング | | `POST` | `/api/settings/ai/embedded/stop` | `SETTINGS_AI_MANAGE` | サイドカーの停止と LOCAL プロバイダーの無効化 | | `PUT` | `/api/settings/ai/embedded/config` | `SETTINGS_AI_MANAGE` | フォルダ/モデルの上書き設定 `{ "dir", "model" }` を保存(null は現状維持、空文字はクリア。dir が欠けている場合は 400) | diff --git a/docs/ja/Administration.md b/docs/ja/Administration.md index e44a7650..eaf21701 100644 --- a/docs/ja/Administration.md +++ b/docs/ja/Administration.md @@ -1,5 +1,7 @@ # 管理 +[1.0.5.1 の変更点](Whats-New-v1.0.5.1.md) + このページでは管理者専用の機能をすべて扱います: ユーザー管理、ロールテンプレート、監査ログ、セキュリティ設定、SMTP 設定。 > 特に断りのない限り、このページのすべての操作には**システム管理者**権限が必要です。 @@ -221,6 +223,24 @@ v1.0.4 のアクションコードはフィルター UI で **モニタリング 3 つとも管理者権限が必要です。Prometheus のスクレイプ設定は `application-prod.yaml` の `management` 配下にあります。 +### ビジネスメトリクス & Grafana + +デフォルトの JVM/HTTP メーターに加えて、OsWL は以下のビジネスメトリクスを記録します(すべて `/actuator/prometheus` で公開。Prometheus 名表記 — ドットはアンダースコアに変換されます): + +| メトリクス | 型 | タグ | 説明 | +|---|---|---|---| +| `oswl_scan_duration_seconds` | Timer | `outcome` (`completed`\|`failed`) | スキャンパイプライン全体の所要時間 | +| `oswl_quickimport_queue_depth` | Gauge | — | ワーカースロット待ちの Quick Import ジョブ数 | +| `oswl_quickimport_running` | Gauge | — | 現在実行中の Quick Import ジョブ数 | +| `oswl_components_ingested_total` | Counter | `ecosystem` | スキャン取り込みで保存されたコンポーネント数 | +| `oswl_ai_calls_total` | Counter | `provider` | 記録された AI 呼び出し数 | +| `oswl_ai_tokens_total` | Counter | `provider`, `direction` (`in`\|`out`) | AI プロンプト/完了トークン数 | +| `oswl_ai_cost_usd_total` | Counter | `provider` | AI 推定コスト (USD) | +| `oswl_gate_evaluations_total` | Counter | `outcome` (`pass`\|`fail`) | セキュリティゲート評価数 | +| `oswl_external_api_calls_total` | Counter | `source` (`depsdev`, `osv`, `epss`, `kev`, `github-advisory`, `nvd`), `outcome` (`success`\|`failure`\|`ratelimited`) | 外部データソースへの呼び出し数 | + +これらのメトリクスを網羅する Grafana ダッシュボードが [`deploy/observability/grafana/oswl-dashboard.json`](../../deploy/observability/grafana/oswl-dashboard.json) に同梱されています。**Dashboards → New → Import** からインポートすると Prometheus データソースの選択を求められるため、JSON の編集は不要です。 + --- ## オフラインスナップショットバンドル(v1.0.4) @@ -296,7 +316,7 @@ CVE/ライセンス要約用の LLM プロバイダーとエンリッチメン 各プロバイダーのモデル欄は自由入力コンボボックスです: ドロップダウンには現行モデルが提案として表示されますが、アカウントがアクセス可能な任意のモデル ID を直接入力できます。 -同じタブの**内蔵 AI(組み込みローカルモデル)**カードは、バンドルされた llama.cpp の `llama-server` サイドカー(CPU 専用、localhost 専用、API キー不要)を実行し、LOCAL プロバイダーとして登録します。既定でバンドルされているモデルは**Qwen3 1.7B**(初回使用時にダウンロード)で、カードには**モデルのドロップダウン**(フォルダ内の任意の `.gguf`、または自動優先順位)、**保存**付きの**フォルダの上書き**(永続化され、実行中に変更するとサイドカーが停止)、最初の選択が起動に失敗した場合に次に利用可能なモデルへ切り替える**自動フォールバック**があります。[内蔵 AI](Embedded-AI.md)を参照してください。 +内蔵 AI は別途インストールした llama.cpp を使用し、既定のダウンロードモデルは **Qwen3.5-2B Q4_K_M** です。**Gemma 4 E2B** は任意で手動インストールします。実行ファイルは `embedded-ai/llama/`、モデルは `embedded-ai/model/<系列>/` に配置します。起動時の事前取得はダウンロードのみで、サーバーの起動や LOCAL の有効化は行いません。既定の取得先は Hugging Face の固定リビジョンで、SHA-256 とサイズを検証します。既定の代替ミラーはなく、エアギャップモードではダウンロードしません。モデルを変更する場合は設定で停止し、モデルを選択・保存してから再度開始します。最新の要件と設定は[内蔵 AI](Embedded-AI.md)を参照してください。 同時に**アクティブ**にできるプロバイダーは 1 つだけです。タブには次も表示されます: @@ -354,3 +374,49 @@ AI カードには本日の呼び出し件数、トークン合計、推定コ | **クリア** | `POST /api/settings/cache/clear?cacheKey=…` | クリア時刻以前に取得されたライブラリは、次回のスキャンで古いものとして扱われる | 変更は `CACHE.UPDATE_TTL` と `CACHE.CLEAR` として監査記録されます。 + +--- + +## SAML 2.0 SSO および SCIM 2.0 プロビジョニング + +OsWL は、Okta、Entra ID、オンプレミス AD FS を利用する企業向けに SAML 2.0 シングルサインオンをサポートしています。SAML IdP が設定されると、`/login` に **SSO でサインイン** オプションが表示されます。 + +### SAML セットアップ + +1. SP 署名鍵ペアを生成します(任意ですが推奨): + ```bash + openssl req -x509 -newkey rsa:2048 -keyout oswl-saml-sp.key -out oswl-saml-sp.crt -nodes -days 3650 -subj "/CN=oswl" + ``` +2. `application-prod.yaml` の SAML ブロックのコメントを外し、環境変数を設定します: + | 環境変数 | 用途 | + |---|---| + | `OSWL_SAML_IDP_METADATA_URL` | IdP メタデータ URL(例: Okta/Entra アプリメタデータ) | + | `OSWL_SAML_IDP_CERTIFICATE` | IdP 署名証明書ファイルのパス | + | `OSWL_SAML_SP_PRIVATE_KEY` | SP 秘密鍵ファイルのパス | + | `OSWL_SAML_SP_CERTIFICATE` | SP 証明書ファイルのパス | +3. IdP に SP メタデータを登録します。メタデータエンドポイントは以下です: + ``` + https:///saml2/service-provider-metadata/oswl + ``` +4. IdP が email クレーム(NameID または `email`/`mail` 属性)を送信することを確認します。 + +> SAML ログインでは、IdP がすでにユーザーを認証しているため、メール OTP ステップをスキップします。既存の OsWL アカウントと一致しないメールアドレスは、SCIM が有効化してロールを割り当てられるよう、無効化されたローカルアカウントとして自動作成されます。 + +### SCIM 2.0 プロビジョニング + +SCIM を使用すると、IdP のユーザー ライフサイクルを OsWL と同期できます。 + +| リソース | エンドポイント | 備考 | +|---|---|---| +| Users | `/scim/v2/Users` | GET/POST/PUT/PATCH/DELETE | +| Groups | `/scim/v2/Groups` | GET/POST/PUT/PATCH/DELETE | + +**認証:** すべての SCIM リクエストに `Authorization: Bearer ` を含める必要があります。専用 SCIM トークンは `ApiKeyService#issueScimToken` でプログラム的に発行します。SCIM トークンは `api_keys` テーブルに保存されますが、スコープは `SCIM` であり、通常の CLI スキャン API では拒否されます。 + +**グループ マッピング:** `oswl.scim.group-mapping`(環境変数: `OSWL_SCIM_GROUP_MAPPING`)で SCIM グループの表現方法を選択します: +- `TEAM`(既定)— 各 SCIM グループは Team になり、メンバーは TeamMember 行になります。 +- `ROLE_TEMPLATE` — 各 SCIM グループは RoleTemplate になり、メンバーにはそのロール テンプレートが割り当てられます。 + +**ユーザー無効化:** `DELETE /scim/v2/Users/{id}` は OsWL 上で `active=false` に設定します。SCIM 経由ではユーザーを物理的に削除しないため、監査の帰属情報が保持されます。 + +**監査アクション:** SCIM 操作は `SCIM.USER_CREATE`、`SCIM.USER_UPDATE`、`SCIM.USER_DEACTIVATE`、`SCIM.GROUP_CREATE`、`SCIM.GROUP_UPDATE`、`SCIM.GROUP_DELETE`、`SCIM.GROUP_MEMBER_ADD`、`SCIM.GROUP_MEMBER_REMOVE`、`SCIM.AUTH_FAILURE`、`SCIM_KEY.CREATE` として記録されます。SAML ログイン イベントは `SAML.LOGIN_SUCCESS` および `SAML.LOGIN_FAILURE` として記録されます。 diff --git a/docs/ja/Backup-And-Restore.md b/docs/ja/Backup-And-Restore.md new file mode 100644 index 00000000..40145349 --- /dev/null +++ b/docs/ja/Backup-And-Restore.md @@ -0,0 +1,69 @@ +# バックアップと復旧 + +実務で最も多いインシデントは PostgreSQL の喪失ではなく、**DB バックアップは無事なのに `OSWL_ENCRYPTION_KEY` だけを紛失する**ケースです。DB に保存されているすべての VCS アクセストークン、AI プロバイダー API キー、Jira API トークン、SMTP メールパスワードはこのキーで暗号化されています。キーを失うと、DB は完璧に復元されても、その中のシークレットはすべて永久に復号できなくなります — すべての VCS 接続、AI プロバイダー、Jira 連携をゼロから再設定する必要が出てきます。 + +このページは [本番デプロイ](Production-Deployment-Checklist.md) の運用者向けの対になるページです — デプロイ方法はまずそちらを読んでください。このページはバックアップと、「復旧が実際に機能するか」の検証だけに焦点を当てています。 + +--- + +## バックアップ対象 + +| 項目 | 場所 | 重要な理由 | +|---|---|---| +| PostgreSQL データベース | `deploy/docker/compose.prod.yml` の `db-data-prod` ボリューム、またはマネージド PostgreSQL インスタンス | プロジェクト、スキャン、検出結果、ユーザー、暗号化されたシークレットなど、すべてのアプリケーションデータ。 | +| `OSWL_ENCRYPTION_KEY` | 注入方法による(`.env.prod`、シークレットマネージャーなど) | DB に保存されたすべての VCS トークン/AI API キー/Jira トークン/SMTP パスワードを復号します。**これがなければ、上記の DB バックアップはこれらのシークレットが必要な用途には使えません。** | +| オフラインスナップショットストア | `OSWL_AIRGAPPED_IMPORT_DIR`(閉域網モード使用時) | 復旧後の再インポートはこれがなくても可能ですが、インポート履歴が失われ、バンドルを再取得・再検証する必要があります。 | +| 内蔵 AI モデルディレクトリ | `OSWL_EMBEDDED_AI_DIR`(既定値 `embedded-ai/`) | 再ダウンロード可能([内蔵 AI](Embedded-AI.md) を参照) — 閉域網で再ダウンロードできない場合のみバックアップしてください。 | +| 設定ファイル | `.env.prod`、`deploy/docker/compose.prod.yml`、`application-prod.yaml` のオーバーライド | これがないと、データは無事でもインスタンスが実際どう設定されていたか(SMTP ホスト、HSTS 設定、機能フラグなど)が分かりません。 | + +それ以外(`OSWL_LOG_DIR` のファイルログ、Quick Import のクローン一時ディレクトリ)は使い捨てなのでバックアップ不要です。 + +--- + +## PostgreSQL のバックアップ + +```bash +# スキーマ + データ、カスタムフォーマット(並列リストアに対応、plain SQL より小さい) +pg_dump -Fc -h -U -d -f oswl-$(date +%Y%m%d).dump +``` + +**推奨頻度:** 毎晩フルダンプ、30 日保持。ダンプの間の時点への復旧が必要なら PostgreSQL の WAL アーカイビングも併用してください。ダンプは DB ホストから独立した場所(オブジェクトストレージ、別のアベイラビリティゾーン)に保管してください — バックアップがバックアップ対象のすぐ隣にあると、その対象を巻き込むインシデントで一緒に失われます。 + +`OSWL_ENCRYPTION_KEY` は **`pg_dump` の出力とは別の**シークレットマネージャーにバックアップしてください — これらのシークレットを保存時に暗号化する意味自体が、同じバックアップの塊にキーと暗号文を一緒に置いてしまうと失われます。 + +--- + +## 復旧手順 + +1. **新しい PostgreSQL インスタンスを用意**(または対象をクリアし)、ダンプを復元します: + ```bash + pg_restore -h -U -d --clean --if-exists oswl-20260730.dump + ``` +2. **バックアップ時と同じ `OSWL_ENCRYPTION_KEY` を注入**します — 別のキー(一見有効に見える新規生成キーであっても)を使うと、保存されているすべてのシークレットが復号不能になり、データ破損と見分けがつかなくなります。 +3. **復元した DB に対してアプリを起動**します(`SPRING_PROFILES_ACTIVE=prod`、`ddl-auto: validate` — 復元したスキーマは実行バージョンとすでに一致している必要があります。バックアップ時点より新しいバージョンに復旧する場合は、起動前に `src/main/resources/db/` の未適用の手動マイグレーションスクリプトを適用してください)。 +4. **以下の検証スクリプトを実行**し、復旧が「プロセスが起動した」レベルを超えて実際に使えることを確認します。 + +```bash +OSWL_VERIFY_EMAIL=you@example.com \ +OSWL_VERIFY_PASSWORD='...' \ +OSWL_VERIFY_PROJECT_ID=1 \ +./scripts/ops/verify-restore.sh https://your-instance.example.com +``` + +このスクリプトは対話形式です(実際のログインと同様、メール OTP コードの入力を待ちます)。以下を確認します: + +| チェック | 何を証明するか | +|---|---| +| `GET /actuator/health` → 200 | 復元した DB と注入したキーでアプリが正常に起動した。 | +| ログイン + OTP | 復元した `users` テーブルに対して認証・セッション基盤が機能する。 | +| `GET /api/settings/vcs` → 200 | **`OSWL_ENCRYPTION_KEY` が正しい** — 保存されている VCS トークンの少なくとも 1 件がエラーなく復号できた。キーが間違っていればここで即座に 500 として表面化し、数週間後に気づく静かなバグにはなりません。 | +| `GET /projects/{id}/scan-history` → 200 | スキャン履歴が復元されクエリ可能(復元データに実在するプロジェクト ID を `OSWL_VERIFY_PROJECT_ID` に設定する必要あり)。 | +| `GET /api/admin/audit-logs` → 200 | 監査ログが復元されクエリ可能(`SYSTEM_ADMIN` アカウントが必要)。 | + +このスクリプトは実際に定期的な**リハーサル**として実行してください(例: 四半期ごとに検証用環境で) — 作成されてから一度も実行されていない復旧手順は、検証済みの手順とは言えません。 + +--- + +## キーローテーション + +現時点では**再暗号化バッチ処理はありません。** 今日 `OSWL_ENCRYPTION_KEY` をローテーションすると、既存の暗号化された値(VCS トークン、AI API キー、Jira トークン、SMTP パスワード)はすべて復号できなくなります — 実務的なローテーション手順は、キーをローテーションした後、各設定画面(VCS 接続、AI プロバイダーキー、Jira 連携、SMTP 認証情報)でシークレットを再入力し、新しいキーで再暗号化させることです。適切なローテーションツール(旧キーで復号 → 新キーで再暗号化、対象となるすべてのテーブルに対して in-place で実行)は別途バックログ項目として追跡しています — このツールができるまでは、`OSWL_ENCRYPTION_KEY` は一度設定したら事実上変更不可能な値として扱い、それに見合った保護をしてください(シークレットマネージャーに置き、リポジトリ内の `.env` ファイルには置かない)。 diff --git a/docs/ja/CLI-Integration.md b/docs/ja/CLI-Integration.md index 784cd834..f1017ac0 100644 --- a/docs/ja/CLI-Integration.md +++ b/docs/ja/CLI-Integration.md @@ -89,6 +89,8 @@ ScanIngestService → async CVE + license enrichment (OSV / deps.dev) --- +マニフェストアーカイブの収集範囲は `/scripts/manifest-rules.json` に従います。依存関係マニフェストのほか、ビルド設定、ラッパーファイル、`buildSrc` の Java/Kotlin ファイルも含まれるため、送信前に収集ルールを確認してください。 + ## 前提条件 1. OsWL に登録済みの**プロジェクト**。 @@ -108,12 +110,12 @@ ScanIngestService → async CVE + license enrichment (OSV / deps.dev) | `POST` | `/api/scan` | API key + ユーザーパスワード | エンリッチメント用のスキャン送信 | | `GET` | `/api/scan/{scanId}/status` | セッション | スキャン状態のポーリング(UI) | | `POST` | `/api/scan/gate` | API key | **v1.0.4** — PR / CI セキュリティゲート。`exitCode` を含む判定を返す | -| `GET` | `/api/projects/{projectId}/sbom` | セッション / キー | **v1.0.4** — CycloneDX 1.6 SBOM | -| `GET` | `/api/projects/{projectId}/vex` | セッション / キー | **v1.0.4** — CycloneDX VEX | -| `GET` | `/api/projects/{projectId}/sarif` | セッション / キー | **v1.0.4** — SARIF 2.1.0 | +| `GET` | `/api/projects/{projectId}/sbom` | セッション | **v1.0.4** — CycloneDX 1.6 SBOM | +| `GET` | `/api/projects/{projectId}/vex` | セッション | **v1.0.4** — CycloneDX VEX | +| `GET` | `/api/projects/{projectId}/sarif` | セッション | **v1.0.4** — SARIF 2.1.0 | | `POST` | `/api/sbom/import` | セッション | **v1.0.4** — サードパーティの CycloneDX ファイルをインポート | -> CLI エンドポイントは `Authorization: Bearer` ヘッダーのみで認証し、セッションクッキーや CSRF トークンは不要です。`POST /api/scan`、`POST /api/scan/parse`、`GET /api/scan/ping` はブラウザの CSRF 検査から除外されます。それ以外のルートは通常どおり CSRF 保護が維持されます。[スキャン API セキュリティ](Scan-Api-Security.md)を参照してください。 +> CLI リクエストは `Authorization: Bearer` にプロジェクト API キーを指定します。`POST /api/scan` は送信者のメールアドレス・パスワード・権限・プロジェクトへのアクセス権限も確認します。`POST /api/scan`、`POST /api/scan/parse`、`POST /api/scan/gate` にブラウザセッションや CSRF トークンは不要です。`GET /api/scan/ping` はキーを検証します。[スキャン API セキュリティ](Scan-Api-Security.md)を参照してください。 --- @@ -127,9 +129,9 @@ POST /api/projects/{projectId}/keys UI: プロジェクト → **設定 (⚙)** → **CLI** → **キーを生成**。 -### 管理者用グローバルキー +### 管理者によるキー管理 -**設定 → 管理者 → CLI キー** — [API リファレンス](API-Reference.md)を参照してください。 +管理者は各プロジェクトの CLI キーを一覧表示・失効させ、指定した `projectId` にキーを発行できます。スキャンキーの範囲はプロジェクト単位です。別の SCIM トークンではスキャンを送信できません。 --- @@ -252,8 +254,14 @@ exit "$(echo "$verdict" | jq -r .exitCode)" | `failOnEpss` | `OSWL_GATE_FAIL_ON_EPSS` | `0.5` | | `failOnLicenseViolation` | `OSWL_GATE_FAIL_ON_LICENSE_VIOLATION` | `true` | | `onlyNew` | `OSWL_GATE_ONLY_NEW` | `true` | +| `onlyReachable` | `OSWL_GATE_ONLY_REACHABLE` | `false` | +| `failOnSecrets` | `OSWL_GATE_FAIL_ON_SECRETS` | `false` | + +`onlyNew` はベースラインで確認済みの CVE・ライセンス問題を除外しますが、ゲートの通過を保証しません。悪意あるパッケージ、有効にしたシークレット検出など、ほかの適用ルールによって失敗する場合があります。`onlyReachable` は、対応するバイトコード解析またはソース参照解析で `REACHABLE` となったコンポーネントの CVE のみを評価します。有効時は `UNKNOWN` の CVE を除外しますが、参照を確認できないことは悪用不可能である証明にはなりません。ライセンスと悪意あるパッケージの検査はこのフィルターとは独立しています。解析範囲を確認してから有効にしてください。GitHub の対象を設定すると Check Run と PR コメントで結果を公開できます。 + +確定的に悪性と判定されたパッケージ(OSV `MAL-` アドバイザリ)は、上記のすべてのしきい値および `onlyNew`/`onlyReachable` に関係なく常にブロックされます — 解除する唯一の方法は承認済みのポリシー例外(waiver、**v1.0.5**、`/api/policies/exceptions` 参照)です。 -`onlyNew` は直前の完了スキャンをベースラインとして比較するため、既存の技術的負債がマージを妨げることはありません。リクエストに GitHub の対象を指定すると、判定結果は Check Run と PR コメントとしても投稿されます。 +`failOnSecrets`(**v1.0.5**)は、Quick Import クローンのスキャンで CRITICAL/HIGH severity のシークレット検出(正規表現 + エントロピー規則 — AWS キー、GitHub/GitLab/Slack/npm トークン、埋め込みプライベートキーブロックなど)が1件でもあればブロックします。上記の他のしきい値と同様に、リクエストのオーバーライド → 組織/チーム/プロジェクトのポリシー階層 → インスタンスの既定値の順で解決されます。 --- diff --git a/docs/ja/Database-Schema.md b/docs/ja/Database-Schema.md index 47c48d7c..36ee7502 100644 --- a/docs/ja/Database-Schema.md +++ b/docs/ja/Database-Schema.md @@ -1,6 +1,6 @@ # データベーススキーマとマイグレーション -OsWL はすべてのアプリケーションデータを PostgreSQL(`prod`)または H2 ファイルモード(`local`)に保存します。`domain/entity/` 配下の JPA エンティティが、実際のスキーマの**信頼できる情報源**です。 +OsWL はすべてのアプリケーションデータを PostgreSQL(`prod`)または H2 ファイルモード(`local`)に保存します。`domain/entity/`, `auth/entity/` 配下の JPA エンティティが、実際のスキーマの**信頼できる情報源**です。 --- @@ -16,7 +16,9 @@ OsWL はすべてのアプリケーションデータを PostgreSQL(`prod`) ### Flyway(v1.0.4、オプトイン) -`OSWL_FLYWAY_ENABLED=true` にすると、スキーマ管理が Flyway に委ねられます(`baseline-on-migrate` が有効なため、既存のデータベースは拒否されずベースライン処理されます)。有効化する前に、現在のスキーマに一致するベースラインを生成してください。既定値は `false` で、上記の `ddl-auto` の挙動が維持されます。 +`OSWL_FLYWAY_ENABLED=true` で `src/main/resources/db/migration/` のバージョン別マイグレーションを有効にします。既定値は `false` です。リポジトリには `V1__baseline.sql` と後続のマイグレーションが含まれています。空の PostgreSQL DB では V1 から順に実行し、その後 Hibernate がスキーマを検証します。Flyway 履歴のない既存 DB では、`baseline-on-migrate` が V1 を実行せずにバージョン 1 を記録し、V2 以降を実行します。有効化前にバックアップを取得し、既存スキーマとマイグレーションを比較してください。手動適用済みの変更と後続マイグレーションが競合する場合があります。共有 DB に適用済みのファイルを再生成・変更しないでください。SQL を手動管理する場合は、対象バージョンに必要な変更をすべて順番に適用します。以下の一部の旧スクリプトだけでは新規インストール用のスキーマを構成できません。 + +現在のスキーマには v1.0.4 以降の変更も含まれます。組織・チーム(V11)、SAML/SCIM(V13)、Webhook(V14)、CVE の出所と C/C++ メタデータ(V15–V16)、ポリシー継承・例外(V17、V28)、到達可能性と根拠(V18、V29–V30)、監査ログの整合性(V19)、UI 設定・オンボーディング(V20、V23–V24)、シークレット・IaC 検出(V21)、スキャンのアーカイブ(V22)、レポートのブランド設定(V25)、キャッシュ集計・無効化(V26–V27)、永続化されたインポートジョブ(V31)です。完全な適用順序は実際のマイグレーションファイルで確認してください。一部のマイグレーションは再実行できないため、無条件に繰り返さないでください。 ### v1.0.4 で追加されたカラム @@ -73,7 +75,7 @@ OsWL はすべてのアプリケーションデータを PostgreSQL(`prod`) | `project_versions.imported_at`、`last_updated_at` | 未使用のタイムスタンプ | | `projects.updated_at`、`version`、`last_scanned_at` | 非正規化されたフィールド。UI は代わりに最新の `scan_results` を読む | -[運用デプロイチェックリスト](Production-Deployment-Checklist.md) §8 を参照してください。 +[運用デプロイチェックリスト](Production-Deployment-Checklist.md) §9 を参照してください。 --- @@ -94,7 +96,7 @@ libraries (shared) airgapped_snapshot_entries ── airgapped_snapshot_meta (オフラインスナップショットストア) -users, role_templates, audit_logs, cache_settings, vcs_connections, … +users, role_templates, audit_logs, cache_settings, user_vcs_connections, … ``` - **プロジェクトカードのバージョン/最終スキャン** — `projects.version` ではなく、最新の `scan_results` 行から導出されます。 diff --git a/docs/ja/Embedded-AI.md b/docs/ja/Embedded-AI.md index cdfa3782..8e943806 100644 --- a/docs/ja/Embedded-AI.md +++ b/docs/ja/Embedded-AI.md @@ -1,166 +1,99 @@ -# 内蔵 AI (Embedded AI) +# 内蔵AI -内蔵 AI を使うと、OsWL はクラウドアカウントも API キーも不要で、そのままローカル LLM を実行できます。**何もマシンの外に出ません**。バンドルされた [llama.cpp](https://github.com/ggml-org/llama.cpp) の `llama-server` をサイドカープロセスとして起動し、**OpenAI 互換**のエンドポイント(`http://127.0.0.1:/v1`)を公開します。このエンドポイントは **LOCAL** AI プロバイダーとして登録され、CVE トリアージ要約やライセンスインサイトはすべて自分自身の CPU 上で実行されます。 +OsWLはllama.cppを `http://127.0.0.1:11435/v1` で実行し、LOCALに登録します。推論データはサーバー内で処理され、インストールにはネットワークまたはオフライン転送が必要です。**CPU既定はQwen3.5-2B Q4_K_M、選択肢はGemma 4 E2B Q4_K_Mです。** 一度に1モデルを実行します。テキスト専用でGPU・画像や音声のプロジェクターは不要です。 ---- +## 要件とフォルダー -## 動作方式 +| モデル | 重みダウンロード | サーバー全体の想定最小 | 推奨 | +|---|---:|---|---| +| Qwen3.5 2B Q4_K_M | 1.28 GB | 2 vCPU / RAM 8 GB | 4 vCPU / RAM 8~16 GB | +| Gemma 4 E2B Q4_K_M | 3.11 GB | 4 vCPU / RAM 8 GB | 4 vCPU / RAM 16 GB | -* OsWL は**モデルディレクトリ**(既定 `./embedded-ai`)から `llama-server` を起動し、`/health` に応答するまで待機します。`.gguf` がまだない場合、起動直後にバックグラウンドで既定の Qwen3 モデルのダウンロードが自動的に始まります(初回のみ、約 1.2 GB)— そのため設定画面で **開始** をクリックする頃には、すでにダウンロードが完了しているか進行中であることが多くなります。`oswl.ai.embedded.auto-download-on-boot=false` にすると、以前どおり開始をクリックしたときだけダウンロードする動作に戻ります。**閉域網(エアギャップ)**環境(`oswl.airgapped.enabled=true`)では、このバックグラウンドダウンロードは一切実行されないため、事前に `.gguf` ファイルを自分で配置してください([要求事項とディレクトリ構成](#要求事項とディレクトリ構成)を参照)。 -* サーバーは**localhost のみ**(`127.0.0.1`)にバインドされ、他のマシンから到達することはありません。 -* 起動に成功すると、OsWL はそのエンドポイントを **LOCAL** プロバイダーとして保存し有効化します(同時にアクティブにできるプロバイダーは 1 つだけなので、他にアクティブなプロバイダーがあれば無効化されます)。 -* 内蔵 AI を停止すると LOCAL プロバイダーも無効化されるため、AI 呼び出しが停止済みエンドポイントに対して失敗することはありません。 -* サーバーは推論を無効化(`--reasoning-budget 0`)し、Web UI なし(`--no-webui`)で起動します。既定では GPU へのモデルレイヤーのオフロードも試みます(`-ngl 999`)— GPU での起動に失敗した場合(GPU 対応ビルドがない、VRAM 不足など)、OsWL は同じモデルを CPU のみで自動的に再試行してから、そのモデルをあきらめます。 -* 同時に来た AI 呼び出しは 1 件ずつキューイングされるのではなく並列に処理されます。サーバーは既定で `--parallel 4 --cont-batching`、フラッシュアテンション(`-fa`)、プレフィックスキャッシュの再利用(`--cache-reuse 256`)付きで起動し、OsWL 自身も独立した AI 呼び出しを並列に実行します(最大 `oswl.ai.enrichment.max-parallel-calls`、既定 `3`)。 -* LOCAL プロバイダーに送られるバッチプロンプトは、より単純化された JSON スキーマのバリアント(`oswl.ai.enrichment.local-simple-schema`、既定 `true`)を使用します — 小さなローカルモデルは、大規模なクラウドモデル向けの完全なスキーマよりも、単純な出力スキーマの方がはるかに確実に従います。 -* CVE とライセンスの要約は入力コンテキストのハッシュでキャッシュされるため、変更のないコンポーネントは次回のスキャンで再度問い合わせされません。 -* ポスチャー、セキュリティトレンド、ライセンストレンド、バージョン差分のインサイトは、4 回の個別呼び出しではなく単一の統合呼び出し(`insights.combined`)で生成され、固定オーバーヘッドを削減します。 +これは**性能保証ではなく容量見積もり**です。OsWLとPostgreSQLの同居、4K~8Kコンテキスト、小規模スキャンと生成を1件ずつ断続的に処理する条件です。大きなアーカイブ・同時スキャン・DB増加には追加容量が必要です。ファイルサイズは最大メモリではありません。重みに空きディスク6 GB以上を確保し、ランタイム・DB・ログ・旧モデルは別途計算します。ビルド要件は含みません。 -> 必要な権限: `SETTINGS_AI_MANAGE` またはシステム管理者 — 他の AI 設定と同じです。 +Qwenの既定採用はCPU共有サーバーに合う小さな重みが理由であり、常に高精度という意味ではありません。実際の英語・韓国語・日本語で検証してください。バーストvCPU数は持続性能を保証しません。クレジット枯渇時も確認し、連続分析は持続性能のあるCPUまたは別CPUワーカーを検討します。GPUは必須ではありません。 ---- +OS・アーキテクチャに合うCPUランタイムと同じ配布物のライブラリを使用します。ローカル検証基準はllama.cpp **b10068 (571d0d540)**です。両モデルと `--chat-template-kwargs`、`-fa on` をサポートする互換版が必要です。 -## 要求事項とディレクトリ構成 - -モデルディレクトリにはサーバーバイナリが必要です。`.gguf` モデルがまだ存在しない場合、初めて**開始**をクリックした際に自動で取得されます: - -``` +```text embedded-ai/ - llama-server(.exe) — llama.cpp サーバーバイナリ(自分で用意) - qwen3-1.7b-q4_k_m.gguf — 既定モデル(Apache 2.0)— 初回開始時に自動ダウンロード + llama/ + llama-server(.exe) + ... companion libraries ... + model/ + Qwen/Qwen3.5-2B-Q4_K_M.gguf + Gemma/gemma-4-E2B-it-Q4_K_M.gguf + llama-server.log ``` -ディレクトリが空の場合、Qwen3-1.7B(約 1.2 GB)がそのディレクトリに直接ダウンロードされます — -起動後に自動的に、またはまだ終わっていなければ **開始** をクリックした時点で — SHA256 チェック -サムが検証された後にサイドカーが起動します。カードにはダウンロードの進捗がリアルタイムで表示され、 -`java -jar app.jar` だけですべてが完結します — 別のスクリプトやビルド手順は不要です。Qwen3 は -Apache 2.0 ライセンスのため([THIRD_PARTY_LICENSES.md](../../THIRD_PARTY_LICENSES.md#qwen3-17b-gguf) -参照)、これは安全です — ユーザーに代わって取得・同梱しても追加の再配布義務は発生しません。モデルは -既定で上流の [Hugging Face リポジトリ](https://huggingface.co/ggml-org/Qwen3-1.7B-GGUF)から -ダウンロードされます。サードパーティのホストに依存したくない場合は、 -`OSWL_EMBEDDED_DEFAULT_MODEL_URL` にバイト単位で同一の自己ホスティングミラーを指定してください。 - -既定モデルの URL、SHA256、サイズの各設定は 1 つの組として扱ってください。URL だけを変更して -SHA256 やサイズを合わせないと、すべてのダウンロードがチェックサム検証で失敗します。 - -他の `.gguf` モデルも自分で追加して使えます — OsWL は既定の Qwen3 だけでなく、このディレクトリに直接置かれたすべての `.gguf` ファイルを認識します。再配布・共有する前に、そのモデル自体のライセンスを必ず確認してください — OsWL が同梱・自動取得するのは Qwen3 のみです。 - -| 項目 | OsWL が探す場所 | -|---|---| -| サーバーバイナリ | `/llama-server(.exe)`、次に `/bin/`、次にシステムの `PATH`([llama.cpp のリリース](https://github.com/ggml-org/llama.cpp/releases))— **自動ダウンロードされないため**、自分で配置する必要あり | -| モデル | ディレクトリ直下にあるすべての `.gguf` ファイル | - -設定の既定値(UI で保存されたフォルダが `dir` より優先されます): - -| 設定キー | 環境変数 | 既定値 | 説明 | -|---|---|---|---| -| `oswl.ai.embedded.dir` | `OSWL_EMBEDDED_AI_DIR` | `embedded-ai` | モデルディレクトリ(作業ディレクトリからの相対パス) | -| `oswl.ai.embedded.port` | `OSWL_EMBEDDED_AI_PORT` | `11435` | サイドカー用の localhost ポート | -| `oswl.ai.embedded.context-size` | `OSWL_EMBEDDED_AI_CONTEXT` | `8192` | `llama-server -c` に渡すコンテキストウィンドウ — **総**コンテキストサイズで、並列スロット数に応じて分割されます | -| `oswl.ai.embedded.gpu-layers` | `OSWL_EMBEDDED_AI_GPU_LAYERS` | `-1` | オフロードする GPU レイヤー数(`-ngl`): `-1` はビルドが対応する最大数、`0` は CPU のみ、正の数を指定すると明示的なレイヤー数になります。GPU での起動に失敗した場合は同じモデルを CPU のみで自動的に再試行します | -| `oswl.ai.embedded.threads` | `OSWL_EMBEDDED_AI_THREADS` | `0` | スレッド数(`-t`);`0` は llama.cpp が自動検出 | -| `oswl.ai.embedded.parallel-slots` | `OSWL_EMBEDDED_AI_PARALLEL` | `4` | `>1` の場合 `--parallel N --cont-batching` が付与され、サーバー側で AI 呼び出しが直列化されなくなります。各スロットには `context-size / N` が割り当てられ、2048 未満になると警告ログが出力されます | -| `oswl.ai.embedded.flash-attn` | `OSWL_EMBEDDED_AI_FLASH_ATTN` | `true` | フラッシュアテンション(`-fa`) | -| `oswl.ai.embedded.cache-reuse` | `OSWL_EMBEDDED_AI_CACHE_REUSE` | `256` | 呼び出し間でのプレフィックスキャッシュ再利用のための `--cache-reuse N`;`<=0` で無効化 | -| `oswl.ai.embedded.extra-args` | `OSWL_EMBEDDED_AI_EXTRA_ARGS` | (空) | llama-server の CLI 追加引数をそのまま付加します — サーバー設定専用で、リクエスト入力からは決して取得されません | -| `oswl.ai.embedded.startup-timeout-seconds` | `OSWL_EMBEDDED_AI_STARTUP_TIMEOUT_SEC` | `120` | 各モデル候補が healthy になるまで、起動試行ごとに許容される時間(秒) | -| `oswl.ai.embedded.default-model-url` | `OSWL_EMBEDDED_DEFAULT_MODEL_URL` | 上流の Hugging Face `ggml-org/Qwen3-1.7B-GGUF` アセット | 既定 Qwen3 モデルの一次ダウンロード元 | -| `oswl.ai.embedded.default-model-sha256` | `OSWL_EMBEDDED_DEFAULT_MODEL_SHA256` | (THIRD_PARTY_LICENSES.md 参照) | 期待される SHA256 — URL と必ず同時に変更 | -| `oswl.ai.embedded.default-model-size-bytes` | `OSWL_EMBEDDED_DEFAULT_MODEL_SIZE_BYTES` | `1282439264` | 想定サイズ(バイト)。ダウンロード進捗バーの初期表示に使用され、URL/SHA256 と同じ組として扱います | -| `oswl.ai.embedded.fallback-model-url` | `OSWL_EMBEDDED_FALLBACK_MODEL_URL` | (空) | 一次 URL 失敗時に 1 回だけ再試行。一次を自己ホスティングミラーに変更する場合は、この値に上流 URL を指定してください | -| `oswl.ai.embedded.auto-download-on-boot` | `OSWL_EMBEDDED_AUTO_DOWNLOAD` | `true` | 起動時にバックグラウンドで既定モデルを先読み;`oswl.airgapped.enabled=true` の場合は実行されない | - ---- - -## 開始と停止 - -**設定 → AI** を開き、**内蔵 AI(組み込みローカルモデル)**カードを使用します: - -1. ステータス行を確認 — **実行中** / **停止中**、バイナリが見つからない場合はその旨のメモが表示されます。 -2. **開始**をクリックします。ディレクトリにまだモデルがない場合、サーバー起動前に Qwen3 のダウンロード進捗バー(約 1.2 GB — 回線速度によっては数分かかる場合あり)が表示されます。モデルがすでにある場合でも、読み込みだけで最大 1 分ほどかかることがあります。 -3. 実行中になると、カードに**実行モデル(Active)**が表示され、そのエンドポイントが LOCAL プロバイダーとして稼働します。 -4. **停止**をクリックするとサイドカーがシャットダウンします(ダウンロード済みのモデルは削除されません — 次回の開始時に即座に再利用されます)。 +| Model | Bytes | SHA-256 | +|---|---:|---| +| Qwen3.5-2B Q4_K_M | 1280835840 | `aaf42c8b7c3cab2bf3d69c355048d4a0ee9973d48f16c731c0520ee914699223` | +| Gemma 4 E2B Q4_K_M | 3106738272 | `740185b21d22ceb83a11c3aa62ad5842ef32c70f6096d756bbee85a1e4ec34b8` | -カードには使用中のフォルダ、検出されたすべての `.gguf` ファイル、直近の起動エラー(あれば赤字で)も表示されます。 +- [Qwen model card](https://huggingface.co/Qwen/Qwen3.5-2B) +- [Qwen GGUF, pinned revision](https://huggingface.co/unsloth/Qwen3.5-2B-GGUF/tree/f6d5376be1edb4d416d56da11e5397a961aca8ae) +- [Gemma model card](https://huggingface.co/google/gemma-4-E2B-it) +- [Gemma GGUF, pinned revision](https://huggingface.co/unsloth/gemma-4-E2B-it-GGUF/tree/0314792d7f1f7e229411f620751375812bb9faf2) +- [llama.cpp releases](https://github.com/ggml-org/llama.cpp/releases) +- [License notices](../../THIRD_PARTY_LICENSES.md#embedded-ai-runtime-and-models) ---- +設定はファミリーフォルダーではなくルートを指定します。`model/` と直下のファミリーを探索します。旧ルートGGUFおよびルート/bin実行ファイルも認識し、同名はmodel側を優先します。`mmproj*`、`mtp-*`、`imatrix*` とルート外を指すファイルは除外します。 -## モデルの切り替え +ほかのインストールの旧モデルは自動削除・交換しません。不要なら探索対象外へ移動します。保存モデルが消えた場合はインストール済みの既定優先順位に戻ります。 -既定のモデルだけに限定されません — llama.cpp 互換の `.gguf` であれば何でも使えます: +プロジェクトの多言語プロンプトで両モデルの英語・韓国語・日本語の応答を確認しました。ただし一部のQwen応答では、入力した修正バージョンや件数が誤って生成されました。これは実行互換性の確認であり、正確性の保証ではありません。AIの推奨は元のスキャン結果と照合してください。既定モデルはリソース基準の選択であり、品質順位ではありません。 -1. 量子化された GGUF モデルを**ダウンロード**します(例: [Hugging Face](https://huggingface.co/models?library=gguf))。 -2. `.gguf` ファイルをカードに表示されているモデルフォルダに**配置**します。 -3. **モデル**ドロップダウン(フォルダから再読み込みされる一覧)で**選択**し、**保存**をクリックします。 -4. **開始**をクリックすると、選択したモデルが最初に試行されます。モデルの選択は次回の開始時に反映され、実行中にモデルを切り替えることはできません(ドロップダウンは無効化されます)。 +## インストール・モデル・言語選択 -**自動(優先順位)** オプションは次の順で試行します: ドロップダウンに保存されたモデル → `qwen3…` → 残りの `.gguf` ファイルの中で最初のもの(アルファベット順)。 +1. 実行ファイルとライブラリを `llama/` に置きます。 +2. モデルがなければ起動時に **Qwen3.5 2Bのみ** `model/Qwen/` に事前取得します。開始ボタンも必要時に取得します。`OSWL_EMBEDDED_AUTO_DOWNLOAD=false` で事前取得を停止できます。 +3. Gemmaは上記固定リビジョンから手動取得し、SHA-256とバイト数を確認して `model/Gemma/` に置きます。 +4. **設定 → AI → プロバイダー → 内蔵AI**でモデル名と要件を確認します。 +5. **停止 → モデル選択 → 保存 → 開始**で変更します。開始失敗時は代替が実行される場合があるため、実行中モデルと代替表示を確認します。 +6. **分析範囲/コンテキスト**で英語・韓国語・日本語を選び保存します。内蔵識別とURLは維持されます。既存要約は再生成前には翻訳されません。 -> CPU 専用の推論では、**1B〜4B パラメータ**程度の小さな量子化モデル(Q4_K_M など)を推奨します。より大きなモデルはより多くの RAM を必要とし、遅いマシンでは起動タイムアウトに失敗することがあります。コンテキストウィンドウは `OSWL_EMBEDDED_AI_CONTEXT`(既定 `8192`)で、並列スロット数に応じて分割されます — 既定の 4 スロットでは呼び出しごとに 2048 トークンのコンテキストになるため、呼び出しごとに長いコンテキストが必要な場合はコンテキストを増やすか `OSWL_EMBEDDED_AI_PARALLEL` を下げてください。 +既存のAI設定管理権限を使います。開始は実際のモデルをLOCALに登録してほかのプロバイダーを無効にし、停止はLOCALも無効にします。ほかのプロバイダー選択だけではプロセスが止まらないため、メモリ解放には別途停止します。 ---- +既定優先順位はQwen3.5-2B Q4_K_M → ほかのQwen3 → Gemma 4 E2B → 残りの互換GGUFです。明示選択と有効な保存値を優先します。追加モデルは自身の要件とライセンスを確認してください。 -## モデルフォルダの変更 +フォルダー変更はllama/とmodel/を含むルートを保存します。変更時は管理中のサーバーが停止します。移動もロック回避のため停止後に行います。Linux実行ファイルには実行権限が必要です。 -OsWL に別のディレクトリを指定する方法は 2 つあります: +## CPU既定値 -| 方法 | 適用範囲 | +| Environment variable | Default | |---|---| -| 内蔵 AI カードの**フォルダ**入力欄 + **保存** | データベースに永続化されます(`ai_preferences.embedded_dir` / `embedded_model`)。既定値より優先されます | -| `OSWL_EMBEDDED_AI_DIR` 環境変数 / `oswl.ai.embedded.dir` の yaml 設定 | UI でフォルダが保存されていない場合に使われる既定値 | +| `OSWL_EMBEDDED_AI_DIR` | `embedded-ai` | +| `OSWL_EMBEDDED_AI_PORT` | `11435` | +| `OSWL_EMBEDDED_AI_GPU_LAYERS` | `0` | +| `OSWL_EMBEDDED_AI_THREADS` | `1` | +| `OSWL_EMBEDDED_AI_PARALLEL` | `1` | +| `OSWL_EMBEDDED_AI_CONTEXT` | `8192` | +| `OSWL_EMBEDDED_AI_FLASH_ATTN` | `true` | +| `OSWL_EMBEDDED_AI_CACHE_REUSE` | `256` | +| `OSWL_EMBEDDED_AI_STARTUP_TIMEOUT_SEC` | `120` | +| `OSWL_EMBEDDED_AI_EXTRA_ARGS` | empty | +| `OSWL_EMBEDDED_AUTO_DOWNLOAD` | `true` | -`PUT /api/settings/ai/embedded/config` が適用するルール: +`-ngl 0` と生成スロット1を明示します。`LLAMA_ARG_CHAT_TEMPLATE_KWARGS={"enable_thinking":false}` とreasoning budget 0を併用します。予算0だけでは新モデルが空回答になる場合があります。4 vCPUで2スレッドを使う場合はWeb・スキャンの遅延を確認してください。スレッド数はCPU使用率上限ではなく、必要ならOS・コンテナ制限を使います。 -* フォルダは**すでに存在している**必要があります — そうでない場合、保存は拒否されます(`400`、"Folder not found or not a directory")。 -* **サイドカーの実行中**にフォルダを変更すると、まずサイドカーが停止し(実行中の `llama-server` は旧ディレクトリのファイルロックを保持しているため)、LOCAL プロバイダーも無効化されます。新しいフォルダから再度開始してください。 -* フォルダ入力を空にすると、上書き設定が解除され、設定済みの既定値にフォールバックします。 +全体コンテキストはスロット間で分割します。`OSWL_AI_MAX_PARALLEL_CALLS` の既定3は**スキャン単位**です。1スロットでも待機が非ストリーミング読み取り制限90秒を超える場合があります。小型サーバーは1に設定し、スキャン重複を避けます。全体の受付制限ではありません。 ---- +## 整合性・閉域・GitHub配布 -## 自動フォールバック +既定URLは上記の固定リビジョンです。旧GitHub `models-v1` 資産はQwen3 1.7Bなので新モデルに再利用できません。この変更は新しいGitHubモデル資産を公開せず、存在も仮定しません。 -一度の開始クリックで複数のモデルが試行されることがあります。候補は順番に試されます — 要求/保存されたモデル → 内蔵の優先順位 → 残りの `.gguf` — 各候補には起動試行ごとに最大 **120 秒**(`oswl.ai.embedded.startup-timeout-seconds`)が与えられ、healthy になる必要があります。GPU オフロードが有効な場合(既定)、GPU での起動に失敗した候補はまず CPU のみで再試行されます。それでもクラッシュまたはタイムアウトするモデルはスキップされ、次の候補に進みます。 +`OSWL_EMBEDDED_DEFAULT_MODEL_URL`、`OSWL_EMBEDDED_DEFAULT_MODEL_SHA256`、`OSWL_EMBEDDED_DEFAULT_MODEL_SIZE_BYTES` は同一ファイルの値にします。ハッシュとサイズを検証してからインストールします。`OSWL_EMBEDDED_FALLBACK_MODEL_URL` は既定で空で、同じバイト列のミラーのみ指定できます。ミラーも既定Qwen名で保存するため、別モデルは実際の名前で手動配置します。 -OsWL が最終的に**最初の選択ではない**モデルを実行することになった場合、カードのモデルセレクタの横にオレンジ色の **「フォールバックモデルで開始しました」** バッジが表示されます。これは通常、優先モデルの読み込みに失敗したことを意味します(大きすぎる、ダウンロードが壊れているなど)— **実行モデル**の行で実際に稼働しているモデルを確認できます。 +閉域では取得せず、対応ランタイム・検証済み重み・告知を転送します。Dockerにも含まれないためルートをマウントして **Linux用** ランタイムを用意します。自動取得には書き込み権限が必要です。Windows実行ファイルはLinuxコンテナで使えません。 ---- - -## 状態とログ - -`GET /api/settings/ai/embedded` は `running`、`external`、`binaryFound`、`activeModel`、`fallbackUsed`、`lastError`、`availableModels`、`modelsDir`、`baseUrl` を返し、開始によってトリガーされた既定モデルのダウンロードが進行中の間は `downloading`、`downloadedBytes`、`downloadTotalBytes` も返します。 - -`external` は、設定されたポートですでに何か(手動で起動された `llama-server`、または以前の OsWL プロセス・クラッシュにより取り残されたもの)が `/health` に応答しており、それを OsWL 自身が起動したのではない場合に `true` になります。この場合も `running` は `true` のままです — エンドポイント自体は LOCAL プロバイダーとして正常に使用できるためです — しかし**停止**をクリックしても OsWL が所有していないプロセスは終了できず、実行され続けたままステータスは `external: true` を報告し続けます。 - -`POST /api/settings/ai/embedded/start` は、Qwen3 のダウンロードを開始する際、完了を待たずに即座に応答します(`success: true`, `downloading: true`)— 設定ページは進捗と最終的な `running` / `lastError` の結果をポーリングで確認します。ダウンロード自体はブラウザセッションとは無関係にサーバー上で進行するため、ページを更新(または閉じて)もキャンセルされません。ページを再度開くと進捗表示が再開されます。 - -`llama-server` は自身の標準出力/標準エラー出力を **`/llama-server.log`** に書き込みます。起動に失敗した場合、そのログの最後の数行が `lastError` に含まれカード上に赤字で表示されます — 詳細はログファイル全体を確認してください。 - ---- +`embedded-ai/` はGit・Dockerビルドコンテキストから除外し、JARに重み・実行ファイルは入りません。Qwen3.5・Gemma 4はApache 2.0、llama.cppはMITです。ライセンス全文・出典・上流告知・Unsloth GGUF Q4_K_M量子化の事実を保存します。実行ファイル再配布では同梱ライブラリの告知も保ちます。 ## トラブルシューティング -| 症状 | 考えられる原因/対処法 | -|---|---| -| "llama-server binary not found" | カードに表示されているフォルダ(またはその `bin/` サブフォルダ、または `PATH`)に `llama-server(.exe)` を配置してください — これは自動ダウンロードされません | -| モデルがまだなく、開始をクリックしても何も起きないように見える | インターネット接続を確認してください — 既定モデルのダウンロードには一度だけそれが必要です。閉域網環境では、`.gguf` ファイルをモデルフォルダに直接自分で配置してください | -| "Model download failed" / チェックサム不一致 | ダウンロード中にネットワークが中断された、または転送が破損した — 不完全なファイルは自動的に削除されます。**開始**を再度クリックして再試行してください | -| ログに `failed to open GGUF file` | 設定上のフォルダが実際のモデルの場所と一致していません — **フォルダ**フィールドと、ファイル名がドロップダウンの項目と一致しているか確認してください | -| "did not become healthy within 120s" | マシンが遅い、またはモデルが大きすぎます — より小さい量子化(例: 1B〜2B パラメータ級の Q4_K_M `.gguf`)を試すか、`OSWL_EMBEDDED_AI_STARTUP_TIMEOUT_SEC` を上げてください。`(GPU)` での失敗は自動的に CPU のみで再試行されるため、このメッセージが続く場合は CPU での再試行も失敗しています | -| ログに `slotContext … is below 2048` という警告 | コンテキストサイズに対して並列スロット数が多すぎます — `OSWL_EMBEDDED_AI_CONTEXT` を上げるか `OSWL_EMBEDDED_AI_PARALLEL` を下げてください | -| ポートがすでに使用中 | 別のプロセス(または手動で起動した `llama-server`)がポートを占有しています — それを停止するか `OSWL_EMBEDDED_AI_PORT` を設定してください。ポートですでに正常に待ち受けているサーバーは「実行中」とみなされ、ステータスレスポンスで `external` としてフラグが立ちます | -| **停止**をクリックしてもカードがオフにならない | 実行中のサーバーが `external`(この OsWL インスタンスが起動したものではない)です — プロセスを自分で停止するか、それが動作しているマシン/コンテナを再起動してください。OsWL はそれを終了できません | -| 保存時に "Folder not found or not a directory" | 先にディレクトリを作成してください。保存は既存のフォルダのみを受け付けます | - ---- - -## セキュリティとプライバシー - -* サイドカーは `127.0.0.1` のみにバインドされ、**API キーは不要**です — ネットワークには一切公開されません。 -* `llama-server.log` に出る llama.cpp の **CORS 警告**は想定内で無害です — サーバーは localhost でのみリッスンしています。 -* テレメトリなし: モデルに送られるプロンプト、コードスニペット、CVE データがマシンの外に出ることは一切ありません。 - ---- - -## REST API 概要 - -[API リファレンス — AI](API-Reference.md#ai)を参照してください。対話的なスキーマ: Swagger UI(`local` プロファイル)。 +- 実行ファイルなし: llama/、OS・アーキテクチャ・ライブラリ・権限を確認します。 +- 空回答: 互換版と明示的な非思考設定を確認します。 +- 開始失敗: llama-server.logでモデル対応・RAM不足を確認します。 +- 別モデル実行: 停止・選択・保存・開始後に代替表示を確認します。 +- タイムアウト: 同時スキャン・呼び出しとCPUクレジットを確認します。 +- 取得失敗: URL・サイズ・SHA-256を同時に確認します。 diff --git a/docs/ja/Getting-Started.md b/docs/ja/Getting-Started.md index 8131c0d4..92ee38ef 100644 --- a/docs/ja/Getting-Started.md +++ b/docs/ja/Getting-Started.md @@ -8,11 +8,11 @@ | コンポーネント | 要件 | |---|---| -| **JDK** | 25 以降 | +| **JDK** | 25 | | **ビルドツール** | Gradle Wrapper(同梱 — `./gradlew`) | | **データベース** | H2 ファイルモード(ローカル/開発用)または PostgreSQL 15 以降(本番用) | | **OS** | Linux、macOS、Windows | -| **メモリ** | 最小 512 MB、1 GB 以上を推奨 | +| **メモリ** | スキャンの処理量によって異なり、組み込み AI には追加メモリが必要 | > Node.js や npm は不要です — Tailwind CSS のスタンドアロンバイナリは初回ビルド時に Gradle が自動でダウンロードします。 @@ -38,44 +38,24 @@ OsWL には 2 つの Spring プロファイルが用意されています。 ### 3. アプリケーションを起動 -**ローカル(H2、設定不要):** +ローカル開発では `./gradlew bootRun` を実行します(PowerShell: `.\gradlew.bat bootRun`)。既定の `local` プロファイルは H2 と固定の開発用暗号化キーを使用します。開発環境以外では別の永続キーを使用し、開発用キーを本番に流用しないでください。 -```bash -./gradlew bootRun -``` - -**本番(PostgreSQL):** +本番では `./gradlew bootJar verifyProdJar` で配布用 JAR をビルドし、`prod` プロファイルで実行します。`DB_URL`、`DB_USERNAME`、`DB_PASSWORD` と、Base64 デコード後に 32 バイトとなる永続的な `OSWL_ENCRYPTION_KEY` を設定してください。キーは `openssl rand -base64 32` で一度生成し、再起動や復元でも維持します。`prod` はスキーマを自動作成せず検証するため、先に PostgreSQL のスキーマを準備します。 -```bash -export SPRING_PROFILES_ACTIVE=prod -export DB_URL=jdbc:postgresql://localhost:5432/oswl -export DB_USERNAME=oswl -export DB_PASSWORD=changeme -export OSWL_ENCRYPTION_KEY=$(openssl rand -base64 32) - -./gradlew bootRun -``` +手順は[本番デプロイチェックリスト](Production-Deployment-Checklist.md)と [Docker の実行案内](../../deploy/README.md)を参照してください。`bootRun` と `java -jar` は `.env` を自動では読み込みません。起動するシェルで環境変数を設定するか Spring の設定を使用し、Compose では `--env-file` を指定します。 -> **`OSWL_ENCRYPTION_KEY`** — VCS トークンなど保存された機密情報を保護する鍵です。`local` では開発用の鍵が自動生成されることがあります。**`prod`** では起動前に必ず固定値を設定してください。設定しないとアプリケーションは起動しません。鍵を紛失すると、それまでに保存された VCS 認証情報は使用できなくなります。 - -アプリケーションは既定でポート **8080** で起動します。 - ---- +既定のアプリケーションポートは **8080** です。必要なメモリは処理量によって異なり、ローカル AI にはモデルとコンテキスト用の追加メモリが必要です。 ## 組み込み AI モデル(初回起動) -OsWL は組み込みの llama.cpp サイドカーを通じて、AI 機能を完全にオンプレミスで実行できます。初回起動時に `embedded-ai/` に `.gguf` モデルが存在しない場合、OsWL は既定の **Qwen3-1.7B** モデル(約 1.2 GB)のバックグラウンドダウンロードを自動で開始します: +現在の既定モデルは **Qwen3.5-2B Q4_K_M** です(1,280,835,840 バイト、約 1.28 GB)。Hugging Face の `unsloth/Qwen3.5-2B-GGUF` にある固定リビジョンのファイルを取得し、`embedded-ai/model/Qwen/` に保存して設定済みの SHA-256 で検証します。llama.cpp の実行ファイルは別途インストールし、`embedded-ai/llama/` または `PATH` に配置してください。 -* 上流の Hugging Face リポジトリ(`ggml-org/Qwen3-1.7B-GGUF`)から取得し、SHA-256 で整合性を検証します。サードパーティのホストへの依存を避けたい場合は、`OSWL_EMBEDDED_DEFAULT_MODEL_URL` に自己ホスティングミラーを指定してください。 -* ダウンロードのみを行います — サイドカーの起動やアクティブな AI プロバイダーの変更は行いません。進行状況は **設定 → AI** に表示され、ファイルの準備ができたらそこで **開始** をクリックしてください。 -* `OSWL_EMBEDDED_AUTO_DOWNLOAD=false` で無効化できます。エアギャップモードではダウンロードは一切試行されません(後述)。 - -`llama-server(.exe)` バイナリ本体のみ手動での手順です — [llama.cpp releases](https://github.com/ggml-org/llama.cpp/releases) からダウンロードし、`embedded-ai/`(または `PATH` 上)に配置してください。詳細は [組み込み AI](Embedded-AI.md) を参照してください。 - ---- +モデルがなく `OSWL_EMBEDDED_AUTO_DOWNLOAD=true` の場合、起動時にバックグラウンドで事前取得します。この処理はサイドカーの起動や AI プロバイダーの有効化を行いません。実行ファイルの導入後、**設定 → AI** から開始してください。起動時の事前取得を無効にするには値を `false` にします。閉域網モードではモデルの自動ダウンロードは無効です。モデルの検出、設定、問題の解決方法は[組み込み AI](Embedded-AI.md)を参照してください。 ## エアギャップ(オフライン)環境での起動 +閉域網モードは対応する脆弱性情報源をスナップショットに切り替える機能であり、ネットワークの遮断機能ではありません。VCS・SMTP・Webhook・AI の接続先は環境に応じて別途設定してください。 + 外部インターネットへのアウトバウンド接続がないホストの場合: 1. 起動前に `OSWL_AIRGAPPED_ENABLED=true` を設定します。脆弱性/脅威インテリの参照(OSV、deps.dev、EPSS、KEV)はインポート済みのオフラインスナップショットから提供され、アウトバウンド HTTP は一切試行されず、組み込みモデルの自動ダウンロードもスキップされます。 @@ -93,7 +73,7 @@ OsWL は組み込みの llama.cpp サイドカーを通じて、AI 機能を完 ## セットアップウィザード -初回起動時(データベースが空の状態)は、すべてのリクエストが `http://localhost:8080/setup` にリダイレクトされます。 +初回起動時(データベースが空の状態)は、通常のアプリケーションページへのリクエストが `http://localhost:8080/setup` にリダイレクトされます。 ウィザードで入力する項目: @@ -111,35 +91,24 @@ OsWL は組み込みの llama.cpp サイドカーを通じて、AI 機能を完 ## 初回ログイン -1. `http://localhost:8080/login` にアクセスします。 -2. セットアップウィザードで作成したメールアドレスとパスワードを入力します。 -3. **二要素認証(Two-Factor Authentication)** が有効な場合(管理者が設定可能)、メールで送信される 6 桁の OTP の入力を求められます。 - * `local` モードでは OTP がサーバーログに出力されます: `*** OTP CODE: NNNNNN ***` - * 開発用ショートカット: test プロファイル使用時は `000000` が受け付けられます。 -4. 仮パスワードでの初回ログイン時、OsWL はその場でのパスワード変更を強制します。 - ---- +1. `http://localhost:8080/login` でセットアップ時に作成したアカウントにログインします。 +2. メールによる二要素認証が必要な場合、現在のセッションに発行された 6 桁のコードを入力します。有効期限は 3 分で、再送には 60 秒の待機時間があります。 +3. 既定のローカル GreenMail SMTP 設定では、受信したコードがサーバーログに `*** OTP CODE: ... ***` と表示されます。外部 SMTP を設定した場合は受信者のメールボックスを確認してください。固定コード `000000` による認証の回避機能はありません。 +4. パスワード変更が必要と設定されたアカウントは、新しいパスワードを設定してから利用を続けます。 ## テストデータの投入(ローカル専用) -ログイン後、次の URL を呼び出します: - -``` -GET http://localhost:8080/data/test -``` +セットアップ完了後に `GET /data/test` を呼び出すと、既存のプロジェクト・スキャン・ライブラリのデータを削除し、`DemoImportCatalog` にある公開リポジトリの実際の Quick Import ジョブをキューに登録します。プロジェクト画面に移動した後も非同期処理が続き、ネットワーク接続が必要です。固定のテストアカウントや決められた脆弱性一覧は作成しません。 -このエンドポイントは `local` プロファイルでのみ利用可能で、次を行います: +これらの開発用エンドポイントは local ソースセットの `local`/`test` プロファイルで提供され、本番 JAR には含まれません。`local` では `/data/**` に認証なしでアクセスできるため、このデータ初期化機能は隔離された開発環境でのみ使用してください。 -* 既存のすべてのプロジェクト・スキャン・ライブラリ・CVE を削除します。 -* Maven・npm など複数エコシステムにまたがる複数プロジェクト、様々な深刻度の数十件の CVE、混在するライセンス状態、トレンド可視化用の複数の過去スキャンなど、リアルなデータセットでデータベースを再構築します。 +`GET /data/test-api-key` は最初のプロジェクトに紐づく API キーを発行します。プロジェクトがない場合は 404 を返すため、デモのインポートでプロジェクトが作成されるまで待ってください。 -テスト用の API キーも次で取得できます: +## UI/アクセシビリティ テストハーネス(開発者専用) -``` -GET http://localhost:8080/data/test-api-key -``` +`./gradlew uiTest` は実際のアプリケーションに対して既存の Playwright ブラウザテストを実行します。アクセシビリティテストは指定ページで axe-core を実行し、ほかのテストは画面操作やリクエストの流れを確認します。必要に応じて Chromium をダウンロードし、`test`/`check` とは別に実行します。 ---- +JUnit HTML レポートは `build/reports/tests/uiTest/`、axe レポートは `build/reports/axe/` に保存されます。閉域網ではブラウザとビルド依存関係を事前に用意し、必要に応じて `PLAYWRIGHT_BROWSERS_PATH` を設定してください。ブラウザの導入だけではビルド依存関係は揃いません。 ## アクセス制御(推奨読み物) diff --git a/docs/ja/Home.md b/docs/ja/Home.md index 60bb3d9d..9304fe88 100644 --- a/docs/ja/Home.md +++ b/docs/ja/Home.md @@ -1,5 +1,7 @@ # OsWL ドキュメント +[1.0.5.1 の変更点](Whats-New-v1.0.5.1.md) + **OsWL**(Open-source Software Watchlist)のドキュメントハブへようこそ。 OsWL は、単一のマイクロサービスから製品ポートフォリオ全体まで、すべての OSS 依存関係の CVE 脆弱性とライセンスコンプライアンスをチームが一箇所で追跡できる社内向け **SCA(ソフトウェア構成分析)** プラットフォームです。 @@ -30,7 +32,7 @@ OsWL は、単一のマイクロサービスから製品ポートフォリオ全 | [API リファレンス](API-Reference.md) | REST エンドポイントの全一覧 | | [用語集](Glossary.md) | OsWL のすべての用語の定義 | -製品 UI 自体は日本語に完全対応しています(上部バーの言語切り替え、または `?lang=ja`)。他言語のドキュメントは[英語](../Home.md)と[韓国語](../ko/Home.md)でも利用できます。 +製品 UI 自体は日本語に完全対応しています(上部バーの言語切り替え、または `?lang=ja`)。他言語のドキュメントは[英語](../en/Home.md)と[韓国語](../ko/Home.md)でも利用できます。 --- diff --git a/docs/ja/Production-Deployment-Checklist.md b/docs/ja/Production-Deployment-Checklist.md index 2f5ea61d..e156c608 100644 --- a/docs/ja/Production-Deployment-Checklist.md +++ b/docs/ja/Production-Deployment-Checklist.md @@ -19,32 +19,32 @@ OsWL をインターネットに公開する前に、この 1 ページのチェ | `DB_PASSWORD` | データベースパスワード | | `OSWL_ENCRYPTION_KEY` | インスタンス暗号化キー(`openssl rand -base64 32` で生成) | -`.env.prod.example` を `.env.prod` にコピーし、すべての値を埋めてください。`application-prod.yaml` には DB や暗号化の**既定値はありません**。 +`deploy/docker/.env.prod.example` を `.env.prod` にコピーし、すべての値を埋めてください。`application-prod.yaml` には DB や暗号化の**既定値はありません**。 -起動時、不足している変数やその他の設定問題は、(アプリケーションの準備が完了した後)ログ内の**1 つの `OSWL STARTUP WARNINGS` ブロック**にまとめて出力されます。**`prod`** で `OSWL_ENCRYPTION_KEY` が不足している場合、アプリケーションは**起動に失敗**します — 本番公開前に固定値を設定してください(`local` プロファイルは開発専用として一時的なキーを使うことがあります)。 +起動後の設定警告は `OSWL STARTUP WARNINGS` ログブロックにまとめて出力されます。本番用暗号化キーの不足や DB 設定の不備などにより、このブロックが表示される前に起動が失敗する場合があります。本番環境では固定の `OSWL_ENCRYPTION_KEY` を維持してください。`local` YAML の固定の代替キーは開発専用であり、本番では使用できません。 ## 3. ネットワークバインディング -| チェック | 対応 | -|-------|--------| -| 既定のバインド | `SERVER_ADDRESS=127.0.0.1`(`application-prod.yaml` 参照) | -| 公開アクセス | **nginx / Caddy / Traefik**(またはクラウド LB)を前段に置き、TLS はそこで終端させる | -| 直接 `0.0.0.0` | JVM の HTTP スタックを公開するリスクを受け入れる場合のみ。リスクとファイアウォールを文書化すること | - -`docker-compose.prod.yml` は既定で **`127.0.0.1:8080:8080`** にマッピングされているため、コンテナはすべてのインターフェースには公開されません。 +ホストで JVM を直接実行する場合、`application-prod.yaml` の既定値は `SERVER_ADDRESS=127.0.0.1` で、同じホストのリバースプロキシから接続できます。**Docker Compose ではコンテナ内を `SERVER_ADDRESS=0.0.0.0`** にして、Docker からアプリケーションへ転送できるようにします。ホスト側の公開範囲は別の設定です。`deploy/docker/compose.prod.yml` はホストの **`127.0.0.1:8080:8080`** にのみポートを割り当てます。本番用サンプルはこのコンテナ設定を使います。更新時は既存の `.env.prod` も確認してください。 -プロキシが HSTS とセキュアクッキーのために `X-Forwarded-Proto` を送信する場合は `server.forward-headers-strategy=framework`(`application.yaml` の既定値)を設定してください。 +リバースプロキシで TLS を終端してください。付属のホストループバックへのポート割り当てを使う場合、プロキシは Docker ホストで実行します。プロキシもコンテナで動かす場合は、共有 Docker ネットワーク上のサービスアドレスに接続します。転送ヘッダーは信頼できるプロキシからのものだけを受け入れてください。 ## 4. Docker Compose(本番) +リポジトリのルートで実行します。既存の `.env.prod` の値は保持し、新規インストール時だけテンプレートをコピーします。両 Compose ファイルの既定プロジェクト名は `oswl` です。既存環境が別の名前を使用していた場合は `-p YOUR_EXISTING_PROJECT` または `COMPOSE_PROJECT_NAME` で同じ名前を維持し、既存ボリュームに接続してください。[デプロイファイルの案内](../../deploy/README.md)を参照してください。 + ```bash -cp .env.prod.example .env.prod +cp deploy/docker/.env.prod.example .env.prod # DB_*, OSWL_ENCRYPTION_KEY, SMTP_* を編集 -docker compose -f docker-compose.prod.yml up -d --build +docker compose --env-file .env.prod -f deploy/docker/compose.prod.yml up -d --build ``` +Compose は `--env-file` で `.env.prod` を読み込みます。`java -jar` や `bootRun` で直接実行する場合、このファイルは自動で読み込まれません。環境変数を設定するかサービス管理ツールに登録してください。本番環境の初回起動前に DB スキーマを準備します(§9 参照)。 + ログを確認: 変数不足の警告がない、PostgreSQL に接続済み、H2 や Swagger の URL がない。 +`deploy/docker/compose.prod.yml` は、コンテナ自体の stdout/stderr(docker の `json-file` ドライバ、100MB × 10 ファイル)と、アプリ自身のローテーションファイルログ(`oswl-logs-prod` ボリュームにマウント)の両方に上限を設けています — 後者は §5 を参照。 + ## 5. ロギングと可観測性 | チェック | 対応 | @@ -55,6 +55,17 @@ docker compose -f docker-compose.prod.yml up -d --build | メトリクス収集 | Prometheus を `/actuator/prometheus` に向ける — スクレイパーは管理者資格情報を提示する必要あり | | Actuator 認証 | **SYSTEM_ADMIN** セッションが必要(公開ではない) | +### ログローテーションとリクエスト相関 + +`local`/`test` はコンソール出力のみです。`prod` では `logback-spring.xml` がローテーションするファイルログを追加で書き出します: + +| 変数 | 既定値 | 用途 | +|------|--------|------| +| `OSWL_LOG_DIR` | `./logs`(docker では `/var/log/oswl`、§4 参照) | `oswl.log` の保存先。100MB または日次でローテーションし、最大 30 ファイル保持、合計 5GB を上限とします。 | +| `OSWL_LOG_JSON` | `false` | `true` にすると、ファイル(コンソールではない)が 1 行 1 JSON オブジェクトの形式に切り替わります — ログシッパーをこれに向けて SIEM に取り込んでください。 | + +すべてのリクエストには `requestId` が付与され(レスポンスヘッダー `X-Request-Id` としても返されます)、認証済みであれば `userId` も付与されます — どちらも MDC 経由でそのリクエストのすべてのログ行に現れるため(プレーンテキストモードでは `[req=...] [user=...]`、JSON モードではトップレベルのフィールド)、あるリクエストを指すサポートチケットを、タイムスタンプで grep することなくログ全体から追跡できます。 + ## 6. 本番で有効なセキュリティ機能 - Springdoc / Swagger UI: **無効** @@ -75,7 +86,7 @@ docker compose -f docker-compose.prod.yml up -d --build | 変数 | 既定値 | 有効化した場合の効果 | |---|---|---| -| `OSWL_FLYWAY_ENABLED` | `false` | `baseline-on-migrate` によるバージョン管理されたマイグレーション。先に完全なベースラインを生成すること | +| `OSWL_FLYWAY_ENABLED` | `false` | 付属の V1 と後続マイグレーションを実行。既存スキーマは事前確認 | | `OSWL_AIRGAPPED_ENABLED` | `false` | すべての脆弱性/脅威インテリジェンス参照がインポート済みのオフラインスナップショットから提供される。外向きの HTTP なし | | `OSWL_GATE_*` | [v1.0.4 の新機能](Whats-New-v1.0.4.md)を参照 | `POST /api/scan/gate` の既定閾値 | @@ -98,6 +109,8 @@ docker compose -f docker-compose.prod.yml up -d --build ### 7.1 閉域網 / オフライン スナップショット (v1.0.4) +このモードは対応する脆弱性・脅威情報フィードをスナップショットに切り替える機能であり、ネットワークファイアウォールではありません。閉域環境では VCS、SMTP、Webhook、外部 AI プロバイダーも別途設定してください。 + `OSWL_AIRGAPPED_ENABLED=true` に設定すると、脆弱性・脅威インテリジェンスの参照(OSV、deps.dev、EPSS、CISA KEV)がライブ外部 API ではなく、インポート済みのオフライン スナップショットから提供されます。エンリッチメント用の外向き HTTP は試行されません。 | 手順 | 対応 | @@ -105,7 +118,7 @@ docker compose -f docker-compose.prod.yml up -d --build | 1. バンドルの作成 | インターネットに接続されたマシンで `oswl-vdb` ビルダーを実行します。ラッパー スクリプト: `scripts/oswl-vdb/oswl-vdb.sh`(Linux/macOS)または `scripts/oswl-vdb/oswl-vdb.ps1`(Windows)。いずれも `./gradlew vdbBuild --args="..."` を呼び出します。 | | 2. バンドルの対象選定 | 対象インスタンスが実際にスキャンしているコンポーネントを `GET /api/admin/snapshot/wanted-list`(SYSTEM_ADMIN)でエクスポートし、`build --wanted wanted-list.jsonl` に渡します。ビルダーは完全なアップストリーム ミラーではなく、実際に使用するコンポーネントのみを取得します。 | | 3. バンドルのインポート | `POST /api/admin/snapshot/import?mode=replace|merge`(multipart `.zip`)。大きなバンドルでは `OSWL_AIRGAPPED_IMPORT_DIR` ホワイトリスト ディレクトリを設定したうえで、`POST /api/admin/snapshot/import-from-path` に `{"path":"bundle.zip","mode":"merge"}` を送信します。 | -| 4. モデルの配置(内蔵 AI を使用する場合) | 閉域網ホストは自動ダウンロードを無効にします。`.gguf` ファイルを `embedded-ai/` に直接配置するか、内部ミラーを運用してください(§8 参照)。 | +| 4. モデルの配置(内蔵 AI を使う場合) | オフラインホストの起動前に `embedded-ai/llama/` に実行ファイル、`embedded-ai/model/<系列>/` に検証済みモデルを配置します。内部ミラーを設定してもエアギャップモードではダウンロードできません。§8 参照。 | `oswl-vdb build` オプション(`VdbBuilderCli` 参照): - `--sources osv,epss,kev,depsdev`(既定値はすべて)。 @@ -118,41 +131,17 @@ docker compose -f docker-compose.prod.yml up -d --build - `merge` は `(source, entry_key)` 単位で upsert し、`"_deleted":true` 行は削除として扱います。 - v2 バンドルは `meta.json` に記録されたファイル単位の SHA-256 チェックサムを検証し、不一致の場合はバンドル全体を拒否し、既存ストアは変更しません。 -定義の鮮度(E7): `OSWL_AIRGAPPED_STALENESS_WARN_DAYS`(既定値 `7`)と `OSWL_AIRGAPPED_STALENESS_CRITICAL_DAYS`(既定値 `30`)は、インポートされたスナップショットのソース別 `sourceAsOf` 日付のうち最も古い値を基準に管理 UI バッジを決定します。 +定義の鮮度: `OSWL_AIRGAPPED_STALENESS_WARN_DAYS`(既定値 `7`)と `OSWL_AIRGAPPED_STALENESS_CRITICAL_DAYS`(既定値 `30`)は、インポートされたスナップショットのソース別 `sourceAsOf` 日付のうち最も古い値を基準に管理 UI バッジを決定します。 バンドルが 50MB を超える場合は、`OSWL_MULTIPART_MAX_FILE_SIZE` / `OSWL_MULTIPART_MAX_REQUEST_SIZE`(既定値はそれぞれ `50MB`)を調整する必要があるかもしれません。 -## 8. 内蔵 AI モデル(任意、オンプレミス向け) - -クラウドプロバイダーの代わりに、またはそれに加えて**内蔵 AI**(設定 → AI → ローカル)を使う予定がある場合のみ関係します。 +## 8. 内蔵AI(任意、CPU専用) -| チェック | 対応 | -|-------|--------| -| サーバーバイナリ | [llama.cpp のリリース](https://github.com/ggml-org/llama.cpp/releases)からお使いのプラットフォーム用の `llama-server(.exe)` をダウンロードし、`embedded-ai/`(またはその配下の `bin/`、あるいは `PATH` 上の任意の場所)に配置してください — これが唯一の手動手順です | -| モデル | 何もする必要はありません — 新規インストールで**開始**をクリックすると、Apache-2.0 ライセンスの Qwen3-1.7B モデルが自動でダウンロードされます(約 1.2 GB、SHA256 を検証、UI に進捗表示) | -| 閉域網(エアギャップ)ホスト | 自動ダウンロードには一度だけ外向きのインターネットアクセスが必要です。それがない場合は、開始をクリックする前に自分で入手した `.gguf` ファイルを `embedded-ai/` に配置してください | -| カスタムモデル | OsWL が同梱・自動取得するのは Qwen3-1.7B のみです。それ以外の `.gguf`(サイズやライセンスが異なるもの)を使いたい場合は、そのモデル自体のライセンスを確認したうえで自分で `embedded-ai/` に配置してください。[内蔵 AI](Embedded-AI.md)を参照 | -| ディレクトリ | 既定では JVM が起動する作業ディレクトリからの相対パス `./embedded-ai` — 別のパスにするには `OSWL_EMBEDDED_AI_DIR` を設定 | +既定は **Qwen3.5-2B Q4_K_M**、選択肢は **Gemma 4 E2B Q4_K_M**です。OsWL・PostgreSQLの同居と断続的使用で、Qwen想定最小2 vCPU / RAM 8 GB、Gemma推奨4 vCPU / RAM 16 GBを目安にします。性能保証ではなく、CPUクレジットと同時スキャンの検証が必要です。 -Gradle タスクや別のスクリプトは関与しません — ダウンロードは開始が初めてクリックされたときにアプリケーション自体の中で実行されるため、単純な `java -jar app.jar` によるデプロイでも動作します。 +実行ファイルは `embedded-ai/llama/`、重みは `model/Qwen/` と `model/Gemma/` に配置します。自動取得はQwenのみです。GPUレイヤー0、スレッド1、生成スロット1、コンテキスト8192が既定です。固定URL・ハッシュ・サイズを組として維持します。旧 `models-v1` は新モデルではありません。DockerはルートのマウントとLinuxランタイムが必要です。 -### 内蔵 AI チューニング (B1 / v1.0.4) - -既定値は本番環境で安全です。測定された理由がある場合のみ上書きしてください。 - -| 変数 | 既定値 | 用途 | -|---|---|---| -| `OSWL_EMBEDDED_AI_CONTEXT` | `8192` | 総コンテキスト サイズ(`-c`)。`--parallel` 使用時はスロット間で分割され、スロット コンテキストが 2048 を下回ると警告ログが出力されます。 | -| `OSWL_EMBEDDED_AI_GPU_LAYERS` | `-1` | `-ngl`: `-1` はビルドがサポートする限りオフロード(`999` を渡す)、`0` は CPU のみ、正の値は明示的なレイヤー数 | -| `OSWL_EMBEDDED_AI_THREADS` | `0` | `-t`: `0` は llama.cpp の自動検出、正の値はスレッド数を固定 | -| `OSWL_EMBEDDED_AI_PARALLEL` | `4` | `--parallel N --cont-batching` を有効化;1 より大きいと同時 AI 呼び出しが直列化されません | -| `OSWL_EMBEDDED_AI_FLASH_ATTN` | `true` | `-fa`(flash attention)を追加 | -| `OSWL_EMBEDDED_AI_CACHE_REUSE` | `256` | `--cache-reuse` トークン数;`0` 以下は無効化 | -| `OSWL_EMBEDDED_AI_EXTRA_ARGS` | (空) | `llama-server` CLI 引数を空白区切りでそのまま追加(管理者専用設定、リクエスト入力ではない) | -| `OSWL_EMBEDDED_AI_STARTUP_TIMEOUT_SEC` | `120` | `/health` 応答を待つ秒数。時間内に失敗すると CPU のみでの再試行、または次のモデル候補に進みます。 | -| `OSWL_EMBEDDED_DEFAULT_MODEL_URL` / `SHA256` / `SIZE_BYTES` | 上流の Hugging Face `ggml-org/Qwen3-1.7B-GGUF` | 既定 Qwen3-1.7B ダウンロード用のマッチング セット;自己ホスティング ミラーを使用する場合は 3 つすべてを上書き(バイト単位で同一の再ホストなら URL のみ変更) | -| `OSWL_EMBEDDED_FALLBACK_MODEL_URL` | Hugging Face | 既定 URL が失敗した場合に 1 回再試行;primary と同じか空にすると再試行を無効化 | -| `OSWL_EMBEDDED_AUTO_DOWNLOAD` | `true` | 起動時に既定モデルをバックグラウンドでプリフェッチ(ダウンロードのみで、サイドカーは起動しません)。**`OSWL_AIRGAPPED_ENABLED=true` の場合は無視されます**。 | +詳細要件・構成・チェックサム・ミラー/閉域導入・CPU調整・モデルと言語選択・再配布告知は [内蔵AI](Embedded-AI.md) を参照してください。 ## 9. データベーススキーマ(アップグレード) @@ -176,7 +165,7 @@ OsWL は `prod` で **Hibernate `ddl-auto=validate`** を使用します — ア ### Flyway(v1.0.4、オプトイン) -`OSWL_FLYWAY_ENABLED=true` を設定すると、手動実行スクリプトの代わりに Flyway がスキーマを管理します。`baseline-on-migrate` が有効なため、データが入った既存の DB も拒否されずベースライン処理されます — ただし、有効化する**前に**、現在のスキーマと一致する完全なベースラインマイグレーションを生成してください。既定の `false` のままなら何も変わりません。 +`OSWL_FLYWAY_ENABLED=true` で `src/main/resources/db/migration/` のバージョン別マイグレーションを有効にします。既定値は `false` です。リポジトリには `V1__baseline.sql` と後続のマイグレーションが含まれています。空の PostgreSQL DB では V1 から順に実行し、その後 Hibernate がスキーマを検証します。Flyway 履歴のない既存 DB では、`baseline-on-migrate` が V1 を実行せずにバージョン 1 を記録し、V2 以降を実行します。有効化前にバックアップを取得し、既存スキーマとマイグレーションを比較してください。手動適用済みの変更と後続マイグレーションが競合する場合があります。共有 DB に適用済みのファイルを再生成・変更しないでください。SQL を手動管理する場合は、対象バージョンに必要な変更をすべて順番に適用します。以下の一部の旧スクリプトだけでは新規インストール用のスキーマを構成できません。 ### v1.0.4 のカラム @@ -193,6 +182,10 @@ ALTER TABLE scan_results ADD COLUMN IF NOT EXISTS ai_locale varchar(16); (Flyway ユーザー: 新しい 3 つの `libraries` カラムは `V3__component_metadata.sql` でカバーされています。[データベーススキーマ](Database-Schema.md)を参照。) +### v1.0.5: Spring Session / ShedLock テーブル(オプトイン) + +**マルチインスタンス**構成に移行する場合にのみ必要です(§12 参照)。`spring_session`、`spring_session_attributes`、`shedlock` を追加します。Flyway ユーザーは `db/migration/V10__spring_session_and_shedlock.sql` から、手動スクリプトのユーザーは `db/spring_session_and_shedlock.sql` を実行してください。シングルインスタンス構成であれば完全にスキップできます — `OSWL_SESSION_STORE_TYPE=jdbc` や `OSWL_SCHEDULER_LOCK_ENABLED=true` を設定するまで、これらのテーブルは誰にも参照されません。 + ## 10. デプロイ後のスモークテスト 1. HTTPS リバースプロキシ経由でのみ UI を開く。 @@ -204,10 +197,39 @@ ALTER TABLE scan_results ADD COLUMN IF NOT EXISTS ai_locale varchar(16); ## 11. 運用 -- PostgreSQL をバックアップし、`OSWL_ENCRYPTION_KEY` をシークレットマネージャーに保管する(紛失すると VCS トークンが読めなくなります)。 +- PostgreSQL をバックアップし、`OSWL_ENCRYPTION_KEY` をシークレットマネージャーに保管する(紛失すると VCS トークンが読めなくなります) — 手順全体と復旧リハーサル用スクリプトは [バックアップと復旧](Backup-And-Restore.md) を参照。 - 侵害があった場合は API キーと SMTP 認証情報をローテーションする。 - `local` として実行されるべきでないイメージから `SPRING_PROFILES_ACTIVE` を除外しておく。 +## 12. マルチインスタンス配備(水平スケーリング / HA) + +OsWL は既定では**シングルインスタンス**として動作します — インメモリの HTTP セッションと、インスタンスごとの `@Scheduled` ジョブです。コンテナ/プロセスが 1 つならこれで問題ありませんが、ロードバランサーの背後に 2 つ目のインスタンスを置くと崩れます。ユーザーのセッションはログインしたインスタンスに固定され、夜間モニタリング/猶予期限切れ/ごみ箱クリーンアップの各ジョブは、クラスタ全体で 1 回ではなく**インスタンスごと**に実行されてしまいます。この節は、同じ PostgreSQL データベースに対して **2 台以上のインスタンス**を配備する場合にのみ関係します。 + +**1. まずスキーマを適用してください。** 以下の機能を有効にしたインスタンスを起動する前に、`spring_session`、`spring_session_attributes`、`shedlock` が存在することを確認してください(§9「v1.0.5: Spring Session / ShedLock テーブル」)。テーブルが存在しない状態で以下の環境変数を先に展開すると、最初のリクエスト/ジョブ実行時に全インスタンスがクラッシュします。 + +**2. 環境変数:** + +| 変数 | 目的 | +|------|------| +| `OSWL_SESSION_STORE_TYPE=jdbc` | HTTP セッションを Tomcat のインメモリ保存から PostgreSQL(`spring_session`)へ移します。ログイン状態とシングルセッション強制(`maximumSessions(1)`)が、インスタンス単位ではなくクラスタ全体で機能するようになります。 | +| `OSWL_SCHEDULER_LOCK_ENABLED=true` | 3 つのスケジュールジョブ(`ContinuousMonitoringScheduler`、`DeferExpiryScheduler`、`TrashCleanupScheduler`)を、`shedlock` テーブルを利用したクラスタ全体のロック(ShedLock)で包み、サイクルごとに 1 インスタンスだけが実行するようにします。 | + +実際にマルチインスタンス配備する場合は両方を同時に設定してください — 片方だけ有効にすると、もう片方の穴がそのまま残ります。 + +**3. ロードバランサー:** nginx や ALB など一般的な L7 LB で構いません — `OSWL_SESSION_STORE_TYPE=jdbc` を設定すればセッション状態はインスタンスのメモリではなく PostgreSQL に一元化されるため、**スティッキーセッションは不要です。** + +**4. ただしスキャン進捗のポーリングだけは例外です。** Quick Import/スキャンのエンリッチメント中に表示されるライブ進捗(`EnrichmentProgressHolder`、`ScanStatusEmitterRegistry`)は、依然としてインスタンスごとのインメモリ状態であり、DB には保存されません。推奨策: ロードバランサーのルーティングを**アクティブなスキャンが実行されている間だけ**スティッキーにする(例: セッション基準のクッキーアフィニティ)ことで、進捗ポーリングのリクエストが実際にそのスキャンを実行しているインスタンスに戻るようにしてください。スキャン進捗を DB に移し UI を純粋なポーリング方式に切り替える代替案はより大きな変更になるため別途追跡しており、現時点ではスティッキールーティングが実用的な既定策です。 + +**5. ローリングデプロイの手順:** + 1. まず未適用の DB マイグレーションを適用します(旧バージョンのアプリコードが新しいスキーマに耐えられる必要があるため — `db/migration` はこの方針に従い追加のみの変更にしています)。 + 2. インスタンスは一斉にではなく 1 台ずつ入れ替え、新しいインスタンスの readiness チェックが通るのを待ってから次に進みます。 + 3. `OSWL_SESSION_STORE_TYPE=jdbc` を設定していれば、セッションはインスタンスのメモリではなく PostgreSQL にあるため、ローリング再起動でユーザーがログアウトされることはありません。 + +**6. 動作確認:** + - インスタンス A にログインした後、LB がインスタンス B にルーティングする後続リクエストを送っても、認証状態が維持される(`/login` にリダイレクトされない)ことを確認します。 + - インスタンス A を停止しても、セッション(およびシングルセッション強制)がインスタンス B から引き続き機能することを確認します。 + - 夜間ジョブの実行後、両方のインスタンスのログを確認し、そのジョブのログ行が両方ではなく正確に 1 つのインスタンスにのみ現れることを確認します。 + --- -**ローカル開発:** `SPRING_PROFILES_ACTIVE=local`、`.env.example` を `.env` にコピー、`OSWL_ENCRYPTION_KEY` を設定、`./gradlew bootRun` を実行。H2 ファイル DB、H2 コンソール、Swagger、`GET /data/test` はこのプロファイルでのみ利用可能です。 +**ローカル開発:** `./gradlew bootRun`(PowerShell: `.\gradlew.bat bootRun`)で `local` プロファイルと H2 を使用します。ローカル YAML には開発専用の暗号化キーがあります。必要に応じてプロセスの環境変数で変更してください。`.env` は起動ツールが明示的に読み込む場合のみ適用されます。 diff --git a/docs/ja/Scan-Api-Security.md b/docs/ja/Scan-Api-Security.md index 66dd7843..c253d702 100644 --- a/docs/ja/Scan-Api-Security.md +++ b/docs/ja/Scan-Api-Security.md @@ -30,7 +30,7 @@ |------|------------------| | 1 | `Authorization: Bearer …` の**プロジェクト API キー** | | 2 | JSON 本文内の**提出者**のメールアドレスとパスワード | -| 3 | 提出者が **`SCAN_SUBMIT`**(ロールテンプレートの権限)を持ち、そのプロジェクトの **`project_members`** に登録されていること | +| 3 | 送信者が **`SCAN_SUBMIT`** 権限を持ち、プロジェクトへの直接所属またはチームの権限付与によりアクセスできることを確認します。システム管理者には権限・アクセス検査の例外が適用されます。 | ロールテンプレートとプロジェクトメンバーシップの違いについては[権限レイヤー](Authorization-Layers.md)を参照してください。 diff --git a/docs/ja/Security-Center.md b/docs/ja/Security-Center.md index e5d1734a..e43e9f86 100644 --- a/docs/ja/Security-Center.md +++ b/docs/ja/Security-Center.md @@ -89,7 +89,7 @@ OsWL は標準的な CVSS 3.x の範囲を使って CVSS ベーススコアを | SBOM(CycloneDX) | `GET /api/projects/{projectId}/sbom` | CycloneDX 1.6 JSON | | VEX | `GET /api/projects/{projectId}/vex` | CycloneDX VEX — トリアージ判断を反映 | | SARIF | `GET /api/projects/{projectId}/sarif` | SARIF 2.1.0、GitHub コードスキャンにアップロード可能 | -| コンプライアンスレポート | `GET /security-center/compliance-report` | 印刷用 HTML(*印刷 → PDF として保存*を使用) | +| コンプライアンスレポート | `GET /projects/{projectId}/security-center/compliance-report` | 印刷用 HTML(*印刷 → PDF として保存*を使用) | | CVE/ライセンス CSV | — | 現在フィルタリングされているビュー | 一括操作には**アップグレード PR を作成**も含まれます。選択したすべてのコンポーネントを修正バージョンに引き上げる単一のプルリクエストを作成します。 diff --git a/docs/ja/User-Guide.md b/docs/ja/User-Guide.md index 7585eb66..afee624b 100644 --- a/docs/ja/User-Guide.md +++ b/docs/ja/User-Guide.md @@ -1,5 +1,7 @@ # ユーザーガイド +[1.0.5.1 の変更点](Whats-New-v1.0.5.1.md) + このページでは OsWL Web ダッシュボードの日常的な使い方を説明します。 表示されるのは自分が**メンバー**であるプロジェクトのみです(**システム管理者**を除く)。プロジェクト内でできることは **ロールテンプレート**(Admin / Developer / Viewer)によって決まります。[権限レイヤー](Authorization-Layers.md)を参照してください。 @@ -96,7 +98,7 @@ OsWL は入力のコンテキストハッシュを使って AI 要約をキャ ## 内蔵 AI モデル -OsWL の内蔵ローカル AI は llama.cpp サイドカーを実行し、既定モデルとして **Qwen3 1.7B** GGUF を使用します。初回起動時、OsWL は既定モデルをバックグラウンドで先読みダウンロードするため(`oswl.ai.embedded.auto-download-on-boot` で設定変更可能、閉域網(エアギャップ)モードでは無効)、設定画面で内蔵 AI を有効化する際の待ち時間が短縮されます。既定のダウンロードは OsWL 自身の GitHub Release アセットを使用し、失敗した場合は元の Hugging Face リポジトリへフォールバックします。すべてのダウンロードは SHA-256 チェックサムで検証されます。設定した embedded-ai ディレクトリに、互換性のある任意の `.gguf` ファイルを直接配置することもできます。インストールとトラブルシューティングについては [Embedded AI](Embedded-AI.md) を参照してください。 +内蔵 AI は別途インストールした llama.cpp を使用し、既定のダウンロードモデルは **Qwen3.5-2B Q4_K_M** です。**Gemma 4 E2B** は任意で手動インストールします。実行ファイルは `embedded-ai/llama/`、モデルは `embedded-ai/model/<系列>/` に配置します。起動時の事前取得はダウンロードのみで、サーバーの起動や LOCAL の有効化は行いません。既定の取得先は Hugging Face の固定リビジョンで、SHA-256 とサイズを検証します。既定の代替ミラーはなく、エアギャップモードではダウンロードしません。モデルを変更する場合は設定で停止し、モデルを選択・保存してから再度開始します。最新の要件と設定は[内蔵 AI](Embedded-AI.md)を参照してください。 --- diff --git a/docs/ja/Whats-New-v1.0.4.md b/docs/ja/Whats-New-v1.0.4.md index 7ecd7d5d..42ad55d5 100644 --- a/docs/ja/Whats-New-v1.0.4.md +++ b/docs/ja/Whats-New-v1.0.4.md @@ -64,7 +64,7 @@ VEX は検出結果そのものではなく、**トリアージの判断**を伝 ### コンプライアンスレポートパック -`GET /security-center/compliance-report` は印刷用レポートを描画します。コンポーネント一覧、ライセンス義務、NOTICE 文、深刻度別の未対応項目が含まれます。ブラウザの *印刷 → PDF として保存* を使ってください。プレビュー表示時に印刷ダイアログが自動で開かないため、出力前に内容を確認できます。 +`GET /projects/{projectId}/security-center/compliance-report` は印刷用レポートを描画します。コンポーネント一覧、ライセンス義務、NOTICE 文、深刻度別の未対応項目が含まれます。ブラウザの *印刷 → PDF として保存* を使ってください。プレビュー表示時に印刷ダイアログが自動で開かないため、出力前に内容を確認できます。 --- @@ -88,7 +88,7 @@ VEX は検出結果そのものではなく、**トリアージの判断**を伝 リクエストに GitHub の対象を指定すると、判定結果が **Check Run** と PR コメントとしても投稿されます。 -リクエスト形式の詳細とパイプライン例は [CLI 連携](../CLI-Integration.md) を参照してください。 +リクエスト形式の詳細とパイプライン例は [CLI 連携](CLI-Integration.md) を参照してください。 --- @@ -154,7 +154,7 @@ VEX は検出結果そのものではなく、**トリアージの判断**を伝 | バンドルの書き出し | `GET /api/admin/snapshot/export` | | バンドルの状態 | `GET /api/admin/snapshot` | -インターネットに接続された端末で書き出し、バンドルを持ち込んで取り込みます。スナップショットに無いコンポーネントは「脆弱性なし」ではなく**データなし**として扱われます。[組み込み AI](../Embedded-AI.md) サイドカーと組み合わせれば、ネットワークを切断した状態でもスキャン・トリアージ・AI 分析まで動作します。 +インターネットに接続された端末で書き出し、バンドルを持ち込んで取り込みます。スナップショットに無いコンポーネントは「脆弱性なし」ではなく**データなし**として扱われます。[組み込み AI](Embedded-AI.md) サイドカーと組み合わせれば、ネットワークを切断した状態でもスキャン・トリアージ・AI 分析まで動作します。 --- @@ -164,7 +164,7 @@ VEX は検出結果そのものではなく、**トリアージの判断**を伝 |---|---| | **Prometheus メトリクス** | `/actuator/prometheus` — micrometer で公開、管理者権限が必要 | | **ヘルス/情報** | `/actuator/health`、`/actuator/info` | -| **Flyway マイグレーション** | `OSWL_FLYWAY_ENABLED=true` でオプトイン(`baseline-on-migrate`)。既定は `ddl-auto` のまま — [DB スキーマ](../Database-Schema.md) 参照 | +| **Flyway マイグレーション** | `OSWL_FLYWAY_ENABLED=true` でオプトイン(`baseline-on-migrate`)。既定は `ddl-auto` のまま — [DB スキーマ](Database-Schema.md) 参照 | | **OIDC シングルサインオン** | `application-prod.yaml` の `spring.security.oauth2.client` ブロックのコメントを解除し、`OSWL_OIDC_CLIENT_ID` / `OSWL_OIDC_CLIENT_SECRET` / `OSWL_OIDC_ISSUER_URI` を設定(Okta、Entra ID など任意の OIDC プロバイダー)。プロバイダーが登録されている場合のみログイン画面に SSO ボタンが表示されます。 | | **監査ログの SIEM エクスポート** | `GET /api/admin/audit-logs/export?format=jsonl\|cef` — 既存の監査ログフィルターをそのまま使用し、`AUDIT_LOG_EXPORT` 権限が必要です。エクスポート自体も監査記録されます。 | diff --git a/docs/ja/Whats-New-v1.0.5.1.md b/docs/ja/Whats-New-v1.0.5.1.md new file mode 100644 index 00000000..1b9f3998 --- /dev/null +++ b/docs/ja/Whats-New-v1.0.5.1.md @@ -0,0 +1,24 @@ +# OsWL 1.0.5.1 の変更点 + +依存関係の情報、スキャンの安定性、ライトモードの画面を改善しました。 + +## 主な変更 + +- ログイン・検索・設定はライトモードを使用します。未保存の変更とエラーを区別し、AIの入力エラーを該当欄の下に表示します。保存完了の表示は保存ボタンの横に残ります。 +- 応答の多様性は0〜2(既定値0.15)、最大トークン数は256〜8192の整数(既定値1200)です。空欄では既定値を使います。プロバイダーや分析処理ごとの制限が優先される場合があります。Anthropicにはtemperature設定を適用しません。 +- 削除したプロジェクトをごみ箱で確認し、保持期間内に復元できます。設定からCLI APIキーを削除できます。削除したキーは使用できなくなります。 +- CVEの横からNVDを開けます。対応する数値バージョンの区間から修正バージョンを選択し、Mavenの親POMのプロパティと対応するGradleバージョン制約を解決します。 +- 診断で失敗・要確認・対象外を区別し、ディスク容量を読みやすく表示します。アーカイブ対象がなければ理由を表示し、対象があればJSONをダウンロードします。 +- キャッシュの「常に更新」はスキャンでデータを要求するときに適用します。バックグラウンドで継続的に更新する設定ではありません。 + +## アップグレード + +Java 25が必要です。データベースと永続ファイルを先にバックアップし、[デプロイチェックリスト](Production-Deployment-Checklist.md)と[バックアップ・復元](Backup-And-Restore.md)を確認してください。マイグレーションを使用するDBにはV35まで適用してください。JARの置換だけでDBも更新されたとは限りません。 + +該当するGitHub Releaseから`oswl-1.0.5.1.jar`と`SHA256SUMS`を取得してください。構成は[はじめに](Getting-Started.md)を参照してください。ブラウザー通知にはWeb Push/VAPIDの設定とブラウザーの許可が必要です。外部AI・SMTP・VCS連携にもそれぞれ設定が必要です。 + +## 精度と制限 + +アップグレードだけでは保存済みスキャンを書き換えません。パーサーと情報補完の修正を適用するには再スキャンしてください。宣言された依存関係とビルドで選択される依存関係は異なる場合があります。未解決のバージョンは確認が必要です。ライセンスや修正バージョンの情報がないことは、安全性やパッチが存在しないことの証明ではありません。複雑なバージョン範囲、生態系ごとの比較、オンライン・オフラインの結果の一致は、このリリースですべて解決したわけではありません。 + +[分析範囲の説明(英語)](../en/Analysis-Coverage.md)も参照してください。 diff --git a/docs/ko/API-Reference.md b/docs/ko/API-Reference.md index b105331d..da4bb858 100644 --- a/docs/ko/API-Reference.md +++ b/docs/ko/API-Reference.md @@ -1,6 +1,6 @@ # API 레퍼런스 -이 페이지는 OsWL이 노출하는 모든 REST 엔드포인트를 요약합니다. 인터랙티브 스키마는 **`local` 프로파일**의 Swagger UI(`http://localhost:8080/swagger-ui.html`)에서 확인합니다. **`prod`에서는 Swagger가 꺼져 있습니다.** +이 페이지는 OsWL이 노출하는 모든 REST 엔드포인트를 요약합니다. 인터랙티브 스키마는 **`local` 프로필**의 Swagger UI(`http://localhost:8080/swagger-ui.html`)에서 확인합니다. **`prod`에서는 Swagger가 꺼져 있습니다.** OpenAPI 스펙 (JSON): `http://:8080/v3/api-docs` @@ -86,7 +86,7 @@ Authorization: Bearer oswl_ | 메서드 | 경로 | 설명 | |---|---|---| | `POST` | `/api/github/connect` | GitHub PAT 연결 | -| `DELETE` | `/api/github/disconnect` | GitHub 연결 제거 | +| `POST` | `/api/github/disconnect` | GitHub 연결 제거 | | `GET` | `/api/github/status` | 연결 상태 | | `GET` | `/api/github/accounts` | 인증된 계정 목록 | | `GET` | `/api/github/repos` | 접근 가능한 저장소 목록 | @@ -101,7 +101,6 @@ Authorization: Bearer oswl_ | 메서드 | 경로 | 인증 | 설명 | |---|---|---|---| -| `POST` | `/api/auth` | API 키 | API 키 검증 (레거시) | | `GET` | `/api/scan/ping` | API 키 | 연결 및 키 유효성 확인 | | `GET` | `/api/scan/manifest-rules` | API 키 | manifest 수집 규칙 (`/scripts/manifest-rules.json`과 동일) | | `POST` | `/api/scan/parse` | API 키 | manifest zip 파싱 (CLI 1단계) | @@ -119,7 +118,7 @@ Authorization: Bearer oswl_ | `PATCH` | `/projects/{id}/security-center/bulk-status` | `SECURITY_CENTER_UPDATE_STATUS` | CVE 상태 일괄 업데이트 | | `GET` | `/projects/{id}/security-center/export` | `SECURITY_CENTER_EXPORT` | CVE 목록 CSV 다운로드 (`?scanId=`, `?format=csv`) | | `POST` | `/projects/{id}/security-center/batch-pr` | `SECURITY_CENTER_UPDATE_STATUS` | **v1.0.4** — 선택 컴포넌트 일괄 업그레이드 PR 생성 | -| `GET` | `/security-center/compliance-report` | `SECURITY_CENTER_EXPORT` | **v1.0.4** — 인쇄용 컴플라이언스 리포트 | +| `GET` | `/projects/{projectId}/security-center/compliance-report` | `SECURITY_CENTER_EXPORT` | **v1.0.4** — 인쇄용 컴플라이언스 리포트 | ### SBOM / VEX / SARIF (v1.0.4) @@ -251,8 +250,8 @@ Authorization: Bearer oswl_ | 메서드 | 경로 | 설명 | |---|---|---| -| `GET` | `/api/admin/cli-keys` | 전역 CLI 키 목록 | -| `POST` | `/api/admin/cli-keys` | 전역 키 생성 | +| `GET` | `/api/admin/cli-keys` | 프로젝트별 CLI 키 통합 조회 | +| `POST` | `/api/admin/cli-keys` | 프로젝트별 키 발급 (`projectId` 필수) | | `PATCH` | `/api/admin/cli-keys/{keyId}/toggle` | 키 활성화/비활성화 | --- @@ -281,7 +280,7 @@ Authorization: Bearer oswl_ | `GET` | `/api/settings/ai/usage` | `SETTINGS_AI_MANAGE` | AI 사용량 통계 — 오늘 호출 수/토큰/예상 비용, 일일 상한, 최근 7일 집계(일별 집계 테이블에서 조회) | | `GET` | `/api/settings/ai/usage/events` | `SETTINGS_AI_MANAGE` | 최근 AI 호출 이벤트, 최신순 (`?page=`, `?size=`, 기본 크기 `10`). 최근 **100건**만 보존되며(FIFO), 최대 10페이지까지 존재 | | `GET` | `/api/settings/ai/embedded` | `SETTINGS_AI_MANAGE` | 내장 AI 상태 (`running`, `external`, `binaryFound`, `activeModel`, `fallbackUsed`, `lastError`, `availableModels`, `modelsDir`, `baseUrl`, 기본 모델 다운로드 진행 중이면 `downloading`, `downloadedBytes`, `downloadTotalBytes`도 포함) | -| `POST` | `/api/settings/ai/embedded/start?model=` | `SETTINGS_AI_MANAGE` | llama.cpp 사이드카 시작 (모델 파일명 선택 지정; 후보 자동 폴백, 실패 시 400과 사유). `.gguf`가 하나도 없는 신규 설치에서는 대신 Apache 2.0 Qwen3-1.7B 모델을 백그라운드로 다운로드하고 즉시 응답(`downloading: true`) — 진행률은 `GET .../embedded`로 폴링 | +| `POST` | `/api/settings/ai/embedded/start?model=` | `SETTINGS_AI_MANAGE` | llama.cpp 사이드카 시작 (모델 파일명 선택 지정; 후보 자동 폴백, 실패 시 400과 사유). `.gguf`가 하나도 없는 신규 설치에서는 대신 Apache 2.0 Qwen3.5-2B Q4_K_M 모델을 백그라운드로 다운로드하고 즉시 응답(`downloading: true`) — 진행률은 `GET .../embedded`로 폴링 | | `POST` | `/api/settings/ai/embedded/stop` | `SETTINGS_AI_MANAGE` | 사이드카 중지 및 LOCAL 프로바이더 비활성화 | | `PUT` | `/api/settings/ai/embedded/config` | `SETTINGS_AI_MANAGE` | 폴더/모델 오버라이드 저장 `{ "dir", "model" }` (null은 유지, 공백은 해제; dir이 없으면 400) | @@ -324,7 +323,7 @@ Authorization: Bearer oswl_ --- -## 로컬/테스트 (local 프로파일 전용) +## 로컬/테스트 (local 프로필 전용) | 메서드 | 경로 | 설명 | |---|---|---| diff --git a/docs/ko/Administration.md b/docs/ko/Administration.md index efe6d0b2..e011704b 100644 --- a/docs/ko/Administration.md +++ b/docs/ko/Administration.md @@ -1,5 +1,7 @@ # 관리 +[1.0.5.1 변경 사항](Whats-New-v1.0.5.1.md) + 이 페이지는 모든 관리자 전용 기능을 다룹니다: 사용자 관리, 역할 템플릿, 감사 로그, 보안 설정, SMTP 구성. > 별도로 명시되지 않는 한 이 페이지의 모든 작업에는 **시스템 관리자** 권한이 필요합니다. @@ -219,6 +221,24 @@ v1.0.4의 작업 코드는 필터 UI에서 **모니터링**(`MONITOR.*`), **연 세 엔드포인트 모두 관리자 권한이 필요합니다. Prometheus 스크랩 설정은 `application-prod.yaml`의 `management` 블록에 있습니다. +### 비즈니스 메트릭 & Grafana + +기본 JVM/HTTP 미터 외에도 OsWL은 다음 비즈니스 메트릭을 기록합니다 (모두 `/actuator/prometheus`로 노출되며, Prometheus 이름 기준 — 점(.)은 밑줄(_)로 변환됩니다): + +| 메트릭 | 타입 | 태그 | 설명 | +|---|---|---|---| +| `oswl_scan_duration_seconds` | Timer | `outcome` (`completed`\|`failed`) | 스캔 파이프라인 전체 소요 시간 | +| `oswl_quickimport_queue_depth` | Gauge | — | 워커 슬롯을 기다리는 Quick Import 작업 수 | +| `oswl_quickimport_running` | Gauge | — | 현재 실행 중인 Quick Import 작업 수 | +| `oswl_components_ingested_total` | Counter | `ecosystem` | 스캔 인제스트로 저장된 컴포넌트 수 | +| `oswl_ai_calls_total` | Counter | `provider` | 기록된 AI 호출 수 | +| `oswl_ai_tokens_total` | Counter | `provider`, `direction` (`in`\|`out`) | AI 프롬프트/완성 토큰 수 | +| `oswl_ai_cost_usd_total` | Counter | `provider` | 추정 AI 비용 (USD) | +| `oswl_gate_evaluations_total` | Counter | `outcome` (`pass`\|`fail`) | 시큐리티 게이트 평가 수 | +| `oswl_external_api_calls_total` | Counter | `source` (`depsdev`, `osv`, `epss`, `kev`, `github-advisory`, `nvd`), `outcome` (`success`\|`failure`\|`ratelimited`) | 외부 데이터 소스 호출 수 | + +이 메트릭들을 다루는 Grafana 대시보드가 [`deploy/observability/grafana/oswl-dashboard.json`](../../deploy/observability/grafana/oswl-dashboard.json)에 포함되어 있습니다. **Dashboards → New → Import**로 임포트하면 Prometheus 데이터소스를 선택하라는 prompt가 표시되므로 JSON을 직접 수정할 필요가 없습니다. + --- ## 오프라인 스냅샷 번들 (v1.0.4) @@ -294,7 +314,7 @@ CVE/라이선스 요약에 사용할 LLM 제공업체와 보강 동작을 구성 각 프로바이더의 모델 입력란은 자유 입력 콤보박스입니다: 드롭다운에는 현재 모델이 제안으로 표시되지만, 계정에서 접근 가능한 어떤 모델 ID든 직접 입력할 수 있습니다. -같은 탭의 **내장 AI (기본 제공 로컬 모델)** 카드는 함께 제공되는 llama.cpp `llama-server`를 사이드카로 실행(CPU 전용, localhost 전용, API 키 불필요)하여 LOCAL 프로바이더로 등록합니다. 기본으로 번들되는 모델은 **Qwen3 1.7B**(최초 사용 시 다운로드)이며, 카드에서 **모델 드롭다운**(폴더 안의 모든 `.gguf` 또는 자동 순서), **폴더 변경 + 저장**(DB에 유지되며, 실행 중 변경 시 사이드카가 중지됨), 첫 번째 모델 시작 실패 시 다음 모델로 넘어가는 **자동 폴백**을 사용할 수 있습니다. [내장 AI](Embedded-AI.md) 참고. +내장 AI는 별도로 설치한 llama.cpp 실행 파일을 사용하며, 기본 다운로드 모델은 **Qwen3.5-2B Q4_K_M**입니다. **Gemma 4 E2B**는 선택적으로 직접 설치합니다. 실행 파일은 `embedded-ai/llama/`, 모델은 `embedded-ai/model/<계열>/`에 둡니다. 부팅 시 미리 받기는 다운로드만 수행하며 서버 실행이나 LOCAL 활성화는 하지 않습니다. 기본 다운로드는 Hugging Face의 고정 리비전을 사용하고 SHA-256 및 크기를 검증합니다. 기본 대체 미러는 없으며, 에어갭 모드에서는 다운로드하지 않습니다. 모델을 바꾸려면 설정에서 중지한 뒤 모델을 선택·저장하고 다시 시작하세요. 최신 요구 사항과 설정은 [내장 AI](Embedded-AI.md)를 참고하세요. 활성 제공업체는 **하나**만 둘 수 있습니다. 탭에서 추가로 설정할 수 있는 항목: @@ -353,3 +373,49 @@ AI 카드는 오늘의 호출 수, 토큰 합계, 예상 비용을 보여주고( 변경 사항은 `CACHE.UPDATE_TTL`, `CACHE.CLEAR`로 감사 로그에 기록됩니다. + +--- + +## SAML 2.0 SSO 및 SCIM 2.0 프로비저닝 + +OsWL은 Okta, Entra ID, 온프레미스 AD FS를 사용하는 기업용 SAML 2.0 단일 로그인을 지원합니다. SAML IdP가 설정되면 `/login`에 **SSO로 로그인** 옵션이 표시됩니다. + +### SAML 설정 + +1. SP 서명 키 쌍을 생성합니다(선택 사항이지만 권장): + ```bash + openssl req -x509 -newkey rsa:2048 -keyout oswl-saml-sp.key -out oswl-saml-sp.crt -nodes -days 3650 -subj "/CN=oswl" + ``` +2. `application-prod.yaml`의 SAML 블록의 주석을 해제하고 환경 변수를 설정합니다: + | 환경 변수 | 용도 | + |---|---| + | `OSWL_SAML_IDP_METADATA_URL` | IdP 메타데이터 URL(예: Okta/Entra 앱 메타데이터) | + | `OSWL_SAML_IDP_CERTIFICATE` | IdP 서명 인증서 파일 경로 | + | `OSWL_SAML_SP_PRIVATE_KEY` | SP 개인 키 파일 경로 | + | `OSWL_SAML_SP_CERTIFICATE` | SP 인증서 파일 경로 | +3. IdP에 SP 메타데이터를 등록합니다. 메타데이터 엔드포인트는 다음과 같습니다: + ``` + https:///saml2/service-provider-metadata/oswl + ``` +4. IdP가 email 클레임(NameID 또는 `email`/`mail` 속성)을 전송하는지 확인합니다. + +> SAML 로그인은 IdP가 이미 사용자를 인증했으므로 이메일 OTP 단계를 건너뜁니다. 기존 OsWL 계정과 일치하지 않는 이메일은 SCIM이 활성화하고 역할을 할당할 수 있도록 비활성화된 로컬 계정으로 자동 생성됩니다. + +### SCIM 2.0 프로비저닝 + +SCIM을 사용하면 IdP의 사용자 생명주기를 OsWL과 동기화할 수 있습니다. + +| 리소스 | 엔드포인트 | 참고 | +|---|---|---| +| Users | `/scim/v2/Users` | GET/POST/PUT/PATCH/DELETE | +| Groups | `/scim/v2/Groups` | GET/POST/PUT/PATCH/DELETE | + +**인증:** 모든 SCIM 요청에 `Authorization: Bearer `을 포함해야 합니다. 전용 SCIM 토큰은 `ApiKeyService#issueScimToken`을 통해 프로그래밍 방식으로 발급합니다. SCIM 토큰은 `api_keys` 테이블에 저장되지만 범위가 `SCIM`이며, 일반 CLI 스캔 API에서는 거부됩니다. + +**그룹 매핑:** `oswl.scim.group-mapping`(환경 변수: `OSWL_SCIM_GROUP_MAPPING`)으로 SCIM 그룹의 표현 방식을 선택합니다: +- `TEAM`(기본값) — 각 SCIM 그룹은 Team이 되고, 멤버는 TeamMember 행이 됩니다. +- `ROLE_TEMPLATE` — 각 SCIM 그룹은 RoleTemplate이 되고, 멤버는 해당 역할 템플릿이 할당됩니다. + +**사용자 비활성화:** `DELETE /scim/v2/Users/{id}`는 OsWL에서 `active=false`로 설정합니다. SCIM을 통해 사용자를 물리적으로 삭제하지는 않으므로 감사 귀속 정보가 보존됩니다. + +**감사 액션:** SCIM 작업은 `SCIM.USER_CREATE`, `SCIM.USER_UPDATE`, `SCIM.USER_DEACTIVATE`, `SCIM.GROUP_CREATE`, `SCIM.GROUP_UPDATE`, `SCIM.GROUP_DELETE`, `SCIM.GROUP_MEMBER_ADD`, `SCIM.GROUP_MEMBER_REMOVE`, `SCIM.AUTH_FAILURE`, `SCIM_KEY.CREATE`로 기록됩니다. SAML 로그인 이벤트는 `SAML.LOGIN_SUCCESS` 및 `SAML.LOGIN_FAILURE`로 기록됩니다. diff --git a/docs/ko/Backup-And-Restore.md b/docs/ko/Backup-And-Restore.md new file mode 100644 index 00000000..e1c056eb --- /dev/null +++ b/docs/ko/Backup-And-Restore.md @@ -0,0 +1,69 @@ +# 백업 및 복구 + +실무에서 가장 흔한 사고는 PostgreSQL 손실이 아니라 **DB 백업은 멀쩡한데 `OSWL_ENCRYPTION_KEY`만 잃어버리는 경우**입니다. DB에 저장된 모든 VCS 접근 토큰, AI 공급자 API 키, Jira API 토큰, SMTP 메일 비밀번호는 이 키로 암호화되어 있습니다. 키를 잃으면 DB는 완벽히 복원되지만 그 안의 시크릿은 전부 영구적으로 복호화할 수 없게 됩니다 — 모든 VCS 연결, AI 공급자, Jira 연동을 처음부터 다시 설정해야 합니다. + +이 문서는 [운영 배포](Production-Deployment-Checklist.md)의 운영자용 짝입니다 — 배포 방법은 그쪽을 먼저 읽고, 이 문서는 백업과 "복구가 실제로 되는지" 검증하는 것에만 집중합니다. + +--- + +## 백업 대상 + +| 항목 | 위치 | 중요한 이유 | +|---|---|---| +| PostgreSQL 데이터베이스 | `deploy/docker/compose.prod.yml`의 `db-data-prod` 볼륨, 또는 관리형 PostgreSQL 인스턴스 | 프로젝트·스캔·발견 사항·사용자·암호화된 시크릿 등 모든 애플리케이션 데이터. | +| `OSWL_ENCRYPTION_KEY` | 주입 방식에 따라 다름(`.env.prod`, 시크릿 매니저 등) | DB에 저장된 모든 VCS 토큰/AI API 키/Jira 토큰/SMTP 비밀번호를 복호화합니다. **이게 없으면 위 DB 백업은 이런 시크릿이 필요한 용도로는 쓸모없습니다.** | +| 오프라인 스냅샷 저장소 | `OSWL_AIRGAPPED_IMPORT_DIR` (폐쇄망 모드 사용 시) | 복구 후 재임포트는 이것 없이도 가능하지만, 임포트 이력이 사라져 번들을 다시 받고 검증해야 합니다. | +| 임베디드 AI 모델 디렉터리 | `OSWL_EMBEDDED_AI_DIR` (기본값 `embedded-ai/`) | 재다운로드 가능([내장 AI](Embedded-AI.md) 참고) — 폐쇄망이라 재다운로드가 안 될 때만 백업하세요. | +| 설정 파일 | `.env.prod`, `deploy/docker/compose.prod.yml`, `application-prod.yaml` 오버라이드 | 이게 없으면 데이터는 멀쩡해도 인스턴스가 실제로 어떻게 설정되어 있었는지(SMTP 호스트, HSTS 설정, 기능 플래그 등) 알 수 없습니다. | + +그 외(`OSWL_LOG_DIR`의 파일 로그, Quick Import 클론 임시 디렉터리)는 소모성이므로 백업하지 마세요. + +--- + +## PostgreSQL 백업 + +```bash +# 스키마 + 데이터, 커스텀 포맷(병렬 복원 지원, plain SQL보다 작음) +pg_dump -Fc -h -U -d -f oswl-$(date +%Y%m%d).dump +``` + +**권장 주기:** 매일 밤 전체 덤프, 30일 보관, 덤프 사이 시점 복구가 필요하면 PostgreSQL WAL 아카이빙도 함께. 덤프는 DB 호스트와 독립된 곳(오브젝트 스토리지, 다른 가용 영역)에 보관하세요 — 백업이 백업 대상 바로 옆에 있으면 그 대상을 앗아가는 사고에서 같이 사라집니다. + +`OSWL_ENCRYPTION_KEY`는 **`pg_dump` 결과물과 별도의** 시크릿 매니저에 백업하세요 — 이 시크릿들을 저장 시 암호화하는 이유 자체가, 같은 백업 덩어리에 키와 암호문을 같이 두면 무의미해집니다. + +--- + +## 복구 절차 + +1. **새 PostgreSQL 인스턴스를 준비**(또는 대상을 비우고) 덤프를 복원합니다: + ```bash + pg_restore -h -U -d --clean --if-exists oswl-20260730.dump + ``` +2. **백업 당시와 동일한 `OSWL_ENCRYPTION_KEY`를 주입**합니다 — 다른 키(심지어 유효해 보이는 새로 생성한 키라도)를 쓰면 저장된 모든 시크릿이 복호화 불가능해지며, 데이터 손상과 구분되지 않습니다. +3. **복원된 DB를 대상으로 앱을 기동**합니다(`SPRING_PROFILES_ACTIVE=prod`, `ddl-auto: validate` — 복원된 스키마가 실행 버전과 이미 일치해야 합니다. 백업 시점보다 최신 버전으로 복구한다면 기동 전에 `src/main/resources/db/`의 미적용 수동 마이그레이션 스크립트를 먼저 적용하세요). +4. **아래 검증 스크립트를 실행**해 복구가 "프로세스가 떴다" 수준을 넘어 실제로 쓸 수 있는지 확인합니다. + +```bash +OSWL_VERIFY_EMAIL=you@example.com \ +OSWL_VERIFY_PASSWORD='...' \ +OSWL_VERIFY_PROJECT_ID=1 \ +./scripts/ops/verify-restore.sh https://your-instance.example.com +``` + +이 스크립트는 대화형입니다(실제 로그인과 동일하게 이메일 OTP 코드 입력을 기다립니다) 아래를 확인합니다: + +| 확인 항목 | 무엇을 증명하는가 | +|---|---| +| `GET /actuator/health` → 200 | 복원된 DB와 주입된 키로 앱이 정상 기동했습니다. | +| 로그인 + OTP | 복원된 `users` 테이블에 대해 인증·세션 인프라가 동작합니다. | +| `GET /api/settings/vcs` → 200 | **`OSWL_ENCRYPTION_KEY`가 올바릅니다** — 저장된 VCS 토큰 최소 1건이 오류 없이 복호화되었습니다. 키가 틀리면 여기서 즉시 500으로 드러나며, 몇 주 뒤에야 발견되는 은근한 버그가 되지 않습니다. | +| `GET /projects/{id}/scan-history` → 200 | 스캔 이력이 복원되어 조회 가능합니다(복원된 데이터에 실제 존재하는 프로젝트 ID를 `OSWL_VERIFY_PROJECT_ID`로 설정해야 함). | +| `GET /api/admin/audit-logs` → 200 | 감사 로그가 복원되어 조회 가능합니다(`SYSTEM_ADMIN` 계정 필요). | + +이 스크립트는 실제 정기 **리허설**로 실행하세요(예: 분기별로 임시 환경에서) — 작성된 이후 한 번도 실행해 본 적 없는 복구 절차는 검증된 절차가 아닙니다. + +--- + +## 키 교체(rotation) + +현재 **재암호화 배치 작업은 없습니다.** 오늘 `OSWL_ENCRYPTION_KEY`를 교체하면 기존에 암호화된 모든 값(VCS 토큰, AI API 키, Jira 토큰, SMTP 비밀번호)이 복호화 불가능해집니다 — 실무적인 교체 절차는: 키를 교체한 뒤, 각 설정 화면(VCS 연결, AI 공급자 키, Jira 연동, SMTP 자격 증명)에서 시크릿을 다시 입력해 새 키로 재암호화되도록 하는 것입니다. 제대로 된 교체 도구(구 키로 복호화 → 신 키로 재암호화, 영향받는 모든 테이블에 대해 in-place로)는 별도 백로그 항목으로 추적 중입니다 — 이 도구가 나오기 전까지는 `OSWL_ENCRYPTION_KEY`를 사실상 한 번 정하면 영구적인 값으로 취급하고, 그에 맞게 보호하세요(시크릿 매니저에, 저장소의 `.env` 파일이 아니라). diff --git a/docs/ko/CLI-Integration.md b/docs/ko/CLI-Integration.md index 3ab161f2..52169e63 100644 --- a/docs/ko/CLI-Integration.md +++ b/docs/ko/CLI-Integration.md @@ -89,6 +89,8 @@ ScanIngestService → CVE·라이선스 비동기 보강 (OSV / deps.dev) --- +매니페스트 압축 파일의 수집 범위는 `/scripts/manifest-rules.json`을 따릅니다. 의존성 매니페스트 외에 빌드 설정, 래퍼 파일, `buildSrc`의 Java/Kotlin 파일도 포함될 수 있으므로 전송 전에 수집 규칙을 확인하세요. + ## 사전 요구사항 1. OsWL에 등록된 **프로젝트** @@ -108,12 +110,12 @@ ScanIngestService → CVE·라이선스 비동기 보강 (OSV / deps.dev) | `POST` | `/api/scan` | API key + 비밀번호 | 스캔 제출·보강 | | `GET` | `/api/scan/{scanId}/status` | 세션 | 스캔 상태 폴링 (UI) | | `POST` | `/api/scan/gate` | API 키 | **v1.0.4** — PR / CI 보안 게이트, `exitCode`가 담긴 판정 반환 | -| `GET` | `/api/projects/{projectId}/sbom` | 세션 / 키 | **v1.0.4** — CycloneDX 1.6 SBOM | -| `GET` | `/api/projects/{projectId}/vex` | 세션 / 키 | **v1.0.4** — CycloneDX VEX | -| `GET` | `/api/projects/{projectId}/sarif` | 세션 / 키 | **v1.0.4** — SARIF 2.1.0 | +| `GET` | `/api/projects/{projectId}/sbom` | 세션 | **v1.0.4** — CycloneDX 1.6 SBOM | +| `GET` | `/api/projects/{projectId}/vex` | 세션 | **v1.0.4** — CycloneDX VEX | +| `GET` | `/api/projects/{projectId}/sarif` | 세션 | **v1.0.4** — SARIF 2.1.0 | | `POST` | `/api/sbom/import` | 세션 | **v1.0.4** — 외부 CycloneDX 파일 가져오기 | -> CLI 엔드포인트는 `Authorization: Bearer` 헤더만으로 인증하며, 세션 쿠키나 CSRF 토큰은 필요 없습니다. `POST /api/scan`, `POST /api/scan/parse`, `GET /api/scan/ping`은 브라우저 CSRF 검사에서 제외되고, 그 외 경로는 기존 CSRF 보호가 유지됩니다. [Scan API 보안](Scan-Api-Security.md) 참고. +> CLI 요청은 `Authorization: Bearer`에 프로젝트 API 키를 전달합니다. `POST /api/scan`은 제출자의 이메일·비밀번호·권한·프로젝트 접근 권한도 확인합니다. `POST /api/scan`, `POST /api/scan/parse`, `POST /api/scan/gate`에는 브라우저 세션이나 CSRF 토큰이 필요하지 않습니다. `GET /api/scan/ping`은 키를 검증합니다. [스캔 API 보안](Scan-Api-Security.md)을 참고하세요. --- @@ -127,9 +129,9 @@ POST /api/projects/{projectId}/keys UI: 프로젝트 → **설정(⚙)** → **CLI** → **키 생성** -### 관리자 전역 키 +### 관리자의 키 관리 -**설정 → 관리자 → CLI 키** — [API 레퍼런스](API-Reference.md) 참고 +관리자는 여러 프로젝트의 CLI 키를 조회·폐기하고 지정한 `projectId`에 키를 발급할 수 있습니다. 스캔 키의 범위는 프로젝트 단위입니다. 별도 SCIM 토큰은 스캔 제출에 사용할 수 없습니다. --- @@ -238,8 +240,14 @@ exit "$(echo "$verdict" | jq -r .exitCode)" | `failOnEpss` | `OSWL_GATE_FAIL_ON_EPSS` | `0.5` | | `failOnLicenseViolation` | `OSWL_GATE_FAIL_ON_LICENSE_VIOLATION` | `true` | | `onlyNew` | `OSWL_GATE_ONLY_NEW` | `true` | +| `onlyReachable` | `OSWL_GATE_ONLY_REACHABLE` | `false` | +| `failOnSecrets` | `OSWL_GATE_FAIL_ON_SECRETS` | `false` | + +`onlyNew`는 기준 스캔에 이미 존재하던 CVE·라이선스 문제를 제외합니다. 통과를 보장하지는 않습니다. 악성 패키지, 활성화한 시크릿 탐지 등 다른 적용 규칙으로 실패할 수 있습니다. `onlyReachable`은 지원되는 바이트코드 또는 소스 참조 분석 결과가 `REACHABLE`인 컴포넌트의 CVE만 평가합니다. 활성화하면 `UNKNOWN`인 CVE는 제외하지만, 참조가 확인되지 않았다는 사실이 악용 불가능함을 뜻하지는 않습니다. 라이선스와 악성 패키지 검사는 이 필터와 별개입니다. 분석 범위를 검토한 뒤 활성화하세요. GitHub 대상을 설정하면 Check Run과 PR 댓글로 결과를 게시할 수 있습니다. + +확정 악성 패키지(OSV `MAL-` 어드바이저리)는 위의 모든 임계값 및 `onlyNew`/`onlyReachable`과 무관하게 항상 차단됩니다 — 유일한 해제 방법은 승인된 정책 예외(waiver, **v1.0.5**, `/api/policies/exceptions` 참고)뿐입니다. -`onlyNew`는 직전 완료 스캔을 베이스라인으로 비교하므로 기존 부채가 머지를 막지 않습니다. 요청에 GitHub 대상을 포함하면 판정이 Check Run과 PR 코멘트로도 게시됩니다. +`failOnSecrets`(**v1.0.5**)는 Quick Import 클론 스캔에서 CRITICAL/HIGH 등급 시크릿 탐지(정규식 + 엔트로피 규칙 — AWS 키, GitHub/GitLab/Slack/npm 토큰, 임베디드 프라이빗 키 블록 등)가 하나라도 있으면 차단합니다. 위의 다른 임계값과 마찬가지로 요청 오버라이드 → 조직/팀/프로젝트 정책 계층 → 인스턴스 기본값 순으로 해석됩니다. --- diff --git a/docs/ko/Database-Schema.md b/docs/ko/Database-Schema.md index 7103ef11..7d0a9298 100644 --- a/docs/ko/Database-Schema.md +++ b/docs/ko/Database-Schema.md @@ -1,12 +1,12 @@ # 데이터베이스 스키마 및 마이그레이션 -OsWL 애플리케이션 데이터는 PostgreSQL(`prod`) 또는 H2 파일 모드(`local`)에 저장됩니다. `domain/entity/` 아래 JPA 엔티티가 **실제 스키마의 기준**입니다. +OsWL 애플리케이션 데이터는 PostgreSQL(`prod`) 또는 H2 파일 모드(`local`)에 저장됩니다. `domain/entity/`, `auth/entity/` 아래 JPA 엔티티가 **실제 스키마의 기준**입니다. --- -## 프로파일별 동작 +## 프로필별 동작 -| 프로파일 | `ddl-auto` | 의미 | +| 프로필 | `ddl-auto` | 의미 | |----------|------------|------| | `local` | `update` | 엔티티 변경 시 H2 스키마가 자동 반영 | | `prod` | `validate` | PostgreSQL이 엔티티와 다르면 기동 실패 — **자동 마이그레이션 없음** | @@ -16,7 +16,9 @@ OsWL 애플리케이션 데이터는 PostgreSQL(`prod`) 또는 H2 파일 모드( ### Flyway (v1.0.4, 옵트인) -`OSWL_FLYWAY_ENABLED=true`로 설정하면 스키마 관리를 Flyway에 위임합니다(`baseline-on-migrate` 활성 — 기존 DB는 거부되지 않고 베이스라인 처리). 켜기 전에 현재 스키마와 일치하는 베이스라인을 생성하세요. 기본값 `false`에서는 위의 `ddl-auto` 동작이 그대로 유지됩니다. +`OSWL_FLYWAY_ENABLED=true`로 `src/main/resources/db/migration/`의 버전별 마이그레이션을 활성화합니다. 기본값은 `false`입니다. 저장소에는 이미 `V1__baseline.sql`과 이후 마이그레이션이 있습니다. 빈 PostgreSQL DB에서는 V1부터 순서대로 실행한 후 Hibernate가 스키마를 검증합니다. Flyway 이력이 없는 기존 DB에서는 `baseline-on-migrate`가 V1 실행 없이 버전 1을 기록하고 V2부터 실행합니다. 활성화 전에 백업하고 기존 스키마와 마이그레이션을 비교하세요. 이미 수동 적용한 변경과 후속 마이그레이션이 충돌할 수 있습니다. 공유 DB에 적용한 마이그레이션 파일은 재생성하거나 수정하지 마세요. SQL을 수동 관리한다면 대상 버전에 필요한 변경을 순서대로 모두 적용해야 합니다. 아래의 일부 레거시 스크립트만으로 신규 설치 스키마를 구성할 수는 없습니다. + +현재 스키마에는 v1.0.4 이후 변경도 포함됩니다. 조직·팀(V11), SAML/SCIM(V13), 웹훅(V14), CVE 출처와 C/C++ 메타데이터(V15–V16), 정책 상속·예외(V17, V28), 도달 가능성 및 근거(V18, V29–V30), 감사 로그 무결성(V19), UI 설정·온보딩(V20, V23–V24), 시크릿·IaC 탐지(V21), 스캔 아카이빙(V22), 보고서 브랜딩(V25), 캐시 집계·무효화(V26–V27), 영속적 가져오기 작업(V31)입니다. 전체 적용 순서는 실제 마이그레이션 파일을 기준으로 확인하세요. 일부 마이그레이션은 재실행을 허용하지 않으므로 무조건 반복 실행하면 안 됩니다. ### v1.0.4에서 추가된 컬럼 @@ -73,7 +75,7 @@ PostgreSQL에 `psql`, DBeaver, CI 마이그레이션 등으로 실행합니다. | `project_versions.imported_at`, `last_updated_at` | 미사용 타임스탬프 | | `projects.updated_at`, `version`, `last_scanned_at` | 비정규화; UI는 최신 `scan_results` 사용 | -[운영 배포 체크리스트](Production-Deployment-Checklist.md) §8 참고. +[운영 배포 체크리스트](Production-Deployment-Checklist.md) §9 참고. --- @@ -94,7 +96,7 @@ libraries (공유) airgapped_snapshot_entries ── airgapped_snapshot_meta (오프라인 스냅샷 저장소) -users, role_templates, audit_logs, cache_settings, vcs_connections, … +users, role_templates, audit_logs, cache_settings, user_vcs_connections, … ``` - **프로젝트 카드 버전 / 마지막 스캔** — `projects.version`이 아니라 최신 `scan_results`에서 계산. diff --git a/docs/ko/Embedded-AI.md b/docs/ko/Embedded-AI.md index fcae4d79..064be7a2 100644 --- a/docs/ko/Embedded-AI.md +++ b/docs/ko/Embedded-AI.md @@ -1,172 +1,99 @@ -# 내장 AI (Embedded AI) +# 내장 AI -내장 AI는 별도의 클라우드 계정이나 API 키 없이 OsWL이 로컬 LLM을 바로 사용할 수 있게 해주는 기능입니다. **데이터는 절대 기기 밖으로 나가지 않습니다.** 함께 제공되는 [llama.cpp](https://github.com/ggml-org/llama.cpp) `llama-server`를 사이드카 프로세스로 실행해 **OpenAI 호환** 엔드포인트(`http://127.0.0.1:/v1`)를 띄우고, 이를 **LOCAL** AI 프로바이더로 등록합니다. CVE 트리아지 요약과 라이선스 인사이트가 모두 이 컴퓨터 안에서만 처리됩니다. +OsWL은 llama.cpp를 `http://127.0.0.1:11435/v1`에서 실행해 LOCAL 제공자로 등록합니다. 추론 데이터는 서버 안에서 처리되며, 모델 설치에는 인터넷 또는 파일의 오프라인 전달이 필요합니다. **CPU 기본 모델은 Qwen3.5-2B Q4_K_M, 선택 모델은 Gemma 4 E2B Q4_K_M입니다.** 한 번에 하나만 실행합니다. 텍스트 전용이므로 GPU와 이미지·오디오 프로젝터는 필요하지 않습니다. ---- +## 사양과 폴더 구조 -## 동작 방식 +| 모델 | 가중치 다운로드 | 서버 전체 예상 최소 | 권장 | +|---|---:|---|---| +| Qwen3.5 2B Q4_K_M | 1.28 GB | 2 vCPU / RAM 8 GB | 4 vCPU / RAM 8~16 GB | +| Gemma 4 E2B Q4_K_M | 3.11 GB | 4 vCPU / RAM 8 GB | 4 vCPU / RAM 16 GB | -* OsWL이 **모델 디렉터리**(기본값 `./embedded-ai`)에서 `llama-server`를 실행하고 `/health`에 응답할 때까지 기다립니다. `.gguf` 파일이 아직 없다면 부팅 직후 백그라운드에서 기본 Qwen3 모델 다운로드가 자동으로 시작됩니다(최초 1회, 약 1.2GB) — 그래서 Settings에서 **시작**을 누를 때는 이미 다 받아져 있거나 다운로드가 진행 중인 경우가 많습니다. `oswl.ai.embedded.auto-download-on-boot=false`로 설정하면 예전처럼 시작 버튼을 눌러야만 다운로드가 시작됩니다. **폐쇄망**(`oswl.airgapped.enabled=true`) 환경에서는 이 백그라운드 다운로드가 절대 실행되지 않으므로 미리 직접 `.gguf` 파일을 넣어두세요([요구 사항 및 디렉터리 구조](#요구-사항-및-디렉터리-구조) 참고). -* 서버는 **localhost(`127.0.0.1`)에만 바인딩**되므로 다른 기기에서 접근할 수 없습니다. -* 시작에 성공하면 OsWL이 이 엔드포인트를 **LOCAL** 프로바이더로 저장하고 활성화합니다(활성 프로바이더는 하나뿐이므로 기존 프로바이더는 비활성화됩니다). -* 내장 AI를 중지하면 LOCAL 프로바이더도 함께 비활성화되어, 죽은 엔드포인트로 AI 호출이 나가는 일이 없습니다. -* 서버는 reasoning 비활성화(`--reasoning-budget 0`), 웹 UI 비활성화(`--no-webui`) 옵션으로 실행됩니다. 기본적으로 GPU 레이어 오프로드를 시도하며(`-ngl 999`), GPU 시작이 실패하면(GPU 빌드 부재, VRAM 부족 등) 같은 모델을 CPU 전용으로 1회 자동 재시도한 뒤에야 다음 후보로 넘어갑니다. -* 병렬 AI 콜을 지원합니다. 서버는 기본적으로 `--parallel 4 --cont-batching`, 플래시 어텐션(`-fa`), 프리픽스 캐시 재사용(`--cache-reuse 256`) 옵션으로 시작하고, OsWL 자체적으로 독립적인 AI 콜을 병렬로 실행합니다(최대 `oswl.ai.enrichment.max-parallel-calls`, 기본 `3`). -* LOCAL 프로바이더로 복사되는 배치 프롬프트는 더 단순한 JSON 스키마 변형(`oswl.ai.enrichment.local-simple-schema`, 기본 `true`)을 사용합니다. 소형 로컬 모델이 클라우드 대형 모델용 전체 스키마보다 단순한 출력 스키마를 훨씬 잘 따릅니다. -* CVE/라이선스 요약은 입력 컨텍스트 해시를 기준으로 캐싱되며, 변경되지 않은 항목은 다음 스캔에서 다시 AI를 호출하지 않습니다. -* 보안 자세(posture), 보안 트렌드, 라이선스 트렌드, 버전 차이 인사이트는 기존 4개의 개별 호출을 `insights.combined` 하나로 합쳐 고정 오버헤드를 줄입니다. +이는 **성능 보증이 아닌 용량 산정안**입니다. OsWL·PostgreSQL 동시 실행, 4K~8K 컨텍스트, 소규모 스캔과 AI 생성 1건씩의 간헐적 처리가 기준입니다. 대형 압축파일·동시 스캔·DB 증가에는 추가 자원이 필요합니다. 파일 크기는 최대 메모리가 아닙니다. 두 모델에 여유 디스크 6 GB 이상을 확보하고 실행 파일·DB·로그·기존 모델은 별도로 계산하세요. 빌드 사양은 포함하지 않습니다. -> 필요 권한: 다른 AI 설정과 동일하게 `SETTINGS_AI_MANAGE` 또는 시스템 관리자. +Qwen을 기본으로 고른 이유는 CPU 공유 서버에 맞는 작은 가중치 크기입니다. 항상 더 정확하다는 뜻은 아닙니다. 실제 한국어·영어·일본어 입력으로 품질을 비교하세요. 버스터블의 vCPU 수는 지속 성능을 보장하지 않으므로 CPU 크레딧 소진 상태에서도 확인해야 합니다. 연속 분석은 고정 성능 CPU나 별도 CPU 워커를 고려하되 GPU를 필수로 요구하지 않습니다. ---- +서버 OS·아키텍처에 맞는 CPU 실행 파일과 같은 배포본의 라이브러리를 사용합니다. 로컬 검증 기준은 llama.cpp **b10068 (571d0d540)**입니다. 두 모델 구조와 `--chat-template-kwargs`, `-fa on`을 지원하는 호환 버전이 필요합니다. -## 요구 사항 및 디렉터리 구조 - -모델 디렉터리에는 서버 바이너리가 있어야 하며, `.gguf` 모델이 없으면 **시작** 버튼을 처음 -누를 때(또는 부팅 시 백그라운드로) 자동으로 받아옵니다: - -``` +```text embedded-ai/ - llama-server(.exe) — llama.cpp 서버 바이너리 (직접 준비) - qwen3-1.7b-q4_k_m.gguf — 기본 모델 (Apache 2.0) — 최초 시작 시 자동 다운로드 + llama/ + llama-server(.exe) + ... companion libraries ... + model/ + Qwen/Qwen3.5-2B-Q4_K_M.gguf + Gemma/gemma-4-E2B-it-Q4_K_M.gguf + llama-server.log ``` -디렉터리가 비어 있으면 Qwen3-1.7B(~1.2GB)를 바로 그 위치로 다운로드합니다 — 부팅 직후 자동으로 -시작되거나, 아직 안 끝났다면 **시작**을 누르는 즉시 — SHA256 체크섬을 검증한 뒤에야 사이드카를 -실행합니다. 카드에 실시간 다운로드 진행률이 표시되며, `java -jar app.jar`만으로 전 과정이 -끝납니다. 별도 스크립트나 빌드 단계가 필요 없습니다. 이렇게 할 수 있는 이유는 Qwen3가 Apache 2.0 -라이선스이기 때문입니다([THIRD_PARTY_LICENSES.md](../../THIRD_PARTY_LICENSES.md#qwen3-17b-gguf) -참고) — 사용자를 대신해 받아오는 데 별도 재배포 의무가 없습니다. 모델은 기본적으로 업스트림 -[Hugging Face 저장소](https://huggingface.co/ggml-org/Qwen3-1.7B-GGUF)에서 다운로드됩니다. -서드파티 호스트에 의존하고 싶지 않다면 `OSWL_EMBEDDED_DEFAULT_MODEL_URL`을 바이트 단위로 -동일한 자체 호스팅 미러로 지정하세요. - -기본 모델 URL, SHA256, 크기 설정은 한 세트로 묶인 값입니다. URL만 바꾸고 SHA256/크기를 맞추지 -않으면 모든 다운로드가 체크섬 검증 실패로 거부됩니다. - -다른 `.gguf` 모델도 직접 넣어 쓸 수 있습니다 — OsWL은 기본 Qwen3뿐만 아니라 이 디렉터리에 직접 -넣은 모든 `.gguf` 파일을 인식합니다. 재배포하거나 공유하기 전에 해당 모델 자체의 라이선스를 -먼저 확인하세요 — OsWL이 번들/자동 다운로드하는 것은 Qwen3뿐입니다. - -| 항목 | OsWL이 찾는 위치 | -|---|---| -| 서버 바이너리 | `/llama-server(.exe)` → `/bin/` → 시스템 `PATH` 순 ([llama.cpp 릴리스](https://github.com/ggml-org/llama.cpp/releases)) — **자동 다운로드 안 됨**, 직접 준비 | -| 모델 | 디렉터리 바로 아래의 모든 `.gguf` 파일 | - -설정 기본값(UI에서 저장한 폴더가 `dir`보다 우선): - -| 설정 키 | 환경 변수 | 기본값 | 설명 | -|---|---|---|---| -| `oswl.ai.embedded.dir` | `OSWL_EMBEDDED_AI_DIR` | `embedded-ai` | 모델 디렉터리 (작업 디렉터리 기준 상대 경로) | -| `oswl.ai.embedded.port` | `OSWL_EMBEDDED_AI_PORT` | `11435` | 사이드카가 사용할 localhost 포트 | -| `oswl.ai.embedded.context-size` | `OSWL_EMBEDDED_AI_CONTEXT` | `8192` | `llama-server -c`에 전달되는 전체 컨텍스트 크기. 병렬 슬롯 수로 나눠 각 슬롯에 할당됩니다 | -| `oswl.ai.embedded.gpu-layers` | `OSWL_EMBEDDED_AI_GPU_LAYERS` | `-1` | GPU 레이어 오프로드(`-ngl`): `-1`은 빌드가 지원하는 한 최대한, `0`은 CPU 전용, 양수는 명시적 레이어 수. GPU 시작 실패 시 CPU 전용으로 자동 재시도 | -| `oswl.ai.embedded.threads` | `OSWL_EMBEDDED_AI_THREADS` | `0` | 스레드 수(`-t`); `0`은 llama.cpp 자동 감지 | -| `oswl.ai.embedded.parallel-slots` | `OSWL_EMBEDDED_AI_PARALLEL` | `4` | `>1`이면 `--parallel N --cont-batching`을 추가해 서버에서 AI 콜이 직렬화되지 않게 합니다. 각 슬롯은 `context-size / N`을 받으며, 2048 미만이면 경고 로그가 남습니다 | -| `oswl.ai.embedded.flash-attn` | `OSWL_EMBEDDED_AI_FLASH_ATTN` | `true` | 플래시 어텐션(`-fa`) | -| `oswl.ai.embedded.cache-reuse` | `OSWL_EMBEDDED_AI_CACHE_REUSE` | `256` | 프리픽스 캐시 재사용(`--cache-reuse N`); `<=0`이면 비활성화 | -| `oswl.ai.embedded.extra-args` | `OSWL_EMBEDDED_AI_EXTRA_ARGS` | (비어 있음) | 추가 llama-server CLI 인자를 공백으로 구분해 그대로 덧붙임 — 서버 설정 전용이며 요청 입력에서 받지 않음 | -| `oswl.ai.embedded.startup-timeout-seconds` | `OSWL_EMBEDDED_AI_STARTUP_TIMEOUT_SEC` | `120` | 모델 후보 한 개가 healthy 상태가 될 때까지 기다리는 최대 시간(초) | -| `oswl.ai.embedded.default-model-url` | `OSWL_EMBEDDED_DEFAULT_MODEL_URL` | 업스트림 Hugging Face `ggml-org/Qwen3-1.7B-GGUF` 자산 | 기본 Qwen3 모델의 1차 다운로드 소스 | -| `oswl.ai.embedded.default-model-sha256` | `OSWL_EMBEDDED_DEFAULT_MODEL_SHA256` | (THIRD_PARTY_LICENSES.md 참고) | 기대 SHA256 — URL과 항상 함께 변경 | -| `oswl.ai.embedded.default-model-size-bytes` | `OSWL_EMBEDDED_DEFAULT_MODEL_SIZE_BYTES` | `1282439264` | 기대 파일 크기(바이트). 진행률 바를 미리 채우는 데 사용되며, URL/SHA256과 같은 세트입니다 | -| `oswl.ai.embedded.fallback-model-url` | `OSWL_EMBEDDED_FALLBACK_MODEL_URL` | (비어 있음) | 1차 URL 실패 시 1회 재시도. 1차를 자체 호스팅 미러로 바꿀 때 이 값에 업스트림 URL을 지정하세요 | -| `oswl.ai.embedded.auto-download-on-boot` | `OSWL_EMBEDDED_AUTO_DOWNLOAD` | `true` | 부팅 시 백그라운드로 기본 모델을 미리 받음; `oswl.airgapped.enabled=true`면 절대 실행 안 함 | - ---- - -## 시작 및 중지 - -**설정 → AI**의 **내장 AI (기본 제공 로컬 모델)** 카드에서 관리합니다: - -1. 상태 표시(**실행 중** / **중지됨**)와 바이너리 누락 경고를 확인합니다. -2. **시작**을 클릭합니다. 디렉터리에 모델이 없으면 Qwen3 다운로드 진행률(~1.2GB, 연결 속도에 - 따라 몇 분 소요)이 먼저 표시된 뒤 서버가 실행됩니다. 모델이 이미 있으면 로딩만 최대 2분 - 정도 걸릴 수 있습니다(GPU 로딩 시 `startup-timeout-seconds`까지 허용). -3. 실행되면 카드에 **실행 모델**이 표시되고, 엔드포인트가 LOCAL 프로바이더로 동작합니다. -4. **중지**를 클릭하면 사이드카가 종료됩니다(다운로드된 모델 파일은 삭제되지 않으므로 다음 - 시작 시 바로 재사용됩니다). +| Model | Bytes | SHA-256 | +|---|---:|---| +| Qwen3.5-2B Q4_K_M | 1280835840 | `aaf42c8b7c3cab2bf3d69c355048d4a0ee9973d48f16c731c0520ee914699223` | +| Gemma 4 E2B Q4_K_M | 3106738272 | `740185b21d22ceb83a11c3aa62ad5842ef32c70f6096d756bbee85a1e4ec34b8` | -카드에는 현재 사용 중인 폴더, 감지된 `.gguf` 파일 목록, 마지막 시작 오류(있으면 빨간색)도 함께 표시됩니다. +- [Qwen model card](https://huggingface.co/Qwen/Qwen3.5-2B) +- [Qwen GGUF, pinned revision](https://huggingface.co/unsloth/Qwen3.5-2B-GGUF/tree/f6d5376be1edb4d416d56da11e5397a961aca8ae) +- [Gemma model card](https://huggingface.co/google/gemma-4-E2B-it) +- [Gemma GGUF, pinned revision](https://huggingface.co/unsloth/gemma-4-E2B-it-GGUF/tree/0314792d7f1f7e229411f620751375812bb9faf2) +- [llama.cpp releases](https://github.com/ggml-org/llama.cpp/releases) +- [License notices](../../THIRD_PARTY_LICENSES.md#embedded-ai-runtime-and-models) ---- +설정 폴더는 모델 계열 폴더가 아닌 최상위 경로입니다. `model/`과 바로 아래 계열 폴더를 탐색합니다. 기존 최상위 GGUF와 최상위/bin 실행 파일도 인식합니다. 같은 파일 이름이면 새 model 폴더가 우선합니다. `mmproj*`, `mtp-*`, `imatrix*`와 최상위 경로 밖을 가리키는 파일은 선택에서 제외합니다. -## 모델 교체 +다른 설치의 기존 모델은 자동 삭제·교체하지 않습니다. 목록에서 빼려면 탐색 폴더 밖으로 옮기세요. 저장한 모델이 없어졌다면 설치된 기본 우선순위로 돌아갑니다. -기본 Qwen3 외에도 llama.cpp와 호환되는 `.gguf`라면 무엇이든 사용할 수 있습니다: +프로젝트의 다국어 프롬프트로 두 모델의 영어·한국어·일본어 응답을 확인했습니다. 다만 일부 Qwen 응답은 입력한 수정 버전이나 건수를 잘못 생성했습니다. 이 확인은 실행 호환성 검증이며 정확도 보장이 아닙니다. AI 권고는 원본 스캔 결과와 대조해야 하며, 기본 모델 선정은 자원 기준이지 품질 순위가 아닙니다. -1. 양자화된 GGUF 모델을 **다운로드**합니다 (예: [Hugging Face](https://huggingface.co/models?library=gguf)). -2. `.gguf` 파일을 카드에 표시된 모델 폴더에 **넣습니다**. -3. **모델** 드롭다운에서 파일을 **선택**하고(목록은 폴더 기준으로 갱신됨) **저장**을 클릭합니다. -4. **시작**을 클릭하면 선택한 모델이 가장 먼저 시도됩니다. 모델 선택은 다음 시작부터 적용되며, 실행 중에는 드롭다운이 비활성화되어 바꿀 수 없습니다. +## 설치·모델·언어 선택 -**자동 (기본 순서)** 옵션은 저장된 모델 → `qwen3…` → 나머지 `.gguf` 중 첫 번째 파일(가나다순) 순으로 시도합니다. +1. 실행 파일과 라이브러리를 `llama/`에 넣습니다. +2. 모델이 하나도 없으면 부팅 시 **Qwen3.5 2B만** `model/Qwen/`에 미리 다운로드합니다. 시작 버튼도 필요 시 다운로드합니다. 사전 다운로드는 `OSWL_EMBEDDED_AUTO_DOWNLOAD=false`로 끕니다. +3. Gemma는 위 고정 리비전의 파일을 직접 받아 SHA-256·바이트 수를 검증한 뒤 `model/Gemma/`에 넣습니다. +4. **설정 → AI → 제공자 → 내장 AI**에서 설치된 모델과 사양 안내를 확인합니다. +5. **중지 → 모델 선택 → 저장 → 시작** 순서로 변경합니다. 시작 실패 시 대체 모델이 실행될 수 있으므로 실행 중인 모델과 대체 표시를 확인하세요. +6. **분석 범위/컨텍스트**에서 한국어·영어·일본어를 선택해 저장합니다. 내장 AI 식별과 주소는 유지됩니다. 저장된 기존 요약은 재생성 전까지 번역되지 않습니다. -> CPU 전용 추론에는 **1B–4B 파라미터** 규모의 작은 양자화 모델(Q4_K_M 등)을 권장합니다. 더 큰 모델은 RAM을 많이 쓰고 느린 기기에서는 시작 제한 시간을 넘길 수 있습니다. 컨텍스트 크기는 `OSWL_EMBEDDED_AI_CONTEXT`(기본 `8192`)이며 병렬 슬롯 수로 나뉩니다 — 기본 4 슬롯이면 호출당 2048 토큰이므로, 호출당 더 긴 컨텍스트가 필요하면 컨텍스트를 늘리거나 `OSWL_EMBEDDED_AI_PARALLEL`을 낮추세요. +기존 AI 설정 관리 권한을 사용합니다. 시작하면 실제 모델이 LOCAL로 등록되고 다른 활성 제공자는 비활성화됩니다. 중지는 LOCAL도 비활성화합니다. 다른 제공자 선택만으로 내장 프로세스가 중지되지는 않으므로 메모리를 반환하려면 따로 중지하세요. ---- +기본 우선순위는 Qwen3.5-2B Q4_K_M → 다른 Qwen3 → Gemma 4 E2B → 나머지 호환 GGUF입니다. 명시적 선택과 유효한 저장값이 더 우선합니다. 다른 호환 모델을 추가할 때는 해당 모델의 사양과 라이선스를 확인하세요. -## 모델 폴더 변경 +폴더를 바꿀 때는 llama/와 model/을 담는 최상위 경로를 저장합니다. 경로 변경 시 관리 중인 서버는 중지됩니다. 파일 이동도 잠금을 피하기 위해 중지 후 수행하세요. Linux 실행 파일에는 실행 권한이 필요합니다. -OsWL이 다른 디렉터리를 바라보게 하는 방법은 두 가지입니다: +## CPU 기본값 -| 방법 | 적용 범위 | +| Environment variable | Default | |---|---| -| 내장 AI 카드의 **폴더** 입력 + **저장** | DB에 저장(`ai_preferences.embedded_dir` / `embedded_model`); 기본값보다 우선 | -| `OSWL_EMBEDDED_AI_DIR` 환경 변수 / `oswl.ai.embedded.dir` yaml | UI에 저장된 폴더가 없을 때 사용되는 기본값 | +| `OSWL_EMBEDDED_AI_DIR` | `embedded-ai` | +| `OSWL_EMBEDDED_AI_PORT` | `11435` | +| `OSWL_EMBEDDED_AI_GPU_LAYERS` | `0` | +| `OSWL_EMBEDDED_AI_THREADS` | `1` | +| `OSWL_EMBEDDED_AI_PARALLEL` | `1` | +| `OSWL_EMBEDDED_AI_CONTEXT` | `8192` | +| `OSWL_EMBEDDED_AI_FLASH_ATTN` | `true` | +| `OSWL_EMBEDDED_AI_CACHE_REUSE` | `256` | +| `OSWL_EMBEDDED_AI_STARTUP_TIMEOUT_SEC` | `120` | +| `OSWL_EMBEDDED_AI_EXTRA_ARGS` | empty | +| `OSWL_EMBEDDED_AUTO_DOWNLOAD` | `true` | -`PUT /api/settings/ai/embedded/config`에 적용되는 규칙: +CPU 전용 `-ngl 0`과 생성 슬롯 1개를 명시합니다. `LLAMA_ARG_CHAT_TEMPLATE_KWARGS={"enable_thinking":false}`와 reasoning budget 0을 함께 사용합니다. 예산만 0으로 설정하면 새 모델은 빈 답변을 반환할 수 있습니다. 4 vCPU에서는 웹·스캔 지연 확인 후 스레드 2개를 검토하세요. 스레드 수는 CPU 사용률 상한이 아니므로 필요하면 OS·컨테이너 제한을 적용합니다. -* 폴더는 **미리 존재**해야 합니다 — 없으면 저장이 거부됩니다(`400`, "폴더가 없거나 디렉터리가 아닙니다"). -* 사이드카 **실행 중에 폴더를 바꾸면** 먼저 사이드카가 중지되고(실행 중인 `llama-server`가 기존 디렉터리의 파일 잠금을 잡고 있기 때문) LOCAL 프로바이더가 비활성화됩니다. 새 폴더에서 다시 시작하세요. -* 폴더 입력을 비우면(공백) 오버라이드가 제거되어 설정 기본값으로 돌아갑니다. +전체 컨텍스트는 슬롯 간 분할됩니다. `OSWL_AI_MAX_PARALLEL_CALLS` 기본값 3은 **스캔별** 제한입니다. 서버는 한 슬롯으로 생성해도 대기 요청이 비스트리밍 읽기 제한 90초를 초과할 수 있습니다. 소형 서버는 이 값을 1로 설정하고 스캔 중첩을 피하세요. 서버 전체 작업 접수 제한 기능은 아닙니다. ---- +## 무결성·폐쇄망·GitHub 배포 -## 자동 폴백 +기본 다운로드는 위 업스트림 고정 리비전을 사용합니다. 기존 GitHub `models-v1` 자산은 Qwen3 1.7B이므로 새 기본 모델 주소로 재사용하면 안 됩니다. 이 변경으로 새 GitHub 모델 자산을 게시하거나 존재한다고 가정하지 않습니다. -시작 버튼 한 번으로 여러 모델을 순서대로 시도할 수 있습니다. 요청/저장된 모델 → 내장 선호 순서 → 나머지 `.gguf` 순으로 후보를 시도하며, 각 후보는 최대 **120초** 안에 healthy 상태가 되어야 합니다. GPU 오프로드가 켜진 상태(기본값)에서 후보의 GPU 시작이 실패하면, OsWL은 먼저 같은 모델을 CPU 전용으로 1회 재시도한 뒤 다음 후보로 넘어갑니다. 크래시나 시간 초과된 모델도 건너뛰고 다음으로 넘어갑니다. +`OSWL_EMBEDDED_DEFAULT_MODEL_URL`, `OSWL_EMBEDDED_DEFAULT_MODEL_SHA256`, `OSWL_EMBEDDED_DEFAULT_MODEL_SIZE_BYTES`는 같은 파일을 가리켜야 합니다. 부분 다운로드의 해시와 크기를 모두 검증한 뒤 설치합니다. `OSWL_EMBEDDED_FALLBACK_MODEL_URL`은 기본적으로 비어 있으며 바이트 단위로 동일한 미러만 지정하세요. 미러도 기본 Qwen 이름으로 저장하므로 다른 모델은 실제 이름으로 직접 설치합니다. -첫 번째 후보가 아닌 모델로 실행된 경우, 카드의 모델 선택 옆에 주황색 **"대체 모델로 실행 중"** 배지가 표시됩니다. 보통 선호 모델 로딩에 실패했다는 뜻이며(모델이 너무 크거나 다운로드가 깨진 경우), **실행 모델** 항목에서 실제로 돌고 있는 모델을 확인할 수 있습니다. +폐쇄망에서는 다운로드하지 않습니다. 실행 파일·검증한 가중치·라이선스 고지를 함께 전달하세요. Docker에도 포함되지 않으므로 최상위 폴더를 마운트하고 **Linux용** 실행 파일을 준비합니다. 자동 다운로드에는 쓰기 권한이 필요합니다. Windows 실행 파일은 Linux 컨테이너에서 쓸 수 없습니다. ---- - -## 상태 및 로그 - -`GET /api/settings/ai/embedded`는 `running`, `external`, `binaryFound`, `activeModel`, `fallbackUsed`, `lastError`, `availableModels`, `modelsDir`, `baseUrl`을 반환하며, 시작 버튼으로 트리거된 기본 모델 다운로드가 진행 중일 때는 `downloading`, `downloadedBytes`, `downloadTotalBytes`도 함께 반환합니다. - -`external`은 설정된 포트에서 OsWL이 직접 시작하지 않은 무언가(수동으로 띄운 `llama-server`, 또는 이전 OsWL 프로세스·크래시로 남겨진 고아 프로세스)가 이미 `/health`에 응답 중일 때 `true`가 됩니다. 이 경우에도 `running`은 `true`로 유지됩니다 — 엔드포인트 자체는 LOCAL 프로바이더로 정상 사용 가능하기 때문입니다 — 하지만 **중지** 버튼을 눌러도 OsWL이 소유하지 않은 프로세스는 종료할 수 없어 계속 실행 상태로 남고 상태 응답도 `external: true`를 유지합니다. - -`POST /api/settings/ai/embedded/start`는 Qwen3 다운로드를 기다리는 대신 트리거만 하고 즉시 응답합니다(`success: true`, `downloading: true`) — 설정 페이지가 상태를 폴링하며 진행률과 최종 `running`/`lastError` 결과를 표시합니다. 다운로드 자체는 브라우저 세션과 무관하게 서버에서 진행되므로, 페이지를 새로고침하거나 닫아도 취소되지 않고 다시 열으면 진행 상황이 이어서 표시됩니다. - -`llama-server`의 stdout/stderr는 **`/llama-server.log`**에 기록됩니다. 시작에 실패하면 이 로그의 마지막 몇 줄이 `lastError`에 포함되어 카드에 빨간색으로 표시되며, 자세한 내용은 파일 전체를 확인하세요. - ---- +`embedded-ai/`는 Git·Docker 빌드 컨텍스트에서 제외되며 배포 JAR에 모델·실행 파일이 포함되지 않습니다. Qwen3.5·Gemma 4는 Apache 2.0, llama.cpp는 MIT입니다. 라이선스 전문·출처·제공되는 업스트림 고지·Unsloth GGUF Q4_K_M 양자화 사실을 유지하세요. 실행 파일 재배포 시 동봉 라이브러리의 고지도 보존해야 합니다. ## 문제 해결 -| 증상 | 원인 및 해결 | -|---|---| -| "llama-server 바이너리를 찾을 수 없습니다" | 카드에 표시된 폴더(또는 그 `bin/` 하위, `PATH`)에 `llama-server(.exe)`를 넣으세요 — 이건 자동으로 받아지지 않습니다 | -| 모델이 없는데 시작을 눌러도 반응이 없어 보임 | 인터넷 연결을 확인하세요 — 기본 모델 다운로드에 최초 1회 인터넷이 필요합니다. 폐쇄망 환경이면 `.gguf` 파일을 직접 모델 폴더에 넣으세요 | -| "모델 다운로드 실패" / 체크섬 불일치 | 다운로드 도중 네트워크가 끊기거나 손상됐습니다 — 손상된 파일은 자동 삭제되니 **시작**을 다시 누르면 재시도됩니다 | -| 로그에 `failed to open GGUF file` | 설정의 폴더와 모델이 실제 있는 위치가 다릅니다 — **폴더** 입력값과 파일 이름이 드롭다운 항목과 일치하는지 확인 | -| "did not become healthy within 120s" | 기기가 느리거나 모델이 너무 큽니다 — 더 작은 양자화 모델(예: 1B~2B급 Q4_K_M `.gguf`)로 시도하거나, `OSWL_EMBEDDED_AI_STARTUP_TIMEOUT_SEC`을 늘리세요. `(GPU)` 실패는 CPU 전용으로 자동 재시도되므로, 이 메시지가 지속되면 CPU 재시도도 실패한 것입니다 | -| 로그에 `slotContext … is below 2048` 경고 | 병렬 슬롯이 컨텍스트 크기에 비해 너무 많습니다 — `OSWL_EMBEDDED_AI_CONTEXT`를 늘리거나 `OSWL_EMBEDDED_AI_PARALLEL`을 낮추세요 | -| 포트가 이미 사용 중 | 다른 프로세스(또는 수동으로 띄운 `llama-server`)가 포트를 점유 중입니다 — 종료하거나 `OSWL_EMBEDDED_AI_PORT`를 변경하세요. 해당 포트에서 이미 healthy한 서버가 응답하면 "실행 중"으로 간주되며 상태 응답에 `external`로 표시됩니다 | -| **중지**를 눌러도 카드가 꺼지지 않음 | 실행 중인 서버가 `external`(이 OsWL 인스턴스가 시작하지 않음) 상태입니다 — 해당 프로세스를 직접 종료하세요(또는 실행 중인 머신/컨테이너를 재시작). OsWL은 이 프로세스를 종료할 수 없습니다 | -| 저장 시 "폴더가 없거나 디렉터리가 아닙니다" | 디렉터리를 먼저 만드세요 — 저장은 존재하는 폴더만 허용됩니다 | - ---- - -## 보안 및 프라이버시 - -* 사이드카는 `127.0.0.1`에만 바인딩되고 **API 키가 필요 없습니다** — 네트워크에 아무것도 노출되지 않습니다. -* `llama-server.log`에 보이는 llama.cpp의 **CORS 경고**는 localhost만 수신하기 때문에 예상된 것이며 무해합니다. -* 텔레메트리가 없습니다. 모델로 전송되는 프롬프트, 코드 조각, CVE 데이터는 기기를 벗어나지 않습니다. - ---- - -## REST API 요약 - -[API 레퍼런스 — AI](API-Reference.md#ai) 참고. 인터랙티브 스키마는 Swagger UI(`local` 프로파일)에서 확인할 수 있습니다. +- 실행 파일 없음: llama/ 위치·OS·아키텍처·라이브러리·실행 권한을 확인하세요. +- 빈 답변: 호환 버전과 명시적인 비추론 템플릿 옵션을 확인하세요. +- 시작 실패: llama-server.log에서 모델 지원·RAM 부족을 확인하세요. +- 다른 모델 실행: 중지·선택·저장·시작 후 대체 표시를 확인하세요. +- 시간 초과: 동시 스캔·호출 수와 CPU 크레딧을 확인하세요. +- 다운로드 실패: URL·크기·SHA-256을 함께 확인하세요. diff --git a/docs/ko/Getting-Started.md b/docs/ko/Getting-Started.md index 988b258d..36a3a35b 100644 --- a/docs/ko/Getting-Started.md +++ b/docs/ko/Getting-Started.md @@ -8,11 +8,11 @@ | 구성 요소 | 요구사항 | |---|---| -| **JDK** | 25 이상 | +| **JDK** | 25 | | **빌드 도구** | Gradle Wrapper (포함 — `./gradlew`) | | **데이터베이스** | H2 파일 모드 (로컬/개발) 또는 PostgreSQL 15 이상 (운영) | | **OS** | Linux, macOS, Windows | -| **메모리** | 최소 512MB, 1GB 이상 권장 | +| **메모리** | 스캔 작업량에 따라 다르며, 내장 AI에는 추가 메모리 필요 | > Node.js나 npm은 필요 없습니다 — Tailwind CSS 독립 실행형 바이너리가 첫 번째 빌드 시 Gradle에 의해 자동으로 다운로드됩니다. @@ -27,58 +27,38 @@ git clone https://github.com/SalkCoding/Oswl.git cd Oswl ``` -### 2. 프로파일 선택 +### 2. 프로필 선택 -OsWL은 두 가지 Spring 프로파일을 제공합니다: +OsWL은 두 가지 Spring 프로필을 제공합니다: -| 프로파일 | 데이터베이스 | 용도 | +| 프로필 | 데이터베이스 | 용도 | |---|---|---| | `local` *(기본값)* | H2 파일 (`./oswl-db.mv.db`) | 개발 및 평가 | | `prod` | PostgreSQL | 운영 배포 | ### 3. 애플리케이션 시작 -**로컬 (H2, 설정 없음):** +로컬 개발에서는 `./gradlew bootRun`을 실행합니다(PowerShell: `.\gradlew.bat bootRun`). 기본 `local` 프로필은 H2와 고정된 개발용 암호화 키를 사용합니다. 개발 환경 밖에서는 별도의 영구 키를 사용하고, 개발용 키를 운영 환경에 복사하지 마세요. -```bash -./gradlew bootRun -``` - -**운영 (PostgreSQL):** +운영 배포에서는 `./gradlew bootJar verifyProdJar`로 배포용 JAR를 빌드하고 `prod` 프로필로 실행합니다. `DB_URL`, `DB_USERNAME`, `DB_PASSWORD`와 Base64 디코딩 시 32바이트인 영구 `OSWL_ENCRYPTION_KEY`를 설정하세요. 키는 `openssl rand -base64 32`로 한 번 생성한 뒤 재시작과 복원 시에도 동일하게 유지합니다. `prod`는 스키마를 자동 생성하지 않고 검증하므로 PostgreSQL 스키마를 먼저 준비해야 합니다. -```bash -export SPRING_PROFILES_ACTIVE=prod -export DB_URL=jdbc:postgresql://localhost:5432/oswl -export DB_USERNAME=oswl -export DB_PASSWORD=changeme -export OSWL_ENCRYPTION_KEY=$(openssl rand -base64 32) - -./gradlew bootRun -``` +배포 절차는 [운영 배포 체크리스트](Production-Deployment-Checklist.md)와 [Docker 실행 안내](../../deploy/README.md)를 참고하세요. `bootRun`과 `java -jar`는 `.env` 파일을 자동으로 읽지 않습니다. 실행 셸에서 환경변수를 설정하거나 Spring 설정 파일을 사용하고, Compose에서는 `--env-file`을 지정하세요. -> **`OSWL_ENCRYPTION_KEY`** — VCS 등 저장 비밀 보호용. `local`에서는 개발용 키가 자동 생성될 수 있습니다. **`prod`에서는 기동 전 필수**이며, 없으면 애플리케이션이 시작되지 않습니다. 키 분실 시 기존 저장 자격증명을 사용할 수 없습니다. - -애플리케이션은 기본적으로 포트 **8080**에서 시작됩니다. - ---- +기본 애플리케이션 포트는 **8080**입니다. 필요한 메모리는 작업량에 따라 달라지며, 로컬 AI에는 모델과 컨텍스트를 위한 추가 메모리가 필요합니다. ## 임베디드 AI 모델 (최초 실행) -OsWL은 임베디드 llama.cpp 사이드카를 통해 AI 기능을 완전한 온프레미스로 실행할 수 있습니다. 최초 기동 시 `embedded-ai/`에 `.gguf` 모델이 없으면, OsWL이 기본 **Qwen3-1.7B** 모델(~1.2GB)을 백그라운드에서 자동으로 다운로드합니다: +현재 기본 모델은 **Qwen3.5-2B Q4_K_M**입니다(1,280,835,840바이트, 약 1.28GB). Hugging Face의 `unsloth/Qwen3.5-2B-GGUF`에서 고정된 리비전의 파일을 내려받아 `embedded-ai/model/Qwen/`에 저장하고 설정된 SHA-256으로 검증합니다. llama.cpp 실행 파일은 별도로 설치해 `embedded-ai/llama/` 또는 `PATH`에 둡니다. -* 업스트림 Hugging Face 저장소(`ggml-org/Qwen3-1.7B-GGUF`)에서 받으며 SHA-256 무결성을 검증합니다. 서드파티 호스트 의존을 피하려면 `OSWL_EMBEDDED_DEFAULT_MODEL_URL`을 자체 호스팅 미러로 지정하세요. -* 다운로드만 수행합니다 — 사이드카를 시작하거나 활성 AI 프로바이더를 변경하지 않습니다. 진행률은 **설정 → AI**에서 확인할 수 있으며, 파일이 준비되면 거기서 **시작**을 누르세요. -* `OSWL_EMBEDDED_AUTO_DOWNLOAD=false`로 끌 수 있습니다. 에어갭 모드에서는 다운로드를 시도하지 않습니다(아래 참조). - -`llama-server(.exe)` 바이너리만 수동 단계입니다 — [llama.cpp releases](https://github.com/ggml-org/llama.cpp/releases)에서 받아 `embedded-ai/`(또는 `PATH`)에 두세요. 자세한 내용은 [임베디드 AI](Embedded-AI.md)를 참조하세요. - ---- +모델이 없고 `OSWL_EMBEDDED_AUTO_DOWNLOAD=true`이면 기동 시 백그라운드에서 미리 다운로드합니다. 이 작업은 사이드카를 시작하거나 AI 제공자를 활성화하지 않습니다. 실행 파일을 설치한 뒤 **설정 → AI**에서 시작하세요. 기동 시 미리 받기를 끄려면 값을 `false`로 설정합니다. 폐쇄망 모드에서는 모델 자동 다운로드가 비활성화됩니다. 모델 탐색·설정·문제 해결은 [내장 AI](Embedded-AI.md)를 참고하세요. ## 에어갭(오프라인) 환경에서 시작하기 +폐쇄망 모드는 지원되는 취약점 정보원을 스냅샷으로 전환하는 기능이며 네트워크 방화벽이 아닙니다. VCS·SMTP·웹훅·AI 주소는 환경에 맞게 별도로 설정하세요. + 아웃바운드 인터넷 접속이 없는 호스트의 경우: -1. 기동 전 `OSWL_AIRGAPPED_ENABLED=true`를 설정합니다. 취약점/위협 인텔 조회(OSV, deps.dev, EPSS, KEV)는 import된 오프라인 스냅샷에서 제공되며 아웃바운드 HTTP를 시도하지 않고, 임베디드 모델 자동 다운로드도 건너뜁니다. +1. 기동 전 `OSWL_AIRGAPPED_ENABLED=true`를 설정합니다. 취약점/위협 인텔 조회(OSV, deps.dev, EPSS, KEV)는 가져온 오프라인 스냅샷에서 제공되며 아웃바운드 HTTP를 시도하지 않고, 임베디드 모델 자동 다운로드도 건너뜁니다. 2. 인터넷에 연결된 머신에서 스냅샷 번들을 빌드합니다: ```bash @@ -93,7 +73,7 @@ OsWL은 임베디드 llama.cpp 사이드카를 통해 AI 기능을 완전한 온 ## 설정 마법사 -최초 실행 시(빈 데이터베이스), OsWL은 모든 요청을 `http://localhost:8080/setup`으로 리다이렉트합니다. +최초 실행 시(빈 데이터베이스), OsWL은 일반 애플리케이션 페이지 요청을 `http://localhost:8080/setup`으로 리다이렉트합니다. 마법사에서 수집하는 정보: @@ -111,35 +91,24 @@ OsWL은 임베디드 llama.cpp 사이드카를 통해 AI 기능을 완전한 온 ## 첫 로그인 -1. `http://localhost:8080/login`으로 이동합니다. -2. 설정 마법사에서 생성한 이메일과 비밀번호를 입력합니다. -3. **이중 인증(2FA)**이 활성화된 경우(관리자 설정 가능), 이메일로 발송된 6자리 OTP를 입력하라는 메시지가 표시됩니다. - * `local` 모드에서는 OTP가 서버 로그에 표시됩니다: `*** OTP CODE: NNNNNN ***` - * 개발용 단축키: 테스트 프로파일 사용 시 `000000`이 허용됩니다. -4. 임시 비밀번호로 첫 로그인 시, OsWL이 즉시 비밀번호 변경을 강제합니다. - ---- +1. `http://localhost:8080/login`에서 설정 마법사로 만든 계정으로 로그인합니다. +2. 이메일 2단계 인증이 필요한 경우 현재 세션에 발급된 6자리 코드를 입력합니다. 코드는 3분간 유효하며, 재발송에는 60초의 대기 시간이 있습니다. +3. 기본 로컬 GreenMail SMTP 설정에서는 수신한 코드가 서버 로그의 `*** OTP CODE: ... ***`에 표시됩니다. 외부 SMTP를 설정했다면 수신자 메일함에서 확인하세요. 고정 코드 `000000`으로 인증을 우회하는 기능은 없습니다. +4. 비밀번호 변경 대상으로 지정된 계정은 새 비밀번호를 설정해야 계속 사용할 수 있습니다. ## 테스트 데이터 시드 (로컬 전용) -로그인 후 다음을 호출하세요: - -``` -GET http://localhost:8080/data/test -``` +설정 마법사를 완료한 뒤 `GET /data/test`를 호출하면 기존 프로젝트·스캔·라이브러리 데이터를 삭제하고 `DemoImportCatalog`에 등록된 공개 저장소의 실제 Quick Import 작업을 큐에 추가합니다. 프로젝트 화면으로 이동한 후에도 가져오기는 비동기로 진행되며 네트워크 연결이 필요합니다. 고정 테스트 계정이나 미리 정해진 취약점 목록을 생성하는 기능은 아닙니다. -이 엔드포인트(**`local` 프로파일에서만** 사용 가능): +이 개발용 엔드포인트는 local 소스 세트의 `local`/`test` 프로필에서 제공하며 운영 JAR에는 포함되지 않습니다. `local`에서는 `/data/**`에 인증 없이 접근할 수 있으므로, 데이터를 초기화하는 이 기능은 격리된 개발 인스턴스에서만 사용하세요. -* 기존의 모든 프로젝트, 스캔, 라이브러리, CVE를 삭제합니다. -* Maven과 npm 에코시스템의 여러 프로젝트, 다양한 심각도의 수십 개 CVE, 혼합된 라이선스 상태, 트렌드 시각화를 위한 다수의 히스토리 스캔이 포함된 풍부한 현실적 데이터셋으로 DB를 재구성합니다. +`GET /data/test-api-key`는 현재 첫 번째 프로젝트에 연결된 API 키를 발급합니다. 프로젝트가 없으면 404를 반환하므로 데모 가져오기로 프로젝트가 생성될 때까지 기다리세요. -테스트 API 키도 다음에서 확인할 수 있습니다: +## UI/접근성 테스트 하네스 (개발자 전용) -``` -GET http://localhost:8080/data/test-api-key -``` +`./gradlew uiTest`는 실제 애플리케이션을 대상으로 기존 Playwright 브라우저 테스트를 실행합니다. 접근성 테스트는 지정된 페이지에서 axe-core 검사를 수행하고, 다른 테스트는 화면 조작과 요청 흐름 등을 확인합니다. 필요한 경우 Chromium을 다운로드하며, `test`/`check`와 별도로 실행합니다. ---- +JUnit HTML 리포트는 `build/reports/tests/uiTest/`, axe 리포트는 `build/reports/axe/`에 저장됩니다. 폐쇄망에서는 브라우저 실행 파일과 빌드 의존성을 미리 준비하고, 필요한 경우 `PLAYWRIGHT_BROWSERS_PATH`를 지정하세요. 브라우저 설치만으로 빌드 의존성까지 준비되지는 않습니다. ## 접근 제어 (권장) diff --git a/docs/ko/Glossary.md b/docs/ko/Glossary.md index 43570151..88b9a1bd 100644 --- a/docs/ko/Glossary.md +++ b/docs/ko/Glossary.md @@ -170,7 +170,7 @@ OsWL은 사용자당 하나의 활성 세션만 허용합니다. 다른 브라 오픈소스 패키지에 초점을 맞춘 Google 호스팅 취약점 데이터베이스 및 API([osv.dev](https://osv.dev)). OsWL은 영향받는 버전 범위와 수정 버전을 포함한 어드바이저리 데이터를 OSV에서 조회합니다. **OTP (일회용 비밀번호, One-Time Password)** -2FA 인증의 두 번째 요소로서 사용자 이메일 주소로 발송되는 6자리 코드. 로컬 개발 프로파일에서 OTP 코드는 서버 로그에 `*** OTP CODE: NNNNNN ***`으로 표시됩니다. +2FA 인증의 두 번째 요소로서 사용자 이메일 주소로 발송되는 6자리 코드. 로컬 개발 프로필에서 OTP 코드는 서버 로그에 `*** OTP CODE: NNNNNN ***`으로 표시됩니다. **의존성 경로 (Dependency Path)** 루트 프로젝트에서 특정 라이브러리까지 이어지는 패키지 체인. 하나의 라이브러리는 여러 경로(직접 및/또는 전이적)로 도달할 수 있습니다. OsWL은 컴포넌트별로 해결된 모든 경로를 기록하고 표시합니다. diff --git a/docs/ko/Home.md b/docs/ko/Home.md index deb8a4f8..b13c4223 100644 --- a/docs/ko/Home.md +++ b/docs/ko/Home.md @@ -1,5 +1,7 @@ # OsWL 문서 +[1.0.5.1 변경 사항](Whats-New-v1.0.5.1.md) + **OsWL** (Open-source Software Watchlist) 문서 허브에 오신 것을 환영합니다. OsWL은 단일 마이크로서비스부터 전체 제품 포트폴리오까지 모든 OSS 의존성의 CVE 취약점과 라이선스 컴플라이언스를 팀이 한 곳에서 추적할 수 있도록 하는 사내 **SCA(Software Composition Analysis)** 플랫폼입니다. @@ -8,6 +10,7 @@ OsWL은 단일 마이크로서비스부터 전체 제품 포트폴리오까지 ## 탐색 + | 페이지 | 내용 | |---|---| | [v1.0.4 새로운 기능](Whats-New-v1.0.4.md) | SBOM / VEX / SARIF 내보내기, CI 게이트, 연속 모니터링, 조직 대시보드, 폐쇄망 모드 | @@ -24,13 +27,13 @@ OsWL은 단일 마이크로서비스부터 전체 제품 포트폴리오까지 | [관리](Administration.md) | 사용자 관리, 역할, 감사 로그, 보안 및 SMTP 설정 | | [권한 레이어](Authorization-Layers.md) | 역할 템플릿 vs 프로젝트 멤버십 | | [프로젝트 접근 제어](Project-Access-Control.md) | ACL 기술 참고 | -| [운영 배포 체크리스트](Production-Deployment-Checklist.md) | `prod` 프로파일 출시 전 점검 | +| [운영 배포 체크리스트](Production-Deployment-Checklist.md) | `prod` 프로필 출시 전 점검 | | [데이터베이스 스키마](Database-Schema.md) | `ddl-auto` 전략 및 `db/` 마이그레이션 | | [스캔 API 보안](Scan-Api-Security.md) | CLI 스캔 제출 보호 개요 | | [API 레퍼런스](API-Reference.md) | 전체 REST 엔드포인트 목록 | | [용어사전](Glossary.md) | OsWL 모든 용어 정의 | -> 영어 문서는 [`docs/`](../Home.md)에, 일본어 문서는 [`docs/ja/`](../ja/Home.md)에 있습니다. +> 영어 문서는 [`docs/en/`](../en/Home.md)에, 일본어 문서는 [`docs/ja/`](../ja/Home.md)에 있습니다. --- @@ -64,8 +67,8 @@ OsWL은 단일 마이크로서비스부터 전체 제품 포트폴리오까지 ## 도움 받기 -* **Swagger UI** (로컬 프로파일만): `http://localhost:8080/swagger-ui.html` -* **H2 콘솔** (로컬 프로파일만): `http://localhost:8080/h2-console` +* **Swagger UI** (로컬 프로필만): `http://localhost:8080/swagger-ui.html` +* **H2 콘솔** (로컬 프로필만): `http://localhost:8080/h2-console` * **이슈**: [GitHub Issues](https://github.com/SalkCoding/Oswl/issues) 운영 환경에서는 API 문서와 H2 콘솔이 비활성화됩니다. 이 문서와 [API 레퍼런스](API-Reference.md)를 참고하세요. diff --git a/docs/ko/Production-Deployment-Checklist.md b/docs/ko/Production-Deployment-Checklist.md index 3cdaba53..15137183 100644 --- a/docs/ko/Production-Deployment-Checklist.md +++ b/docs/ko/Production-Deployment-Checklist.md @@ -2,11 +2,11 @@ OsWL을 인터넷에 공개하기 전에 확인할 한 페이지 목록입니다. **`prod`를 `local` 기본값**(H2, Swagger, `/data/**`, 커밋된 암호화 키)으로 실행하지 마세요. -## 1. 프로파일 및 빌드 +## 1. 프로필 및 빌드 | 확인 | 조치 | |------|------| -| 프로파일 | `SPRING_PROFILES_ACTIVE=prod` 설정 | +| 프로필 | `SPRING_PROFILES_ACTIVE=prod` 설정 | | JAR | `./gradlew bootJar verifyProdJar`로 빌드 — JAR에 `TestDataController`가 **없어야** 함 | | 로컬 전용 코드 | `src/local/java`는 `bootRun`/개발용이며 `bootJar`에 포함되지 않음 | @@ -19,32 +19,32 @@ OsWL을 인터넷에 공개하기 전에 확인할 한 페이지 목록입니다 | `DB_PASSWORD` | DB 비밀번호 | | `OSWL_ENCRYPTION_KEY` | 인스턴스 암호화 키 (`openssl rand -base64 32`) | -`.env.prod.example` → `.env.prod` 복사 후 모든 값 입력. `application-prod.yaml`에는 DB·암호화 **기본값 없음**. +`deploy/docker/.env.prod.example` → `.env.prod` 복사 후 모든 값 입력. `application-prod.yaml`에는 DB·암호화 **기본값 없음**. -기동 시 누락 변수 등은 로그의 **`OSWL STARTUP WARNINGS`** 블록에 한 번에 출력됩니다. **`prod`에서 `OSWL_ENCRYPTION_KEY`가 없으면 기동 실패** — 출시 전 안정적인 키를 설정하세요. (`local` 프로파일은 개발 전용으로 임시 키를 사용할 수 있습니다.) +시작 후 설정 경고는 `OSWL STARTUP WARNINGS` 로그 블록에 모아서 출력됩니다. 운영 암호화 키 누락이나 잘못된 DB 설정 등은 이 블록이 출력되기 전에 시작 실패를 일으킬 수 있습니다. 운영에서는 고정된 `OSWL_ENCRYPTION_KEY`를 유지하세요. `local` YAML의 고정 대체 키는 개발 전용이며 운영에서 사용하면 안 됩니다. ## 3. 네트워크 바인딩 -| 확인 | 조치 | -|------|------| -| 기본 바인딩 | `SERVER_ADDRESS=127.0.0.1` (`application-prod.yaml`) | -| 공개 접근 | **nginx / Caddy / Traefik**(또는 클라우드 LB) 앞단, TLS 종료 | -| 직접 `0.0.0.0` | JVM HTTP 스택 노출을 감수할 때만; 방화벽·위험 문서화 | - -`docker-compose.prod.yml`은 기본 **`127.0.0.1:8080:8080`** 매핑. +호스트에서 JVM을 직접 실행하면 `application-prod.yaml`의 기본값인 `SERVER_ADDRESS=127.0.0.1`로 바인딩하며, 같은 호스트의 리버스 프록시가 연결할 수 있습니다. **Docker Compose에서는 컨테이너 내부의 `SERVER_ADDRESS=0.0.0.0`**을 사용해야 Docker가 애플리케이션으로 트래픽을 전달할 수 있습니다. 호스트 공개 범위는 별도입니다. `deploy/docker/compose.prod.yml`은 호스트 포트를 **`127.0.0.1:8080:8080`**에만 연결합니다. 운영 예제는 이 컨테이너 설정을 사용하며, 기존 `.env.prod`도 업그레이드 시 확인해야 합니다. -프록시가 `X-Forwarded-Proto`를 내면 HSTS·보안 쿠키를 위해 `server.forward-headers-strategy=framework` 사용 (`application.yaml` 기본). +리버스 프록시에서 TLS를 종료하세요. 제공된 호스트 루프백 포트 매핑을 사용할 때는 Docker 호스트에서 프록시를 실행합니다. 프록시도 컨테이너라면 공유 Docker 네트워크의 서비스 주소로 연결하세요. 전달 헤더는 신뢰하는 프록시에서 온 것만 수용해야 합니다. ## 4. Docker Compose (운영) +저장소 루트에서 실행합니다. 기존 `.env.prod` 값은 유지하고 새 설치에서만 템플릿을 복사합니다. 두 Compose 파일의 기본 프로젝트명은 `oswl`입니다. 기존 설치가 다른 프로젝트명을 사용했다면 `-p YOUR_EXISTING_PROJECT` 또는 `COMPOSE_PROJECT_NAME`으로 그 이름을 유지해야 기존 볼륨에 연결됩니다. [배포 파일 안내](../../deploy/README.md)를 참고하세요. + ```bash -cp .env.prod.example .env.prod +cp deploy/docker/.env.prod.example .env.prod # DB_*, OSWL_ENCRYPTION_KEY, SMTP_* 편집 -docker compose -f docker-compose.prod.yml up -d --build +docker compose --env-file .env.prod -f deploy/docker/compose.prod.yml up -d --build ``` +Compose는 `--env-file`로 `.env.prod`를 읽습니다. `java -jar`나 `bootRun`으로 직접 실행할 때는 이 파일을 자동으로 읽지 않으므로, 환경 변수를 내보내거나 서비스 관리 도구에 등록하세요. 운영 환경의 첫 시작 전에 DB 스키마를 준비해야 합니다(§9 참고). + 로그 확인: 누락 env 배너 없음, PostgreSQL 연결, H2/Swagger URL 없음. +`deploy/docker/compose.prod.yml`은 컨테이너 자체의 stdout/stderr(docker `json-file` 드라이버, 100MB × 10개)와 앱의 자체 회전 파일 로그(`oswl-logs-prod` 볼륨에 마운트) 둘 다 상한을 둡니다 — 후자는 §5 참고. + ## 5. 로깅 및 관측 | 확인 | 조치 | @@ -55,6 +55,17 @@ docker compose -f docker-compose.prod.yml up -d --build | 메트릭 스크랩 | Prometheus를 `/actuator/prometheus`로 지정 — 스크래퍼도 관리자 인증 필요 | | Actuator 인증 | **SYSTEM_ADMIN** 세션 필요 (공개 아님) | +### 로그 로테이션 및 요청 상관관계 + +`local`/`test`는 콘솔 전용입니다. `prod`에서는 `logback-spring.xml`이 회전 파일 로그를 추가로 기록합니다: + +| 변수 | 기본값 | 용도 | +|------|--------|------| +| `OSWL_LOG_DIR` | `./logs` (도커: `/var/log/oswl`, §4 참고) | `oswl.log` 저장 디렉터리. 100MB 또는 하루 단위로 회전, 최대 30개 파일 보관, 전체 5GB 상한. | +| `OSWL_LOG_JSON` | `false` | `true`로 설정하면 파일(콘솔 아님)이 한 줄당 JSON 오브젝트 하나로 바뀝니다 — 로그 수집기를 여기에 연결해 SIEM으로 보내세요. | + +모든 요청에는 `requestId`가 찍히고(응답 헤더 `X-Request-Id`로도 반환), 인증된 요청이라면 `userId`도 함께 찍힙니다 — 둘 다 MDC를 통해 해당 요청의 모든 로그 라인에 나타나므로(평문 모드는 `[req=...] [user=...]`, JSON 모드는 최상위 필드), 특정 요청을 언급하는 지원 티켓을 타임스탬프로 grep하지 않고도 로그 전체에서 추적할 수 있습니다. + ## 6. 운영에서 활성화되는 보안 - Springdoc / Swagger UI: **비활성** @@ -75,7 +86,7 @@ docker compose -f docker-compose.prod.yml up -d --build | 변수 | 기본값 | 활성화 시 동작 | |---|---|---| -| `OSWL_FLYWAY_ENABLED` | `false` | `baseline-on-migrate` 기반 버전 마이그레이션. 먼저 전체 베이스라인을 생성해야 합니다 | +| `OSWL_FLYWAY_ENABLED` | `false` | 제공되는 V1 및 이후 마이그레이션 실행. 기존 스키마는 먼저 검토 | | `OSWL_AIRGAPPED_ENABLED` | `false` | 취약점·위협 인텔 조회를 반입된 오프라인 스냅샷에서 처리, 외부 HTTP 없음 | | `OSWL_GATE_*` | [v1.0.4 새로운 기능](Whats-New-v1.0.4.md) 참고 | `POST /api/scan/gate`의 기본 임계값 | @@ -98,6 +109,8 @@ docker compose -f docker-compose.prod.yml up -d --build ### 7.1 폐쇄망 / 오프라인 스냅샷 (v1.0.4) +이 모드는 지원되는 취약점·위협 정보 피드를 스냅샷으로 전환하는 기능이며 네트워크 방화벽은 아닙니다. 폐쇄망에서는 VCS, SMTP, 웹훅, 외부 AI 프로바이더도 별도로 설정하세요. + `OSWL_AIRGAPPED_ENABLED=true`로 설정하면 취약점·위협 인텔 조회(OSV, deps.dev, EPSS, CISA KEV)를 실시간 외부 API 대신 반입된 오프라인 스냅샷에서 처리합니다. 보강을 위한 아웃바운드 HTTP는 시도되지 않습니다. | 단계 | 조치 | @@ -105,7 +118,7 @@ docker compose -f docker-compose.prod.yml up -d --build | 1. 번들 제작 | 인터넷에 연결된 머신에서 `oswl-vdb` 빌더를 실행합니다. 래퍼 스크립트: `scripts/oswl-vdb/oswl-vdb.sh`(Linux/macOS) 또는 `scripts/oswl-vdb/oswl-vdb.ps1`(Windows). 둘 다 `./gradlew vdbBuild --args="..."`를 호출합니다. | | 2. 번들 대상 선정 | 타깃 인스턴스가 실제로 스캔한 컴포넌트를 `GET /api/admin/snapshot/wanted-list`(SYSTEM_ADMIN)로 낸 뒤 `build --wanted wanted-list.jsonl`에 넘기세요. 빌더는 전체 업스트림 미러 대신 실제 사용 컴포넌트만 가져옵니다. | | 3. 번들 반입 | `POST /api/admin/snapshot/import?mode=replace|merge`(multipart `.zip`). 큰 번들은 `OSWL_AIRGAPPED_IMPORT_DIR` 화이트리스트 디렉터리를 설정한 뒤 `POST /api/admin/snapshot/import-from-path`에 `{"path":"bundle.zip","mode":"merge"}`로 요청하세요. | -| 4. 모델 배치 (내장 AI 사용 시) | 폐쇄망 호스트는 자동 다운로드를 비활성화합니다. `.gguf` 파일을 `embedded-ai/`에 직접 넣거나 내부 미러를 운영하세요(§8 참고). | +| 4. 모델 배치(내장 AI 사용 시) | 오프라인 서버를 시작하기 전에 `embedded-ai/llama/`에 실행 파일, `embedded-ai/model/<계열>/`에 검증한 모델을 반입하세요. 내부 미러를 지정해도 에어갭 모드의 다운로드는 비활성화됩니다. §8 참고. | `oswl-vdb build` 옵션(`VdbBuilderCli` 참고): - `--sources osv,epss,kev,depsdev`(기본값 전체). @@ -118,48 +131,23 @@ docker compose -f docker-compose.prod.yml up -d --build - `merge`는 `(source, entry_key)` 기준으로 upsert하고 `"_deleted":true` 라인은 삭제로 처리합니다. - v2 번들은 `meta.json`에 기록된 파일별 SHA-256 체크섬을 검증하며, 불일치 시 전체 번들을 거부하고 기존 스토어를 변경하지 않습니다. -정의 최신성(E7): `OSWL_AIRGAPPED_STALENESS_WARN_DAYS`(기본값 `7`)와 `OSWL_AIRGAPPED_STALENESS_CRITICAL_DAYS`(기본값 `30`)는 반입된 스냅샷의 소스별 `sourceAsOf` 날짜 중 가장 오래된 값을 기준으로 관리 UI 배지를 결정합니다. +정의 최신성: `OSWL_AIRGAPPED_STALENESS_WARN_DAYS`(기본값 `7`)와 `OSWL_AIRGAPPED_STALENESS_CRITICAL_DAYS`(기본값 `30`)는 반입된 스냅샷의 소스별 `sourceAsOf` 날짜 중 가장 오래된 값을 기준으로 관리 UI 배지를 결정합니다. 번들이 50MB를 초과하면 `OSWL_MULTIPART_MAX_FILE_SIZE` / `OSWL_MULTIPART_MAX_REQUEST_SIZE`(기본값 각 `50MB`)를 조정해야 할 수 있습니다. -## 8. 내장 AI 모델 (선택, 온프레미스) - -**내장 AI**(설정 → AI → 로컬)를 클라우드 프로바이더 대신 또는 함께 쓸 계획일 때만 해당됩니다. +## 8. 내장 AI (선택, CPU 전용) -| 확인 | 조치 | -|------|------| -| 서버 바이너리 | [llama.cpp releases](https://github.com/ggml-org/llama.cpp/releases)에서 플랫폼에 맞는 `llama-server(.exe)`를 받아 `embedded-ai/`(또는 그 하위 `bin/`, 혹은 `PATH`)에 배치 — 유일한 수동 단계입니다 | -| 모델 | 별도 조치 불필요 — 신규 설치에서 **시작**을 누르면 Apache 2.0 라이선스인 Qwen3-1.7B 모델(~1.2GB)이 자동으로 다운로드됩니다(SHA256 검증, UI에 진행률 표시) | -| 폐쇄망 환경 | 자동 다운로드는 최초 1회 아웃바운드 인터넷 접근이 필요합니다. 인터넷이 없다면 시작을 누르기 전 직접 받은 `.gguf` 파일을 `embedded-ai/`에 미리 넣어두세요 | -| 커스텀 모델 | OsWL이 번들/자동 다운로드하는 것은 Qwen3-1.7B뿐입니다. 다른 `.gguf`(다른 크기나 라이선스)를 쓰고 싶다면 해당 모델의 라이선스를 직접 확인한 뒤 `embedded-ai/`에 넣으세요 — [내장 AI](Embedded-AI.md) 참고 | -| 디렉터리 | 기본값은 JVM이 시작되는 작업 디렉터리 기준 `./embedded-ai` — 다른 경로를 쓰려면 `OSWL_EMBEDDED_AI_DIR` 설정 | +기본은 **Qwen3.5-2B Q4_K_M**, 선택 모델은 **Gemma 4 E2B Q4_K_M**입니다. OsWL·PostgreSQL 동시 실행과 간헐적 사용 기준으로 Qwen 예상 최소 2 vCPU / RAM 8 GB, Gemma 권장 4 vCPU / RAM 16 GB를 잡습니다. 성능 보증이 아닌 추정치이며 CPU 크레딧과 스캔 중첩을 확인해야 합니다. -Gradle 태스크나 별도 스크립트가 필요 없습니다 — 다운로드는 시작 버튼을 처음 누를 때 앱 -자체에서 실행되므로, 단순히 `java -jar app.jar`로 배포해도 동작합니다. +실행 파일은 `embedded-ai/llama/`, 모델은 `model/Qwen/`, `model/Gemma/`에 둡니다. Qwen만 자동 다운로드합니다. 기본 GPU 레이어 0, 스레드 1, 생성 슬롯 1, 컨텍스트 8192입니다. 고정 URL·해시·크기를 함께 유지하세요. 기존 `models-v1`은 새 모델이 아닙니다. Docker는 폴더 마운트와 Linux 실행 파일이 필요합니다. -### 내장 AI 튜닝 (B1 / v1.0.4) - -기본값은 운영 환경에서 안전합니다. 측정된 이유가 있을 때만 오버라이드하세요. - -| 변수 | 기본값 | 용도 | -|---|---|---| -| `OSWL_EMBEDDED_AI_CONTEXT` | `8192` | 전체 컨텍스트 크기(`-c`). `--parallel` 사용 시 슬롯 간에 나뉘며, 슬롯 컨텍스트가 2048 아래로 떨어지면 경고 로그가 출력됩니다. | -| `OSWL_EMBEDDED_AI_GPU_LAYERS` | `-1` | `-ngl`: `-1`은 빌드가 지원하는 만큼 오프로드(`999` 전달), `0`은 CPU 전용, 양수는 명시적 레이어 수 | -| `OSWL_EMBEDDED_AI_THREADS` | `0` | `-t`: `0`은 llama.cpp 자동 감지, 양수는 스레드 수 고정 | -| `OSWL_EMBEDDED_AI_PARALLEL` | `4` | `--parallel N --cont-batching` 활성화; 1보다 크면 동시 AI 호출이 직렬화되지 않습니다. | -| `OSWL_EMBEDDED_AI_FLASH_ATTN` | `true` | `-fa`(flash attention) 추가 | -| `OSWL_EMBEDDED_AI_CACHE_REUSE` | `256` | `--cache-reuse` 토큰 수; `0` 이하면 비활성화 | -| `OSWL_EMBEDDED_AI_EXTRA_ARGS` | (비어 있음) | `llama-server` CLI 인자를 공백으로 구분해 그대로 덧붙임(관리자 전용 설정, 요청 입력 아님) | -| `OSWL_EMBEDDED_AI_STARTUP_TIMEOUT_SEC` | `120` | `/health` 응답을 기다리는 시간(초). 시간 내 실패 시 CPU 전용 재시도 또는 다음 모델 후보로 넘어갑니다. | -| `OSWL_EMBEDDED_DEFAULT_MODEL_URL` / `SHA256` / `SIZE_BYTES` | 업스트림 Hugging Face `ggml-org/Qwen3-1.7B-GGUF` | 기본 Qwen3-1.7B 다운로드용 매칭 세트; 자체 호스팅 미러 사용 시 셋 모두 오버라이드(바이트 단위로 동일한 재호스팅이면 URL만 변경) | -| `OSWL_EMBEDDED_FALLBACK_MODEL_URL` | Hugging Face | 기본 URL 실패 시 1회 재시도; primary와 같거나 비워두면 재시도 비활성화 | -| `OSWL_EMBEDDED_AUTO_DOWNLOAD` | `true` | 부팅 시 기본 모델을 백그라운드로 미리 다운로드(다운로드만, 사이드카는 시작 안 함). **`OSWL_AIRGAPPED_ENABLED=true`이면 무시됩니다**. | +전체 사양·구조·체크섬·미러 및 폐쇄망 설치·CPU 조정·모델과 언어 선택·재배포 고지는 [내장 AI](Embedded-AI.md)를 참고하세요. ## 9. 데이터베이스 스키마 (업그레이드) OsWL **`prod`는 Hibernate `ddl-auto=validate`** — 기동 시 PostgreSQL을 자동 변경하지 않습니다. -| 프로파일 | 스키마 관리 | +| 프로필 | 스키마 관리 | |----------|-------------| | `local` | `ddl-auto: update` — H2가 JPA 엔티티를 따름 | | `prod` | `ddl-auto: validate` — 업그레이드 시 SQL 스크립트 수동 실행 | @@ -177,7 +165,7 @@ OsWL **`prod`는 Hibernate `ddl-auto=validate`** — 기동 시 PostgreSQL을 ### Flyway (v1.0.4, 옵트인) -`OSWL_FLYWAY_ENABLED=true`로 설정하면 수동 스크립트 대신 Flyway가 스키마를 관리합니다. `baseline-on-migrate`가 켜져 있어 데이터가 있는 기존 DB도 거부되지 않고 베이스라인 처리되지만, **켜기 전에** 현재 스키마와 일치하는 전체 베이스라인 마이그레이션을 만들어 두어야 합니다. 기본값 `false`에서는 아무것도 달라지지 않습니다. +`OSWL_FLYWAY_ENABLED=true`로 `src/main/resources/db/migration/`의 버전별 마이그레이션을 활성화합니다. 기본값은 `false`입니다. 저장소에는 이미 `V1__baseline.sql`과 이후 마이그레이션이 있습니다. 빈 PostgreSQL DB에서는 V1부터 순서대로 실행한 후 Hibernate가 스키마를 검증합니다. Flyway 이력이 없는 기존 DB에서는 `baseline-on-migrate`가 V1 실행 없이 버전 1을 기록하고 V2부터 실행합니다. 활성화 전에 백업하고 기존 스키마와 마이그레이션을 비교하세요. 이미 수동 적용한 변경과 후속 마이그레이션이 충돌할 수 있습니다. 공유 DB에 적용한 마이그레이션 파일은 재생성하거나 수정하지 마세요. SQL을 수동 관리한다면 대상 버전에 필요한 변경을 순서대로 모두 적용해야 합니다. 아래의 일부 레거시 스크립트만으로 신규 설치 스키마를 구성할 수는 없습니다. ### v1.0.4 신규 컬럼 @@ -194,6 +182,10 @@ ALTER TABLE scan_results ADD COLUMN IF NOT EXISTS ai_locale varchar(16); (Flyway 사용자: 새로운 `libraries` 컬럼 3개는 `V3__component_metadata.sql`에서 처리됩니다. [데이터베이스 스키마](Database-Schema.md) 참고.) +### v1.0.5: Spring Session / ShedLock 테이블 (옵트인) + +**다중 인스턴스** 배포로 전환할 때만 필요합니다(§12 참고). `spring_session`, `spring_session_attributes`, `shedlock`이 추가됩니다. Flyway 사용자는 `db/migration/V10__spring_session_and_shedlock.sql`에서, 수동 스크립트 사용자는 `db/spring_session_and_shedlock.sql`을 실행하면 됩니다. 단일 인스턴스 배포라면 완전히 건너뛰어도 됩니다 — `OSWL_SESSION_STORE_TYPE=jdbc` 및/또는 `OSWL_SCHEDULER_LOCK_ENABLED=true`를 설정하기 전까지는 이 테이블을 아무도 참조하지 않습니다. + ## 10. 배포 후 스모크 테스트 1. HTTPS 리버스 프록시로만 UI 접근. @@ -205,10 +197,39 @@ ALTER TABLE scan_results ADD COLUMN IF NOT EXISTS ai_locale varchar(16); ## 11. 운영 -- PostgreSQL 백업, `OSWL_ENCRYPTION_KEY`는 시크릿 매니저에 보관(분실 시 VCS 토큰 복호 불가). +- PostgreSQL 백업, `OSWL_ENCRYPTION_KEY`는 시크릿 매니저에 보관(분실 시 VCS 토큰 복호 불가) — 전체 절차와 복구 리허설 스크립트는 [백업 및 복구](Backup-And-Restore.md) 참고. - 유출 시 API 키·SMTP 자격 증명 교체. - `local`로 돌리면 안 되는 이미지에 `SPRING_PROFILES_ACTIVE=local` 넣지 않기. +## 12. 다중 인스턴스 배포 (수평 확장 / HA) + +OsWL은 기본적으로 **단일 인스턴스**로 동작합니다 — 인메모리 HTTP 세션과 인스턴스별 `@Scheduled` 작업이죠. 컨테이너/프로세스 1개일 때는 이걸로 충분하지만, 로드밸런서 뒤에 두 번째 인스턴스를 두면 문제가 생깁니다: 사용자 세션이 로그인했던 인스턴스에 고정되고, 야간 모니터링/유예 만료/휴지통 정리 작업이 클러스터당 1회가 아니라 **인스턴스마다** 실행됩니다. 이 절은 동일한 PostgreSQL DB를 바라보는 **인스턴스 2대 이상**을 배포할 때만 해당됩니다. + +**1. 스키마부터 적용하세요.** 아래 기능을 켠 인스턴스를 기동하기 전에 `spring_session`, `spring_session_attributes`, `shedlock`이 존재하는지 먼저 확인하세요(§9, "v1.0.5: Spring Session / ShedLock 테이블"). 테이블이 없는 상태에서 아래 환경변수부터 배포하면 첫 요청/스케줄 시점에 모든 인스턴스가 죽습니다. + +**2. 환경 변수:** + +| 변수 | 용도 | +|------|------| +| `OSWL_SESSION_STORE_TYPE=jdbc` | HTTP 세션을 Tomcat 인메모리 저장에서 PostgreSQL(`spring_session`)로 옮깁니다. 로그인 상태와 단일세션 강제(`maximumSessions(1)`)가 인스턴스별이 아니라 클러스터 전체에서 동작합니다. | +| `OSWL_SCHEDULER_LOCK_ENABLED=true` | 스케줄 작업 3종(`ContinuousMonitoringScheduler`, `DeferExpiryScheduler`, `TrashCleanupScheduler`)을 클러스터 전역 락(ShedLock, `shedlock` 테이블 기반)으로 감싸서 사이클당 한 인스턴스에서만 실행되도록 합니다. | + +실제 다중 인스턴스 배포에서는 둘 다 함께 설정하세요 — 하나만 켜면 나머지 한쪽 공백이 그대로 남습니다. + +**3. 로드밸런서:** nginx, ALB 등 일반적인 L7 LB면 됩니다 — `OSWL_SESSION_STORE_TYPE=jdbc`를 설정하면 세션 상태가 인스턴스 메모리가 아니라 PostgreSQL에 중앙화되므로 **스티키 세션이 필요 없습니다.** + +**4. 단, 스캔 진행률 폴링은 예외입니다.** Quick Import/스캔 보강 중 표시되는 실시간 진행률(`EnrichmentProgressHolder`, `ScanStatusEmitterRegistry`)은 여전히 인스턴스별 인메모리 상태이며 DB에 저장되지 않습니다. 권장 방법: 로드밸런서 라우팅을 **활성 스캔이 진행되는 동안만** 스티키하게(예: 세션 기준 쿠키 어피니티) 설정해, 진행률 폴링 요청이 실제로 스캔을 실행 중인 인스턴스로 되돌아가도록 하세요. 스캔 진행률을 DB로 옮기고 UI를 순수 폴링 방식으로 바꾸는 대안은 더 큰 변경이라 별도로 추적하며, 지금은 스티키 라우팅이 실용적인 기본값입니다. + +**5. 롤링 배포 순서:** + 1. 대기 중인 DB 마이그레이션을 먼저 적용하세요(구버전 앱 코드가 새 스키마를 견뎌야 하므로 — `db/migration`은 이 원칙대로 추가 전용으로 작성됩니다). + 2. 인스턴스를 한 번에 하나씩 순차적으로 교체하고(전체 동시 교체 금지), 다음으로 넘어가기 전에 새 인스턴스가 준비성 검사를 통과할 때까지 기다리세요. + 3. `OSWL_SESSION_STORE_TYPE=jdbc`를 설정하면 세션이 인스턴스 메모리가 아니라 PostgreSQL에 있으므로, 롤링 재시작으로 사용자가 로그아웃되지 않습니다. + +**6. 정상 동작 확인:** + - 인스턴스 A에 로그인한 뒤, LB가 인스턴스 B로 라우팅하는 후속 요청을 보내도 인증이 유지되는지(`/login`으로 리다이렉트되지 않는지) 확인합니다. + - 인스턴스 A를 내려도 세션(및 단일세션 강제)이 인스턴스 B에서 계속 동작하는지 확인합니다. + - 야간 작업 실행 후 두 인스턴스의 로그를 확인해, 해당 작업의 로그 라인이 두 곳이 아니라 정확히 한 인스턴스에서만 나타나는지 확인합니다. + --- -**로컬 개발:** `SPRING_PROFILES_ACTIVE=local`, `.env.example` → `.env`, `OSWL_ENCRYPTION_KEY` 설정, `./gradlew bootRun`. H2, H2 콘솔, Swagger, `GET /data/test`는 이 프로파일에서만. +**로컬 개발:** `./gradlew bootRun`(PowerShell: `.\gradlew.bat bootRun`)으로 `local` 프로필과 H2를 사용합니다. 로컬 YAML에는 개발 전용 암호화 키가 있으며 필요하면 프로세스 환경 변수로 변경하세요. `.env`는 실행 도구가 명시적으로 읽을 때만 적용됩니다. diff --git a/docs/ko/Quick-Import.md b/docs/ko/Quick-Import.md index 25b48308..b9574fef 100644 --- a/docs/ko/Quick-Import.md +++ b/docs/ko/Quick-Import.md @@ -121,7 +121,7 @@ GitLab·Bitbucket 자체 호스팅은 VCS 연결의 API 기본 URL로 지원합 ## REST API 요약 -[API 레퍼런스 — Quick Import](API-Reference.md#quick-import) 참고. 인터랙티브 스키마는 Swagger UI(`local` 프로파일). +[API 레퍼런스 — Quick Import](API-Reference.md#quick-import) 참고. 인터랙티브 스키마는 Swagger UI(`local` 프로필). --- diff --git a/docs/ko/Scan-Api-Security.md b/docs/ko/Scan-Api-Security.md index cf80f75b..c380e2d4 100644 --- a/docs/ko/Scan-Api-Security.md +++ b/docs/ko/Scan-Api-Security.md @@ -30,7 +30,7 @@ CSRF 예외는 다음만 해당합니다. |------|-----------| | 1 | `Authorization: Bearer` **프로젝트 API 키** | | 2 | 본문의 제출자 **이메일·비밀번호** | -| 3 | 제출자에게 **`SCAN_SUBMIT`** 권한 및 해당 프로젝트 **`project_members`** 등록 | +| 3 | 제출자에게 **`SCAN_SUBMIT`** 권한이 있고, 직접 프로젝트 멤버십 또는 팀 권한 부여를 통해 프로젝트에 접근할 수 있는지 확인합니다. | 역할 템플릿과 프로젝트 멤버십: [권한 레이어](Authorization-Layers.md). diff --git a/docs/ko/Security-Center.md b/docs/ko/Security-Center.md index 6fea4540..380be446 100644 --- a/docs/ko/Security-Center.md +++ b/docs/ko/Security-Center.md @@ -89,7 +89,7 @@ OsWL은 표준 CVSS 3.x 구간에 따라 기본 점수를 심각도로 분류합 | SBOM (CycloneDX) | `GET /api/projects/{projectId}/sbom` | CycloneDX 1.6 JSON | | VEX | `GET /api/projects/{projectId}/vex` | CycloneDX VEX — 트리아지 판단 포함 | | SARIF | `GET /api/projects/{projectId}/sarif` | SARIF 2.1.0, GitHub 코드 스캐닝 업로드 가능 | -| 컴플라이언스 리포트 | `GET /security-center/compliance-report` | 인쇄용 HTML (*인쇄 → PDF로 저장* 사용) | +| 컴플라이언스 리포트 | `GET /projects/{projectId}/security-center/compliance-report` | 인쇄용 HTML (*인쇄 → PDF로 저장* 사용) | | CVE / 라이선스 CSV | — | 현재 필터가 적용된 목록 | 일괄 작업에는 선택한 컴포넌트를 모두 수정 버전으로 올리는 **업그레이드 PR 생성**도 포함됩니다. diff --git a/docs/ko/User-Guide.md b/docs/ko/User-Guide.md index 949ec9a6..06d66855 100644 --- a/docs/ko/User-Guide.md +++ b/docs/ko/User-Guide.md @@ -1,5 +1,7 @@ # 사용자 가이드 +[1.0.5.1 변경 사항](Whats-New-v1.0.5.1.md) + 이 페이지는 OsWL 웹 대시보드의 일상적인 사용법을 설명합니다. **시스템 관리자**가 아니면 **멤버로 등록된 프로젝트**만 보입니다. 프로젝트 안에서 할 수 있는 일은 **역할 템플릿**(Admin / Developer / Viewer)에 따릅니다. [권한 레이어](Authorization-Layers.md) 참고. @@ -96,7 +98,7 @@ OsWL은 입력 컨텍스트 해시로 AI 요약을 캐싱하므로, 재스캔 ## 내장 AI 모델 -OsWL의 내장 로컬 AI는 llama.cpp 사이드카를 실행하며, 기본 모델로 **Qwen3 1.7B** GGUF를 사용합니다. 최초 부팅 시 `oswl.ai.embedded.auto-download-on-boot` 설정이 켜져 있고 에어갭 모드가 아니라면 기본 모델을 백그라운드에서 미리 다운로드해, Settings에서 시작을 누를 때 대기 시간을 줄입니다. 기본 다운로드는 OsWL 자체 GitHub Release 자산을 사용하며 실패하면 원본 Hugging Face 저장소로 폴백합니다. 모든 다운로드는 SHA-256 체크섬으로 검증됩니다. 설정된 embedded-ai 디렉터리에 호환되는 임의의 `.gguf` 파일을 직접 넣을 수도 있습니다. 설치 및 문제 해결은 [Embedded AI](Embedded-AI.md)를 참조하세요. +내장 AI는 별도로 설치한 llama.cpp 실행 파일을 사용하며, 기본 다운로드 모델은 **Qwen3.5-2B Q4_K_M**입니다. **Gemma 4 E2B**는 선택적으로 직접 설치합니다. 실행 파일은 `embedded-ai/llama/`, 모델은 `embedded-ai/model/<계열>/`에 둡니다. 부팅 시 미리 받기는 다운로드만 수행하며 서버 실행이나 LOCAL 활성화는 하지 않습니다. 기본 다운로드는 Hugging Face의 고정 리비전을 사용하고 SHA-256 및 크기를 검증합니다. 기본 대체 미러는 없으며, 에어갭 모드에서는 다운로드하지 않습니다. 모델을 바꾸려면 설정에서 중지한 뒤 모델을 선택·저장하고 다시 시작하세요. 최신 요구 사항과 설정은 [내장 AI](Embedded-AI.md)를 참고하세요. --- diff --git a/docs/ko/Whats-New-v1.0.4.md b/docs/ko/Whats-New-v1.0.4.md index 1741fcc4..87f56ce1 100644 --- a/docs/ko/Whats-New-v1.0.4.md +++ b/docs/ko/Whats-New-v1.0.4.md @@ -64,7 +64,7 @@ VEX는 원시 탐지 결과가 아니라 **트리아지 판단**을 담습니다 ### 컴플라이언스 리포트 팩 -`GET /security-center/compliance-report`는 인쇄용 리포트를 렌더링합니다. 컴포넌트 인벤토리, 라이선스 의무, NOTICE 문구, 심각도별 미조치 항목이 포함됩니다. 브라우저의 *인쇄 → PDF로 저장*을 사용하세요. 미리보기 진입 시 인쇄 창이 자동으로 열리지 않으므로 내보내기 전에 내용을 확인할 수 있습니다. +`GET /projects/{projectId}/security-center/compliance-report`는 인쇄용 리포트를 렌더링합니다. 컴포넌트 인벤토리, 라이선스 의무, NOTICE 문구, 심각도별 미조치 항목이 포함됩니다. 브라우저의 *인쇄 → PDF로 저장*을 사용하세요. 미리보기 진입 시 인쇄 창이 자동으로 열리지 않으므로 내보내기 전에 내용을 확인할 수 있습니다. --- diff --git a/docs/ko/Whats-New-v1.0.5.1.md b/docs/ko/Whats-New-v1.0.5.1.md new file mode 100644 index 00000000..19185b5e --- /dev/null +++ b/docs/ko/Whats-New-v1.0.5.1.md @@ -0,0 +1,24 @@ +# OsWL 1.0.5.1 변경 사항 + +의존성 정보, 스캔 안정성, 라이트 모드 화면을 개선했습니다. + +## 사용 시 달라진 점 + +- 로그인·검색·설정은 라이트 모드를 사용합니다. 미저장 변경과 오류를 구분하고, AI 입력 오류는 해당 필드 아래에 표시합니다. 저장 성공 문구는 저장 버튼 옆에 남습니다. +- 응답 다양성은 0~2(기본값 0.15), 최대 토큰 수는 256~8192의 정수(기본값 1200)를 받습니다. 비우면 기본값을 사용합니다. 공급자·분석 작업별 제한이 우선할 수 있으며 Anthropic에는 응답 다양성 설정이 적용되지 않습니다. +- 삭제한 프로젝트를 휴지통에서 확인하고 보존 기간 내에 복원할 수 있습니다. 설정에서 CLI API 키를 삭제할 수 있으며, 삭제한 키는 더 이상 사용할 수 없습니다. +- CVE 옆에서 NVD를 바로 열 수 있습니다. 지원하는 숫자 버전 구간에서 수정 버전을 선택하고, Maven 상위 POM 속성과 지원되는 Gradle 버전 제약 조건을 해석합니다. +- 진단에서 실패·확인 필요·검사 제외를 구분하고 디스크 용량을 읽기 쉽게 표시합니다. 아카이브 대상이 없으면 이유를 안내하고, 대상이 있으면 JSON을 다운로드합니다. +- 캐시의 ‘항상 새로고침’은 스캔이 데이터를 요청할 때 적용됩니다. 백그라운드에서 계속 새로고침하는 설정이 아닙니다. + +## 업그레이드 + +Java 25가 필요합니다. 데이터베이스와 영구 저장 파일을 먼저 백업하고 [배포 체크리스트](Production-Deployment-Checklist.md)와 [백업·복원 안내](Backup-And-Restore.md)를 확인하세요. 마이그레이션을 사용하는 DB에는 V35까지 적용해야 합니다. JAR 교체만으로 DB 변경까지 적용됐다고 가정하지 마세요. + +해당 GitHub Release의 `oswl-1.0.5.1.jar`와 `SHA256SUMS`를 받으세요. 구성 방법은 [시작하기](Getting-Started.md)를 참고하세요. 브라우저 보안 알림에는 웹 푸시/VAPID 설정과 브라우저 권한이 필요합니다. 외부 AI·SMTP·VCS 연동도 각각 설정해야 합니다. + +## 정확도와 한계 + +업그레이드로 기존 저장 스캔이 다시 작성되지는 않습니다. 파싱·보강 수정 사항을 적용하려면 다시 스캔하세요. 선언된 의존성과 실제 빌드가 선택한 의존성은 다를 수 있으며, 해석하지 못한 버전은 추가 확인이 필요합니다. 라이선스·수정 버전 정보가 없다는 사실은 안전하거나 패치가 없다는 뜻이 아닙니다. 복잡한 버전 범위, 생태계별 버전 비교, 온라인·오프라인 결과 일치는 이번 버전에서 모두 해결된 상태가 아닙니다. + +지원 범위와 한계는 [분석 범위 안내(영어)](../en/Analysis-Coverage.md)를 참고하세요. diff --git a/landing/i18n/en.json b/landing/i18n/en.json index 11dd3c97..3905c5ce 100644 --- a/landing/i18n/en.json +++ b/landing/i18n/en.json @@ -8,18 +8,20 @@ "platform": "Platform", "howItWorks": "How It Works", "download": "Go to Download", - "themeToggle": "Toggle light/dark theme" + "docs": "Documentation", + "docsUrl": "https://github.com/SalkCoding/Oswl/blob/main/docs/en/Home.md" }, "hero": { - "badge": "Enterprise SCA Platform", - "title": "Know Every Vulnerability
in Your Open Source Stack", - "subtitle": "OsWL continuously scans your dependencies for CVEs, license obligations, and transitive risks — giving security and compliance teams complete SBOM visibility from a single self-hosted platform.", + "badge": "OsWL 1.0.5.1", + "title": "Understand Dependency Risks
in Your Open Source Stack", + "subtitle": "Track CVEs, license obligations, and dependency risks from one self-hosted platform. Scan repositories or submit dependencies from CI, then review findings with your security and compliance teams.", "ctaPrimary": "Install Now", - "ctaSecondary": "See the Platform" + "ctaSecondary": "See the Platform", + "installUrl": "https://github.com/SalkCoding/Oswl/blob/main/docs/en/Getting-Started.md" }, "stats": { "multiSource": "Multi-source", - "multiSourceLabel": "GHSA · OSV Advisories", + "multiSourceLabel": "OSV · GitHub Advisory · NVD", "ecosystemsLabel": "Package Ecosystems", "onDemand": "On-demand", "onDemandLabel": "deps.dev Enrichment", @@ -30,54 +32,60 @@ "features": { "label": "Core Capabilities", "title": "End-to-End Software
Composition Analysis", - "subtitle": "From dependency ingestion to remediation, OsWL covers the full SCA lifecycle with enterprise-grade accuracy and auditability.", + "subtitle": "Collect dependencies, assess findings, and track remediation with project access controls and audit logs.", "cveTitle": "CVE Vulnerability Tracking", - "cveDesc": "On-demand enrichment from deps.dev and OSV. Every component is cross-referenced against CVSS 3.x scores and patch availability, and a reminder banner surfaces when a deferred component's grace period expires.", + "cveDesc": "Review vulnerability data, available CVSS scores and known fix versions. Open NVD from a CVE and track remediation decisions. Missing fix data does not mean no patch exists.", "cveTag": "deps.dev · OSV · GHSA", "licenseTitle": "License Risk Management", - "licenseDesc": "Automatic SPDX classification of OSS licenses — copyleft, permissive, proprietary — with obligation rules, conflict detection, and NOTICE / SPDX SBOM export per scan.", + "licenseDesc": "Classify detected SPDX license identifiers against your license policy, review obligations and potential conflicts, and export NOTICE files or SPDX SBOMs for each scan.", "licenseTag": "GPL · LGPL · Apache · MIT · AGPL · MPL", "riskTitle": "Risk Trend Analysis", "riskDesc": "Track your security posture across scans. Visualize critical, high, medium, and low severity trends over time, with optional AI-generated deviation insights when an LLM provider is configured.", "riskTag": "Version-over-version delta", "transitiveTitle": "Transitive Dependency Traversal", - "transitiveDesc": "Uncover vulnerabilities hidden deep in your dependency graph. DependencyPath analysis traces both direct and transitive exposure across every supported manifest.", - "transitiveTag": "Maven · npm · PyPI · Go · Cargo · NuGet · RubyGems · Composer · Conan", + "transitiveDesc": "Trace direct and transitive dependencies when supported lock files or graph data provide paths. Coverage depends on the ecosystem and input; a manifest alone may not contain the complete dependency graph.", + "transitiveTag": "Maven · npm · PyPI · Go · Cargo · NuGet · RubyGems · Composer", "gitTitle": "Git-Native Repository Integration", - "gitDesc": "Connect GitHub, GitLab, or Bitbucket via Personal Access Token. Quick Import queues bulk repository imports with a cancel button for jobs still running, and every scan keeps branch-aware snapshots and per-project history out of the box.", + "gitDesc": "Connect supported GitHub, GitLab, or Bitbucket credentials, or import a public repository URL. Queue repository imports, cancel active jobs, and retain branch information and scan history for each project.", "gitTag": "GitHub · GitLab · Bitbucket", "cliTitle": "CLI & CI/CD Pipeline Scanning", - "cliDesc": "Integrate OsWL into any build pipeline with a single CLI command. Only parsed manifest metadata is uploaded — no source code leaves your build host. Bearer-token API keys per project or organization-wide.", - "cliTag": "Manifest-only upload", + "cliDesc": "Submit scans from local projects or CI. The CLI uploads selected dependency manifests and build files for server-side parsing, then submits components with a project-scoped API key and the submitting user’s credentials.", + "cliTag": "Build-input upload", "sbomTitle": "CycloneDX SBOM & VEX", "sbomDesc": "Export a CycloneDX 1.6 SBOM, a VEX document carrying your triage decisions, or a SARIF report for code-scanning dashboards. Import third-party CycloneDX files to scan components you don’t build yourself.", "sbomTag": "CycloneDX 1.6 · VEX · SARIF 2.1.0", "gateTitle": "Policy Gates for Pull Requests", - "gateDesc": "Define severity, license, and KEV thresholds per project, then let the gate pass or fail each scan. Results publish back to GitHub as a check run and PR comment, new findings are compared against a baseline so existing debt never blocks a merge, and any finding can be escalated to a linked Jira issue.", + "gateDesc": "Evaluate scan results against severity, license, and KEV policies. Use a baseline to assess newly introduced findings when needed. CI integrations can publish GitHub checks and PR comments, and Jira integration links findings to issues.", "gateTag": "Baseline diff · GitHub check run", "orgTitle": "Organization Dashboard", - "orgDesc": "One view across every project: KEV-listed and actively exploited CVEs first, EPSS exploit probability for ranking the rest, license warnings, and an audit-log export for your SIEM.", - "orgTag": "CISA KEV · EPSS · SIEM export", + "orgDesc": "Review vulnerability and license totals across projects, including unaddressed KEV-listed findings. Projects are ranked by severity counts. Organization and team membership control access; audit logs can be exported separately.", + "orgTag": "Project risk totals · Teams · Audit export", "embeddedAiTitle": "Embedded, Offline AI Insights", - "embeddedAiDesc": "A built-in llama.cpp sidecar starts and stops from Settings, running Qwen3 1.7B Q4_K_M locally (Gemma 3 1B on low-spec hardware) — CPU-only, no API key, and nothing leaves the host. Insights honor your chosen reasoning effort and are tracked in the same usage log as every cloud provider.", - "embeddedAiTag": "llama.cpp · CPU-only · Zero internet", + "embeddedAiDesc": "Run local inference with a separately installed llama.cpp runtime. Qwen3.5-2B Q4_K_M is the default download; Gemma 4 E2B is an optional manual installation. CPU-only defaults require no cloud API key. Prepare the runtime and model in advance for offline use.", + "embeddedAiTag": "llama.cpp · CPU default · Local inference", "airgappedTitle": "Air-Gapped & Offline-Ready", - "airgappedDesc": "Import versioned OSV, deps.dev, EPSS, and KEV snapshot bundles — checksummed and provenance-stamped — to keep vulnerability intelligence current with zero outbound internet access. A wanted-list export tells you exactly which components the next bundle needs to cover, and every finding shows its data-as-of date.", + "airgappedDesc": "Import checksummed OSV, deps.dev, EPSS, and KEV snapshot bundles for offline data lookups. Export a wanted list to target the next bundle and check its data freshness. Configure repository, email, webhook, and AI connections separately for an isolated deployment.", "airgappedTag": "Checksum · Provenance · Wanted-list export", - "remediationTitle": "One-Click Remediation PRs", - "remediationDesc": "Bump a vulnerable dependency straight from the Security Center — OsWL opens a version-bump PR or MR against GitHub, GitLab, or Bitbucket, with reviewers and a change summary attached. Run it in batch to open one PR per patchable component across an entire scan in a single action.", + "remediationTitle": "Dependency Upgrade PRs", + "remediationDesc": "Create version-bump PRs or MRs for supported manifests in connected GitHub, GitLab, and Bitbucket repositories. Batch actions attempt one PR per eligible component and report individual outcomes. Repository permissions and a supported version edit are required; reviewer assignment depends on the provider.", "remediationTag": "GitHub · GitLab · Bitbucket · Batch upgrade", - "supplyChainTitle": "Supply-Chain Attack Detection", - "supplyChainDesc": "Every resolved package name is screened for typosquatting — edit-distance and confusable-character matching against a per-ecosystem popular-package list — plus lightweight dependency-confusion signals like internal-looking names resolved from a public registry. Flagged packages surface as a verify-origin warning, never a silent block.", + "supplyChainTitle": "Supply-Chain Risk Signals", + "supplyChainDesc": "Flag suspicious package-name similarities and dependency-confusion patterns for review. These heuristics do not prove an attack or establish package provenance. Confirmed malware advisories are tracked separately and can fail the security gate.", "supplyChainTag": "Typosquat detection · Dependency confusion", - "ssoTitle": "Enterprise SSO via OIDC", - "ssoDesc": "Sign in through Okta, Entra ID, or any OpenID Connect provider — SSO activates automatically once an OIDC client is configured. Logins skip the email OTP step since the identity provider already authenticated the user, while every existing role and permission rule still applies.", - "ssoTag": "Okta · Entra ID · Any OIDC provider" + "ssoTitle": "SSO & User Provisioning", + "ssoDesc": "Configure OIDC or SAML 2.0 single sign-on and SCIM 2.0 provisioning. Map identity-provider users to OsWL accounts and manage group assignments. Integration setup and account provisioning are required; OsWL roles and project access still apply.", + "ssoTag": "OIDC · SAML 2.0 · SCIM 2.0", + "sourceTitle": "Source & Bytecode References", + "sourceDesc": "Inspect Java bytecode references and supported Python or JavaScript source imports when the corresponding inputs are provided. Evidence and coverage help explain findings. A reference does not prove execution, and incomplete analysis remains unknown.", + "sourceTag": "Java · Python · JavaScript · Coverage evidence", + "secretTitle": "Secret & IaC Findings", + "secretDesc": "Inspect collected Quick Import files for secret patterns and infrastructure configuration issues. Review locations and rules alongside vulnerability findings. Coverage depends on collected files and enabled scanners; full source checkout is a separate option.", + "secretTag": "Collected files · Rule-based findings" }, "platform": { - "label": "Live Platform", + "label": "Product Screenshots", "title": "Built for Security &
Compliance Teams", - "subtitle": "Every screen is engineered for actionability — from CVE triage to remediation tracking, no context switching required.", + "subtitle": "Explore example screens for vulnerability review, project management, license analysis, and risk trends. Screenshots use sample data.", "tabSc": "Security Center", "tabProjects": "Projects", "tabLicense": "License Analysis", @@ -94,12 +102,13 @@ "vuln": { "label": "Vulnerability Intelligence", "title": "Prioritize What
Actually Matters", - "desc": "OsWL surfaces exploitable, patchable vulnerabilities first. CVSS scores, exploit maturity, and fix availability are enriched automatically from deps.dev and OSV advisory feeds.", + "desc": "Use CVSS severity, EPSS probability, KEV listings, and known fix versions to prioritize review. Available signals depend on the advisory sources and component coverage; they do not prove exploitability in your deployment.", "bullet1": "Critical & High vulnerabilities flagged with patch targets", "bullet2": "Fix version surfaced — know if a patched release exists before triaging", "bullet3": "Transitive exposure traced to root dependency paths", "bullet4": "Bulk status updates — mark findings reviewed or ignored in one action", - "demoTitle": "backend-api · v3.2.0 — Security Center" + "demoTitle": "Example findings · CVSS 3.x", + "demoNote": "Illustrative findings, not an OsWL project scan. CVE links identify the source records; scores can differ by source." }, "table": { "component": "Component", @@ -127,27 +136,27 @@ "license": { "label": "License Intelligence", "title": "Eliminate
Compliance Blind Spots", - "desc": "Automatically classify every SPDX license across your dependency tree. Identify copyleft obligations, license conflicts, and components that need manual legal review before they reach production.", - "bullet1": "Copyleft licenses requiring full source disclosure", - "bullet2": "Weak copyleft or dual-licensed — legal review recommended", - "bullet3": "Permissive licenses — safe for commercial use" + "desc": "Compare detected SPDX license identifiers with your policy and review obligations in the context of use and distribution. The example classifications are policy outcomes, not a legal approval.", + "bullet1": "Copyleft obligations depend on how software is used and distributed", + "bullet2": "Review weak copyleft and dual-license terms in context", + "bullet3": "Permissive licenses still have terms and notice requirements" }, "workflow": { "label": "Workflow", "title": "Up and Scanning
in Minutes", "subtitle": "OsWL integrates directly into your existing development workflow. No agents, no heavyweight setup.", "step1Title": "Connect Your Repository", - "step1Desc": "Add a GitHub, GitLab, or Bitbucket Personal Access Token. OsWL discovers repositories and branches and imports manifests — no source code upload required.", + "step1Desc": "Connect a supported VCS account to browse repositories, or enter a public repository URL. Quick Import collects supported files on the OsWL server; build execution is disabled by default.", "step2Title": "Run a Dependency Scan", - "step2Desc": "OsWL parses your lock files and build manifests, resolves the transitive dependency graph, and cross-references every component against deps.dev, OSV, and your license policy.", + "step2Desc": "OsWL parses supported lock files and build manifests, resolves the dependency data they provide, and enriches components with vulnerability and license information.", "step3Title": "Triage & Remediate", "step3Desc": "Review findings in the Security Center, filter by severity, bulk-update statuses, and track your risk posture scan-over-scan in the Risk Trend dashboard.", "step4Title": "Automate in CI/CD", - "step4Desc": "Gate pull requests with the OsWL CLI. Bearer-token API keys enable automated scanning in any pipeline — Jenkins, GitHub Actions, GitLab CI, or custom build servers." + "step4Desc": "Submit scans from CI with a project API key and submitter credentials, then evaluate completed results through the gate API or CLI. Configure your pipeline to enforce the returned verdict." }, "cta": { "title": "Secure Your
Open Source Stack", - "sub": "Deploy OsWL on-premise and gain full SBOM visibility across every project, team, and dependency in your organization.", + "sub": "Deploy OsWL on your own infrastructure to review scanned dependencies, track remediation, and manage project access in one place.", "primary": "Start Now →", "secondary": "See How It Works" }, diff --git a/landing/i18n/ja.json b/landing/i18n/ja.json index 1b706a5b..65e28a10 100644 --- a/landing/i18n/ja.json +++ b/landing/i18n/ja.json @@ -8,18 +8,20 @@ "platform": "プラットフォーム", "howItWorks": "使い方", "download": "ダウンロードへ", - "themeToggle": "ライト/ダークテーマを切り替え" + "docs": "ドキュメント", + "docsUrl": "https://github.com/SalkCoding/Oswl/blob/main/docs/ja/Home.md" }, "hero": { - "badge": "エンタープライズ SCA プラットフォーム", - "title": "オープンソース
脆弱性をすべて把握する", - "subtitle": "OsWL は依存関係の CVE、ライセンス義務、推移的リスクを継続的にスキャンし、セキュリティ・コンプライアンスチームに単一のセルフホスト基盤で完全な SBOM 可視性を提供します。", + "badge": "OsWL 1.0.5.1", + "title": "OSSの構成と
リスクを把握", + "subtitle": "セルフホスト型のプラットフォームで CVE、ライセンス義務、依存関係のリスクを管理します。リポジトリをスキャンするか CI から依存関係を送信し、セキュリティ・コンプライアンスチームで検出結果を確認できます。", "ctaPrimary": "今すぐインストール", - "ctaSecondary": "プラットフォームを見る" + "ctaSecondary": "プラットフォームを見る", + "installUrl": "https://github.com/SalkCoding/Oswl/blob/main/docs/ja/Getting-Started.md" }, "stats": { "multiSource": "マルチソース", - "multiSourceLabel": "GHSA · OSV アドバイザリ", + "multiSourceLabel": "OSV · GitHub Advisory · NVD", "ecosystemsLabel": "パッケージエコシステム", "onDemand": "オンデマンド", "onDemandLabel": "deps.dev による情報拡充", @@ -30,54 +32,60 @@ "features": { "label": "主要機能", "title": "エンドツーエンドの
ソフトウェア構成分析", - "subtitle": "依存関係の取り込みから修正対応まで、OsWL はエンタープライズ品質の精度と監査性で SCA ライフサイクル全体をカバーします。", + "subtitle": "依存関係の収集から検出結果の評価、対応状況の追跡まで。プロジェクトのアクセス制御と監査ログも提供します。", "cveTitle": "CVE 脆弱性の追跡", - "cveDesc": "deps.dev と OSV からオンデマンドで情報を拡充します。すべてのコンポーネントを CVSS 3.x スコアとパッチの提供状況と照合し、猶予期間が切れた保留コンポーネントにはリマインダーバナーを表示します。", + "cveDesc": "脆弱性情報、利用可能なCVSSスコア、確認できた修正バージョンを確認します。CVEからNVDを開き、対応状況を管理できます。修正バージョンの表示がなくても、パッチが存在しないとは限りません。", "cveTag": "deps.dev · OSV · GHSA", "licenseTitle": "ライセンスリスク管理", - "licenseDesc": "OSS ライセンスを SPDX で自動分類(コピーレフト/寛容/独占)し、義務ルール、競合検出、スキャンごとの NOTICE / SPDX SBOM 出力を提供します。", + "licenseDesc": "検出した SPDX ライセンス識別子をポリシーに従って分類し、義務や潜在的な競合を確認します。スキャンごとに NOTICE ファイルや SPDX SBOM を出力できます。", "licenseTag": "GPL · LGPL · Apache · MIT · AGPL · MPL", "riskTitle": "リスク傾向分析", "riskDesc": "スキャンをまたいでセキュリティ状態を追跡します。Critical・High・Medium・Low の推移を可視化し、LLM プロバイダーを設定すれば AI による差分インサイトも利用できます。", "riskTag": "バージョン間の差分", "transitiveTitle": "推移的依存関係の探索", - "transitiveDesc": "依存グラフの奥に潜む脆弱性を発見します。DependencyPath 解析により、対応するすべてのマニフェストで直接・推移的な影響範囲を追跡します。", - "transitiveTag": "Maven · npm · PyPI · Go · Cargo · NuGet · RubyGems · Composer · Conan", + "transitiveDesc": "対応するロックファイルやグラフデータにパスが含まれる場合、直接依存と推移的依存を追跡します。対応範囲はエコシステムと入力に依存し、マニフェストだけでは完全な依存関係グラフを得られない場合があります。", + "transitiveTag": "Maven · npm · PyPI · Go · Cargo · NuGet · RubyGems · Composer", "gitTitle": "Git ネイティブなリポジトリ連携", - "gitDesc": "Personal Access Token で GitHub、GitLab、Bitbucket に接続します。Quick Import は大量インポートをキューイングし進行中のジョブをキャンセルでき、ブランチ単位のインポート、バージョンごとのスナップショット、プロジェクト別スキャン履歴を標準で備えています。", + "gitDesc": "GitHub・GitLab・Bitbucket に対応する認証情報を接続するか、公開リポジトリの URL から取り込みます。インポートのキュー登録や実行中ジョブのキャンセルができ、プロジェクトごとにブランチ情報とスキャン履歴を保存します。", "gitTag": "GitHub · GitLab · Bitbucket", "cliTitle": "CLI & CI/CD パイプラインスキャン", - "cliDesc": "単一の CLI コマンドで、あらゆるビルドパイプラインに OsWL を組み込めます。アップロードされるのは解析済みのマニフェスト情報のみで、ソースコードはビルドホストから出ません。プロジェクト単位・組織全体の Bearer トークン API キーに対応します。", - "cliTag": "マニフェストのみ送信", + "cliDesc": "ローカルプロジェクトや CI からスキャンを送信できます。CLI は収集対象の依存関係マニフェストやビルドファイルをサーバーへ送って解析し、プロジェクト単位の API キーと送信者の認証情報でコンポーネントを登録します。", + "cliTag": "ビルド入力ファイルの送信", "sbomTitle": "CycloneDX SBOM & VEX", "sbomDesc": "CycloneDX 1.6 の SBOM、トリアージ判断を含む VEX 文書、コードスキャン用の SARIF レポートを出力できます。自社でビルドしないコンポーネントは、外部の CycloneDX ファイルを取り込んでスキャンします。", "sbomTag": "CycloneDX 1.6 · VEX · SARIF 2.1.0", "gateTitle": "プルリクエストのポリシーゲート", - "gateDesc": "プロジェクトごとに深刻度・ライセンス・KEV のしきい値を定めると、ゲートがスキャンごとに合否を判定します。結果は GitHub のチェックランと PR コメントとして返され、新規検出のみをベースラインと比較するため既存の負債がマージを妨げることはなく、各検出結果は連携済みの Jira 課題へワンクリックでエスカレーションできます。", + "gateDesc": "深刻度、ライセンス、KEV のポリシーに従ってスキャン結果を評価します。必要に応じてベースラインと比較し、新たに発生した問題を評価できます。CI 連携で GitHub のチェック結果や PR コメントを公開し、Jira 連携で検出結果を課題に関連付けます。", "gateTag": "ベースライン比較 · GitHub チェックラン", "orgTitle": "組織ダッシュボード", - "orgDesc": "すべてのプロジェクトを一画面で。KEV 収載・実際に悪用されている CVE を最優先で表示し、それ以外は EPSS の悪用確率で並べ替え、ライセンス警告と SIEM 向け監査ログ出力も備えます。", - "orgTag": "CISA KEV · EPSS · SIEM 出力", + "orgDesc": "各プロジェクトの脆弱性・ライセンスの集計と未対応の KEV 項目を確認できます。プロジェクトは深刻度別の脆弱性件数で並びます。組織・チームの所属でアクセスを管理し、監査ログは別途出力できます。", + "orgTag": "プロジェクトのリスク集計 · チーム · 監査ログ出力", "embeddedAiTitle": "内蔵オフライン AI インサイト", - "embeddedAiDesc": "設定画面から起動・停止できる内蔵の llama.cpp サイドカーが、Qwen3 1.7B Q4_K_M をローカルで実行します(低スペック環境では Gemma 3 1B にフォールバック)。CPU のみで動作し、API キーも不要、データが外部に出ることもありません。インサイトは設定した推論強度に従い、他のクラウドプロバイダーと同じ使用量ログに記録されます。", - "embeddedAiTag": "llama.cpp · CPU のみ · インターネット不要", + "embeddedAiDesc": "別途インストールした llama.cpp でローカル推論を実行します。既定のダウンロードモデルは Qwen3.5-2B Q4_K_M で、Gemma 4 E2B は任意で手動インストールできます。既定設定は CPU のみで、クラウドの API キーは不要です。オフラインで使う場合は実行ファイルとモデルを事前に用意してください。", + "embeddedAiTag": "llama.cpp · CPU が既定 · ローカル推論", "airgappedTitle": "エアギャップ/オフライン対応", - "airgappedDesc": "チェックサムと出所情報が保証された OSV・deps.dev・EPSS・KEV のスナップショットバンドルを取り込み、外部インターネット接続なしで脆弱性情報を最新に保ちます。wanted-list のエクスポートで次のバンドルに必要なコンポーネントを正確に把握でき、各検出結果にはデータの基準日が表示されます。", + "airgappedDesc": "チェックサムで検証する OSV・deps.dev・EPSS・KEV のスナップショットを取り込み、オフラインで照会できます。必要なコンポーネントの一覧を出力して次のバンドルの対象を絞り、データの鮮度を確認できます。閉域環境ではリポジトリ・メール・Webhook・AI の接続も別途設定してください。", "airgappedTag": "チェックサム · 出所情報 · wanted-list エクスポート", - "remediationTitle": "ワンクリック修正 PR", - "remediationDesc": "セキュリティセンターから直接、脆弱な依存関係のバージョンを引き上げられます — OsWL が GitHub、GitLab、Bitbucket に対してレビュアーと変更概要付きのバージョンアップ PR/MR を作成します。一括実行すれば、スキャン全体の修正可能なコンポーネントそれぞれに対して一度に PR を作成できます。", + "remediationTitle": "依存関係のアップグレード PR", + "remediationDesc": "接続した GitHub・GitLab・Bitbucket リポジトリの対応マニフェストに、バージョン変更の PR/MR を作成します。一括操作では対象コンポーネントごとに作成を試み、個別の結果を表示します。リポジトリの権限と対応するバージョン編集形式が必要で、レビュアーの指定はプロバイダーに依存します。", "remediationTag": "GitHub · GitLab · Bitbucket · 一括アップグレード", - "supplyChainTitle": "サプライチェーン攻撃の検知", - "supplyChainDesc": "解決されたすべてのパッケージ名を、エコシステムごとの人気パッケージリストとの編集距離・紛らわしい文字の一致によるタイポスクワッティング検査と、公開レジストリから解決された内部用らしき名前などの軽量な依存関係混同シグナルでスクリーニングします。検出されたパッケージは強制ブロックではなく「出所確認」の警告として表示されます。", + "supplyChainTitle": "サプライチェーンのリスク情報", + "supplyChainDesc": "不審なパッケージ名の類似や依存関係混同のパターンをレビュー対象として表示します。ヒューリスティックだけでは攻撃やパッケージの出所を断定できません。確認済みの悪意あるパッケージのアドバイザリは別途追跡し、セキュリティゲートを失敗させる場合があります。", "supplyChainTag": "タイポスクワッティング検知 · 依存関係混同", - "ssoTitle": "OIDC によるエンタープライズ SSO", - "ssoDesc": "Okta、Entra ID、またはあらゆる OpenID Connect プロバイダーでサインインできます。OIDC クライアントを設定すると自動的に有効になります。IdP がすでにユーザーを認証しているためメール OTP のステップはスキップされ、既存のロール・権限ルールはそのまま適用されます。", - "ssoTag": "Okta · Entra ID · あらゆる OIDC プロバイダー" + "ssoTitle": "SSO とユーザープロビジョニング", + "ssoDesc": "OIDC または SAML 2.0 の SSO と SCIM 2.0 プロビジョニングを構成できます。IdP のユーザーを OsWL アカウントに対応付け、グループ割り当てを管理します。連携設定とアカウントのプロビジョニングが必要で、OsWL のロールとプロジェクトへのアクセス権限も適用されます。", + "ssoTag": "OIDC · SAML 2.0 · SCIM 2.0", + "sourceTitle": "ソース・バイトコードの参照解析", + "sourceDesc": "必要な入力があれば、Java バイトコードの参照と、対応する Python・JavaScript ソースのインポートを解析します。根拠と解析範囲を確認できます。参照は実行を証明するものではなく、不完全な解析は不明のまま扱います。", + "sourceTag": "Java · Python · JavaScript · 解析の根拠", + "secretTitle": "シークレット・IaC の検出", + "secretDesc": "Quick Import で収集したファイルからシークレットのパターンやインフラ設定の問題を検出します。脆弱性の結果と併せて場所とルールを確認できます。範囲は収集ファイルと有効なスキャナーに依存し、ソース全体のチェックアウトは別のオプションです。", + "secretTag": "収集ファイル · ルールに基づく検出" }, "platform": { - "label": "実画面", + "label": "製品画面", "title": "セキュリティ &
コンプライアンスチームのために", - "subtitle": "すべての画面が「次の行動」を前提に設計されています。CVE のトリアージから対応追跡まで、画面を行き来する必要はありません。", + "subtitle": "脆弱性のレビュー、プロジェクト管理、ライセンス分析、リスク推移の画面を確認できます。スクリーンショットにはサンプルデータを使用しています。", "tabSc": "セキュリティセンター", "tabProjects": "プロジェクト", "tabLicense": "ライセンス分析", @@ -94,12 +102,13 @@ "vuln": { "label": "脆弱性インテリジェンス", "title": "本当に重要なものから
優先順位をつける", - "desc": "OsWL は悪用可能かつ修正可能な脆弱性を先に提示します。CVSS スコア、悪用の成熟度、修正版の有無は deps.dev と OSV のアドバイザリから自動的に拡充されます。", + "desc": "CVSS の深刻度、EPSS の確率、KEV 掲載状況、既知の修正バージョンを使ってレビューの優先順位を決めます。利用可能な情報はアドバイザリの出所と対応範囲に依存し、実環境での悪用可能性を証明するものではありません。", "bullet1": "Critical & High の脆弱性をパッチ対象とともに提示", "bullet2": "修正バージョンを表示 — トリアージ前に修正版の有無を確認", "bullet3": "推移的な影響をルート依存パスまで追跡", "bullet4": "ステータスの一括更新 — 確認済み・無視をまとめて反映", - "demoTitle": "backend-api · v3.2.0 — セキュリティセンター" + "demoTitle": "検出結果の例 · CVSS 3.x", + "demoNote": "OsWL プロジェクトの実際のスキャン結果ではなく表示例です。CVE リンクから出典を確認できます。スコアは出所によって異なる場合があります。" }, "table": { "component": "コンポーネント", @@ -115,8 +124,8 @@ "licCard": { "gpl2Type": "コピーレフト · 強", "lgplType": "コピーレフト · 弱", - "apacheType": "寛容", - "mitType": "寛容", + "apacheType": "パーミッシブ", + "mitType": "パーミッシブ", "mplType": "コピーレフト · ファイル単位", "proprietaryType": "商用", "restricted": "制限", @@ -127,27 +136,27 @@ "license": { "label": "ライセンスインテリジェンス", "title": "コンプライアンスの
死角をなくす", - "desc": "依存ツリー全体の SPDX ライセンスを自動分類します。コピーレフト義務、ライセンス競合、本番投入前に法務レビューが必要なコンポーネントを特定できます。", - "bullet1": "ソース全体の開示が必要なコピーレフトライセンス", - "bullet2": "弱いコピーレフトまたはデュアルライセンス — 法務レビュー推奨", - "bullet3": "寛容なライセンス — 商用利用でも安全" + "desc": "検出した SPDX ライセンス識別子をポリシーと比較し、利用・配布方法に応じた義務を確認します。表示例の分類はポリシー上の状態であり、法的な承認ではありません。", + "bullet1": "利用・配布方法に応じたコピーレフトの義務", + "bullet2": "弱いコピーレフトやデュアルライセンスの適用条件を確認", + "bullet3": "パーミッシブライセンスでも利用条件や表示義務への対応が必要" }, "workflow": { "label": "ワークフロー", "title": "数分で
スキャンを開始", "subtitle": "OsWL は既存の開発ワークフローにそのまま組み込めます。エージェントも大掛かりな準備も不要です。", "step1Title": "リポジトリを接続", - "step1Desc": "GitHub、GitLab、Bitbucket の Personal Access Token を登録します。OsWL がリポジトリとブランチを検出してマニフェストを取り込みます — ソースコードのアップロードは不要です。", + "step1Desc": "対応する VCS アカウントを接続してリポジトリを選ぶか、公開リポジトリの URL を入力します。Quick Import は OsWL サーバー上で対応ファイルを収集します。ビルド実行は既定で無効です。", "step2Title": "依存関係スキャンを実行", - "step2Desc": "OsWL がロックファイルとビルドマニフェストを解析し、推移的な依存グラフを解決したうえで、すべてのコンポーネントを deps.dev、OSV、ライセンスポリシーと照合します。", + "step2Desc": "OsWL が対応するロックファイルやビルドマニフェストから依存関係を読み取り、コンポーネントに脆弱性とライセンスの情報を関連付けます。", "step3Title": "トリアージと修正対応", "step3Desc": "セキュリティセンターで結果を確認し、深刻度で絞り込み、ステータスを一括更新して、リスク傾向ダッシュボードでスキャンごとの状態を追跡します。", "step4Title": "CI/CD で自動化", - "step4Desc": "OsWL CLI でプルリクエストをゲートします。Bearer トークン API キーがあれば、Jenkins、GitHub Actions、GitLab CI、独自のビルドサーバーなど、あらゆるパイプラインで自動スキャンできます。" + "step4Desc": "プロジェクト API キーと送信者の認証情報で CI からスキャンを送信し、完了した結果をゲート API または CLI で評価します。返された判定を反映するようにパイプラインを設定してください。" }, "cta": { "title": "オープンソース
安全に運用する", - "sub": "OsWL をオンプレミスに配置し、組織のすべてのプロジェクト・チーム・依存関係にわたる完全な SBOM 可視性を手に入れましょう。", + "sub": "自社のインフラに OsWL を導入し、スキャンした依存関係の確認、対応状況の追跡、プロジェクトへのアクセス管理を一か所で行えます。", "primary": "今すぐ始める →", "secondary": "使い方を見る" }, diff --git a/landing/i18n/ko.json b/landing/i18n/ko.json index 5b49a44f..77098519 100644 --- a/landing/i18n/ko.json +++ b/landing/i18n/ko.json @@ -8,18 +8,20 @@ "platform": "플랫폼", "howItWorks": "사용 방법", "download": "다운로드", - "themeToggle": "라이트/다크 테마 전환" + "docs": "문서", + "docsUrl": "https://github.com/SalkCoding/Oswl/blob/main/docs/ko/Home.md" }, "hero": { - "badge": "엔터프라이즈 SCA 플랫폼", - "title": "오픈소스 스택
모든 취약점을 파악하세요", - "subtitle": "OsWL은 의존성의 CVE, 라이선스 의무, 전이적 리스크를 지속적으로 스캔하여 보안·컴플라이언스 팀이 단일 자체 호스팅 플랫폼에서 완전한 SBOM 가시성을 확보할 수 있게 합니다.", + "badge": "OsWL 1.0.5.1", + "title": "오픈소스 스택
의존성 리스크를 파악하세요", + "subtitle": "하나의 자체 호스팅 플랫폼에서 CVE, 라이선스 의무, 의존성 리스크를 관리하세요. 저장소를 스캔하거나 CI에서 의존성을 제출하고, 보안·컴플라이언스 팀과 탐지 결과를 검토할 수 있습니다.", "ctaPrimary": "지금 설치하기", - "ctaSecondary": "플랫폼 보기" + "ctaSecondary": "플랫폼 보기", + "installUrl": "https://github.com/SalkCoding/Oswl/blob/main/docs/ko/Getting-Started.md" }, "stats": { "multiSource": "다중 소스", - "multiSourceLabel": "GHSA · OSV Advisories", + "multiSourceLabel": "OSV · GitHub Advisory · NVD", "ecosystemsLabel": "패키지 생태계", "onDemand": "온디맨드", "onDemandLabel": "deps.dev 보강", @@ -30,54 +32,60 @@ "features": { "label": "핵심 기능", "title": "엔드투엔드
소프트웨어 구성 분석", - "subtitle": "의존성 수집부터 조치까지, OsWL은 엔터프라이즈급 정확도와 감사 추적 가능성으로 전체 SCA 라이프사이클을 커버합니다.", + "subtitle": "의존성을 수집하고 탐지 결과를 평가하며 조치 현황을 추적합니다. 프로젝트 접근 제어와 감사 로그도 함께 제공합니다.", "cveTitle": "CVE 취약점 추적", - "cveDesc": "deps.dev와 OSV에서 온디맨드로 보강합니다. 모든 컴포넌트를 CVSS 3.x 점수 및 패치 가능 여부와 교차 검증하며, 유예 처리된 컴포넌트의 유예 기간이 만료되면 리마인더 배너가 표시됩니다.", + "cveDesc": "취약점 정보, 제공되는 CVSS 점수와 확인된 수정 버전을 검토합니다. CVE에서 NVD를 바로 열고 조치 상태를 관리하세요. 수정 버전이 표시되지 않아도 패치가 없다는 뜻은 아닙니다.", "cveTag": "deps.dev · OSV · GHSA", "licenseTitle": "라이선스 리스크 관리", - "licenseDesc": "OSS 라이선스의 SPDX 자동 분류 — 카피레프트, 허용적, 독점 — 의무 규칙, 충돌 감지, 스캔별 NOTICE / SPDX SBOM 출력을 제공합니다.", + "licenseDesc": "탐지한 SPDX 라이선스 식별자를 정책에 따라 분류하고, 준수 의무와 잠재적 충돌을 검토합니다. 스캔별 NOTICE 파일과 SPDX SBOM을 내보낼 수 있습니다.", "licenseTag": "GPL · LGPL · Apache · MIT · AGPL · MPL", "riskTitle": "리스크 트렌드 분석", "riskDesc": "스캔 간 보안 상태를 추적합니다. Critical, High, Medium, Low 심각도 추이를 시각화하고, LLM 프로바이더 설정 시 AI 생성 편차 인사이트를 선택적으로 제공합니다.", "riskTag": "버전 간 델타", "transitiveTitle": "전이적 의존성 탐색", - "transitiveDesc": "의존성 그래프 깊숙이 숨은 취약점을 발견합니다. DependencyPath 분석이 지원되는 모든 매니페스트에서 직접·전이적 노출을 추적합니다.", - "transitiveTag": "Maven · npm · PyPI · Go · Cargo · NuGet · RubyGems · Composer · Conan", + "transitiveDesc": "지원되는 잠금 파일이나 그래프 데이터에 경로가 있으면 직접·전이 의존성을 추적합니다. 지원 범위는 생태계와 입력에 따라 달라지며, 매니페스트만으로 전체 의존성 그래프를 확인하지 못할 수 있습니다.", + "transitiveTag": "Maven · npm · PyPI · Go · Cargo · NuGet · RubyGems · Composer", "gitTitle": "Git 네이티브 저장소 연동", - "gitDesc": "Personal Access Token으로 GitHub, GitLab, Bitbucket을 연결합니다. Quick Import는 대량 저장소 임포트를 큐에 담아 처리하며 진행 중인 작업은 취소 버튼으로 중단할 수 있고, 모든 스캔은 브랜치 인식 스냅샷과 프로젝트별 히스토리를 기본 제공합니다.", + "gitDesc": "GitHub·GitLab·Bitbucket에 맞는 인증 정보를 연결하거나 공개 저장소 URL로 가져오세요. 저장소 가져오기를 대기열에 등록하고 진행 중인 작업을 취소할 수 있으며, 프로젝트별 브랜치 정보와 스캔 이력을 보관합니다.", "gitTag": "GitHub · GitLab · Bitbucket", "cliTitle": "CLI & CI/CD 파이프라인 스캔", - "cliDesc": "단일 CLI 명령으로 모든 빌드 파이프라인에 OsWL을 통합합니다. 파싱된 매니페스트 메타데이터만 업로드되며 소스 코드는 빌드 호스트를 벗어나지 않습니다. 프로젝트별 또는 조직 전체 Bearer 토큰 API 키를 지원합니다.", - "cliTag": "매니페스트 전용 업로드", + "cliDesc": "로컬 프로젝트나 CI에서 스캔을 제출하세요. CLI는 수집 대상 의존성 매니페스트와 빌드 파일을 서버로 전송해 파싱한 뒤, 프로젝트별 API 키와 제출자 계정 인증 정보로 컴포넌트를 제출합니다.", + "cliTag": "빌드 입력 파일 업로드", "sbomTitle": "CycloneDX SBOM & VEX", "sbomDesc": "CycloneDX 1.6 SBOM, 트리아지 판단이 담긴 VEX 문서, 코드 스캐닝 대시보드용 SARIF 리포트로 내보낼 수 있습니다. 직접 빌드하지 않는 컴포넌트는 외부 CycloneDX 파일을 가져와 스캔하세요.", "sbomTag": "CycloneDX 1.6 · VEX · SARIF 2.1.0", "gateTitle": "Pull Request 정책 게이트", - "gateDesc": "프로젝트별로 심각도·라이선스·KEV 기준을 정하면 게이트가 스캔마다 통과/실패를 판정합니다. 결과는 GitHub 체크 런과 PR 코멘트로 게시되며, 새로 발견된 항목만 베이스라인과 비교하므로 기존 부채가 머지를 막지 않고, 각 발견 항목은 연동된 Jira 이슈로 바로 에스컬레이션할 수 있습니다.", + "gateDesc": "심각도, 라이선스, KEV 정책에 따라 스캔 결과를 평가합니다. 필요하면 기준 스캔과 비교해 새로 발생한 문제를 평가할 수 있습니다. CI 연동으로 GitHub 검사 결과와 PR 댓글을 게시하고, Jira 연동으로 탐지 결과를 이슈에 연결합니다.", "gateTag": "베이스라인 비교 · GitHub 체크 런", "orgTitle": "조직 대시보드", - "orgDesc": "모든 프로젝트를 한 화면에서 봅니다. KEV 등재·실제 악용 중인 CVE를 먼저 보여주고, 나머지는 EPSS 악용 확률로 순위를 정하며, 라이선스 경고와 SIEM 연동용 감사 로그 내보내기를 제공합니다.", - "orgTag": "CISA KEV · EPSS · SIEM 내보내기", + "orgDesc": "프로젝트 전반의 취약점·라이선스 집계와 미조치 KEV 항목을 확인합니다. 프로젝트는 심각도별 취약점 수를 기준으로 정렬됩니다. 조직·팀 멤버십으로 접근을 관리하고, 감사 로그는 별도로 내보낼 수 있습니다.", + "orgTag": "프로젝트 리스크 집계 · 팀 · 감사 로그 내보내기", "embeddedAiTitle": "내장 오프라인 AI 인사이트", - "embeddedAiDesc": "설정 화면에서 시작/중지하는 내장 llama.cpp 사이드카가 Qwen3 1.7B Q4_K_M을 로컬에서 구동합니다(저사양 환경은 Gemma 3 1B로 폴백) — CPU 전용이며 API 키도, 외부로 나가는 데이터도 없습니다. 인사이트는 설정한 추론 강도를 따르며 다른 클라우드 프로바이더와 동일한 사용량 로그에 기록됩니다.", - "embeddedAiTag": "llama.cpp · CPU 전용 · 인터넷 불필요", + "embeddedAiDesc": "별도로 설치한 llama.cpp 실행 파일로 로컬 추론을 수행합니다. 기본 다운로드 모델은 Qwen3.5-2B Q4_K_M이며, Gemma 4 E2B는 선택적으로 직접 설치할 수 있습니다. 기본 설정은 CPU 전용이며 클라우드 API 키가 필요하지 않습니다. 오프라인 사용 시 실행 파일과 모델을 미리 준비하세요.", + "embeddedAiTag": "llama.cpp · CPU 기본 설정 · 로컬 추론", "airgappedTitle": "폐쇄망 · 오프라인 대응", - "airgappedDesc": "체크섬과 프로비넌스가 보장된 OSV·deps.dev·EPSS·KEV 스냅샷 번들을 가져와 외부 인터넷 연결 없이 취약점 정보를 최신 상태로 유지합니다. wanted-list 내보내기로 다음 번들에 포함되어야 할 컴포넌트를 정확히 파악할 수 있고, 모든 탐지 결과에 데이터 기준일이 표시됩니다.", + "airgappedDesc": "체크섬으로 검증하는 OSV·deps.dev·EPSS·KEV 스냅샷 번들을 반입해 오프라인에서 조회합니다. 필요한 컴포넌트 목록을 내보내 다음 번들의 대상을 정하고, 데이터 최신성을 확인할 수 있습니다. 폐쇄망에서는 저장소·메일·웹훅·AI 연결도 별도로 구성하세요.", "airgappedTag": "체크섬 · 프로비넌스 · wanted-list 내보내기", - "remediationTitle": "원클릭 조치 PR", - "remediationDesc": "보안 센터에서 바로 취약한 의존성 버전을 올릴 수 있습니다 — OsWL이 GitHub, GitLab, Bitbucket에 리뷰어와 변경 요약이 포함된 버전 업그레이드 PR/MR을 생성합니다. 일괄 실행하면 스캔 전체에서 패치 가능한 모든 컴포넌트에 대해 한 번에 PR을 생성합니다.", + "remediationTitle": "의존성 업그레이드 PR", + "remediationDesc": "연결된 GitHub·GitLab·Bitbucket 저장소의 지원 매니페스트에 버전 변경 PR/MR을 생성합니다. 일괄 실행은 대상 컴포넌트별 PR 생성을 시도하고 각각의 결과를 보여줍니다. 저장소 권한과 지원되는 버전 수정 방식이 필요하며, 리뷰어 지정은 제공자에 따라 달라집니다.", "remediationTag": "GitHub · GitLab · Bitbucket · 일괄 업그레이드", - "supplyChainTitle": "공급망 공격 탐지", - "supplyChainDesc": "모든 해석된 패키지명은 타이포스쿼팅(에코시스템별 인기 패키지 목록과의 편집 거리 및 혼동 문자 매칭)과, 공개 레지스트리에서 해석된 내부용으로 보이는 이름 같은 경량 의존성 혼동 신호를 함께 검사받습니다. 탐지된 패키지는 강제 차단이 아니라 '출처 확인' 경고로 표시됩니다.", + "supplyChainTitle": "공급망 리스크 신호", + "supplyChainDesc": "패키지 이름의 의심스러운 유사성과 의존성 혼동 패턴을 검토 대상으로 표시합니다. 휴리스틱 결과만으로 공격 여부나 패키지 출처를 확정하지 않습니다. 확인된 악성 패키지 권고는 별도로 추적하며 보안 게이트를 실패시킬 수 있습니다.", "supplyChainTag": "타이포스쿼팅 탐지 · 의존성 혼동", - "ssoTitle": "OIDC 기반 엔터프라이즈 SSO", - "ssoDesc": "Okta, Entra ID 또는 모든 OpenID Connect 프로바이더로 로그인할 수 있으며, OIDC 클라이언트를 설정하면 자동으로 활성화됩니다. IdP가 이미 사용자를 인증했으므로 이메일 OTP 단계는 건너뛰고, 기존의 모든 역할·권한 규칙은 그대로 적용됩니다.", - "ssoTag": "Okta · Entra ID · 모든 OIDC 프로바이더" + "ssoTitle": "SSO 및 사용자 프로비저닝", + "ssoDesc": "OIDC 또는 SAML 2.0 SSO와 SCIM 2.0 프로비저닝을 구성할 수 있습니다. IdP 사용자를 OsWL 계정에 연결하고 그룹 할당을 관리하세요. 연동 설정과 계정 프로비저닝이 필요하며, OsWL 역할과 프로젝트 접근 권한은 계속 적용됩니다.", + "ssoTag": "OIDC · SAML 2.0 · SCIM 2.0", + "sourceTitle": "소스·바이트코드 참조 분석", + "sourceDesc": "필요한 입력을 제공하면 Java 바이트코드 참조와 지원되는 Python·JavaScript 소스의 가져오기 구문을 분석합니다. 근거와 분석 범위로 탐지 결과를 검토할 수 있습니다. 참조가 실행을 증명하지는 않으며, 분석이 불완전하면 미확인 상태를 유지합니다.", + "sourceTag": "Java · Python · JavaScript · 분석 근거", + "secretTitle": "시크릿·IaC 탐지", + "secretDesc": "Quick Import가 수집한 파일에서 시크릿 패턴과 인프라 설정 문제를 검사합니다. 취약점 결과와 함께 위치와 탐지 규칙을 검토하세요. 검사 범위는 수집 파일과 활성화한 스캐너에 따라 달라지며, 전체 소스 체크아웃은 별도 옵션입니다.", + "secretTag": "수집 파일 · 규칙 기반 탐지" }, "platform": { - "label": "라이브 플랫폼", + "label": "제품 화면", "title": "보안 &
컴플라이언스 팀을 위해", - "subtitle": "모든 화면은 실행 가능성을 위해 설계되었습니다 — CVE 트리아지부터 조치 추적까지, 컨텍스트 전환 없이 작업할 수 있습니다.", + "subtitle": "취약점 검토, 프로젝트 관리, 라이선스 분석, 리스크 추세 화면을 확인하세요. 스크린샷은 예시 데이터를 사용합니다.", "tabSc": "보안 센터", "tabProjects": "프로젝트", "tabLicense": "라이선스 분석", @@ -94,12 +102,13 @@ "vuln": { "label": "취약점 인텔리전스", "title": "진짜 중요한 것부터
우선순위를 정하세요", - "desc": "OsWL은 악용 가능하고 패치 가능한 취약점을 먼저 표면화합니다. CVSS 점수, 익스플로잇 성숙도, 수정 가능 여부가 deps.dev와 OSV 권고 피드에서 자동으로 보강됩니다.", + "desc": "CVSS 심각도, EPSS 확률, KEV 등재 여부와 알려진 수정 버전으로 검토 우선순위를 정하세요. 제공되는 정보는 권고 출처와 컴포넌트 지원 범위에 따라 달라지며, 해당 배포 환경에서의 악용 가능성을 증명하지는 않습니다.", "bullet1": "Critical & High 취약점을 패치 대상과 함께 표시", "bullet2": "수정 버전 표시 — 트리아지 전에 패치된 릴리스 존재 여부 확인", "bullet3": "전이적 노출을 루트 의존성 경로까지 추적", "bullet4": "일괄 상태 업데이트 — 한 번에 검토 완료 또는 무시 처리", - "demoTitle": "backend-api · v3.2.0 — 보안 센터" + "demoTitle": "탐지 결과 예시 · CVSS 3.x", + "demoNote": "OsWL 프로젝트의 실제 스캔 결과가 아닌 예시입니다. CVE 링크에서 출처를 확인할 수 있으며, 점수는 출처에 따라 다를 수 있습니다." }, "table": { "component": "컴포넌트", @@ -127,27 +136,27 @@ "license": { "label": "라이선스 인텔리전스", "title": "컴플라이언스
사각지대를 제거하세요", - "desc": "의존성 트리의 모든 SPDX 라이선스를 자동 분류합니다. 카피레프트 의무, 라이선스 충돌, 프로덕션 배포 전 법무 검토가 필요한 컴포넌트를 식별합니다.", - "bullet1": "전체 소스 공개가 필요한 카피레프트 라이선스", - "bullet2": "약한 카피레프트 또는 이중 라이선스 — 법무 검토 권장", - "bullet3": "허용적 라이선스 — 상업적 사용에 안전" + "desc": "탐지한 SPDX 라이선스 식별자를 정책과 비교하고 사용·배포 방식에 따른 의무를 검토하세요. 예시 분류는 정책에 따른 상태이며 법적 승인을 뜻하지 않습니다.", + "bullet1": "사용·배포 방식에 따라 달라지는 카피레프트 의무", + "bullet2": "약한 카피레프트와 이중 라이선스의 적용 조건 검토", + "bullet3": "허용적 라이선스도 이용 조건과 고지 의무 준수 필요" }, "workflow": { "label": "워크플로", "title": "몇 분 안에
스캔을 시작하세요", "subtitle": "OsWL은 기존 개발 워크플로에 직접 통합됩니다. 에이전트나 무거운 설정이 필요 없습니다.", "step1Title": "저장소 연결", - "step1Desc": "GitHub, GitLab, Bitbucket Personal Access Token을 등록합니다. OsWL이 저장소와 브랜치를 탐색하고 매니페스트를 임포트합니다 — 소스 코드 업로드는 필요 없습니다.", + "step1Desc": "지원되는 VCS 계정을 연결해 저장소를 탐색하거나 공개 저장소 URL을 입력하세요. Quick Import는 OsWL 서버에서 지원 파일을 수집하며, 빌드 실행은 기본으로 비활성화되어 있습니다.", "step2Title": "의존성 스캔 실행", - "step2Desc": "OsWL이 lock 파일과 빌드 매니페스트를 파싱하고 전이적 의존성 그래프를 해석한 뒤, 모든 컴포넌트를 deps.dev, OSV, 라이선스 정책과 교차 검증합니다.", + "step2Desc": "OsWL이 지원되는 잠금 파일과 빌드 매니페스트에서 의존성 정보를 읽고, 컴포넌트에 취약점 및 라이선스 정보를 연결합니다.", "step3Title": "트리아지 & 조치", "step3Desc": "보안 센터에서 결과를 검토하고, 심각도별로 필터링하며, 상태를 일괄 업데이트하고, 리스크 트렌드 대시보드에서 스캔 간 보안 상태를 추적합니다.", "step4Title": "CI/CD 자동화", - "step4Desc": "OsWL CLI로 Pull Request를 게이트합니다. Bearer 토큰 API 키로 Jenkins, GitHub Actions, GitLab CI 또는 커스텀 빌드 서버 등 모든 파이프라인에서 자동 스캔이 가능합니다." + "step4Desc": "프로젝트 API 키와 제출자 인증 정보로 CI에서 스캔을 제출하고, 완료된 결과를 게이트 API 또는 CLI로 평가하세요. 반환된 판정을 반영하도록 파이프라인을 구성해야 합니다." }, "cta": { "title": "오픈소스 스택
안전하게 보호하세요", - "sub": "OsWL을 온프레미스에 배포하고 조직의 모든 프로젝트, 팀, 의존성에 대한 완전한 SBOM 가시성을 확보하세요.", + "sub": "자체 인프라에 OsWL을 배포해 스캔한 의존성 검토, 조치 추적, 프로젝트 접근 관리를 한곳에서 수행하세요.", "primary": "지금 시작하기 →", "secondary": "사용 방법 보기" }, diff --git a/landing/index.html b/landing/index.html index 2477f744..fbf49519 100644 --- a/landing/index.html +++ b/landing/index.html @@ -1,36 +1,18 @@ - + OsWL - Enterprise SCA Platform - - - + - + + - + @@ -484,17 +463,12 @@
- Enterprise SCA Platform + OsWL 1.0.5.1
-

- Know Every Vulnerability
in Your Open Source Stack -

-

- OsWL continuously scans your dependencies for CVEs, license obligations, and transitive risks — - giving security and compliance teams complete SBOM visibility from a single self-hosted platform. -

+

Understand Dependency Risks
in Your Open Source Stack

+

Track CVEs, license obligations, and dependency risks from one self-hosted platform. Scan repositories or submit dependencies from CI, then review findings with your security and compliance teams.

@@ -505,11 +479,11 @@

Multi-source
-
GHSA · OSV Advisories
+
OSV · GitHub Advisory · NVD
-
9
+
8+
Package Ecosystems
@@ -519,13 +493,13 @@

-
CVSS 3.x
+
CVSS 3.x / 4.0
Scoring Supported
Git-native
-
GitHub · GitLab · Bitbucket
+
GitHub · GitLab · Bitbucket

@@ -535,7 +509,7 @@

End-to-End Software
Composition Analysis

-

From dependency ingestion to remediation, OsWL covers the full SCA lifecycle with enterprise-grade accuracy and auditability.

+

Collect dependencies, assess findings, and track remediation with project access controls and audit logs.

@@ -543,8 +517,8 @@

End-to-End Software

CVE Vulnerability Tracking
-

On-demand enrichment from deps.dev and OSV. Every component is cross-referenced against CVSS 3.x scores and patch availability, and a reminder banner surfaces when a deferred component's grace period expires.

- deps.dev · OSV · GHSA +

Enrich scanned components with vulnerability data, available CVSS scores, and known fix versions. Track review decisions and see a reminder when a component’s deferral period ends.

+ deps.dev · OSV · GHSA
@@ -552,8 +526,8 @@

End-to-End Software

License Risk Management
-

Automatic SPDX classification of OSS licenses — copyleft, permissive, proprietary — with obligation rules, conflict detection, and NOTICE / SPDX SBOM export per scan.

- GPL · LGPL · Apache · MIT · AGPL · MPL +

Classify detected SPDX license identifiers against your license policy, review obligations and potential conflicts, and export NOTICE files or SPDX SBOMs for each scan.

+ GPL · LGPL · Apache · MIT · AGPL · MPL
@@ -570,8 +544,8 @@

End-to-End Software

Transitive Dependency Traversal
-

Uncover vulnerabilities hidden deep in your dependency graph. DependencyPath analysis traces both direct and transitive exposure across every supported manifest.

- Maven · npm · PyPI · Go · Cargo · NuGet · RubyGems · Composer · Conan +

Trace direct and transitive dependencies when supported lock files or graph data provide paths. Coverage depends on the ecosystem and input; a manifest alone may not contain the complete dependency graph.

+ Maven · npm · PyPI · Go · Cargo · NuGet · RubyGems · Composer

@@ -579,8 +553,8 @@

End-to-End Software

Git-Native Repository Integration
-

Connect GitHub, GitLab, or Bitbucket via Personal Access Token. Quick Import queues bulk repository imports with a cancel button for jobs still running, and every scan keeps branch-aware snapshots and per-project history out of the box.

- GitHub · GitLab · Bitbucket +

Connect supported GitHub, GitLab, or Bitbucket credentials, or import a public repository URL. Queue repository imports, cancel active jobs, and retain branch information and scan history for each project.

+ GitHub · GitLab · Bitbucket
@@ -588,8 +562,8 @@

End-to-End Software

CLI & CI/CD Pipeline Scanning
-

Integrate OsWL into any build pipeline with a single CLI command. Only parsed manifest metadata is uploaded — no source code leaves your build host. Bearer-token API keys per project or organization-wide.

- Manifest-only upload +

Submit scans from local projects or CI. The CLI uploads selected dependency manifests and build files for server-side parsing, then submits components with a project-scoped API key and the submitting user’s credentials.

+ Build-input upload
@@ -597,8 +571,8 @@

End-to-End Software

CycloneDX SBOM & VEX
-

Export a CycloneDX 1.6 SBOM, a VEX document carrying your triage decisions, or a SARIF report for code-scanning dashboards. Import third-party CycloneDX files to scan components you don’t build yourself.

- CycloneDX 1.6 · VEX · SARIF 2.1.0 +

Export a CycloneDX 1.6 SBOM, a VEX document carrying your triage decisions, or a SARIF report for code-scanning dashboards. Import third-party CycloneDX files to scan components you don’t build yourself.

+ CycloneDX 1.6 · VEX · SARIF 2.1.0
@@ -606,8 +580,8 @@

End-to-End Software

Policy Gates for Pull Requests
-

Define severity, license, and KEV thresholds per project, then let the gate pass or fail each scan. Results publish back to GitHub as a check run and PR comment, new findings are compared against a baseline so existing debt never blocks a merge, and any finding can be escalated to a linked Jira issue.

- Baseline diff · GitHub check run +

Evaluate scan results against severity, license, and KEV policies. Use a baseline to assess newly introduced findings when needed. CI integrations can publish GitHub checks and PR comments, and Jira integration links findings to issues.

+ Baseline diff · GitHub check run
@@ -615,8 +589,8 @@

End-to-End Software

Organization Dashboard
-

One view across every project: KEV-listed and actively exploited CVEs first, EPSS exploit probability for ranking the rest, license warnings, and an audit-log export for your SIEM.

- CISA KEV · EPSS · SIEM export +

Review vulnerability and license totals across projects, including unaddressed KEV-listed findings. Projects are ranked by severity counts. Organization and team membership control access; audit logs can be exported separately.

+ Project risk totals · Teams · Audit export
@@ -624,8 +598,8 @@

End-to-End Software

Embedded, Offline AI Insights
-

A built-in llama.cpp sidecar starts and stops from Settings, running Qwen3 1.7B Q4_K_M locally (Gemma 3 1B on low-spec hardware) — CPU-only, no API key, and nothing leaves the host. Insights honor your chosen reasoning effort and are tracked in the same usage log as every cloud provider.

- llama.cpp · CPU-only · Zero internet +

Run local inference with a separately installed llama.cpp runtime. Qwen3.5-2B Q4_K_M is the default download; Gemma 4 E2B is an optional manual installation. CPU-only defaults require no cloud API key. Prepare the runtime and model in advance for offline use.

+ llama.cpp · CPU default · Local inference
@@ -633,36 +607,50 @@

End-to-End Software

Air-Gapped & Offline-Ready
-

Import versioned OSV, deps.dev, EPSS, and KEV snapshot bundles — checksummed and provenance-stamped — to keep vulnerability intelligence current with zero outbound internet access. A wanted-list export tells you exactly which components the next bundle needs to cover, and every finding shows its data-as-of date.

- Checksum · Provenance · Wanted-list export +

Import checksummed OSV, deps.dev, EPSS, and KEV snapshot bundles for offline data lookups. Export a wanted list to target the next bundle and check its data freshness. Configure repository, email, webhook, and AI connections separately for an isolated deployment.

+ Checksum · Provenance · Wanted-list export
-
One-Click Remediation PRs
-

Bump a vulnerable dependency straight from the Security Center — OsWL opens a version-bump PR or MR against GitHub, GitLab, or Bitbucket, with reviewers and a change summary attached. Run it in batch to open one PR per patchable component across an entire scan in a single action.

- GitHub · GitLab · Bitbucket · Batch upgrade +
Dependency Upgrade PRs
+

Create version-bump PRs or MRs for supported manifests in connected GitHub, GitLab, and Bitbucket repositories. Batch actions attempt one PR per eligible component and report individual outcomes. Repository permissions and a supported version edit are required; reviewer assignment depends on the provider.

+ GitHub · GitLab · Bitbucket · Batch upgrade
-
Supply-Chain Attack Detection
-

Every resolved package name is screened for typosquatting — edit-distance and confusable-character matching against a per-ecosystem popular-package list — plus lightweight dependency-confusion signals like internal-looking names resolved from a public registry. Flagged packages surface as a verify-origin warning, never a silent block.

- Typosquat detection · Dependency confusion +
Supply-Chain Risk Signals
+

Flag suspicious package-name similarities and dependency-confusion patterns for review. These heuristics do not prove an attack or establish package provenance. Confirmed malware advisories are tracked separately and can fail the security gate.

+ Typosquat detection · Dependency confusion
-
Enterprise SSO via OIDC
-

Sign in through Okta, Entra ID, or any OpenID Connect provider — SSO activates automatically once an OIDC client is configured. Logins skip the email OTP step since the identity provider already authenticated the user, while every existing role and permission rule still applies.

- Okta · Entra ID · Any OIDC provider +
SSO & User Provisioning
+

Configure OIDC or SAML 2.0 single sign-on and SCIM 2.0 provisioning. Map identity-provider users to OsWL accounts and manage group assignments. Integration setup and account provisioning are required; OsWL roles and project access still apply.

+ OIDC · SAML 2.0 · SCIM 2.0
+
+
+
Source & Bytecode References
+

Inspect Java bytecode references and supported Python or JavaScript source imports when the corresponding inputs are provided. Evidence and coverage help explain findings. A reference does not prove execution, and incomplete analysis remains unknown.

+ Java · Python · JavaScript · Coverage evidence +
+ +
+
+
Secret & IaC Findings
+

Inspect collected Quick Import files for secret patterns and infrastructure configuration issues. Review locations and rules alongside vulnerability findings. Coverage depends on collected files and enabled scanners; full source checkout is a separate option.

+ Collected files · Rule-based findings +
+ @@ -670,26 +658,26 @@

End-to-End Software
- -

Built for Security &
Compliance Teams

-

Every screen is engineered for actionability — from CVE triage to remediation tracking, no context switching required.

- -
- - - - + +

Built for Security &
Compliance Teams

+

Explore example screens for vulnerability review, project management, license analysis, and risk trends. Screenshots use sample data.

+ +
+ + + +
-
+
OsWL Security Center — CVE triage with CVSS scores
-
+ -
+ -
+
@@ -704,9 +692,7 @@

Built for Security &am

Prioritize What
Actually Matters

-

- OsWL surfaces exploitable, patchable vulnerabilities first. CVSS scores, exploit maturity, and fix availability are enriched automatically from deps.dev and OSV advisory feeds. -

+

Use CVSS severity, EPSS probability, KEV listings, and known fix versions to prioritize review. Available signals depend on the advisory sources and component coverage; they do not prove exploitability in your deployment.

@@ -714,7 +700,7 @@

- Fix version surfaced — know if a patched release exists before triaging + Fix version surfaced — know if a patched release exists before triaging
@@ -722,7 +708,7 @@

- Bulk status updates — mark findings reviewed or ignored in one action + Bulk status updates — mark findings reviewed or ignored in one action

@@ -731,74 +717,75 @@

- backend-api · v3.2.0 — Security Center + Example findings · CVSS 3.x
+
- - - - - - - - - + + + - - - + + + - - - + + + - + + + + + + +
ComponentCVE IDCVSSSeverity
jackson-databind
2.13.4.2
CVE-2022-420039.8Critical
spring-webmvc
5.3.27
CVE-2023-208618.6jackson-databind
2.13.4
CVE-2022-420037.5 High
commons-io
2.11.0
CVE-2024-220117.5logback-classic
1.4.11
CVE-2023-63787.1 High
logback-classic
1.2.11
CVE-2023-63786.2spring-expression
5.3.25
CVE-2023-208616.5 Medium
guava
31.1-jre
CVE-2023-2976CVE-2023-2976 5.5 Medium
commons-io
2.11.0
CVE-2024-475544.3Medium
+
+

Illustrative findings, not an OsWL project scan. CVE links identify the source records; scores can differ by source.

-
GPL-2.0-only
Copyleft · Strong
Restricted
-
LGPL-2.1-only
Copyleft · Weak
Caution
+
GPL-2.0-only
Copyleft · Strong
Restricted
+
LGPL-2.1-only
Copyleft · Weak
Caution
Apache-2.0
Permissive
Permitted
MIT
Permissive
Permitted
-
MPL-2.0
Copyleft · File-level
Caution
+
MPL-2.0
Copyleft · File-level
Caution
Proprietary
Commercial
Unknown

Eliminate
Compliance Blind Spots

-

- Automatically classify every SPDX license across your dependency tree. Identify copyleft obligations, license conflicts, and components that need manual legal review before they reach production. -

+

Compare detected SPDX license identifiers with your policy and review obligations in the context of use and distribution. The example classifications are policy outcomes, not a legal approval.

Restricted - Copyleft licenses requiring full source disclosure + Copyleft obligations depend on how software is used and distributed
Caution - Weak copyleft or dual-licensed — legal review recommended + Review weak copyleft and dual-license terms in context
Permitted - Permissive licenses — safe for commercial use + Permissive licenses still have terms and notice requirements
@@ -818,12 +805,12 @@

Up and Scanning
in
01

Connect Your Repository

-

Add a GitHub, GitLab, or Bitbucket Personal Access Token. OsWL discovers repositories and branches and imports manifests — no source code upload required.

+

Connect a supported VCS account to browse repositories, or enter a public repository URL. Quick Import collects supported files on the OsWL server; build execution is disabled by default.

02

Run a Dependency Scan

-

OsWL parses your lock files and build manifests, resolves the transitive dependency graph, and cross-references every component against deps.dev, OSV, and your license policy.

+

OsWL parses supported lock files and build manifests, resolves the dependency data they provide, and enriches components with vulnerability and license information.

03
@@ -833,7 +820,7 @@

Triage & Remediate
04

Automate in CI/CD

-

Gate pull requests with the OsWL CLI. Bearer-token API keys enable automated scanning in any pipeline — Jenkins, GitHub Actions, GitLab CI, or custom build servers.

+

Submit scans from CI with a project API key and submitter credentials, then evaluate completed results through the gate API or CLI. Configure your pipeline to enforce the returned verdict.

@@ -843,9 +830,9 @@

Automate in CI/CD

Secure Your
Open Source Stack

-

Deploy OsWL on-premise and gain full SBOM visibility across every project, team, and dependency in your organization.

+

Deploy OsWL on your own infrastructure to review scanned dependencies, track remediation, and manage project access in one place.

@@ -859,7 +846,8 @@

Secure Your
OsWL