Skip to content

Commit bd51052

Browse files
author
Grzegorz Siewruk
committed
fix
1 parent 068f003 commit bd51052

9 files changed

Lines changed: 97 additions & 12 deletions

File tree

.github/workflows/docker-build-backend.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,22 @@ concurrency:
1717
jobs:
1818
unit-testing:
1919
runs-on: ubuntu-latest
20+
# Profile ut normally uses Testcontainers (Docker). CI uses a real Postgres service so tests
21+
# do not depend on Testcontainers detecting the Docker socket on the runner.
22+
services:
23+
postgres:
24+
image: postgres:15-alpine
25+
env:
26+
POSTGRES_USER: test
27+
POSTGRES_PASSWORD: test
28+
POSTGRES_DB: test
29+
ports:
30+
- 5432:5432
31+
options: >-
32+
--health-cmd "pg_isready -U test -d test"
33+
--health-interval 5s
34+
--health-timeout 5s
35+
--health-retries 10
2036
steps:
2137
- name: Checkout code
2238
uses: actions/checkout@v4
@@ -26,6 +42,11 @@ jobs:
2642
distribution: 'temurin'
2743
java-version: 22
2844
- name: Build and test with Maven and profile ut
45+
env:
46+
SPRING_DATASOURCE_URL: jdbc:postgresql://localhost:5432/test
47+
SPRING_DATASOURCE_USERNAME: test
48+
SPRING_DATASOURCE_PASSWORD: test
49+
SPRING_DATASOURCE_DRIVER_CLASS_NAME: org.postgresql.Driver
2950
run: |
3051
cd backend
3152
mvn clean test -Dspring.profiles.active=ut

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@
99
/backend/MixewayFlowAPI.iml
1010
#frontend/src/environments
1111
.vscode
12-
/frontend/dist
12+
/frontend/dist/

backend/pom.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,14 @@
203203
</annotationProcessorPaths>
204204
</configuration>
205205
</plugin>
206+
<plugin>
207+
<groupId>org.apache.maven.plugins</groupId>
208+
<artifactId>maven-surefire-plugin</artifactId>
209+
<configuration>
210+
<!-- Mockito/Byte Buddy: allow running tests on newer JDKs than officially supported -->
211+
<argLine>-Dnet.bytebuddy.experimental=true</argLine>
212+
</configuration>
213+
</plugin>
206214
</plugins>
207215
</build>
208216
<repositories>

backend/src/main/java/io/mixeway/mixewayflowapi/db/entity/Finding.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,18 @@ public void markAiFalsePositive(String modelName, String confidence) {
178178
this.aiAnalyzedAt = LocalDateTime.now();
179179
}
180180

181+
/**
182+
* SAST: model zwróciło FALSE_POSITIVE, ale pewność była niższa niż HIGH — nie wyciszamy automatycznie,
183+
* żeby nie ukrywać prawdziwych problemów (FP tylko przy wysokiej pewności).
184+
*/
185+
public void markAiSastFpNotAutoSuppressed(String modelName, String confidence) {
186+
this.aiAnalyzed = true;
187+
this.aiVerdict = "LIKELY_FP_REVIEW";
188+
this.aiConfidence = confidence;
189+
this.aiModel = modelName;
190+
this.aiAnalyzedAt = LocalDateTime.now();
191+
}
192+
181193
// Method to update status and suppressed reason
182194
public void updateStatus(Status newStatus, SuppressedReason suppressedReason) {
183195
if (newStatus == Status.SUPRESSED && suppressedReason == null) {

backend/src/main/java/io/mixeway/mixewayflowapi/integrations/ollama/service/FalsePositiveAnalyzer.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,18 @@ private boolean analyzeOne(Finding finding, Settings s, UserInfo aiUser) {
134134
String reasoning = v.getReasoning() != null ? v.getReasoning() : "";
135135

136136
if ("FALSE_POSITIVE".equalsIgnoreCase(vStr)) {
137+
// SAST: automatyczne wyciszenie tylko przy wysokiej pewności — unikamy masowego FP przy MEDIUM/LOW
138+
if (finding.getSource() == Finding.Source.SAST && !isHighConfidence(conf)) {
139+
log.info("[AiFp] SAST finding id={}: verdict=FALSE_POSITIVE confidence={} — not auto-suppressing (SAST requires HIGH confidence to suppress)",
140+
finding.getId(), conf);
141+
finding.markAiSastFpNotAutoSuppressed(s.getOllamaModel(), conf);
142+
findingRepository.save(finding);
143+
if (aiUser != null) {
144+
String msg = buildAiCommentSastFpNotSuppressed(v, s, finding, reasoning);
145+
createCommentService.createSystemComment(finding, aiUser, msg);
146+
}
147+
return false;
148+
}
137149
finding.markAiFalsePositive(s.getOllamaModel(), conf);
138150
updateFindingService.suppressFinding(finding, Finding.SuppressedReason.FALSE_POSITIVE.name());
139151
if (aiUser != null) {
@@ -192,6 +204,29 @@ private static void maybeAppendSecretEnrichment(Finding finding, FpVerdictDto v)
192204
finding.setExplanation(base + block);
193205
}
194206

207+
private static boolean isHighConfidence(String conf) {
208+
return conf != null && "HIGH".equalsIgnoreCase(conf.trim());
209+
}
210+
211+
private static String buildAiCommentSastFpNotSuppressed(
212+
FpVerdictDto v,
213+
Settings s,
214+
Finding finding,
215+
String reasoning) {
216+
StringBuilder sb = new StringBuilder();
217+
sb.append("[AI analysis — SAST]\n\n");
218+
sb.append("## Decision\n");
219+
sb.append("Model zwróciło **FALSE_POSITIVE**, ale **nie wyciszono automatycznie**: dla SAST automatyczne wyciszenie ");
220+
sb.append("jest dozwolone tylko przy **confidence: HIGH**, żeby nie ukrywać realnych problemów przy średniej/niskiej pewności.\n\n");
221+
sb.append("## Rationale (model)\n");
222+
sb.append(reasoning != null ? reasoning.trim() : "").append("\n\n");
223+
sb.append("## Szczegóły\n");
224+
sb.append("- Zgłoszona pewność: **").append(v.getConfidence() != null ? v.getConfidence() : "UNKNOWN").append("**\n");
225+
sb.append("- Model: `").append(s.getOllamaModel()).append("`\n");
226+
sb.append("- Status: przejrzyj ręcznie; przy potwierdzeniu FP możesz wyciszyć z poziomu UI.\n");
227+
return sb.toString();
228+
}
229+
195230
private static String buildAiComment(
196231
FpVerdictDto v,
197232
Settings s,

backend/src/main/java/io/mixeway/mixewayflowapi/integrations/ollama/service/FalsePositivePromptBuilder.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ private String buildSast(Finding finding, List<RetrievedChunk> chunks) {
2929
sb.append("1) Does the vulnerable pattern **actually occur** at the indicated location in this codebase snapshot?\n");
3030
sb.append("2) In **any realistic execution path or deployment**, could this lead to a **meaningful security impact** ");
3131
sb.append("(confidentiality, integrity, availability, authn/z bypass, injection, etc.)?\n");
32-
sb.append("3) If the rule fires on a safe API, dead code, unreachable branch, or fully mitigated usage, treat as FALSE POSITIVE.\n\n");
32+
sb.append("3) **Prefer REAL_ISSUE when in doubt** — do not classify as FALSE_POSITIVE unless you can justify it clearly.\n");
33+
sb.append("4) Use FALSE_POSITIVE only when the finding is clearly a **rule misfire**, dead/unreachable code, or safe-by-construction usage; ");
34+
sb.append("not merely \"low severity\".\n");
35+
sb.append("5) Set **confidence** to **HIGH** only if you are **very sure** the finding is not a real security issue; ");
36+
sb.append("if you lean toward false positive but are not fully sure, use **REAL_ISSUE** or **FALSE_POSITIVE** with **MEDIUM**/**LOW** (the pipeline treats SAST auto-suppression only for HIGH).\n\n");
3337
sb.append("Write **reasoning** as **at least 5–8 sentences**, concrete and code-referenced.\n\n");
3438
appendJsonSchema(sb, true);
3539
return sb.toString();

backend/src/main/resources/db/changelog/data_dump_test.sql

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -676,17 +676,18 @@ INSERT INTO public.scan_info VALUES (6, 6, 6, '4fb9f47dfa66947b20d7b83c0666b27d1
676676
-- Data for Name: users; Type: TABLE DATA; Schema: public; Owner: flow_user
677677
--
678678

679-
INSERT INTO public.users VALUES (2, '$2a$10$KtoPDiXK0qnn5kqcn1phK..ogkzljZmkwJU686o60jH8b6rT6xDNO', 'user', NULL, true, true);
680-
INSERT INTO public.users VALUES (3, '$2a$10$LQuErsbpMCVb/3D2LoU9/epUKpXzUuqUJlexVVODcU1Mz2nDnB8xy', 'manager', NULL, true, true);
679+
-- Ids 2–3 are reserved by master changelog (e.g. flowai system user); use 4–5 for fixture users
680+
INSERT INTO public.users VALUES (4, '$2a$10$KtoPDiXK0qnn5kqcn1phK..ogkzljZmkwJU686o60jH8b6rT6xDNO', 'user', NULL, true, true);
681+
INSERT INTO public.users VALUES (5, '$2a$10$LQuErsbpMCVb/3D2LoU9/epUKpXzUuqUJlexVVODcU1Mz2nDnB8xy', 'manager', NULL, true, true);
681682

682683

683684
--
684685
-- Data for Name: users_roles; Type: TABLE DATA; Schema: public; Owner: flow_user
685686
--
686687

687-
INSERT INTO public.users_roles VALUES (2, 1);
688-
INSERT INTO public.users_roles VALUES (3, 3);
689-
INSERT INTO public.users_roles VALUES (3, 1);
688+
INSERT INTO public.users_roles VALUES (4, 1);
689+
INSERT INTO public.users_roles VALUES (5, 3);
690+
INSERT INTO public.users_roles VALUES (5, 1);
690691

691692

692693
--
@@ -695,12 +696,12 @@ INSERT INTO public.users_roles VALUES (3, 1);
695696

696697

697698
INSERT INTO public.users_teams (user_info_id, team_id) VALUES (1, 1);
698-
INSERT INTO public.users_teams (user_info_id, team_id) VALUES (2, 1);
699+
INSERT INTO public.users_teams (user_info_id, team_id) VALUES (4, 1);
699700
INSERT INTO public.users_teams (user_info_id, team_id) VALUES (1, 2);
700-
INSERT INTO public.users_teams (user_info_id, team_id) VALUES (3, 2);
701+
INSERT INTO public.users_teams (user_info_id, team_id) VALUES (5, 2);
701702
INSERT INTO public.users_teams (user_info_id, team_id) VALUES (1, 3);
702-
INSERT INTO public.users_teams (user_info_id, team_id) VALUES (3, 3);
703-
INSERT INTO public.users_teams (user_info_id, team_id) VALUES (2, 3);
703+
INSERT INTO public.users_teams (user_info_id, team_id) VALUES (5, 3);
704+
INSERT INTO public.users_teams (user_info_id, team_id) VALUES (4, 3);
704705

705706

706707
--
@@ -783,7 +784,7 @@ SELECT pg_catalog.setval('public.team_id_seq', 3, true);
783784
-- Name: users_id_seq; Type: SEQUENCE SET; Schema: public; Owner: flow_user
784785
--
785786

786-
SELECT pg_catalog.setval('public.users_id_seq', 3, true);
787+
SELECT pg_catalog.setval('public.users_id_seq', 5, true);
787788

788789

789790
--

frontend/src/app/views/show-repo/vulnerabilities-table/vulnerabilities-table.component.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,8 @@ <h3 id="vuln-filters-heading" class="vuln-filter-panel__title">
379379
</span>
380380
<span *ngIf="row.ai_analyzed && row.ai_verdict === 'FALSE_POSITIVE' && row.status === 'SUPRESSED'"
381381
class="badge bg-info-subtle text-info ms-2">🤖 AI: False Positive</span>
382+
<span *ngIf="row.ai_analyzed && row.ai_verdict === 'LIKELY_FP_REVIEW'"
383+
class="badge bg-warning-subtle text-warning-emphasis ms-2">🤖 AI: FP suggested (review)</span>
382384
<span *ngIf="row.ai_analyzed && row.ai_verdict === 'REAL_ISSUE'"
383385
class="badge bg-secondary-subtle text-secondary ms-2">🤖 AI: Real issue</span>
384386
</div>

frontend/src/app/views/show-team/team-vulnerabilities-table/team-vulnerabilities-table.component.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,8 @@
326326
</span>
327327
<span *ngIf="row.ai_analyzed && row.ai_verdict === 'FALSE_POSITIVE' && row.status === 'SUPRESSED'"
328328
class="badge bg-info-subtle text-info ms-2">🤖 AI: False Positive</span>
329+
<span *ngIf="row.ai_analyzed && row.ai_verdict === 'LIKELY_FP_REVIEW'"
330+
class="badge bg-warning-subtle text-warning-emphasis ms-2">🤖 AI: FP suggested (review)</span>
329331
<span *ngIf="row.ai_analyzed && row.ai_verdict === 'REAL_ISSUE'"
330332
class="badge bg-secondary-subtle text-secondary ms-2">🤖 AI: Real issue</span>
331333
</div>

0 commit comments

Comments
 (0)