Skip to content

fix(tracking): validate app ids in the local tracking client - #946

Open
elijahbenizzy wants to merge 2 commits into
mainfrom
fix/tracking-client-identifier-validation
Open

elijahbenizzy wants to merge 2 commits into
mainfrom
fix/tracking-client-identifier-validation

Conversation

@elijahbenizzy

@elijahbenizzy elijahbenizzy commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

LocalTrackingClient validated project names but not app_id, even though both become directory names under storage_dir. This change applies one rule to both (letters, digits, _, -, ., :; non-empty; not ./..; at most 255 chars) and adds a containment check on every path the client builds, so a malformed id fails at ApplicationBuilder.build() rather than producing files in unexpected places.

Side effects: project names may now contain .; the project-name error message is shorter. The validation helper lives in burr/tracking/common/identifiers.py so the tracking server can share it.


Update after review. The identifier rule is now "any single path segment" rather than a character allowlist, so ids that worked before (user@example.com, chat session 1, non-ASCII names) keep working. Rejected: empty, ., .., /, \, NUL and other control characters (so a trailing newline no longer passes), over 255 characters, and : on Windows. Validation failures raise InvalidIdentifier (a ValueError); the tracking server maps it to HTTP 400, so the bundled demo routes return 400 on a bad id instead of 500. Integration test covers an encoded .. against the counter create route and asserts nothing is written.

The local tracking client turned app ids (and the parent app ids used
for fork/spawn links) straight into directory names under the storage
directory. Apply the rule the project name already used -- letters,
digits, '_', '-', ':' and '.', non-empty, not '.' or '..', at most 255
characters -- to every identifier that becomes a path, and check that
joined paths stay inside the storage directory. The rule lives in
burr.tracking.common.identifiers so the server side can share it.

Errors surface as ValueError from ApplicationBuilder.build().

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

The allowlist plus join_within stops the app_id traversal I tried against the client (../../../escaped, absolute paths, both separators, NUL, overlong ids), and pytest tests/tracking passes on this branch. Two things before it lands.

The identifier pattern still accepts a trailing newline (inline below); the same file is in #948.

Ids outside the allowlist that main accepted, like user@example.com or chat session 1, can no longer be loaded, forked or checked with app_log_exists, which now raises instead of returning a bool. Is rejecting previously valid app ids intentional, and what should existing applications do on upgrade?

Comment thread burr/tracking/common/identifiers.py Outdated
)
if value in (".", ".."):
raise ValueError(f"{what} must not be '.' or '..', got {value!r}")
if not IDENTIFIER_PATTERN.match(value) or (on_windows and ":" in value):

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.

IDENTIFIER_PATTERN.match(value) with a pattern ending in $ accepts "x\n" and "..\n"; $ matches before a final newline. IDENTIFIER_PATTERN.fullmatch(value) (or \Z in the pattern) rejects them.

@skrawcz

skrawcz commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

One remaining integration gap: the bundled demo routers pass URL app_id values into LocalTrackingClient. This PR correctly rejects invalid IDs at the filesystem sink, but the resulting ValueError currently escapes the demo endpoint as HTTP 500.

Before merging, please:

  1. Add route-level validation or exception translation so invalid demo project_id/app_id values return HTTP 400.
  2. Add an integration test covering an encoded .. app ID against at least the counter create endpoint and asserting both HTTP 400 and no writes outside the project application directory.
  3. Fix the regex to use fullmatch() (or \Z) so trailing newlines cannot pass validation.

Keeping validation in LocalTrackingClient is correct—the filesystem sink must remain protected even outside HTTP usage. The route layer should additionally translate that validation failure into the appropriate response.

@elijahbenizzy

Copy link
Copy Markdown
Contributor Author

@skrawcz addressed all three: (1) InvalidIdentifier is translated to HTTP 400 at the app level, so the demo routes no longer 500; (2) integration test posts an encoded .. to the counter create route and checks nothing lands on disk; (3) rule now rejects control characters outright, so the trailing-newline case is closed. On the upgrade question from @sangkyoonnam: the rule is widened to any single path segment, so previously valid ids like user@example.com still work; see the description for the exact list. Sangkyoon, your fixtures are welcome again if you have time.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/tracking Telemetry, tracing, OpenTelemetry

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants