Skip to content

Custom file roots should be considered valid server managed roots. - #8232

Open
labkey-klum wants to merge 6 commits into
developfrom
fb_file_watcher_copy_path
Open

labkey-klum wants to merge 6 commits into
developfrom
fb_file_watcher_copy_path

Conversation

@labkey-klum

Copy link
Copy Markdown
Contributor

Rationale

https://github.com/LabKey/internal-issues/issues/1660

The signature for getContainersForFilePath was : List<Container> but always returned a single container. This introduces a new method : getContainerForFilePath to reflect that reality and deprecates the old method.

The existing implementation only considered paths that were based on the default site file root and custom file roots were not considered. The newer rewrite will now also consider custom file roots when determining which container a path belongs to.

Related Pull Requests

https://github.com/LabKey/premiumModules/pull/820

@labkey-klum labkey-klum self-assigned this Oct 26, 2026
@labkey-klum
labkey-klum requested a review from cnathe October 26, 2026 20:55
@Override
@NotNull
public List<Container> getContainersForFilePath(java.nio.file.Path path)
public @Nullable Container getContainerForFilePath(java.nio.file.Path path)

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.

claude code review came up with a few high level findings that might be worth reviewing before human code review:

Three security concerns from review:

1. **`getContainerForFilePath` doesn't normalize its input** (platform, `FileContentServiceImpl`). The custom-root check is a plain `startsWith`, so a path like `<ProjA root>/../ProjB/@files/secret.txt` resolves to ProjA. `ExperimentController` passes the raw `DataFileUrl` into its Read check, so a user with Read on ProjA can download ProjB files. Normalizing at the top of the method would fix it for every caller.
2. **Falling back to a parent container** (platform, `FileContentServiceImpl`). The develop code only accepted the deepest folder matched in the path. The new code moves up to a parent when that folder's root doesn't hold the path, e.g. the root is disabled or was moved. Files left under `<site>/P/Secret/@files/` are then attributed to `P`, so a user with Read on P but not on Secret can access them.
3. **Watch `location` has the same `..` gap** (premium, `FileWatcherPipelineTriggerType.validate`, existing code). `location` isn't normalized before its `startsWith(fileRoot)` check, so `<fileRoot>/../../../etc` passes, and a null pipeline root raises no error. This gets around the new copy-destination check from the source side, so it's worth fixing here or tracking as a follow-up.

if (c == null || c.isRoot() || isUseDefaultRoot(c))
continue;
java.nio.file.Path rootPath = getExistingFileRootPath(c);
if (rootPath != null && path.startsWith(rootPath) && (anchorPath == null || rootPath.startsWith(anchorPath)))

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.

PipeRoot does all this... Unfortunate it's not directly shareable. I think URIUtil.isDescendant() is preferred and handles some edge case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

@Override
@NotNull
public List<Container> getContainersForFilePath(java.nio.file.Path path)
public @Nullable Container getContainerForFilePath(java.nio.file.Path path)

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.

I can see that in practice this doesn't happen, but two containers could point to the same directory as a pipeline root. I'm not sure what the usage of this method are and how this is meant to be resolved, or if it's fine to be arbitrary?

Container c = ContainerManager.getForId(root.getContainer());
if (c == null || c.isRoot() || isUseDefaultRoot(c))
continue;
java.nio.file.Path rootPath = getExistingFileRootPath(c);

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.

check hasCloudScheme() in this loop somewhere too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

There is a short circuit on line 215 but cloud scheme paths should get resolved during getExistingFileRootPath

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.

3 participants