Conversation
… external ML service
… recognition data, including faces, embeddings, and associated metadata.
Configure Dependabot for Maven and GitHub Actions updates with specific schedules and ignore rules.
Update CodeQL workflow for Java 21 analysis
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@v4...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps the spring group with 2 updates: [org.springframework.boot:spring-boot-starter-parent](https://github.com/spring-projects/spring-boot) and [org.springframework.boot:spring-boot-configuration-processor](https://github.com/spring-projects/spring-boot). Updates `org.springframework.boot:spring-boot-starter-parent` from 3.5.8 to 4.0.2 - [Release notes](https://github.com/spring-projects/spring-boot/releases) - [Commits](spring-projects/spring-boot@v3.5.8...v4.0.2) Updates `org.springframework.boot:spring-boot-configuration-processor` from 3.5.8 to 4.0.2 - [Release notes](https://github.com/spring-projects/spring-boot/releases) - [Commits](spring-projects/spring-boot@v3.5.8...v4.0.2) --- updated-dependencies: - dependency-name: org.springframework.boot:spring-boot-starter-parent dependency-version: 4.0.2 dependency-type: direct:production update-type: version-update:semver-major dependency-group: spring - dependency-name: org.springframework.boot:spring-boot-configuration-processor dependency-version: 4.0.2 dependency-type: direct:production update-type: version-update:semver-major dependency-group: spring ... Signed-off-by: dependabot[bot] <support@github.com>
…s/checkout-6 Bump actions/checkout from 4 to 6
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4 to 5. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](actions/setup-java@v4...v5) --- updated-dependencies: - dependency-name: actions/setup-java dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
…s/setup-java-5 Bump actions/setup-java from 4 to 5
…rieval for field officers
Added locations tracking
Bump the spring group with 2 updates
…ecognition improvements
…izer utility class to prevent log injection
Merge Python Services with the Main
Implement crime evidence file upload and download in Supabase
| public ResponseEntity<String> uploadEvidence(@RequestParam("file") MultipartFile file) throws Exception { | ||
|
|
||
| String fileUrl = supabaseService.uploadFile(file); | ||
| return ResponseEntity.ok(fileUrl); |
Check failure
Code scanning / CodeQL
Cross-site scripting High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
General approach: Ensure that user-controlled input incorporated into values returned to clients is constrained to a safe character set and/or encoded so it cannot be interpreted as executable HTML/JavaScript, even if a client misuses it. For file names and URLs, this typically means either sanitizing the name portion or returning a fully encoded/signed URL obtained from a trusted API.
Best concrete fix here:
-
In
SupabaseService.uploadFile, stop propagating rawfile.getOriginalFilename()characters into the stored file name and ultimately to clients. Instead:- Extract the original file extension (if any).
- Sanitize the base file name to a conservative character set (e.g., letters, digits, dots, underscores, hyphens), replacing anything else with
_. - Keep the random UUID prefix to avoid collisions.
- Optionally limit the overall length.
-
In
CrimeReportController.uploadEvidence, don’t expose the raw storage key directly. Instead, use the existingSupabaseService.getFileUrlmethod to return a signed, absolute URL for that key. This also better matches the method’s name (fileUrl) and reduces the likelihood that clients will treat a storage key as arbitrary HTML.
These changes are local to the shown snippets:
SupabaseService.java: add a privatesanitizeFileNamehelper and use it inuploadFilewhen buildingfileName.CrimeReportController.java: afteruploadFile, callsupabaseService.getFileUrl(fileName)and return that result.
No changes to external APIs are required, and existing functionality (uploading and later retrieving evidence) is preserved, with added safety.
| @@ -57,7 +57,8 @@ | ||
| @PostMapping("/upload-evidence") | ||
| public ResponseEntity<String> uploadEvidence(@RequestParam("file") MultipartFile file) throws Exception { | ||
|
|
||
| String fileUrl = supabaseService.uploadFile(file); | ||
| String fileName = supabaseService.uploadFile(file); | ||
| String fileUrl = supabaseService.getFileUrl(fileName); | ||
| return ResponseEntity.ok(fileUrl); | ||
| } | ||
|
|
| @@ -39,7 +39,9 @@ | ||
| throw new RuntimeException("File size exceeds the limit of 10MB"); | ||
| } | ||
|
|
||
| String fileName = UUID.randomUUID() + "_" + file.getOriginalFilename(); | ||
| String originalFilename = file.getOriginalFilename(); | ||
| String safeOriginalFilename = sanitizeFileName(originalFilename); | ||
| String fileName = UUID.randomUUID() + "_" + safeOriginalFilename; | ||
|
|
||
| String uploadUrl = supabaseUrl + "/storage/v1/object/" + bucket + "/" + fileName; | ||
|
|
||
| @@ -55,6 +57,37 @@ | ||
| return fileName; | ||
| } | ||
|
|
||
| private String sanitizeFileName(String originalFilename) { | ||
| if (originalFilename == null || originalFilename.isBlank()) { | ||
| return "file"; | ||
| } | ||
|
|
||
| String trimmed = originalFilename.trim(); | ||
|
|
||
| int lastDotIndex = trimmed.lastIndexOf('.'); | ||
| String baseName; | ||
| String extension; | ||
|
|
||
| if (lastDotIndex > 0 && lastDotIndex < trimmed.length() - 1) { | ||
| baseName = trimmed.substring(0, lastDotIndex); | ||
| extension = trimmed.substring(lastDotIndex); | ||
| } else { | ||
| baseName = trimmed; | ||
| extension = ""; | ||
| } | ||
|
|
||
| String safeBaseName = baseName.replaceAll("[^A-Za-z0-9._-]", "_"); | ||
| if (safeBaseName.isBlank()) { | ||
| safeBaseName = "file"; | ||
| } | ||
|
|
||
| if (safeBaseName.length() > 100) { | ||
| safeBaseName = safeBaseName.substring(0, 100); | ||
| } | ||
|
|
||
| return safeBaseName + extension; | ||
| } | ||
|
|
||
| public String getFileUrl(String fileName) { | ||
| String url = supabaseUrl + "/storage/v1/object/sign/" + bucket + "/" + fileName; | ||
|
|
| * @param input the object whose string representation should be sanitized | ||
| * @return a sanitized string safe for logging; never {@code null} | ||
| */ | ||
| public static String sanitize(Object input) { |
Check notice
Code scanning / CodeQL
Confusing overloading of methods Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
In general, to fix confusing overloading where one overload takes Object and another takes String, you should avoid having both overloads share the same name. Instead, keep the specific, type-safe method (sanitize(String)) as is and rename the broad-typed convenience method (sanitize(Object)) to a distinct name that clearly describes its behavior, then update its Javadoc accordingly.
In this file, the best fix with no functional change is:
- Keep
public static String sanitize(String input)exactly as it is. - Rename
public static String sanitize(Object input)to a clearer, non-overloaded name such assanitizeObject(Object input)(or similar). - Update the Javadoc
@linkand description above that method to reference the new name and make clear it’s a convenience wrapper aroundtoString()plussanitize(String). - Do not change the method body logic: still handle
nullby returning"null"and otherwise delegate tosanitize(input.toString()).
No additional imports or helper methods are required; all changes are local to LogSanitizer.java around the second method declaration and its Javadoc.
| @@ -72,13 +72,13 @@ | ||
| } | ||
|
|
||
| /** | ||
| * Convenience overload that accepts any {@link Object}. | ||
| * Calls {@link Object#toString()} before sanitizing. | ||
| * Convenience method that accepts any {@link Object}. | ||
| * Calls {@link Object#toString()} before sanitizing via {@link #sanitize(String)}. | ||
| * | ||
| * @param input the object whose string representation should be sanitized | ||
| * @return a sanitized string safe for logging; never {@code null} | ||
| */ | ||
| public static String sanitize(Object input) { | ||
| public static String sanitizeObject(Object input) { | ||
| if (input == null) { | ||
| return "null"; | ||
| } |
There was a problem hiding this comment.
Pull request overview
This PR pulls in changes that expand the Spring Boot backend with (1) ML microservice gateway endpoints (call analysis + facial recognition), (2) officer location tracking, and (3) Supabase-backed evidence upload/signed download support, alongside security and build/config updates.
Changes:
- Add new controllers/services for call analysis and facial recognition microservices, plus a shared
RestTemplateconfig. - Add officer location tracking persistence + APIs (DTO/entity/repo/service/controller).
- Add Supabase storage integration for evidence upload and signed download URLs; refresh-token rotation changes; SecurityConfig updates; CI/security automation files.
Reviewed changes
Copilot reviewed 37 out of 37 changed files in this pull request and generated 19 comments.
Show a summary per file
| File | Description |
|---|---|
| src/main/resources/application.properties | Adds Supabase properties and multipart upload limits |
| src/main/java/com/crimeLink/analyzer/util/LogSanitizer.java | Adds log sanitization utility to mitigate log injection |
| src/main/java/com/crimeLink/analyzer/service/impl/LocationServiceImpl.java | Implements bulk location save and history/last-location queries |
| src/main/java/com/crimeLink/analyzer/service/SupabaseService.java | Implements Supabase storage upload + signed URL retrieval |
| src/main/java/com/crimeLink/analyzer/service/RefreshTokenService.java | Adds token rotation + “valid token” lookup behavior |
| src/main/java/com/crimeLink/analyzer/service/LocationService.java | Introduces location service interface |
| src/main/java/com/crimeLink/analyzer/service/FacialRecognitionService.java | Adds gateway service for facial recognition microservice |
| src/main/java/com/crimeLink/analyzer/service/DutyScheduleService.java | Adds distinct-duty-location retrieval w/ defaults |
| src/main/java/com/crimeLink/analyzer/service/CrimeReportService.java | Adds evidence persistence and signed URL enrichment |
| src/main/java/com/crimeLink/analyzer/service/CallAnalysisService.java | Refactors to JSON passthrough gateway for call analysis microservice |
| src/main/java/com/crimeLink/analyzer/repository/RefreshTokenRepository.java | Adds fetch-join query for refresh token + user |
| src/main/java/com/crimeLink/analyzer/repository/LocationPointRepository.java | Adds repository for location point queries |
| src/main/java/com/crimeLink/analyzer/repository/DutyScheduleRepository.java | Adds query for distinct duty locations |
| src/main/java/com/crimeLink/analyzer/repository/CallAnalysisRepository.java | Removes persisted call analysis record repository |
| src/main/java/com/crimeLink/analyzer/mapper/CrimeReportMapper.java | Adds evidence mapping into CrimeReportDTO |
| src/main/java/com/crimeLink/analyzer/entity/LocationPoint.java | Adds location_points entity with JSON meta |
| src/main/java/com/crimeLink/analyzer/entity/Evidence.java | Adds evidence entity linked to crime reports |
| src/main/java/com/crimeLink/analyzer/entity/CrimeReport.java | Adds @OneToMany evidences relationship |
| src/main/java/com/crimeLink/analyzer/entity/CallAnalysisRecord.java | Removes persisted call analysis record entity |
| src/main/java/com/crimeLink/analyzer/dto/LocationPointDTO.java | Adds DTO for incoming location points |
| src/main/java/com/crimeLink/analyzer/dto/EvidenceDTO.java | Adds DTO for evidence + download URL |
| src/main/java/com/crimeLink/analyzer/dto/CrimeReportDTO.java | Adds evidence list field |
| src/main/java/com/crimeLink/analyzer/dto/CallAnalysisResultDTO.java | Removes typed call analysis result DTO |
| src/main/java/com/crimeLink/analyzer/controller/LocationController.java | Adds endpoints for location uploads/history/debug |
| src/main/java/com/crimeLink/analyzer/controller/FacialRecognitionController.java | Adds REST API gateway endpoints for facial recognition |
| src/main/java/com/crimeLink/analyzer/controller/DutyScheduleController.java | Adds endpoint to fetch duty locations |
| src/main/java/com/crimeLink/analyzer/controller/CrimeReportController.java | Adds evidence upload + evidence download endpoints |
| src/main/java/com/crimeLink/analyzer/controller/CallAnalysisController.java | Refactors call analysis controller to new gateway endpoints |
| src/main/java/com/crimeLink/analyzer/controller/AuthController.java | Updates refresh-token handling to use rotation |
| src/main/java/com/crimeLink/analyzer/config/SecurityConfig.java | Updates route authorization rules + exception handling |
| src/main/java/com/crimeLink/analyzer/config/RestTemplateConfig.java | Adds configured RestTemplate bean (timeouts) |
| src/main/java/com/crimeLink/analyzer/config/JwtAuthenticationFilter.java | Modifies JWT filter behavior and adds debug logging |
| pom.xml | Adds dependencies/plugins and build-helper configuration |
| database/facial_recognition_tables.sql | Adds DB schema for facial recognition feature |
| .github/workflows/codeql.yml | Adds CodeQL analysis workflow |
| .github/dependabot.yml | Adds Dependabot configuration |
| .env.example | Adds example env vars for JWT + Supabase |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @GetMapping("/debug/whoami") | ||
| public Map<String, Object> whoAmI(@AuthenticationPrincipal User user) { | ||
| Map<String, Object> info = new HashMap<>(); | ||
| if (user != null) { | ||
| info.put("email", user.getEmail()); | ||
| info.put("name", user.getName()); | ||
| info.put("role", user.getRole()); | ||
| info.put("authorities", user.getAuthorities().stream() | ||
| .map(auth -> auth.getAuthority()) | ||
| .toList()); | ||
| info.put("userId", user.getUserId()); | ||
| info.put("badgeNo", user.getBadgeNo()); | ||
| } else { | ||
| info.put("error", "No authenticated user"); | ||
| } | ||
| return info; | ||
| } | ||
|
|
There was a problem hiding this comment.
The /api/debug/whoami endpoint exposes user identity and authorities. Combined with permitAll on /api/debug/** in SecurityConfig, this can be used for reconnaissance; restrict this endpoint to admin roles or remove it before merging.
| @GetMapping("/debug/whoami") | |
| public Map<String, Object> whoAmI(@AuthenticationPrincipal User user) { | |
| Map<String, Object> info = new HashMap<>(); | |
| if (user != null) { | |
| info.put("email", user.getEmail()); | |
| info.put("name", user.getName()); | |
| info.put("role", user.getRole()); | |
| info.put("authorities", user.getAuthorities().stream() | |
| .map(auth -> auth.getAuthority()) | |
| .toList()); | |
| info.put("userId", user.getUserId()); | |
| info.put("badgeNo", user.getBadgeNo()); | |
| } else { | |
| info.put("error", "No authenticated user"); | |
| } | |
| return info; | |
| } |
| String fileName = UUID.randomUUID() + "_" + file.getOriginalFilename(); | ||
|
|
||
| String uploadUrl = supabaseUrl + "/storage/v1/object/" + bucket + "/" + fileName; | ||
|
|
There was a problem hiding this comment.
file.getOriginalFilename() is incorporated into the stored object key. If the original filename contains path separators or control characters, this can create unexpected object paths (or confusing keys). Normalize/sanitize the filename (e.g., strip directories and disallow //\\ and control chars) before building fileName/uploadUrl.
| @PostMapping("/upload-evidence") | ||
| public ResponseEntity<String> uploadEvidence(@RequestParam("file") MultipartFile file) throws Exception { | ||
|
|
||
| String fileUrl = supabaseService.uploadFile(file); | ||
| return ResponseEntity.ok(fileUrl); | ||
| } |
There was a problem hiding this comment.
This controller endpoint returns the value from uploadFile, but the variable is named fileUrl even though uploadFile returns a file name/key. This is confusing for API consumers; return a typed JSON object (e.g., {fileName, bucket} or a signed download URL) and rename the variable accordingly.
| try { | ||
| e.setMeta(p.meta() == null ? null : mapper.valueToTree(p.meta())); | ||
| } catch (Exception er) { | ||
| e.setMeta(null); | ||
| } |
There was a problem hiding this comment.
The meta JSON conversion swallows all exceptions and silently drops the metadata. This makes failures hard to diagnose and can lead to unexpected data loss; at minimum, log the exception (sanitizing user-controlled values) and consider rejecting the point if metadata is malformed.
| #Supabase Configuration | ||
| supabase.url=${SUPABASE_URL} | ||
| supabase.service-key=${SUPABASE_SERVICE_KEY} |
There was a problem hiding this comment.
These placeholders have no default values, so the app will fail to start if SUPABASE_URL / SUPABASE_SERVICE_KEY are not provided. If this is optional per environment, provide safe defaults (or use profile-specific properties) to avoid local/dev startup failures.
| private final RestTemplate restTemplate = new RestTemplate(); | ||
| private final ObjectMapper objectMapper = new ObjectMapper(); | ||
|
|
There was a problem hiding this comment.
SupabaseService creates its own RestTemplate and ObjectMapper instances, bypassing the shared configuration (timeouts, interceptors, custom Jackson config). Prefer constructor injection of the existing RestTemplate bean and a shared ObjectMapper bean.
| <plugin> | ||
| <groupId>org.codehaus.mojo</groupId> | ||
| <artifactId>build-helper-maven-plugin</artifactId> | ||
| <version>3.5.0</version> | ||
| <executions> | ||
| <execution> | ||
| <id>add-source</id> | ||
| <phase>generate-test-sources</phase> | ||
| <goals> | ||
| <goal>add-test-source</goal> | ||
| </goals> | ||
| <configuration> | ||
| <sources> | ||
| <source>src/main/java</source> | ||
| </sources> | ||
| </configuration> | ||
| </execution> | ||
| </executions> | ||
| </plugin> |
There was a problem hiding this comment.
Adding src/main/java as a test source directory will cause main production sources to be compiled again in the test scope and can lead to duplicate-class/packaging issues and longer builds. Remove this build-helper configuration unless there is a very specific, documented reason for it.
| <plugin> | |
| <groupId>org.codehaus.mojo</groupId> | |
| <artifactId>build-helper-maven-plugin</artifactId> | |
| <version>3.5.0</version> | |
| <executions> | |
| <execution> | |
| <id>add-source</id> | |
| <phase>generate-test-sources</phase> | |
| <goals> | |
| <goal>add-test-source</goal> | |
| </goals> | |
| <configuration> | |
| <sources> | |
| <source>src/main/java</source> | |
| </sources> | |
| </configuration> | |
| </execution> | |
| </executions> | |
| </plugin> |
| // Allow duty schedule operations for OIC | ||
| .requestMatchers("/api/duty-schedules/**").hasRole("OIC") | ||
| // Public endpoints | ||
| .requestMatchers("/api/vehicle**").permitAll() |
There was a problem hiding this comment.
The matcher /api/vehicle** does not cover the existing vehicles endpoints (e.g., @RequestMapping("/api/vehicles")), so vehicle routes will no longer be permitted as intended. Update the matcher to target /api/vehicles/** (or the exact vehicle routes you want public).
| .requestMatchers("/api/vehicle**").permitAll() | |
| .requestMatchers("/api/vehicles/**").permitAll() |
| public void uploadMyLocations(@AuthenticationPrincipal User user, @RequestBody List<LocationPointDTO> points) { | ||
| System.out.println("Received locations: " + points.size()); // REMOVE: for testing | ||
| if (user == null) { | ||
| throw new RuntimeException("Unauthorized"); | ||
| } |
There was a problem hiding this comment.
Leftover System.out.println debug output should not be in a controller method (noise + potential data leakage). Replace with proper logger calls at DEBUG level or remove entirely.
| if (user == null) { | ||
| throw new RuntimeException("Unauthorized"); | ||
| } | ||
|
|
||
| if (!"FieldOfficer".equalsIgnoreCase(user.getRole())) { | ||
| throw new RuntimeException("Only field officers can upload locations"); | ||
| } | ||
|
|
||
| String officerBadgeNo = user.getBadgeNo(); | ||
| if (officerBadgeNo == null || officerBadgeNo.isBlank()) { | ||
| throw new RuntimeException("Badge number missing"); | ||
| } |
There was a problem hiding this comment.
Throwing generic RuntimeException for auth/authorization failures will typically return HTTP 500. Use a Spring mechanism that maps to the correct status (401/403), such as ResponseStatusException or a dedicated exception annotated with @ResponseStatus.
No description provided.