Skip to content

feat: extract Slice 8 asset capability base#229

Closed
wizzoapp[bot] wants to merge 3 commits into
mainfrom
wizzo/dseg-s8-prereq-base
Closed

feat: extract Slice 8 asset capability base#229
wizzoapp[bot] wants to merge 3 commits into
mainfrom
wizzo/dseg-s8-prereq-base

Conversation

@wizzoapp

@wizzoapp wizzoapp Bot commented Jul 21, 2026

Copy link
Copy Markdown

Summary

Extract the smallest coherent Slice 8 prerequisite from the conflicting #208 branch so relay Slices A/B/C and the final #208 rebase can build on a landable main commit.

Binding design: /home/adam/.openclaw/reports/app-origin-asset-relay-design-2026-07-21.md
Parent threat model: /home/adam/.openclaw/reports/data-segregation-design-2026-07-17.md §2

This base adds:

  • same-origin-relay-v1 capability negotiation and AssetClientUpgradeRequiredError
  • the new-server/old-client fail-closed contract: no unbound private URL is returned
  • audience-bound asset claim schemas/codecs with legacy claims decoding as private and unbound
  • asset URLs keyed to the issuing auth session, with masking 404 for missing or mismatched proof
  • same-surface browser passthrough using the existing authenticated cookie, plus narrow native/mobile proof handling

The asset URL contains only a keyed binding identifier, never the auth session id or proof. Asset responses are no-store. The optional mobile WebView cookie is host-only, HttpOnly, SameSite=Lax, and path-scoped to /api/assets/relay.

Extraction rationale

This is a strict subset of #208 posture. It ports only the capability/session-binding primitives and matching same-surface client support needed to avoid breaking current main. It does not merge the Slice 8 draft or its broader asset/filesystem changes.

Verification

  • pnpm test — full workspace suite passed (14 tasks; server: 214 files passed/2 skipped, 2,507 tests passed/22 skipped)
  • focused contract/client/server unit tests passed
  • integration: same issuing session succeeds; missing/different session masks 404; native proof succeeds; old client receives AssetClientUpgradeRequiredError
  • grep proof found no added bearer/authorization/Cloudflare credential, cookie Domain/SameSite=None, or credential-bearing URL path
  • factory pre-commit gate passed: usage guard, full typecheck, vp check, and clean Codex gpt-5.6-sol high autoreview

Explicitly left for later slices

  • Slice A: /_asset-relay/:claim endpoint and server-to-server forwarding
  • Slice B: client surface-origin advertisement and environment-aware URL rewriting
  • Slice C: preview partition handling
  • Slice D / feat: audience-scoped asset and filesystem guard (dseg slice 8) #208: multi-surface app-origin/backend binding and adversarial acceptance coverage

No project audience classification, pairing-grant ceiling, or listener-split behavior is changed.

@wizzoapp
wizzoapp Bot marked this pull request as ready for review July 22, 2026 00:19

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f12adaa10d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +75 to +78
createUrlQuery({
environmentId: target.environmentId,
input: withAssetClientCapabilities(target.input),
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Plumb asset credentials before advertising relay support

Please don't advertise same-origin-relay-v1 from the shared asset atom until every consumer can present the returned surfaceCredential. This shared path is used by the web app too; for bearer/DPoP web connections there is no browser session cookie on plain <img src> requests, and apps/web/src/assets/assetUrls.ts:23-35 drops surfaceCredential and exposes only the URL. The server now rejects relay URLs without a matching cookie/header, so attachments, favicons, and image previews 404 in those web connection modes; plumb a web binding/header flow or gate the capability to clients that implement it.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Confirmed and fixed in d583bff. The server rejects relay fetches without matching session proof, the web URL helper drops surfaceCredential, and the shared atom advertised the relay unconditionally. Asset negotiation now defaults to no relay capability, mobile explicitly opts in because it presents the credential, and ungated web/bearer/DPoP consumers retain the legacy signed direct-URL path. Unbound claims are accepted only on that direct route; relay claims still require matching proof. Regression coverage verifies the web/bearer gate, capable-client advertisement, and legacy direct fetch.

Comment thread apps/server/src/http.ts Outdated
?.trim()
.toLowerCase();
const secure =
(requestOrigin !== null && new URL(requestOrigin).protocol === "https:") ||

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Guard opaque Origin values before URL parsing

If the binding POST carries an opaque or malformed Origin such as Origin: null (which WebViews or sandboxed browser contexts can send), normalizeCorsOrigin() returns that raw string and this new URL(requestOrigin) throws before the cookie is set. That turns the asset-surface binding flow into a 500 instead of simply treating the request as non-HTTPS; parse defensively here or only inspect the protocol after a successful URL parse.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in d583bff. The binding route now parses a normalized Origin defensively and returns a masked 404 for opaque or malformed values before setting any cookie. The integration test covers both Origin: null and malformed input and asserts no Set-Cookie header. I swept the PR diff for other Origin/Referer-derived new URL calls; this was the only unguarded one (the request.originalUrl fallback and redirect parser were already caught/validated).

Comment on lines +19 to +23
() => ({
uri: props.source.uri,
headers: props.source.headers,
cache: "force-cache" as const,
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid force-caching session-bound image previews

When props.source.headers is present, this image is a session-bound relay fetch that the server marks Cache-Control: no-store; keeping cache: "force-cache" tells React Native to satisfy repeat requests from an existing cached entry regardless of age, so a revoked/expired surface can still display the cached private image instead of revalidating the proof. Use the default/reload cache mode for credentialed sources and keep force-caching only for the local cached-file path.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in d583bff. Credential-bearing WorkspaceFileImagePreview sources now omit force-cache and use React Native default cache behavior; force-cache remains only for the headerless local cached-file source. I swept ProjectFavicon.tsx, ThreadFilesRouteScreen.tsx, WorkspaceFileWebPreview.tsx, ThreadFeed.tsx, and workspaceFileAssetUrl.ts as requested; none contained another force-cache occurrence.

@wizzoapp
wizzoapp Bot marked this pull request as draft July 22, 2026 02:55
@wizzoapp
wizzoapp Bot marked this pull request as ready for review July 22, 2026 02:55
@wizzoapp

wizzoapp Bot commented Jul 22, 2026

Copy link
Copy Markdown
Author

Superseded by a fresh PR from the same branch (wizzo/dseg-s8-prereq-base). #229 reached its per-PR review allowance (2 reviews/day) before landing; the round-2 findings are all fixed class-wide in HEAD 226cc90. Recreating per the starvation recipe so the corrected diff gets a fresh Codex review.

@wizzoapp wizzoapp Bot closed this Jul 22, 2026
@adamfgr
adamfgr deleted the wizzo/dseg-s8-prereq-base branch July 22, 2026 08:15
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.

0 participants