Skip to content

Reject unsafe namespace names before filesystem access - #31

Open
raphaelfeitoza wants to merge 2 commits into
v0.9.30-shopify-patchesfrom
fix/namespace-path-traversal-89265
Open

raphaelfeitoza wants to merge 2 commits into
v0.9.30-shopify-patchesfrom
fix/namespace-path-traversal-89265

Conversation

@raphaelfeitoza

@raphaelfeitoza raphaelfeitoza commented Sep 28, 2026 •

Copy link
Copy Markdown

Why

Fix shop/issues#89265: with namespaces and the admin API enabled, a name such as ..%2F..%2Fvictim or an encoded absolute path could be decoded, joined beneath <data>/dbs, and used to create files outside that directory. Deleting the namespace could then recursively delete unrelated writable files. This PR fixes traversal through the namespace string, rather than broadening the PR to directory-ownership or lifecycle hardening.

What changed

  • Require NamespaceName to be a nonempty single path component: reject ., .., /, \, NUL, and : on Windows. Keep existing harmless names with spaces, punctuation, and Unicode; remove new_unchecked and validate shared-schema names in replicated configuration.
  • Reject unsafe existing namespace names/shared-schema references in the metastore without treating them as missing or allowing --meta-store-destroy-on-error to erase the metastore. Filesystem recovery fails rather than committing a partial inventory when it finds an invalid directory name; symlinked directories are not followed.
  • Validate all persisted task names for a selected migration job before starting its first batch, so a bad name in a later batch cannot strand earlier tenants with writes blocked. Leave an unsafe job/task persisted and unfinished, isolate that job for this scheduler session, and allow independent jobs to continue. The row is checked again after restart.
  • Return errors instead of panicking when stricter validation encounters an invalid name in admin-shell/proxy-auth metadata or an existing scripted-backup snapshot filename. Preserve Unix names containing : when parsing snapshot filenames.
  • Add constructor, replicated/persisted metadata, migration, snapshot, and admin create/delete/fork regression tests, including sentinels outside <data>/dbs.

Compatibility and rollout

This intentionally changes responses for invalid names: some routes that used to return 404 now return 400, while invalid create/checkpoint path parameters are rejected by the extractor (400); an invalid shared_schema_name in a create JSON body is rejected (422). Response bodies differ by route. Invalid namespace claims can also invalidate an entire JWT. Existing Shopify cloud-sync-streamer namespace names were checked in the earlier review and remain valid; other consumers should check whether they rely on the old invalid-name behavior. The admin API documentation describes the name rule and the need to inspect/repair legacy persisted names before upgrade. An invalid namespace row or shared-schema reference prevents startup rather than risking data loss; an invalid migration job is isolated rather than taking down the primary. This patch does not add general recovery for unrelated corrupt persisted data.

Scope / stack

This PR targets v0.9.30-shopify-patches. It does not defend against pre-existing filesystem aliases, symlink replacement, or concurrent directory-ownership races; those belong to stacked #36. It also does not address someone directly changing migration rows while a job is running. #36 needs rebasing after this PR's history rewrite. Merge #31 first.

Validation / review request

  • cargo fmt --all -- --check, git diff --check, and RUSTFLAGS='-D warnings --cfg tokio_unstable' cargo check -p libsql-server --tests -q passed on b02733e21f.
  • Independent agent review passed after fixes for later-batch migration write blocks, partial filesystem recovery, and request-metadata panics.
  • macOS runtime tests could not link due to unresolved bundled SQLite symbols; Linux Run Tests CI on this head is still pending at the time of this edit. Other completed checks, including Windows checks, passed. Please check the live CI status before approval.

Please re-review the current diff, particularly the constructor/persistence boundary, migration prevalidation and isolation, non-destructive recovery, and API/rollout compatibility. Previous review comments refer to commits superseded by the focused history rewrite; human security review is still needed.

@raphaelfeitoza
raphaelfeitoza force-pushed the fix/namespace-path-traversal-89265 branch 2 times, most recently from 3dc7d91 to 5bd6336 Compare September 30, 2026 21:37
@raphaelfeitoza raphaelfeitoza changed the title Validate namespace names before filesystem access Reject unsafe namespace names before filesystem access Sep 30, 2026
@raphaelfeitoza
raphaelfeitoza added this pull request to stack #37 October 1, 2026 14:49
@raphaelfeitoza
raphaelfeitoza marked this pull request as ready for review October 1, 2026 14:57
@raphaelfeitoza
raphaelfeitoza requested a review from a team October 1, 2026 14:57
@raphaelfeitoza
raphaelfeitoza force-pushed the fix/namespace-path-traversal-89265 branch from 5bd6336 to fbd6921 Compare October 1, 2026 19:54

@sle-c sle-c left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The core fix looks right to me. Every NamespaceName constructor now validates, new_unchecked is gone, and each dbs/<name> join takes a checked name. I also checked our own callers. The cloud-sync-streamer names (point-of-sale-<kind>-shop-<id>-location-<id>, with the -snapshot, -shadow and -load-test-shadow suffixes) all pass the new rule, so our clients see no change.

The PR also hardens ATTACH. query_analysis.rs:138 now rejects an invalid name at parse time. On base, this path also needed attach rights and an existing namespace with allow_attach, so the check adds defense in depth. It may be worth a line in the PR body.

Important:

  1. Invalid names get new status codes, and the body shape now depends on the route (inline on name.rs).
  2. One bad migration row now stops the whole primary, which then crashloops (inline on scheduler.rs).

Suggestions:
3. parse_snapshot_path mis-parses names that contain : (inline).
4. Rollout preflight for rows that now block startup (inline on restore()).
5. auth/authenticated.rs:35 still unwraps proxy auth (outside the diff, details below).
6. One invalid name in a JWT now rejects the whole token (outside the diff, details below).

Item 5, auth/authenticated.rs:35: from_proxy_grpc_request calls serde_json::from_str::<Authenticated>(s).unwrap(). Authenticated deserializes NamespaceName in the legacy namespace field and in the scope ns sets. JSON with a name that this PR rejects now panics there. Malformed JSON already did on base. The trigger is narrow: an old replica must forward a token, signed with our key, that carries such a name. The impact is one failed gRPC request. The fix matches what you did in admin_shell.rs:

Some(s) => serde_json::from_str::<Authenticated>(s)
    .map_err(|_| Status::invalid_argument("invalid x-proxy-authorization"))?,

Item 6, auth/authorized.rs:181: Scopes.namespaces is an Option<HashSet<NamespaceName>>, and the legacy id claim is a NamespaceName too. One invalid name in a token now fails the claims decode, and the server returns 401 JwtInvalid for the whole token (jwt.rs:129-134). Before, only that one scope was unusable. Please note this in ADMIN_API.md next to the namespace rules.

While you are in schema/db.rs, a nit. Lines 354-355 still unwrap the stored migration program. This is not new; base had the same unwraps inside the closure. A corrupt row panics, with_conn_async re-panics through .expect, and the server exits. .map_err(Error::CorruptedJobStatus)? and validate_migration(&mut migration)? would at least give a readable error.

Comment thread libsql-server/src/namespace/name.rs Outdated
Comment thread libsql-server/src/schema/scheduler.rs
Comment thread libsql-server/src/replication/script_backup_manager.rs
Comment thread libsql-server/src/namespace/meta_store.rs
@raphaelfeitoza
raphaelfeitoza force-pushed the fix/namespace-path-traversal-89265 branch from 33ce0e1 to fc27a7a Compare October 2, 2026 19:33
@raphaelfeitoza
raphaelfeitoza removed this pull request from stack #37 October 2, 2026 19:35
@raphaelfeitoza
raphaelfeitoza changed the base branch from main to v0.9.30-shopify-patches October 2, 2026 19:35
@raphaelfeitoza
raphaelfeitoza added this pull request to stack #40 October 2, 2026 19:35
@raphaelfeitoza

Copy link
Copy Markdown
Author

Follow-up on the non-inline points in the review (commit fc27a7a614): proxy gRPC auth now returns InvalidArgument for malformed/invalid-namespace JSON rather than panicking, with a regression test. The namespace docs now note that a JWT with one invalid id/ns name invalidates the whole token. Persisted migration JSON and validation errors now carry the job ID and are isolated by the scheduler rather than panicking or taking down the primary; a corrupt-program regression test verifies an unrelated schema still progresses. The PR now targets v0.9.30-shopify-patches; the stack with #36 remains linked. Linux CI on the rebased head is running.

@raphaelfeitoza
raphaelfeitoza requested a review from sle-c October 5, 2026 12:40
@raphaelfeitoza
raphaelfeitoza force-pushed the fix/namespace-path-traversal-89265 branch from fc27a7a to b02733e Compare October 6, 2026 18:55
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