Skip to content

Improve input validation, error handling, and workflow permissions#863

Merged
krusche merged 3 commits intodevelopfrom
feature/improve-input-validation-and-workflow-permissions
Feb 24, 2026
Merged

Improve input validation, error handling, and workflow permissions#863
krusche merged 3 commits intodevelopfrom
feature/improve-input-validation-and-workflow-permissions

Conversation

@krusche
Copy link
Copy Markdown
Member

@krusche krusche commented Feb 24, 2026

Summary

  • UploadService: Replace string-based path checks (contains("..")) with robust Path.normalize() + startsWith() validation in store(), load(), storeBytes(), and deleteFile(). Normalize rootLocation to an absolute path in the constructor so relative config values work correctly. Add alphanumeric extension validation in storeBytes(). Add @Slf4j logging for file operation failures.
  • CalendarController: Remove user-controlled input from error response bodies and set explicit text/plain content type. Add null-safe CRLF sanitization for log messages.
  • format.ts: Fix wordsToFilename() which was a no-op (replacing space with space) — now correctly replaces spaces with underscores using a global regex.
  • GitHub Actions workflows: Add explicit least-privilege permissions blocks to all 6 workflow files (contents: read for test/deploy, contents: read + packages: write for build/push workflows).

Test plan

  • All 624 server tests pass
  • Spotless formatting applied
  • Verify CI pipeline passes with new workflow permissions
  • Verify file upload/download still works correctly in dev environment
  • Verify calendar endpoint returns proper 404 for invalid research group

Summary by CodeRabbit

  • Bug Fixes

    • Filename normalization now handles multiple consecutive spaces.
    • Strengthened upload validation to prevent unsafe path usage and improve storage reliability.
  • Improvements

    • Standardized error response for missing resources to a consistent plain-text message.
  • Chores

    • Updated CI workflow permissions across deployment and test pipelines.

- Use Path.normalize() + startsWith() for robust file path validation in UploadService
- Normalize rootLocation to absolute path in constructor for reliable path checks
- Add extension validation in storeBytes to restrict to alphanumeric extensions
- Add logging to UploadService for file operation failures
- Remove user-controlled input from CalendarController error responses
- Sanitize log inputs to prevent CRLF injection
- Fix wordsToFilename() replacing space with space (no-op) to use underscores
- Add least-privilege permissions blocks to all GitHub Actions workflows
- Update test assertions to match improved error messages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 24, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d1dc58c and 47750f0.

📒 Files selected for processing (1)
  • server/src/main/java/de/tum/cit/aet/thesis/service/UploadService.java

Walkthrough

Added top-level permissions to multiple GitHub Actions workflows; changed client filename normalization to replace all spaces globally; sanitized calendar-controller logging and simplified 404 response; hardened upload service with path normalization, resolved-path validation, directory creation, and safer file store/load handling.

Changes

Cohort / File(s) Summary
GitHub Actions Permissions
.github/workflows/build_docker.yml, .github/workflows/deploy_docker.yml, .github/workflows/dev.yml, .github/workflows/e2e_tests.yml, .github/workflows/prod.yml, .github/workflows/run_tests.yml
Added top-level permissions blocks (e.g., contents: read, packages: write) across workflows. No job/step logic changed.
Client Filename Formatting
client/src/utils/format.ts
wordsToFilename now performs a global space replacement (replaces all spaces with underscores), changing filename normalization when multiple spaces exist.
Calendar Error Handling
server/src/main/java/.../controller/CalendarController.java
When research group not found, the abbreviation is sanitized (CR/LF → underscore) for logs and the response is simplified to plain-text Research group not found with HTTP 404.
Upload Service (storage safety & paths)
server/src/main/java/.../service/UploadService.java
Added Lombok logging (@Slf4j), convert upload root to absolute/normalized Path, ensure directories exist, validate resolved target paths (prevent path traversal using startsWith), and use resolved Paths/Files.copy and FileSystemResource for store/load operations. Error messages and logging updated accordingly.
Upload Service Tests
server/src/test/java/.../service/UploadServiceTest.java
Updated expected exception message in path-traversal test to match new validation wording (outside upload directory).

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client
  participant Service as UploadService
  participant FS as FileSystem

  rect rgba(0,123,255,0.5)
  Client ->> Service: send bytes + metadata (store request)
  Service ->> Service: compute content-hash, normalize uploadRoot, validate size
  Service ->> FS: ensure upload directories exist
  Service ->> FS: write file to resolved target (Files.copy)
  Service -->> Client: return stored filename / identifier
  end

  rect rgba(40,167,69,0.5)
  Client ->> Service: request file load or delete
  Service ->> Service: resolve filename, validate path within uploadRoot
  Service ->> FS: read file (FileSystemResource) or delete file
  Service -->> Client: file stream or acknowledgement
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the three main types of changes: input validation improvements (UploadService path normalization, extension validation), error handling enhancements (CalendarController response sanitization, logging), and workflow permissions additions across six GitHub Actions files.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/improve-input-validation-and-workflow-permissions

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
client/src/utils/format.ts (1)

1-6: ⚠️ Potential issue | 🟡 Minor

Use import type for type-only imports.

Lines 1-3: The interfaces ILightUser, IMinimalUser, IThesis, and IApplication are only used in type annotations and should be imported with import type to ensure they don't get bundled at runtime.

Example fix
import type { ILightUser, IMinimalUser } from '../requests/responses/user'
import type { IThesis } from '../requests/responses/thesis'
import { ThesisState } from '../requests/responses/thesis'
import type { IApplication } from '../requests/responses/application'
import { ApplicationState } from '../requests/responses/application'
// ... rest of imports
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/utils/format.ts` around lines 1 - 6, The imports at the top import
interfaces that are only used as types (ILightUser, IMinimalUser, IThesis,
IApplication); change those to use TypeScript's type-only imports (use "import
type { ... }") while keeping value imports like ThesisState, ApplicationState
and useMantineColorScheme as normal imports so runtime bundles are unchanged;
update the import statements that currently reference ILightUser, IMinimalUser,
IThesis, and IApplication to "import type" to avoid emitting them into runtime
output.
🧹 Nitpick comments (1)
.github/workflows/prod.yml (1)

11-14: LGTM — packages: write is correctly declared at the caller level.

Per GitHub's model, GITHUB_TOKEN permissions can only be downgraded — not elevated — by called workflows. A workflow chain A → B → C where A has package: read means B and C cannot have package: write. Declaring packages: write here is required to propagate that scope into build_docker.yml, where Docker image pushes to ghcr.io occur.

Optional: for slightly tighter scoping, you could override permissions at the individual calling-job level (e.g., grant only contents: read to the run-tests job), but run_tests.yml already self-restricts to contents: read so the practical impact is nil.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/prod.yml around lines 11 - 14, The permissions block
currently declares packages: write which is required to propagate GH_TOKEN write
scope into the downstream build_docker.yml workflow where ghcr.io pushes occur;
leave the top-level permissions: contents: read and packages: write as-is to
ensure called workflows (e.g., build_docker.yml) can perform package writes, and
if you want tighter scope optionally override permissions at the individual job
level (for example the run-tests job or in run_tests.yml) to restrict that job
to contents: read only.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@client/src/utils/format.ts`:
- Around line 1-6: The imports at the top import interfaces that are only used
as types (ILightUser, IMinimalUser, IThesis, IApplication); change those to use
TypeScript's type-only imports (use "import type { ... }") while keeping value
imports like ThesisState, ApplicationState and useMantineColorScheme as normal
imports so runtime bundles are unchanged; update the import statements that
currently reference ILightUser, IMinimalUser, IThesis, and IApplication to
"import type" to avoid emitting them into runtime output.

---

Nitpick comments:
In @.github/workflows/prod.yml:
- Around line 11-14: The permissions block currently declares packages: write
which is required to propagate GH_TOKEN write scope into the downstream
build_docker.yml workflow where ghcr.io pushes occur; leave the top-level
permissions: contents: read and packages: write as-is to ensure called workflows
(e.g., build_docker.yml) can perform package writes, and if you want tighter
scope optionally override permissions at the individual job level (for example
the run-tests job or in run_tests.yml) to restrict that job to contents: read
only.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d261f4 and 79e78d4.

📒 Files selected for processing (10)
  • .github/workflows/build_docker.yml
  • .github/workflows/deploy_docker.yml
  • .github/workflows/dev.yml
  • .github/workflows/e2e_tests.yml
  • .github/workflows/prod.yml
  • .github/workflows/run_tests.yml
  • client/src/utils/format.ts
  • server/src/main/java/de/tum/cit/aet/thesis/controller/CalendarController.java
  • server/src/main/java/de/tum/cit/aet/thesis/service/UploadService.java
  • server/src/test/java/de/tum/cit/aet/thesis/service/UploadServiceTest.java

krusche and others added 2 commits February 24, 2026 23:42
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
These methods belong to PR #864 and were accidentally included here.
Removing them to keep this PR focused and avoid merge conflicts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/src/main/java/de/tum/cit/aet/thesis/service/UploadService.java (1)

81-93: ⚠️ Potential issue | 🟡 Minor

Normalize extension case before allowlist check.

Uppercase extensions (e.g., .PNG) will currently fail the allowlist even though they’re valid. Normalizing avoids user-facing false negatives.

✅ Suggested fix
 import java.util.HexFormat;
+import java.util.Locale;
 import java.util.Set;
@@
-String extension = FilenameUtils.getExtension(originalFilename);
+String extension = FilenameUtils.getExtension(originalFilename).toLowerCase(Locale.ROOT);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/src/main/java/de/tum/cit/aet/thesis/service/UploadService.java` around
lines 81 - 93, Normalize the extracted extension to a consistent case before
checking the allowlist: after getting originalFilename and computing extension,
convert extension to lower-case (e.g., extension =
extension.toLowerCase(Locale.ROOT)) and then perform the allowedExtensions
check; ensure allowedExtensions comparisons are done against the normalized
extension (or normalize the allowlist entries once) so uppercase inputs like
".PNG" pass, while leaving the rest of the logic (computeFileHash, filename
creation, target path checks, rootLocation check) unchanged.
🧹 Nitpick comments (1)
server/src/main/java/de/tum/cit/aet/thesis/service/UploadService.java (1)

169-189: Sanitize filename before logging to avoid CRLF log injection.

If filename can contain CR/LF, it can forge log lines. Consider reusing the CRLF sanitizer used elsewhere or normalize here.

🧹 Suggested tweak
- log.warn("Failed to delete file '{}': {}", filename, e.getMessage());
+ String safeFilename = filename.replaceAll("[\\r\\n]", "_");
+ log.warn("Failed to delete file '{}': {}", safeFilename, e.getMessage());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/src/main/java/de/tum/cit/aet/thesis/service/UploadService.java` around
lines 169 - 189, The log call in deleteFile(String filename) directly logs the
untrusted filename which can contain CR/LF and enable log injection; before
calling Files.deleteIfExists or log.warn, sanitize the filename (e.g. reuse the
project's existing CRLF sanitizer or replace '\r' and '\n' with escaped
equivalents and optionally truncate) and use that sanitized value in the
log.warn call; keep using deleteFile, filename, rootLocation and
Files.deleteIfExists as-is but pass the sanitizedFilename into the logger.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/src/main/java/de/tum/cit/aet/thesis/service/UploadService.java`:
- Around line 129-167: The storeBytes method exposes an un-allowlisted extension
parameter; either reduce its visibility or enforce an allowlist: change
storeBytes to be private (so only internal callers use it) OR add an
UploadFileType parameter (e.g. storeBytes(byte[] bytes, UploadFileType type, int
maxSize) or storeBytes(byte[] bytes, String extension, Set<String>
allowedExtensions, int maxSize)) and validate the provided extension against
UploadFileType's allowed extensions before computing the hash and writing the
file (mirror the validation pattern used in the existing store() method),
throwing UploadException on mismatch.

---

Outside diff comments:
In `@server/src/main/java/de/tum/cit/aet/thesis/service/UploadService.java`:
- Around line 81-93: Normalize the extracted extension to a consistent case
before checking the allowlist: after getting originalFilename and computing
extension, convert extension to lower-case (e.g., extension =
extension.toLowerCase(Locale.ROOT)) and then perform the allowedExtensions
check; ensure allowedExtensions comparisons are done against the normalized
extension (or normalize the allowlist entries once) so uppercase inputs like
".PNG" pass, while leaving the rest of the logic (computeFileHash, filename
creation, target path checks, rootLocation check) unchanged.

---

Nitpick comments:
In `@server/src/main/java/de/tum/cit/aet/thesis/service/UploadService.java`:
- Around line 169-189: The log call in deleteFile(String filename) directly logs
the untrusted filename which can contain CR/LF and enable log injection; before
calling Files.deleteIfExists or log.warn, sanitize the filename (e.g. reuse the
project's existing CRLF sanitizer or replace '\r' and '\n' with escaped
equivalents and optionally truncate) and use that sanitized value in the
log.warn call; keep using deleteFile, filename, rootLocation and
Files.deleteIfExists as-is but pass the sanitizedFilename into the logger.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 79e78d4 and d1dc58c.

📒 Files selected for processing (1)
  • server/src/main/java/de/tum/cit/aet/thesis/service/UploadService.java

@krusche krusche self-assigned this Feb 24, 2026
@krusche krusche merged commit a49d522 into develop Feb 24, 2026
10 of 11 checks passed
@krusche krusche deleted the feature/improve-input-validation-and-workflow-permissions branch February 24, 2026 22:49
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