Skip to content

fix(security): hardening quick wins (S1–S5)#78

Merged
fronzec merged 1 commit into
mainfrom
security/hardening-quick-wins
Jun 21, 2026
Merged

fix(security): hardening quick wins (S1–S5)#78
fronzec merged 1 commit into
mainfrom
security/hardening-quick-wins

Conversation

@fronzec

@fronzec fronzec commented Jun 21, 2026

Copy link
Copy Markdown
Owner

What

Five security fixes from the repo-wide deep-dive audit, all in fr-batch-service. Each was reviewed in a fresh adversarial pass before this PR; two follow-up blockers found in that review are included.

# Fix
S1 Remove env from actuator exposure (base + production). The base value is inherited by any profile that doesn't override it — local did not, so /actuator/env leaked resolved properties in the documented dev flow.
S2 Restrict destructive POST /data/** reset to PLUGIN_ADMIN. It is @Profile("!production"), so it was reachable by any authenticated user (incl. PLUGIN_VIEWER) in the docker profile.
S3 Drop plain-text credential defaults from the base profile and the Java field defaults in SecurityProperties. A no-profile boot now fails fast (password cannot be null) instead of silently running with admin123. The isolated SecurityConfig test supplies explicit test credentials.
S4 Derive the approver from the authenticated principal in approve/reject instead of the request body (forgeable). The body field stays optional/ignored for backward-compat with load-plugins.{hurl,sh} and the CI contract test.
S5 Validate mainClassName against an FQCN pattern on upload, mirroring the existing jobName/version sanitization.

Verification

  • ./mvnw -B package → BUILD SUCCESS, 125 tests, 0 failures.
  • The contract-test CI job exercises the approve flow end-to-end (it sends approvedBy), proving S4 backward-compat.

Risk

No-profile startup now fails fast by design (fail-safe). All real profiles (local, docker, production) supply their own credentials and are unaffected.

Summary by CodeRabbit

Release Notes

  • Security Improvements

    • Implemented stricter authorization controls requiring elevated permissions for data operations.
    • Removed hardcoded development passwords; production deployments now require credentials via environment variables.
    • Enhanced input validation for uploaded job components.
    • Restricted actuator endpoint exposure to prevent information disclosure.
  • Bug Fixes

    • Fixed approval workflows to derive user identity from authentication context instead of client-supplied data.

…s, approver identity, and class-name validation

Closes five findings from the repo-wide audit:

- Remove `env` from actuator exposure in the base and production profiles.
  The base value is inherited by every profile that does not override it
  (including local), so leaving it there leaked resolved properties.
- Restrict the destructive POST /data/** reset endpoint to PLUGIN_ADMIN.
  It is @Profile("!production") so it was reachable by any authenticated
  user (incl. PLUGIN_VIEWER) in the docker profile.
- Drop plain-text credential defaults from the base profile AND the Java
  field defaults in SecurityProperties. Each profile now supplies its own
  credentials; a no-profile boot fails fast (password cannot be null)
  instead of silently running with admin123. The isolated SecurityConfig
  test now supplies explicit test credentials.
- Derive the approver from the authenticated principal in approve/reject
  instead of trusting the request body. The body field is still accepted
  (optional) for backward compatibility with the load-plugins scripts and
  the CI contract test, but its value is ignored.
- Validate mainClassName against a fully-qualified class-name pattern on
  upload, mirroring the existing jobName/version sanitization.
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Several security hardening changes are applied to fr-batch-service: hardcoded default passwords are removed from SecurityProperties and property files (fail-safe startup if passwords are absent); SecurityConfig adds a PLUGIN_ADMIN rule for POST /data/**; JarUploadService gains FQCN regex validation for mainClassName; approve/reject endpoints derive identity from AuditService.currentUserId() instead of the request body; and env is removed from actuator web exposure.

Changes

Security Hardening

Layer / File(s) Summary
Credential hardening: remove default passwords
fr-batch-service/src/main/java/com/fronzec/frbatchservice/config/SecurityProperties.java, fr-batch-service/src/main/resources/application.properties, fr-batch-service/src/main/resources/application-production.properties, fr-batch-service/src/test/java/com/fronzec/frbatchservice/config/SecurityConfigPasswordEncoderProfileTest.java
SecurityProperties drops hardcoded adminPassword/viewerPassword defaults; application.properties removes plain-text password properties and the env actuator endpoint; application-production.properties also removes env; SecurityConfigPasswordEncoderProfileTest injects a TEST_CREDENTIALS constant to keep tests runnable.
Add PLUGIN_ADMIN rule for POST /data/**
fr-batch-service/src/main/java/com/fronzec/frbatchservice/config/SecurityConfig.java
SecurityConfig adds a requestMatchers("/data/**") rule requiring hasRole("PLUGIN_ADMIN") and documents it in Javadoc.
FQCN validation in JarUploadService
fr-batch-service/src/main/java/com/fronzec/frbatchservice/batchjobs/plugins/service/JarUploadService.java
A FQCN regex Pattern is added; validateMainClassName enforces it and throws InvalidJarException on null/blank/non-matching input; uploadJar calls it immediately after validateJar.
Derive approver/reviewer from authenticated principal
fr-batch-service/src/main/java/com/fronzec/frbatchservice/web/JobManagementController.java, fr-batch-service/src/test/java/com/fronzec/frbatchservice/batchjobs/plugins/loader/DynamicJobLoadingIntegrationTest.java
Approve and reject endpoints mark @RequestBody as required = false and replace request.approvedBy() with AuditService.currentUserId() for entity persistence, logging, and audit events; integration test updates the expected approved_by value to "system".

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • fronzec/spring-batch-projects#30: Introduced JarUploadService and the core uploadJar upload/persistence flow that this PR extends with mainClassName FQCN validation.
  • fronzec/spring-batch-projects#47: Previously modified SecurityConfigPasswordEncoderProfileTest.java to validate profile-specific PasswordEncoder behavior, which this PR further adjusts to supply explicit test credentials.

Poem

🐇 A rabbit once hopped through the code late at night,
Sniffed out bare passwords — what a terrible sight!
It patched up the secrets, hashed passwords with care,
Validated class names floating there in the air,
And made sure the approver was who they'd been told.
Now the warren is safer, the system more bold! 🔒

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(security): hardening quick wins (S1–S5)' clearly summarizes the main change: security hardening addressing five specific findings.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch security/hardening-quick-wins

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
fr-batch-service/src/test/java/com/fronzec/frbatchservice/config/SecurityConfigPasswordEncoderProfileTest.java (1)

79-84: ⚡ Quick win

Add a dedicated fail-fast test for missing passwords.

This test now injects credentials, so it no longer verifies the intended no-profile fail-safe boot failure. Add one explicit negative-path test that omits app.security.*-password and asserts context startup failure.

Suggested test addition
+  `@Test`
+  void defaultProfile_withoutCredentials_failsFast() {
+    new WebApplicationContextRunner()
+        .withUserConfiguration(SecurityConfig.class)
+        .run(context -> {
+          assertThat(context).hasFailed();
+          assertThat(context.getStartupFailure()).hasMessageContaining("password cannot be null");
+        });
+  }
🤖 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
`@fr-batch-service/src/test/java/com/fronzec/frbatchservice/config/SecurityConfigPasswordEncoderProfileTest.java`
around lines 79 - 84, The current test defaultProfile_hasSingleNoOpEncoder()
injects credentials via TEST_CREDENTIALS, which prevents it from testing the
intended fail-safe behavior when passwords are missing. Add a new dedicated test
method that creates a WebApplicationContextRunner with SecurityConfig class but
explicitly omits the password property values (do not include TEST_CREDENTIALS
or similar), and assert that the application context startup fails when the
required app.security.*-password properties are absent.
🤖 Prompt for all review comments with 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.

Inline comments:
In
`@fr-batch-service/src/main/java/com/fronzec/frbatchservice/batchjobs/plugins/service/JarUploadService.java`:
- Around line 199-203: The InvalidJarException being thrown in the
JarUploadService contains user-controlled input (mainClassName) directly
embedded in the exception message, which poses a security risk as this message
is later logged by GlobalExceptionHandler and can be exploited for log injection
attacks via CR/LF payloads. Remove the mainClassName variable from the exception
message in the throw statement around lines 201-203, keeping only the
descriptive portion about the fully-qualified Java class name requirement, and
avoid concatenating any user-provided values directly into exception messages.

---

Nitpick comments:
In
`@fr-batch-service/src/test/java/com/fronzec/frbatchservice/config/SecurityConfigPasswordEncoderProfileTest.java`:
- Around line 79-84: The current test defaultProfile_hasSingleNoOpEncoder()
injects credentials via TEST_CREDENTIALS, which prevents it from testing the
intended fail-safe behavior when passwords are missing. Add a new dedicated test
method that creates a WebApplicationContextRunner with SecurityConfig class but
explicitly omits the password property values (do not include TEST_CREDENTIALS
or similar), and assert that the application context startup fails when the
required app.security.*-password properties are absent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: eb8296c9-f2be-4a1b-86be-a3a9266f2c2e

📥 Commits

Reviewing files that changed from the base of the PR and between 10b405e and dc0fee9.

📒 Files selected for processing (8)
  • fr-batch-service/src/main/java/com/fronzec/frbatchservice/batchjobs/plugins/service/JarUploadService.java
  • fr-batch-service/src/main/java/com/fronzec/frbatchservice/config/SecurityConfig.java
  • fr-batch-service/src/main/java/com/fronzec/frbatchservice/config/SecurityProperties.java
  • fr-batch-service/src/main/java/com/fronzec/frbatchservice/web/JobManagementController.java
  • fr-batch-service/src/main/resources/application-production.properties
  • fr-batch-service/src/main/resources/application.properties
  • fr-batch-service/src/test/java/com/fronzec/frbatchservice/batchjobs/plugins/loader/DynamicJobLoadingIntegrationTest.java
  • fr-batch-service/src/test/java/com/fronzec/frbatchservice/config/SecurityConfigPasswordEncoderProfileTest.java

Comment on lines +199 to +203
if (!FQCN.matcher(mainClassName).matches()) {
throw new InvalidJarException(
"mainClassName must be a fully-qualified Java class name "
+ "(e.g. com.example.MyClass): " + mainClassName);
}

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid reflecting raw mainClassName in exception messages.

At Line 201-203, the exception embeds user-controlled input directly. That message is later logged by GlobalExceptionHandler, so CR/LF payloads can forge log lines.

Suggested fix
         if (!FQCN.matcher(mainClassName).matches()) {
             throw new InvalidJarException(
                 "mainClassName must be a fully-qualified Java class name "
-                    + "(e.g. com.example.MyClass): " + mainClassName);
+                    + "(e.g. com.example.MyClass)");
         }

As per coding guidelines, use the established logging framework safely and avoid exposing unsafe user-provided content in error/log paths.

🤖 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
`@fr-batch-service/src/main/java/com/fronzec/frbatchservice/batchjobs/plugins/service/JarUploadService.java`
around lines 199 - 203, The InvalidJarException being thrown in the
JarUploadService contains user-controlled input (mainClassName) directly
embedded in the exception message, which poses a security risk as this message
is later logged by GlobalExceptionHandler and can be exploited for log injection
attacks via CR/LF payloads. Remove the mainClassName variable from the exception
message in the throw statement around lines 201-203, keeping only the
descriptive portion about the fully-qualified Java class name requirement, and
avoid concatenating any user-provided values directly into exception messages.

Source: Coding guidelines

@fronzec
fronzec merged commit 5ebc9d9 into main Jun 21, 2026
8 checks passed
@fronzec
fronzec deleted the security/hardening-quick-wins branch June 21, 2026 01:46
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.

1 participant