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
14 changes: 6 additions & 8 deletions packages/core/src/location-mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ export const Kind = Schema.Literals(["file", "directory"])
export type Kind = typeof Kind.Type

/**
* Mutation paths do not accept project references. Relative paths must stay
* inside the active Location. Absolute paths outside it require separate
* Mutation paths do not accept project references. Relative paths resolve
* from the active Location. Paths outside it require separate
* `external_directory` approval.
*/
export const ResolveInput = Schema.Struct({
Expand All @@ -23,7 +23,7 @@ export type ResolveInput = typeof ResolveInput.Type

export class PathError extends Schema.TaggedErrorClass<PathError>()("LocationMutation.PathError", {
path: Schema.String,
reason: Schema.Literals(["relative_escape", "location_escape", "non_directory_ancestor"]),
reason: Schema.Literals(["location_escape", "non_directory_ancestor"]),
}) {}

export interface ExternalDirectoryAuthorization {
Expand Down Expand Up @@ -51,9 +51,9 @@ export interface Target {

export interface Interface {
/**
* Resolve a path and derive its permission resources. Relative paths must
* stay inside the Location. Absolute paths outside it require separate
* `external_directory` approval. This does not approve the mutation.
* Resolve a path and derive its permission resources. Relative paths resolve
* from the Location. Paths outside it require separate `external_directory`
* approval. This does not approve the mutation.
*/
readonly resolve: (input: ResolveInput) => Effect.Effect<Target, PathError | FSUtil.Error>
}
Expand Down Expand Up @@ -118,10 +118,8 @@ const layer = Layer.effect(
})

const resolve = Effect.fn("LocationMutation.resolve")(function* (input: ResolveInput) {
const relative = !path.isAbsolute(input.path)
const absolute = path.resolve(location.directory, input.path)
const lexicallyInternal = FSUtil.contains(location.directory, absolute)
if (relative && !lexicallyInternal) return yield* new PathError({ path: input.path, reason: "relative_escape" })

const resolved = yield* resolvePath(absolute)
if (lexicallyInternal && !FSUtil.contains(locationRoot, resolved.canonical)) {
Expand Down
14 changes: 11 additions & 3 deletions packages/core/test/location-mutation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,19 @@ describe("LocationMutation", () => {
),
)

it.live("rejects a relative lexical escape instead of promoting it to external authority", () =>
it.live("requires external-directory authorization for a relative lexical escape", () =>
withTmp((directory) =>
Effect.gen(function* () {
const error = yield* Effect.flip((yield* LocationMutation.Service).resolve({ path: "../outside.txt" }))
expect(error).toMatchObject({ _tag: "LocationMutation.PathError", reason: "relative_escape" })
const target = yield* (yield* LocationMutation.Service).resolve({ path: "../outside.txt" })
const root = yield* Effect.promise(() => fs.realpath(path.dirname(directory)))
expect(target).toMatchObject({
canonical: path.join(root, "outside.txt"),
resource: path.join(root, "outside.txt").replaceAll("\\", "/"),
})
expect(target.externalDirectory).toMatchObject({
directory: root,
resource: path.join(root, "*").replaceAll("\\", "/"),
})
}).pipe(provide(directory)),
),
)
Expand Down
4 changes: 2 additions & 2 deletions specs/v2/schema-changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,8 @@ Affected schema:

Change:

- Resolve relative mutation paths within the active Location.
- Accept absolute internal paths and require explicit `external_directory` approval before leaf approval for external absolute paths.
- Resolve relative mutation paths from the active Location.
- Accept absolute internal paths and require explicit `external_directory` approval before leaf approval for external paths.
- Keep named references read-oriented and reject them for mutation.
- Revalidate path authority immediately before write mechanics.

Expand Down
Loading