Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1304,8 +1304,21 @@ public Mono<String> mergeBranch(
.setStrategy(MergeStrategy.RECURSIVE)
.call();
processStopwatch.stopAndLogTimeInMillis();
log.info(
"Git merge completed: repo={}, source={}, dest={}, status={}, durationMs={}",
repoSuffix,
sourceBranch,
destinationBranch,
mergeResult.getMergeStatus().name(),
processStopwatch.getExecutionTime());
return mergeResult.getMergeStatus().name();
} catch (GitAPIException e) {
log.warn(
"Git merge failed: repo={}, source={}, dest={}",
repoSuffix,
sourceBranch,
destinationBranch,
e);
Comment on lines 1315 to +1321

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
file='app/server/appsmith-git/src/main/java/com/appsmith/git/handler/ce/FSGitHandlerCEImpl.java'
printf '%s\n' '--- target context ---'
sed -n '1240,1365p' "$file"
printf '%s\n' '--- relevant declarations and calls ---'
rg -n -C 4 'onErrorResume|Mono\.using|merge\(\)|GitAPIException|merge failed|timeout|Timeout' "$file"
printf '%s\n' '--- method outline ---'
ast-grep outline "$file" | sed -n '1,180p'

Repository: appsmithorg/appsmith

Length of output: 47091


🏁 Script executed:

#!/bin/bash
set -eu
target='app/server/appsmith-git/src/main/java/com/appsmith/git/handler/ce/FSGitHandlerCEImpl.java'
printf '%s\n' '--- merge implementations and overrides ---'
rg -n -C 8 'mergeBranch\s*\(' app/server/appsmith-git
printf '%s\n' '--- merge callers and result handling ---'
rg -n -C 5 '\.mergeBranch\(' app/server --glob '*.java'
printf '%s\n' '--- tests for mergeBranch and failure handling ---'
rg -n -C 6 'mergeBranch|Git merge failed|hard resetting to latest commit|FS_MERGE' app/server --glob '*Test*.java' --glob '*.java'
printf '%s\n' '--- exact operator ordering ---'
sed -n '1284,1352p' "$target"

Repository: appsmithorg/appsmith

Length of output: 50376


Attach merge failure logging to every failure path.

The GitAPIException catch does not cover failures from Git.open, Mono.using cleanup, or timeout. When keepWorkingDirChanges is false, onErrorResume converts merge failures into a successful String after reset and replaces reset failures with a new Throwable. Log before recovery and at the outer Mono.using boundary, and preserve the original Throwable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/server/appsmith-git/src/main/java/com/appsmith/git/handler/ce/FSGitHandlerCEImpl.java`
around lines 1315 - 1321, Update the merge flow around the GitAPIException
handler and outer Mono.using boundary so every failure, including Git.open,
cleanup, timeout, reset, and recovery errors, is logged before recovery.
Preserve and propagate the original Throwable when onErrorResume handles
failures with keepWorkingDirChanges disabled instead of replacing it with a new
Throwable or converting the failure to a successful String.

// On merge conflicts abort the merge => git merge --abort
git.getRepository().writeMergeCommitMsg(null);
git.getRepository().writeMergeHeads(null);
Expand All @@ -1320,6 +1333,13 @@ public Mono<String> mergeBranch(
return Mono.error(error);
}

log.warn(
"Git merge failed, resetting to last commit: repo={}, source={}, dest={}",
repoSuffix,
sourceBranch,
destinationBranch,
error);

try {
return resetToLastCommit(repoSuffix, destinationBranch, keepWorkingDirChanges)
.thenReturn(error.getMessage());
Expand Down Expand Up @@ -1374,10 +1394,15 @@ public Mono<String> fetchRemote(
return fetchMessages;
})
.onErrorResume(error -> {
log.error(error.getMessage());
return Mono.error(error);
})
.timeout(Duration.ofMillis(Constraint.TIMEOUT_MILLIS))
.doOnError(error -> log.error(
"Git fetch failed: repo={}, branch={}, fetchAll={}",
repoSuffix,
branchName,
isFetchAll,
error))
.name(GitSpan.FS_FETCH_REMOTE)
.tap(Micrometer.observation(observationRegistry)),
Git::close)
Expand Down Expand Up @@ -1448,10 +1473,15 @@ public Mono<String> fetchRemote(
return fetchMessages;
})
.onErrorResume(error -> {
log.error(error.getMessage());
return Mono.error(error);
})
.timeout(Duration.ofMillis(Constraint.TIMEOUT_MILLIS))
.doOnError(error -> log.error(
"Git fetch failed: repo={}, refType={}, fetchAll={}",
repoSuffix,
fetchRemoteDTO.getRefType(),
fetchRemoteDTO.getIsFetchAll(),
error))
.name(GitSpan.FS_FETCH_REMOTE)
.tap(Micrometer.observation(observationRegistry)),
Git::close)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ public void stopAndLogTimeInMillis() {
log.debug("Execute time: {}, Time elapsed: {}ms", this.flow, this.watch.getTime(TimeUnit.MILLISECONDS));
}

public void stopAndLogTimeAtInfoLevel() {
if (!this.watch.isStopped()) {
this.watch.stop();
}
log.info("{}: durationMs={}", this.flow, this.watch.getTime(TimeUnit.MILLISECONDS));
}

public void stopTimer() {
if (!this.watch.isStopped()) {
this.watch.stop();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package com.appsmith.server.authentication.handlers.ce;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Slf4j
public class AccessDeniedHandlerCE implements ServerAccessDeniedHandler {
@Override
public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException denied) {
return Mono.fromRunnable(() -> {
log.warn("Access denied: path={}", exchange.getRequest().getPath());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major

Apply one bounded redaction policy to operational telemetry.

These changes emit request-, provider-, and upstream-controlled values without one common length, cardinality, and redaction policy. CRLF replacement alone does not prevent sensitive data or high-cardinality telemetry.

  • app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/ce/AccessDeniedHandlerCE.java#L16-L16: Log a route template or bounded sanitized path instead of the raw request path.
  • app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/ce/AuthenticationFailureHandlerCE.java#L27-L40: Use fixed metric categories and keep only bounded sanitized error context in logs.
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/DatasourceContextServiceCEImpl.java#L238-L244: Redact exception messages and stack traces before logging them.
  • app/server/appsmith-server/src/test/java/com/appsmith/server/authentication/handlers/ce/AuthenticationFailureHandlerCETest.java#L30-L44: Add coverage for null, long, CRLF-containing, and sensitive values in both telemetry fields.
Repository verification
#!/usr/bin/env bash
set -euo pipefail

rg -n -C 5 \
  'Access denied: path=|LOGIN_FAILURE|Datasource connection creation failed|errorMessage|doOnError\(e -> log\.error' \
  app/server/appsmith-server/src/main/java \
  app/server/appsmith-server/src/test/java \
  --glob '*.java'
📍 Affects 4 files
  • app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/ce/AccessDeniedHandlerCE.java#L16-L16 (this comment)
  • app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/ce/AuthenticationFailureHandlerCE.java#L27-L40
  • app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/DatasourceContextServiceCEImpl.java#L238-L244
  • app/server/appsmith-server/src/test/java/com/appsmith/server/authentication/handlers/ce/AuthenticationFailureHandlerCETest.java#L30-L44
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/ce/AccessDeniedHandlerCE.java`
at line 16, Apply one bounded redaction policy across the affected telemetry: in
AccessDeniedHandlerCE, log a route template or bounded sanitized path instead of
the raw path; in AuthenticationFailureHandlerCE, use fixed metric categories and
bounded sanitized error context; in DatasourceContextServiceCEImpl, redact
exception messages and stack traces before logging. Add
AuthenticationFailureHandlerCETest coverage for null, long, CRLF-containing, and
sensitive values in both telemetry fields.

ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,30 @@ public class AuthenticationFailureHandlerCE implements ServerAuthenticationFailu
@Override
public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) {
String source = exception instanceof OAuth2AuthenticationException
? ((OAuth2AuthenticationException) exception).getError().getErrorCode()
? sanitizeLogInput(
((OAuth2AuthenticationException) exception).getError().getErrorCode())
: SOURCE_FORM;

String errorMessage = exception.getMessage();

log.warn(
"Authentication failed: source={}, errorCode={}",
source,
exception.getClass().getSimpleName());
Comment thread
hacktron-app[bot] marked this conversation as resolved.
Comment on lines +33 to +36

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use the correct field name in the warning.

exception.getClass().getSimpleName() is an exception type, not an OAuth2 error code. The current event reports an exception type under errorCode; source already carries the sanitized code. Rename the field to exceptionType.

Proposed fix
-                "Authentication failed: source={}, errorCode={}",
+                "Authentication failed: source={}, exceptionType={}",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
log.warn(
"Authentication failed: source={}, errorCode={}",
source,
exception.getClass().getSimpleName());
log.warn(
"Authentication failed: source={}, exceptionType={}",
source,
exception.getClass().getSimpleName());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/ce/AuthenticationFailureHandlerCE.java`
around lines 33 - 36, Update the warning log in AuthenticationFailureHandlerCE
so exception.getClass().getSimpleName() is labeled with the exceptionType field
instead of errorCode; preserve source as the existing sanitized code field.


meterRegistry
.counter(LOGIN_FAILURE, "source", source, "message", errorMessage)
.increment();
return authenticationFailureRetryHandler.retryAndRedirectOnAuthenticationFailure(webFilterExchange, exception);
}

private static String sanitizeLogInput(String input) {
if (input == null) {
return null;
}
return input.replaceAll("[\\r\\n]", "_");
}

public Mono<Void> handleErrorRedirect(WebFilterExchange webFilterExchange) {
String error =
webFilterExchange.getExchange().getRequest().getQueryParams().getFirst("error");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
.filter(config -> TRUE.equals(config.getConfig().get("value")))
.switchIfEmpty(Mono.defer(instanceConfigHelper::registerInstance))
.onErrorResume(errorSignal -> {
log.debug("Instance registration failed with error: \n{}", errorSignal.getMessage());
log.warn("Instance registration failed: error={}", errorSignal.getMessage());
return Mono.empty();
})
.then(instanceConfigHelper.performRtsHealthCheck());
Expand All @@ -69,7 +69,7 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
try {
startupProcess.block();
} catch (Exception e) {
log.debug("Application start up encountered an error: {}", e.getMessage());
log.error("Application startup failed: error={}", e.getMessage(), e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ public ReactiveRedisConnectionFactory reactiveRedisConnectionFactory() {
final URI redisUri = URI.create(redisURL);
final String scheme = redisUri.getScheme();

log.info(
"Initializing Redis connection: scheme={}, host={}, port={}",
scheme,
redisUri.getHost(),
redisUri.getPort());

switch (scheme) {
case "redis" -> {
final RedisStandaloneConfiguration config =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ public Mono<? extends ArtifactExchangeJson> exportByExportableArtifactIdAndBranc
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, ARTIFACT_CONTEXT));
}

log.info("Export started: artifactId={}, artifactType={}", artifactId, artifactType);

ArtifactBasedExportService<?, ?> artifactBasedExportService = getContextBasedExportService(artifactType);
Map<String, String> artifactContextConstantMap = artifactBasedExportService.getConstantsMap();
String idConstant = artifactContextConstantMap.get(FieldName.ID);
Expand Down Expand Up @@ -177,6 +179,10 @@ public Mono<? extends ArtifactExchangeJson> exportByExportableArtifactIdAndBranc
.flatMap(user -> {
Map<String, String> contextConstants = artifactBasedExportService.getConstantsMap();
stopwatch.stopTimer();
log.info(
"Export completed: artifactId={}, durationMs={}",
exportingMetaDTO.getArtifactId(),
stopwatch.getExecutionTime());
final Map<String, Object> data = new HashMap<>();
data.put(FieldName.FLOW_NAME, stopwatch.getFlow());
data.put("executionTime", stopwatch.getExecutionTime());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1982,6 +1982,10 @@ protected Mono<GitPullDTO> pullArtifact(Artifact baseArtifact, Artifact branched
GitArtifactMetadata branchedGitMetadata = branchedArtifact.getGitArtifactMetadata();
ArtifactType artifactType = baseArtifact.getArtifactType();
String baseArtifactId = branchedGitMetadata.getDefaultArtifactId();
String branchName = branchedGitMetadata.getRefName();

log.info("Git pull started: artifactId={}, branch={}", baseArtifactId, branchName);
long pullStartTime = System.currentTimeMillis();
Comment on lines +1985 to +1988

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

file="app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCEImpl.java"

printf '%s\n' '--- target method context ---'
sed -n '1935,2050p' "$file"

printf '%s\n' '--- reactive timing and pull call sites ---'
rg -n -C 3 'pullStartTime|Git pull started|durationMs|doOnSuccess|doOnError|Mono\.defer|doOnSubscribe' "$file"

printf '%s\n' '--- method outline ---'
ast-grep outline "$file" | sed -n '1,220p'

Repository: appsmithorg/appsmith

Length of output: 17457


🏁 Script executed:

#!/bin/bash
set -eu

file="app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCEImpl.java"

sed -n '1935,2050p' "$file"
rg -n -C 3 'pullStartTime|Git pull started|durationMs|doOnSuccess|doOnError|Mono\.defer|doOnSubscribe' "$file"
ast-grep outline "$file" | sed -n '1,220p'

Repository: appsmithorg/appsmith

Length of output: 17360


🏁 Script executed:

#!/bin/bash
set -eu

file="app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCEImpl.java"

printf '%s\n' '--- pullArtifact call sites ---'
rg -n -C 4 'pullArtifact\s*\(' app/server --glob '*.java' --glob '*Test.java' | sed -n '1,260p'

printf '%s\n' '--- related pull tests and assertions ---'
rg -n -C 4 'pullArtifact|Git pull started|Git pull completed|Git pull failed|durationMs' app/server --glob '*Test.java' | sed -n '1,320p'

printf '%s\n' '--- return shapes in the pull pipeline ---'
sed -n '2055,2145p' "$file"

printf '%s\n' '--- Reactor and test dependency versions ---'
rg -n -C 2 'reactor-core|reactor-test|spring-boot-starter-webflux|spring-boot' app/server --glob 'pom.xml' --glob '*.xml' | sed -n '1,220p'

Repository: appsmithorg/appsmith

Length of output: 32647


🏁 Script executed:

#!/bin/bash
set -eu

file="app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCEImpl.java"

rg -n -C 4 'pullArtifact\s*\(' app/server --glob '*.java' --glob '*Test.java' | sed -n '1,260p'
rg -n -C 4 'pullArtifact|Git pull started|Git pull completed|Git pull failed|durationMs' app/server --glob '*Test.java' | sed -n '1,320p'
sed -n '2055,2145p' "$file"
rg -n -C 2 'reactor-core|reactor-test|spring-boot-starter-webflux|spring-boot' app/server --glob 'pom.xml' --glob '*.xml' | sed -n '1,220p'

Repository: appsmithorg/appsmith

Length of output: 33178


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import re

path = Path("app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCEImpl.java")
text = path.read_text()

start = text.index("    protected Mono<GitPullDTO> pullArtifact(")
end = text.index("\n    /**\n     * Method to pull the files from remote repo", start)
method = text[start:end]

log_pos = method.index('log.info("Git pull started:')
timer_pos = method.index("long pullStartTime = System.currentTimeMillis();")
subscribe_pos = method.index(".subscribe(")

assert log_pos < subscribe_pos
assert timer_pos < subscribe_pos
assert "Mono.usingWhen(" in method
assert "Mono.defer(" in text[text.index("private Mono<GitPullDTO> pullAndRehydrateArtifact"):end]

print("source_check: start log and wall-clock timestamp precede inner subscription")

class ColdMono:
    def __init__(self, events):
        self.events = events

    def subscribe(self):
        self.events.append("work subscribed")

def assemble():
    events = ["start logged", "timestamp captured"]
    return events, ColdMono(events)

events, mono = assemble()
assert events == ["start logged", "timestamp captured"]
assert "work subscribed" not in events

events.append("time passes before subscription")
mono.subscribe()
assert events[-2:] == ["time passes before subscription", "work subscribed"]
print("behavior_check: assembly side effects occur before delayed subscription")

events, mono = assemble()
assert events == ["start logged", "timestamp captured"]
print("behavior_check: assembly side effects occur even when the returned publisher is never subscribed")
PY

Repository: appsmithorg/appsmith

Length of output: 247


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path

path = Path("app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCEImpl.java")
text = path.read_text()

protected_start = text.index("    protected Mono<GitPullDTO> pullArtifact(")
protected_end = text.index(
    "\n    /**\n     * Method to pull the files from remote repo", protected_start
)
method = text[protected_start:protected_end]

log_pos = method.index('log.info("Git pull started:')
timer_pos = method.index("long pullStartTime = System.currentTimeMillis();")
subscribe_pos = method.index(".subscribe(")

assert log_pos < subscribe_pos
assert timer_pos < subscribe_pos
assert "Mono.usingWhen(" in method

private_start = text.index(
    "    private Mono<GitPullDTO> pullAndRehydrateArtifact", protected_end
)
private_end = text.index(
    "\n    `@Override`\n    public Mono<BranchTrackingStatus> fetchRemoteChanges",
    private_start
)
private_method = text[private_start:private_end]
assert "return Mono.defer(" in private_method

print("source_check: start logging and wall-clock timestamp precede the inner subscription")
print("source_check: the pull pipeline itself is deferred")

class ColdMono:
    def __init__(self, events):
        self.events = events

    def subscribe(self):
        self.events.append("work subscribed")

def assemble():
    events = ["start logged", "timestamp captured"]
    return events, ColdMono(events)

events, mono = assemble()
assert events == ["start logged", "timestamp captured"]
assert "work subscribed" not in events
events.append("time passes before subscription")
mono.subscribe()
assert events[-2:] == ["time passes before subscription", "work subscribed"]
print("behavior_check: assembly side effects precede delayed subscription")

events, _ = assemble()
assert events == ["start logged", "timestamp captured"]
print("behavior_check: assembly side effects occur without subscription")
PY

Repository: appsmithorg/appsmith

Length of output: 425


Start the pull timer on subscription.

log.info and pullStartTime execute before lockHandledpullDTOMono is subscribed. A delayed subscription inflates durationMs, and an unsubscribed protected-overload call still emits the start log. Wrap both side effects in Mono.defer and use System.nanoTime() for elapsed time.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCEImpl.java`
around lines 1985 - 1988, Update the pull flow around lockHandledpullDTOMono so
the start log and timer initialization occur inside Mono.defer, executing only
when the Mono is subscribed. Use System.nanoTime() for pullStartTime and
calculate elapsed duration from that monotonic timestamp, while preserving the
existing pull behavior.


Mono<GitPullDTO> lockHandledpullDTOMono = Mono.usingWhen(
gitRedisUtils.acquireGitLock(artifactType, baseArtifactId, GitCommandConstants.PULL, TRUE),
Expand Down Expand Up @@ -2015,8 +2019,19 @@ protected Mono<GitPullDTO> pullArtifact(Artifact baseArtifact, Artifact branched
.name(GitSpan.OPS_PULL)
.tap(Micrometer.observation(observationRegistry));

return Mono.create(
sink -> lockHandledpullDTOMono.subscribe(sink::success, sink::error, null, sink.currentContext()));
return Mono.create(sink -> lockHandledpullDTOMono
.doOnSuccess(result -> log.info(
"Git pull completed: artifactId={}, branch={}, durationMs={}",
baseArtifactId,
branchName,
System.currentTimeMillis() - pullStartTime))
.doOnError(error -> log.warn(
"Git pull failed: artifactId={}, branch={}, durationMs={}",
baseArtifactId,
branchName,
System.currentTimeMillis() - pullStartTime,
error))
.subscribe(sink::success, sink::error, null, sink.currentContext()));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,13 @@ private Mono<Artifact> importArtifactInWorkspace(
ImportArtifactPermissionProvider permissionProvider,
Set<String> permissionGroups) {

log.info(
"Import started: workspaceId={}, artifactId={}, appendToArtifact={}, artifactType={}",
workspaceId,
branchedArtifactId,
appendToArtifact,
artifactExchangeJson.getArtifactJsonType());

ArtifactBasedImportService<?, ?, ?> artifactBasedImportService =
getArtifactBasedImportService(artifactExchangeJson);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,12 @@ public Mono<DatasourceContext<Object>> getCachedDatasourceContextMono(
datasourceStorage.getDatasourceConfiguration());
}
})
.doOnError(e -> log.error(
"Datasource connection creation failed: datasourceId={}, pluginId={}, errorType={}",
datasourceStorage.getDatasourceId(),
datasourceStorage.getPluginId(),
e.getClass().getSimpleName(),
e))
.cache();

Mono<DatasourceContext<Object>> datasourceContextMonoCache = connectionMonoCache
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.appsmith.server.authentication.handlers.ce;

import com.appsmith.server.authentication.helpers.AuthenticationFailureRetryHandler;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import reactor.core.publisher.Mono;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class AuthenticationFailureHandlerCETest {

private AuthenticationFailureHandlerCE handler;
private MeterRegistry meterRegistry;

@BeforeEach
void setUp() {
AuthenticationFailureRetryHandler retryHandler = mock(AuthenticationFailureRetryHandler.class);
when(retryHandler.retryAndRedirectOnAuthenticationFailure(any(), any())).thenReturn(Mono.empty());
meterRegistry = new SimpleMeterRegistry();
handler = new AuthenticationFailureHandlerCE(retryHandler, meterRegistry);
}

@Test
void onAuthenticationFailure_sanitizesCrlfInOAuth2ErrorCode() {
String maliciousErrorCode = "invalid_grant\r\nINFO: Forged log entry";
OAuth2Error error = new OAuth2Error(maliciousErrorCode, "some description", null);
OAuth2AuthenticationException exception = new OAuth2AuthenticationException(error, "auth failed");

// The handler should not throw — it sanitizes and logs safely
handler.onAuthenticationFailure(null, exception).block();

// Verify the metric tag was recorded with sanitized source (no CRLF)
String recordedSource =
meterRegistry.get("appsmith.login_failure").counter().getId().getTag("source");
assertThat(recordedSource).doesNotContain("\r").doesNotContain("\n");
assertThat(recordedSource).isEqualTo("invalid_grant__INFO: Forged log entry");
Comment on lines +32 to +43

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 3 'spring-security|spring-boot-starter-security' app/server --glob 'pom.xml' || true
rg -n -C 6 'exception\.getMessage\(\)|counter\(.*message|log\.warn|OAuth2AuthenticationException' \
  app/server/appsmith-server/src/main app/server/appsmith-server/src/test

Repository: appsmithorg/appsmith

Length of output: 50377


🏁 Script executed:

#!/bin/bash
set -euo pipefail

test_file="$(fd -t f 'AuthenticationFailureHandlerCETest.java' . | head -n 1)"
handler_file="$(fd -t f 'AuthenticationFailureHandlerCE.java' . | head -n 1)"

printf '%s\n' "TEST=$test_file" "HANDLER=$handler_file"
ast-grep outline "$test_file" --lang java
ast-grep outline "$handler_file" --lang java

printf '\n--- test ---\n'
cat -n "$test_file"
printf '\n--- handler ---\n'
cat -n "$handler_file"

printf '\n--- focused dependency declarations ---\n'
rg -n -C 4 'spring-security|spring-boot-starter-security' app/server --glob 'pom.xml' | head -n 160

Repository: appsmithorg/appsmith

Length of output: 9426


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- managed Spring versions ---'
rg -n -C 3 'spring-boot.version|spring-security.version|spring-security' \
  app/server/pom.xml app/server/appsmith-server/pom.xml pom.xml 2>/dev/null | head -n 200

printf '%s\n' '--- authentication failure handler references ---'
rg -n -C 5 'handleErrorRedirect|onAuthenticationFailure|AuthenticationFailureHandlerCE' \
  app/server/appsmith-server/src/main app/server/appsmith-server/src/test \
  --glob '*.java' | head -n 320

printf '%s\n' '--- exact metric tag construction ---'
rg -n -C 3 'LOGIN_FAILURE|appsmith\.login_failure' \
  app/server/appsmith-server/src/main/java/com/appsmith/server/authentication \
  app/server/appsmith-server/src/test/java/com/appsmith/server/authentication \
  --glob '*.java' | head -n 240

Repository: appsmithorg/appsmith

Length of output: 1874


🏁 Script executed:

#!/bin/bash
set -u

printf '%s\n' '--- POM files ---'
git ls-files '*pom.xml' | head -n 120

printf '%s\n' '--- Spring version declarations ---'
for f in $(git ls-files '*pom.xml'); do
  rg -n -H -C 3 'spring-boot.version|spring-security.version|spring-boot-dependencies|spring-security' "$f" || true
done | head -n 260

printf '%s\n' '--- handler references ---'
rg -n -C 5 'handleErrorRedirect|onAuthenticationFailure|AuthenticationFailureHandlerCE' \
  app/server/appsmith-server/src/main app/server/appsmith-server/src/test \
  --glob '*.java' || true

printf '%s\n' '--- login failure metric construction ---'
rg -n -C 3 'LOGIN_FAILURE|appsmith\.login_failure' \
  app/server/appsmith-server/src/main/java/com/appsmith/server/authentication \
  app/server/appsmith-server/src/test/java/com/appsmith/server/authentication \
  --glob '*.java' || true

Repository: appsmithorg/appsmith

Length of output: 23127


🌐 Web query:

Spring Security 6.4 OAuth2AuthenticationException.java constructor OAuth2Error getDescription message

💡 Result:

In Spring Security 6.4, the OAuth2AuthenticationException class provides multiple constructors to handle OAuth 2.0-related authentication errors [1][2]. These constructors often accept an OAuth2Error object, which serves as a representation of the OAuth 2.0 error [3]. When an OAuth2AuthenticationException is constructed using only an OAuth2Error object, the exception's detail message is automatically populated using the description retrieved from that error [4]. Specifically, the implementation performs the following: public OAuth2AuthenticationException(OAuth2Error error) { this(error, error.getDescription); } In this context, the error.getDescription method returns the error description associated with the OAuth2Error instance [3][5]. If the description is not available in the OAuth2Error, it may return null [6][7], which is then passed as the message to the superclass constructor (AuthenticationException) [4]. Other available constructors in Spring Security 6.4 allow for explicit messaging, such as: - OAuth2AuthenticationException(OAuth2Error error, String message) [1][8] - OAuth2AuthenticationException(OAuth2Error error, String message, Throwable cause) [1][8] These constructors permit you to override or provide a custom detail message independently of the description contained within the OAuth2Error object [8].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

handler = Path("app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/ce/AuthenticationFailureHandlerCE.java").read_text()
test = Path("app/server/appsmith-server/src/test/java/com/appsmith/server/authentication/handlers/ce/AuthenticationFailureHandlerCETest.java").read_text()

assert re.search(
    r'counter\(LOGIN_FAILURE,\s*"source",\s*source,\s*"message",\s*errorMessage\)',
    handler,
)
assert re.search(r'String errorMessage = exception\.getMessage\(\);', handler)
assert re.search(
    r'log\.warn\(\s*"Authentication failed: source=\{\}, errorCode=\{\}",\s*source,\s*exception\.getClass\(\)\.getSimpleName\(\)',
    handler,
    re.S,
)
assert 'new OAuth2AuthenticationException(error, "auth failed")' in test
assert 'getTag("message")' not in test
assert 'getTag("source")' in test

print("message metric tag: direct exception.getMessage()")
print("warning log inputs: sanitized source and exception class only")
print("test message: explicit safe literal 'auth failed'")
print("test assertions: source tag only")
PY

Repository: appsmithorg/appsmith

Length of output: 353


Test and sanitize the exception message metric tag.

Construct OAuth2AuthenticationException with the error-derived message and a CRLF-containing description. Assert that the message tag contains no CRLF. onAuthenticationFailure currently passes exception.getMessage() directly to the metric, while this test supplies the safe literal "auth failed" and checks only source.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/server/appsmith-server/src/test/java/com/appsmith/server/authentication/handlers/ce/AuthenticationFailureHandlerCETest.java`
around lines 32 - 43, The test should construct OAuth2AuthenticationException
using the error-derived message with a CRLF-containing description, then verify
the recorded metric’s message tag contains neither carriage returns nor
newlines. Update the onAuthenticationFailure metric-tag handling to sanitize
exception.getMessage() before recording it, and extend
AuthenticationFailureHandlerCETest to assert the sanitized message value while
retaining the source assertion.

}
}
Loading