Skip to content

feat(trusted_endpoints): support {id} and {path:path} placeholders#15

Closed
aural-psynapse wants to merge 1 commit into
mainfrom
feat/trusted-endpoint-prefixes
Closed

feat(trusted_endpoints): support {id} and {path:path} placeholders#15
aural-psynapse wants to merge 1 commit into
mainfrom
feat/trusted-endpoint-prefixes

Conversation

@aural-psynapse
Copy link
Copy Markdown
Contributor

Closes #14.

What

A registered URL in trusted_endpoints may now contain FastAPI/Express-style path placeholders so one entry covers a family of concrete URLs:

Placeholder Matches Example
{name} exactly one path segment (no /) https://api.example.com/customers/{id} matches …/customers/42 but not …/customers/42/orders
{name:path} any subtree, including / separators https://api.example.com/customers/{rest:path} matches both …/customers/42 and …/customers/42/orders

The placeholder name (id, rest, …) is descriptive; matching is content-driven (any URL containing { is treated as a pattern). Plain URLs without { keep exact-match semantics — zero migration, no schema change, all existing rows behave exactly as before.

Why (issue summary)

customer-support-sdk-demo had to enumerate ~70 concrete URLs at startup (5 customers × 5 services × multiple per-id resources). Runtime-generated ids (e.g. POST /tickets returning a fresh ticket id) could not be trusted at all until manually re-registered.

One pattern entry replaces the whole enumeration:

INSERT INTO trusted_endpoints (org_id, normalized_url, display_label, entry_type)
VALUES ('my-org', 'https://api.example.com/customers/{id}', 'Customers (by id)', 'endpoint');

Implementation

  • New private _compile_pattern(registered) and _matches_registered(claim_url, registered) in src/provably/trusted_endpoints.py. Compilation is LRU-cached so repeated lookups don't recompile.
  • is_trusted_endpoint is now two-phase:
    1. Fast path — exact match against the indexed (org_id, normalized_url) row (unchanged from before).
    2. Slow path (only on miss)WHERE normalized_url LIKE '%{%' to fetch pattern rows for the org and test each in Python.
  • The snapshot tamper-check inside check_claim_endpoints_are_trusted runs the same matching logic, so a payload built with pattern entries also verifies cleanly on the receiver side.
  • Auto-detection by URL content. Plain URLs (no {) skip the slow path entirely — registries that don't use patterns see no perf regression.

Why FastAPI/Express syntax (vs path-prefix or glob)

Issue #14 listed three candidates: pattern with {id}, host-only, path-prefix. Path-prefix is easier to implement but broader/customers would also trust /customers/anything/under/it/forever, which is a security regression for users who only meant to allow per-id routes. The {id} syntax is the same shape every popular Python web framework already uses (FastAPI, Starlette, Flask via converters), and {rest:path} is FastAPI's native escape hatch when you genuinely want subtree-wide trust. Versatile, industry-standard, hard to misuse.

Tests

12 new tests, 94 total. Coverage:

  • _compile_pattern returns None for plain URLs (no perf cost when patterns aren't used).
  • Single-segment {id} matches one segment, refuses to swallow extra path components.
  • Multi-segment {rest:path} matches subtree-wide.
  • Multiple placeholders in one URL work.
  • is_trusted_endpoint two-phase lookup: exact-match wins on first query, pattern-match wins on second query, no match returns False.
  • The snapshot tamper-check in check_claim_endpoints_are_trusted honors patterns the same way — accepts a claim URL covered by a pattern in the registry, rejects one outside the pattern.

Migration

None. Existing exact-match entries are unchanged. The new behavior is fully opt-in per row.

Branch

Off main.

… registered URLs

A registered URL may now contain FastAPI/Express-style path placeholders so a
single entry covers a family of concrete URLs:

  {name}        - matches exactly one path segment (no '/').
                  e.g. https://api.example.com/customers/{id} matches
                  /customers/42 but NOT /customers/42/orders.

  {name:path}   - matches any subtree, including '/' separators.
                  e.g. https://api.example.com/customers/{rest:path} matches
                  both /customers/42 and /customers/42/orders.

Closes #14.

Why: customer-support-sdk-demo had to enumerate ~70 concrete URLs at startup
for templated routes (/customers/{id}). Runtime-generated ids (e.g. POST
/tickets returning a fresh id) couldn't be trusted until manually registered.
A single placeholder entry replaces the enumeration.

Implementation:

- Plain URLs without '{' keep exact-match semantics. No schema change. No
  migration needed for existing rows. Existing exact-match tests unchanged.
- Pattern matching is auto-detected from URL content. Pattern compilation is
  LRU-cached so repeated lookups don't recompile the regex.
- is_trusted_endpoint uses a two-phase lookup: exact match first (single
  indexed query, fast path), then a pattern-only scan (LIKE '%{%' filter)
  for rows containing placeholders. Plain registries see no perf regression.
- The snapshot tamper-check inside check_claim_endpoints_are_trusted honors
  the same syntax — a payload built against a pattern entry verifies cleanly
  on the receiver side.

Tests: 12 new (94 total). Ruff clean.
@aural-psynapse
Copy link
Copy Markdown
Contributor Author

Bundled into #17 with the API key onboarding docs and the cluster_b cleanup, per request.

@aural-psynapse aural-psynapse deleted the feat/trusted-endpoint-prefixes branch May 6, 2026 13:14
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.

trusted_endpoints: support prefix / pattern match (not exact-only)

1 participant