Skip to content

Propose a path extension type#115

Open
apg wants to merge 2 commits into
cedar-policy:mainfrom
apg:apg/path-extension-type
Open

Propose a path extension type#115
apg wants to merge 2 commits into
cedar-policy:mainfrom
apg:apg/path-extension-type

Conversation

@apg

@apg apg commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Rendered

Checklist

  • Added RFC to text/SUMMARY.md in numerical order

- Derived from discussion on @cedar-policy/rfcs cedar-policy#103
- path(..., ..., ...) becomes container to call .matches and
  .prefixMatches against

Signed-off-by: Andrew Gwozdziewycz <andrew.gwozdziewycz@docker.com>
@apg apg force-pushed the apg/path-extension-type branch from f4f7c6b to 2f41532 Compare June 5, 2026 23:31

@patjakdev patjakdev 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.

I wonder about the feasibility of generalizing the proposed path type to an ordered list type, where path becomes just a list[string].

The biggest wart that I think can think of is that list[string] would be the only type to allow for the special wildcard syntax when doing matching. A new any keyword could potentially be added to allow for wildcards in lists of other types.


## Summary

This RFC proposes a `path` extension type for Cedar. A path represents a variable-length, ordered sequence of strings — for example, the components of a filesystem path, S3 object key, URL path, MQTT topic, or reversed hostname. The `path` type exposes two matching methods, `.matches()` and `.prefixMatches()`, whose arguments must be string literals. This restriction keeps Cedar's SMT-based policy analysis decidable and efficient. String splitting and delimiter handling are intentionally out of scope; path values are constructed from pre-parsed components supplied by the application.

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 might propose prefixLike instead of prefixMatches for symmetry with the like operator.

You might also consider a prefixEquals for situations where * should be treated as a literal asterisk rather than a wildcard without the need to escape it.

@apg apg Jun 23, 2026

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.

I'm not sure about the Like vs Matches. I think Matches might work better given its relationship to structual pattern matching, though, you're right about the name symmetry--prefixLike might be more ergonomic. Open to other feedback here!


#### `.matches(pattern1, ..., patternN)`

Returns `true` if the path has **exactly** `N` components and each matches the corresponding pattern:

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.

Presumably, this method errors if a non-string is passed as an argument.

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.

Yeah. That would make sense! I'll have to update this.

path(resource.bucket, context.tenant) // dynamic components
```

Arguments may be any string-typed expression — they are not required to be literals. Strict validation requires each argument to have type `string`.

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.

Interesting. This might be the first extension type constructor that doesn't require a string literal for strict validation since it's not possible to pass an invalid value.

Comment thread text/0115-path-extension-type.md

### Schema

```json

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.

Include the Cedar format as well?

Comment thread text/0115-path-extension-type.md Outdated

Records with positional fields work for paths whose length is fixed by the schema. They fail for variable-length paths — S3 keys, URL paths, MQTT topics — which is exactly what this RFC targets.

### Alternative C: String attributes with `like`

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.

Isn't this the same thing as Alternative F? I suppose alternatives A and B are also the same thing as Alternative F.


- **`.get()` inclusion.** Should element access be included here or deferred to a follow-on? The primary value of `path` comes from `.matches()` and `.prefixMatches()`; `.get()` adds convenience but also implementation and specification surface area.

- **Wildcard semantics.** Pattern matching follows Cedar's `like` semantics: `*` matches zero or more characters within a single component and does not cross component boundaries. This means `path("foo", "bar").matches("*")` is `false` (length 2, not 1). The interaction between zero-length matches and the hostname use case (where `*.github.com` should require a non-empty subdomain) should be confirmed.

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 don't quite understand what this is getting at.

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.

Basically, matches("*") is strict on the number of components. In the example above path("foo", "bar").matches("*", "bar") is fine because it has two components. The variant prefixMatches supports the variable number of components.

Comment thread text/0115-path-extension-type.md Outdated

- **Wildcard semantics.** Pattern matching follows Cedar's `like` semantics: `*` matches zero or more characters within a single component and does not cross component boundaries. This means `path("foo", "bar").matches("*")` is `false` (length 2, not 1). The interaction between zero-length matches and the hostname use case (where `*.github.com` should require a non-empty subdomain) should be confirmed.

- **Empty path behavior.** Should `path()` — a zero-component path — be a valid value? `path().matches()` and `path().prefixMatches()` would both return `true`, which seems correct, but edge cases in entity data deserve consideration.

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.

Seems fine to me.

If we were to generalize path into list, though, it would be a problem because the type would be unknown.

@apg

apg commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

I wonder about the feasibility of generalizing the proposed path type to an ordered list type, where path becomes just a list[string].

The biggest wart that I think can think of is that list[string] would be the only type to allow for the special wildcard syntax when doing matching. A new any keyword could potentially be added to allow for wildcards in lists of other types.

I think this is something @emina would have to look at. I simply based this off of this comment #103 (comment). It seems as though general lists would be analyzable if they worked like the proposal (only integer literals for accessing elements, no length).

Signed-off-by: Andrew Gwozdziewycz <andrew.gwozdziewycz@docker.com>
@apg apg force-pushed the apg/path-extension-type branch from c250ee9 to 1ba6996 Compare June 23, 2026 00:32
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.

2 participants