Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
77a448c
chore(deps): bump jacksonVersion from 2.22.0 to 2.22.1
dependabot[bot] Jul 17, 2026
eb44111
chore(deps): update actions/setup-java action to v5.6.0
renovate[bot] Jul 19, 2026
94cb502
fix(deps): update dependency org.junit:junit-bom to v6.1.2
renovate[bot] Jul 20, 2026
20052d3
test: write tests to detect missing paths when reading a crate
Pfeil Jul 22, 2026
2a1f0e1
docs: add javadoc to DataEntity.getPath
Pfeil Jul 22, 2026
ffe5b91
fix: writing files uses decoded ID of an entity
Pfeil Jul 22, 2026
ee2a7f6
fix: detect files matching the decoded or encoded id of an entity
Pfeil Jul 22, 2026
a0564bb
fix(test): avoid invalid paths on windows in a test
Pfeil Jul 22, 2026
14d9f10
test: loading and saving "heals" filename in crates
Pfeil Jul 22, 2026
3f51510
test: all writers write decoded ids as filenames
Pfeil Jul 22, 2026
43fe41f
fix: zip stream writer writes decoded filenames
Pfeil Jul 22, 2026
3898149
chore(deps): update actions/checkout action to v7.0.1
renovate[bot] Jul 23, 2026
668309e
chore(deps): update github/codeql-action action to v4.37.3
renovate[bot] Jul 25, 2026
e75c9b2
test: avoid emoji in filenames because it causes issues on windows
Pfeil Jul 27, 2026
d596dcc
test: avoid pipe in filenames because it might cause issues on windows
Pfeil Jul 27, 2026
e370c45
Revert "test: avoid emoji in filenames because it causes issues on wi…
Pfeil Jul 27, 2026
90f814f
Merge pull request #353 from kit-data-manager/320-encoded-paths-are-n…
Pfeil Jul 27, 2026
f39d27d
Merge pull request #354 from kit-data-manager/renovate/actions-checko…
Pfeil Jul 27, 2026
996b324
Merge pull request #351 from kit-data-manager/dependabot/gradle/jacks…
Pfeil Jul 27, 2026
60b3446
Merge pull request #347 from kit-data-manager/renovate/github-codeql-…
Pfeil Jul 27, 2026
cc14008
Merge pull request #346 from kit-data-manager/renovate/junit-framewor…
Pfeil Jul 27, 2026
3cd9985
Merge pull request #343 from kit-data-manager/renovate/actions-setup-…
Pfeil Jul 27, 2026
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
8 changes: 4 additions & 4 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
Expand All @@ -56,7 +56,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
uses: github/codeql-action/autobuild@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3

# Command-line programs to run using the OS shell.
# See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
Expand All @@ -69,4 +69,4 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
8 changes: 4 additions & 4 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ jobs:
runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up openJDK version
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
with:
java-version: ${{ matrix.jdk }}
distribution: "zulu"
Expand All @@ -47,9 +47,9 @@ jobs:
jdkversion: 25
steps:
- name: Checkout repo
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up OpenJDK version ...
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
with:
distribution: "zulu"
java-version: ${{ env.jdkversion }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/publishRelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Java
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
with:
java-version: 21
distribution: 'zulu' # openjdk
Expand Down
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ repositories {
}

ext {
jacksonVersion = '2.22.0'
jacksonVersion = '2.22.1'
}

dependencies {
// JUnit setup for testing
testImplementation(platform("org.junit:junit-bom:6.1.0"))
testImplementation(platform("org.junit:junit-bom:6.1.2"))
testImplementation('org.junit.jupiter:junit-jupiter')
testRuntimeOnly('org.junit.platform:junit-platform-launcher')
// JSON object mapping / (de-)serialization
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ public void addAuthorId(String id) {
this.addIdProperty("author", id);
}

/**
* Gets the path of this entity.
* <p>
* When reading a crate, this will point to the entity's file within the crate.
* When creating a new entity or crate, it might point to a file outside the crate,
* as set by {@link DataEntityBuilder#setLocation(Path)}.
* Such a file will be copied into the crate when writing.
*
* @return the path to the file this entity represents.
*/
@JsonIgnore
public Path getPath() {
return path;
Expand Down
17 changes: 14 additions & 3 deletions src/main/java/edu/kit/datamanager/ro_crate/reader/CrateReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
Expand Down Expand Up @@ -142,6 +143,7 @@ private RoCrate rebuildCrate(ObjectNode metadataJson, File files, HashSet<String

// Handle data entities with corresponding file
checkFolderHasFile(entityJson.get(PROP_ID).asText(), files).ifPresent(file -> {
System.out.println("Found file for: " + file.toString());
usedFiles.add(file.getPath());
builder.setLocationWithExceptions(file.toPath())
.setId(file.getName());
Expand Down Expand Up @@ -237,15 +239,24 @@ protected String unpackId(JsonNode node) {
}

protected Optional<File> checkFolderHasFile(String filepathOrId, File folder) {
System.out.println("DEBUG: " + Arrays.toString(folder.listFiles()));
if (IdentifierUtils.isUrl(filepathOrId)) {
return Optional.empty();
}
return IdentifierUtils.decode(filepathOrId)
.map(decoded -> folder.toPath().resolve(decoded).normalize())
System.out.println( "DEBUG: " +
IdentifierUtils.decode(filepathOrId)
.map(decoded -> folder.toPath().resolve(decoded).normalize())
.filter(resolved -> resolved.startsWith(folder.toPath()))
.map(Path::toFile)
.map(File::exists)
);
return Stream.of(IdentifierUtils.decode(filepathOrId).orElse(filepathOrId), filepathOrId)
.map(filename -> folder.toPath().resolve(filename).normalize().toAbsolutePath())
// defence-in-depth: ensure we are still inside the crate folder
.filter(resolved -> resolved.startsWith(folder.toPath()))
.map(Path::toFile)
.filter(File::exists);
.filter(File::exists)
.findFirst();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import edu.kit.datamanager.ro_crate.Crate;
import edu.kit.datamanager.ro_crate.entities.data.DataEntity;
import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper;
import edu.kit.datamanager.ro_crate.special.IdentifierUtils;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -54,7 +55,7 @@
inputStream.close();
// save also the preview files to the crate destination
if (crate.getPreview() != null && this.writePreview) {
crate.getPreview().saveAllToFolder(file);

Check warning on line 58 in src/main/java/edu/kit/datamanager/ro_crate/writer/WriteFolderStrategy.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 17)

[removal] saveAllToFolder(File) in CratePreview has been deprecated and marked for removal

Check warning on line 58 in src/main/java/edu/kit/datamanager/ro_crate/writer/WriteFolderStrategy.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 25)

[removal] saveAllToFolder(File) in CratePreview has been deprecated and marked for removal

Check warning on line 58 in src/main/java/edu/kit/datamanager/ro_crate/writer/WriteFolderStrategy.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 25)

[removal] saveAllToFolder(File) in CratePreview has been deprecated and marked for removal

Check warning on line 58 in src/main/java/edu/kit/datamanager/ro_crate/writer/WriteFolderStrategy.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 26)

[removal] saveAllToFolder(File) in CratePreview has been deprecated and marked for removal

Check warning on line 58 in src/main/java/edu/kit/datamanager/ro_crate/writer/WriteFolderStrategy.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 21)

[removal] saveAllToFolder(File) in CratePreview has been deprecated and marked for removal

Check warning on line 58 in src/main/java/edu/kit/datamanager/ro_crate/writer/WriteFolderStrategy.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 17)

[removal] saveAllToFolder(File) in CratePreview has been deprecated and marked for removal

Check warning on line 58 in src/main/java/edu/kit/datamanager/ro_crate/writer/WriteFolderStrategy.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 21)

[removal] saveAllToFolder(File) in CratePreview has been deprecated and marked for removal

Check warning on line 58 in src/main/java/edu/kit/datamanager/ro_crate/writer/WriteFolderStrategy.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 26)

[removal] saveAllToFolder(File) in CratePreview has been deprecated and marked for removal

Check warning on line 58 in src/main/java/edu/kit/datamanager/ro_crate/writer/WriteFolderStrategy.java

View workflow job for this annotation

GitHub Actions / coverage

[removal] saveAllToFolder(File) in CratePreview has been deprecated and marked for removal

Check warning on line 58 in src/main/java/edu/kit/datamanager/ro_crate/writer/WriteFolderStrategy.java

View workflow job for this annotation

GitHub Actions / coverage

[removal] saveAllToFolder(File) in CratePreview has been deprecated and marked for removal
}
for (var e : crate.getUntrackedFiles()) {
if (e.isDirectory()) {
Expand All @@ -64,16 +65,18 @@
}
}
for (DataEntity dataEntity : crate.getAllDataEntities()) {
savetoFile(dataEntity, file);
saveToFile(dataEntity, file);
}
}

private void savetoFile(DataEntity entity, File file) throws IOException {
private void saveToFile(DataEntity entity, File file) throws IOException {
if (entity.getPath() != null) {
String id = entity.getId();
String filename = IdentifierUtils.decode(id).orElse(id);
if (entity.getPath().toFile().isDirectory()) {
FileUtils.copyDirectory(entity.getPath().toFile(), file.toPath().resolve(entity.getId()).toFile());
FileUtils.copyDirectory(entity.getPath().toFile(), file.toPath().resolve(filename).toFile());
} else {
FileUtils.copyFile(entity.getPath().toFile(), file.toPath().resolve(entity.getId()).toFile());
FileUtils.copyFile(entity.getPath().toFile(), file.toPath().resolve(filename).toFile());
}
}
}
Comment on lines +72 to 82

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 | 🔴 Critical | ⚡ Quick win

Unvalidated decoded id used to build output paths in both writer strategies. Both writers decode the entity id and use it directly to construct a destination location, without the containment check that CrateReader.checkFolderHasFile applies on the read side for the same reason ("defence-in-depth: ensure we are still inside the crate folder"). A decoded id that is absolute, or contains ../, escapes the intended output location (arbitrary file write on folder-write, Zip Slip on zip-write).

  • src/main/java/edu/kit/datamanager/ro_crate/writer/WriteFolderStrategy.java#L72-L82: after computing filename, normalize/resolve against the destination folder and reject (or throw) if the result doesn't stay under it, mirroring the reader's startsWith guard.
  • src/main/java/edu/kit/datamanager/ro_crate/writer/WriteZipStreamStrategy.java#L152-L171: reject (or strip) decoded filenames containing traversal segments/absolute markers before using them to build the zip entry name.
📍 Affects 2 files
  • src/main/java/edu/kit/datamanager/ro_crate/writer/WriteFolderStrategy.java#L72-L82 (this comment)
  • src/main/java/edu/kit/datamanager/ro_crate/writer/WriteZipStreamStrategy.java#L152-L171
🤖 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 `@src/main/java/edu/kit/datamanager/ro_crate/writer/WriteFolderStrategy.java`
around lines 72 - 82, Validate decoded destination names before writing in both
strategies: in WriteFolderStrategy.saveToFile, normalize the resolved path under
the destination folder and reject it if it does not remain contained, mirroring
CrateReader.checkFolderHasFile; in WriteZipStreamStrategy at the decoded
filename handling, reject or safely strip absolute markers and traversal
segments before constructing the zip entry name. Apply the changes to
src/main/java/edu/kit/datamanager/ro_crate/writer/WriteFolderStrategy.java lines
72-82 and
src/main/java/edu/kit/datamanager/ro_crate/writer/WriteZipStreamStrategy.java
lines 152-171.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.UUID;

import edu.kit.datamanager.ro_crate.preview.CratePreview;
import edu.kit.datamanager.ro_crate.special.IdentifierUtils;
import edu.kit.datamanager.ro_crate.util.FileSystemUtil;
import edu.kit.datamanager.ro_crate.util.ZipStreamUtil;
import net.lingala.zip4j.io.outputstream.ZipOutputStream;
Expand Down Expand Up @@ -154,16 +155,18 @@ private void saveToStream(DataEntity entity, ZipOutputStream zipStream, String p
}

boolean isDirectory = entity.getPath().toFile().isDirectory();
String id = entity.getId();
String filename = IdentifierUtils.decode(id).orElse(id);
if (isDirectory) {
ZipStreamUtil.addFolderToZipStream(
zipStream,
entity.getPath().toFile(),
prefix + entity.getId());
prefix + filename);
} else {
ZipStreamUtil.addFileToZipStream(
zipStream,
entity.getPath().toFile(),
prefix + entity.getId());
prefix + filename);
}
}
}
Loading
Loading