Skip to content

fix(vulnerability): returning remediated vulnerabilities#1180

Merged
MR2011 merged 5 commits into
mainfrom
kanstantsinbuklis-sap/issue-1121/returning-remediated-vulnerabilities
May 4, 2026
Merged

fix(vulnerability): returning remediated vulnerabilities#1180
MR2011 merged 5 commits into
mainfrom
kanstantsinbuklis-sap/issue-1121/returning-remediated-vulnerabilities

Conversation

@kanstantsinbuklis-sap
Copy link
Copy Markdown
Collaborator

Description

In this PR I've returned cache to the remediation handler and added logic to invalidate cached entries during remediation creation. Also I've added context propagation from the request to database layer

What type of PR is this? (check all applicable)

  • 🍕 Feature
  • 🐛 Bug Fix
  • 📝 Documentation Update
  • 🎨 Style
  • 🧑‍💻 Code Refactor
  • 🔥 Performance Improvements
  • ✅ Test
  • 🤖 Build
  • 🔁 CI
  • 📦 Chore (Release)
  • ⏩ Revert

Related Tickets & Documents

Added tests?

  • 👍 yes
  • 🙅 no, because they aren't needed
  • 🙋 no, because I need help
  • Separate ticket for tests # (issue/pr)

Added to documentation?

  • 📜 README.md
  • 🤝 Documentation pages updated
  • 🙅 no documentation needed
  • (if applicable) generated OpenAPI docs for CRD changes

@kanstantsinbuklis-sap kanstantsinbuklis-sap force-pushed the kanstantsinbuklis-sap/issue-1121/returning-remediated-vulnerabilities branch 4 times, most recently from 817ec99 to e62b880 Compare April 21, 2026 14:39
@kanstantsinbuklis-sap kanstantsinbuklis-sap marked this pull request as ready for review April 21, 2026 14:52
Copilot AI review requested due to automatic review settings April 21, 2026 14:52
@kanstantsinbuklis-sap kanstantsinbuklis-sap linked an issue Apr 21, 2026 that may be closed by this pull request
2 tasks
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 107 out of 107 changed files in this pull request and generated 9 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/cache/cache.go
Comment thread internal/cache/cache.go
Comment thread internal/cache/valkey_cache.go Outdated
Comment on lines +265 to +269
decodedKey, err := cache.DecodeKey(key, cache.DefaultKeyHash)
if err != nil {
wrappedErr := appErrors.InternalError(string(op), "Remediation", "", err)
applog.LogError(rh.logger, wrappedErr, logrus.Fields{
"remediation": remediation,
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cache invalidation decodes keys using cache.DefaultKeyHash, but the cache’s configured KeyHash may differ (SHA256/SHA512/etc.), in which case decoding will fail and invalidation won’t run. Consider exposing the active key-hash (or a decode helper) on the Cache implementation, or avoid decoding entirely by storing non-hashed/tagged keys for invalidation.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check this comment @kanstantsinbuklis-sap

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added method to get key hash depending on config

Comment on lines +736 to 739
stmt, filterParameters, err := s.buildServiceStatement(context.Background(), baseQuery, filter, false, order, l)
if err != nil {
l.Error("Error preparing statement: ", err)
return nil, err
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getServiceAttr accepts a ctx, but it currently uses context.Background() when preparing the statement. This breaks request context propagation (timeouts/cancellation) and defeats the purpose of adding ctx. Use the passed ctx here.

Copilot uses AI. Check for mistakes.
Comment on lines 748 to 752
// Execute the query
rows, err := stmt.Queryx(filterParameters...)
rows, err := stmt.QueryxContext(context.Background(), filterParameters...)
if err != nil {
l.Error("Error executing query: ", err)
return nil, err
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getServiceAttr uses stmt.QueryxContext(context.Background(), ...) instead of the provided ctx, so query cancellation/timeouts won’t propagate. Use ctx for the query call as well.

Copilot uses AI. Check for mistakes.
Comment on lines 621 to 625
rows, err := performListScan(
context.Background(),
stmt,
filterParameters,
l,
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetAllIssueCursors builds the statement with the passed ctx, but then calls performListScan(context.Background(), ...). This drops request context propagation for the query (timeouts/cancellation). Pass ctx through to performListScan.

Copilot uses AI. Check for mistakes.
Comment on lines +275 to +277
if strings.Contains(decodedKey, fmt.Sprintf("\"issue_id\":[%d]\"", newRemediation.IssueId)) &&
(strings.Contains(decodedKey, "GetIssuesWithAggregations") || strings.Contains(decodedKey, "GetIssues") ||
strings.Contains(decodedKey, "GetAllIssueCursors")) {
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The invalidation match uses "issue_id" in the decoded cache key, but entity.IssueFilter serializes the issue id field as "id" (json tag id). This condition will never match, so the stale Issue cache entries won’t be invalidated after remediation creation. Update the match to use the correct serialized field name (and consider matching the function name + structured JSON instead of brittle substrings).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check this comment @kanstantsinbuklis-sap

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added additional check

Comment on lines +253 to +262
if err := rows.Err(); err != nil {
msg := "Error while iterating over result rows"
l.WithFields(
logrus.Fields{
"error": err.Error(),
"parameters": filterParameters,
}).Error(msg)

return nil, fmt.Errorf("%s", msg)
}
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When rows.Err() is non-nil, performListScan logs the underlying error but returns fmt.Errorf("%s", msg), losing the original error and making troubleshooting harder. Return an error that wraps the underlying cause (e.g., include %w).

Copilot uses AI. Check for mistakes.
@kanstantsinbuklis-sap kanstantsinbuklis-sap force-pushed the kanstantsinbuklis-sap/issue-1121/returning-remediated-vulnerabilities branch 2 times, most recently from 637fec5 to 445b392 Compare April 22, 2026 12:30
})

issues, err := app.ListIssues(f, opt)
// TODO: remove it
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leftover?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleted redundant comment

Comment thread Dockerfile Outdated
RUN mockery
# generate graphql code
RUN cd internal/api/graphql && go run github.com/99designs/gqlgen generate
# # generate mock code files
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uncomment again please

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we need to generate mocks and GraphQL in the final image, It has to be earlier
BWT I've mentioned that we avoid some dependencies caching, ldflags and dockerignore that also can reduce the final image size

FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.26 AS builder

WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download

COPY . .

RUN CGO_ENABLED=0 go build \
    -trimpath \
    -ldflags="-s -w" \
    -o /bin/heureka \
    cmd/heureka/main.go


FROM --platform=${TARGETPLATFORM:-linux/amd64} gcr.io/distroless/static-debian12:nonroot

LABEL source_repository="https://github.com/cloudoperators/heureka"
USER nonroot:nonroot
COPY --from=builder /bin/heureka /heureka

ENTRYPOINT ["/heureka"]

Maybe this Dockerfile will be more applicable?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mocks are only required for tests, but the GraphQL generation is required for the app, so it needs to be generated before the build.

Comment thread internal/cache/cache.go Outdated
key, err := c.CacheKey(fnname, fn, filterArgs(args...)...)
if err != nil {
return zero, fmt.Errorf("cache (key): Could not create cache key")
return zero, fmt.Errorf("cache (key): could not create cache key")
Copy link
Copy Markdown
Collaborator

@michalkrzyz michalkrzyz Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to say I am not convinced about this particular solution. It checks for args to find context in there to delete it. In the other function (getCallParameters) it checks for context type I am not sure why it is necessary...that is solution with too many conditions and a bit complex logic behind it and I am not sure we want it here.
Consider two other options:

  • Define new template function: CallCachedWithContext, where context will be passed explicitly in named argument, this way we will be able to skip context in key easily, and add it to the call if necessary. The only requirement is that context will be first argument of called function, but this is what we have in all functions and as far as I know most (if not all) library functions using context have it in first argument.
    If we do not use cache other way (without context) we can even remove callcached (but I would not do that).
  • Second option is to use lambda function to bind call with context and omit it in parameters
    I think you can easily create generic wrapper to remove context parameter and inject it in original call
    so in CallCached you have param:
    ....
    LocalContext(ctx, ir.database.GetIssueRepositories)
    ....
    which will remove context from the function and use local context if necessary.
    This way we will avoid changes in current cache code.
    and this option seems to be very good usage of first class citizen function in golang

@MR2011

@kanstantsinbuklis-sap kanstantsinbuklis-sap force-pushed the kanstantsinbuklis-sap/issue-1121/returning-remediated-vulnerabilities branch from 445b392 to 60cb523 Compare April 23, 2026 10:30
@kanstantsinbuklis-sap kanstantsinbuklis-sap force-pushed the kanstantsinbuklis-sap/issue-1121/returning-remediated-vulnerabilities branch 2 times, most recently from 54241d1 to 1cb9f6f Compare April 27, 2026 13:28
Copy link
Copy Markdown
Collaborator

@michalkrzyz michalkrzyz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is context change in cache related to the fix of the problem with returning remediated vulnerabilities? If not maybe we should split it, because both changes seems that they need to be worked out.

return nil, wrappedErr
}

if rh.cache != nil {
Copy link
Copy Markdown
Collaborator

@michalkrzyz michalkrzyz Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this cache handling function implemented in handler? This is not acceptable, we cannot mix those layers.
If we really want such handler we need to think about the proper pattern to handle that. We can't just mix logical layers this way.

I think that proper solution in here is to change GetAll interface function which is exposing too much and is not acceptable solution, to interface function named Invalidate(.*) which will take some regexp/wildcard/list of strings/wildcard/regex or whatever to filter through keys (decoded inside cache layer)

BTW GetAll is not a good name
'Get All Cache' - what do you expect to be done under such statement?
I would prefer to use GetAllKeys or even GetAllValidKeys

Comment thread internal/cache/cache.go Outdated
return getReturnValues[T](out)
}

func callDisabledWithContext[T any](ctx context.Context, fn any, args ...any) (T, error) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is redundant function, please remove it and use common one

Comment thread internal/cache/cache.go Outdated
if err == nil {
c.IncHit()
c.LaunchRefresh(func() {
_, _ = callFn[T](c, ttl, key, v, in)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are not using any special handling in refresh procedure, why don't you just use bind-like lambda to hide the context?

Comment thread internal/cache/cache.go Outdated
func callEnabledWithContext[T any](ctx context.Context, c Cache, ttl time.Duration, fnname string, fn any, args ...any) (T, error) {
var zero T

v, in, err := getCallParametersWithContext(fn, ctx, args...)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is something I wanted to avoid, why don't you use fn, args... with the old function?

Comment thread internal/cache/cache.go Outdated
return v, in, nil
}

func getCallParametersWithContext(fn any, ctx context.Context, args ...any) (reflect.Value, []reflect.Value, error) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is redundant function

Comment thread internal/cache/in_memory_cache.go Outdated
return valStr, true, nil
}

func (imc *InMemoryCache) GetAll() ([]string, error) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this getting all keys? I don't think we want invalid (ttl elapsed) keys to be considered here

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When getting keys from valkey and redis, you won't get invalid keys. It makes sense to rename the function to GetAllKeys, but GetAllValidKeys doesn't make sense

@kanstantsinbuklis-sap kanstantsinbuklis-sap force-pushed the kanstantsinbuklis-sap/issue-1121/returning-remediated-vulnerabilities branch from 1cb9f6f to be04783 Compare April 30, 2026 12:26
})

// TODO: add context
ctx := context.TODO()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can use Background in here, because that is what we want.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed


// Trigger autopatch whenever a scanner run has completed successfully
if _, err := srh.database.Autopatch(); err != nil {
if _, err := srh.database.Autopatch(context.TODO()); err != nil {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

context.Background() instead of TODO

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed


// Fetch IssueRepositories
issueRepositories, err := db.GetIssueRepositories(&entity.IssueRepositoryFilter{
issueRepositories, err := db.GetIssueRepositories(context.TODO(), &entity.IssueRepositoryFilter{
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

context.Background() - because we will never have foreground context here anyway

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed


// Get Issue Variants
issueVariants, err := db.GetServiceIssueVariants(
context.TODO(),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

context.TODO() -> context.Background()

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed

Debug("Building map of IssueVariants for issues related to assigned Component Version")

issueVariantMap, err := shared.BuildIssueVariantMap(db, &entity.ServiceIssueVariantFilter{
issueVariantMap, err := shared.BuildIssueVariantMap(context.TODO(), db, &entity.ServiceIssueVariantFilter{
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

context.TODO() -> context.Background()

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed

Debug("Fetching issue matches related to assigned Component Instance")

issue_matches, err := db.GetIssueMatches(&entity.IssueMatchFilter{
issue_matches, err := db.GetIssueMatches(context.TODO(), &entity.IssueMatchFilter{
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

context.TODO() -> context.Background()

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed


// Fetch services
services, err := db.GetServices(&entity.ServiceFilter{}, []entity.Order{})
services, err := db.GetServices(context.TODO(), &entity.ServiceFilter{}, []entity.Order{})
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

context.TODO() -> context.Background()

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed

Comment thread internal/cache/valkey_cache.go Outdated
return vc.client.Do(vc.ctx, vc.client.B().Del().Key(key).Build()).Error()
}

func (vc *ValkeyCache) InvalidateByMatch(keys []string, keyMatcher func(string) bool) error {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is some unused code and should be removed. Either we will extend interface or we define this function in cache.go both options are ok but we have to decide to use one.

Anyway why don't we implement it as interface function instead of GetAllKeys?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this as the method for valkey and in memory caches

Copy link
Copy Markdown
Collaborator

@michalkrzyz michalkrzyz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally very good work, but there are still minor change needed regarding the fix (cache interface extension)

@kanstantsinbuklis-sap kanstantsinbuklis-sap force-pushed the kanstantsinbuklis-sap/issue-1121/returning-remediated-vulnerabilities branch from 0985985 to 6847be4 Compare May 4, 2026 07:58
Copy link
Copy Markdown
Collaborator

@michalkrzyz michalkrzyz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a very good job! Thank you. @kanstantsinbuklis-sap

@MR2011 MR2011 merged commit c8709b4 into main May 4, 2026
10 of 11 checks passed
@MR2011 MR2011 deleted the kanstantsinbuklis-sap/issue-1121/returning-remediated-vulnerabilities branch May 4, 2026 11:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(vulnerability): returning remediated vulnerabilities

4 participants