@oxy.so/core 7.22.0 → 7.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -31,7 +31,7 @@ The package exposes two public entry points:
31
31
  - `@oxy.so/core/session` — `SessionClient`, the account dialog controller, auth-state stores, cold boot
32
32
  - `@oxy.so/core/crypto` — `KeyManager`, `SignatureService`, `RecoveryPhraseService` and the primitives under them
33
33
  - `@oxy.so/core/civic`, `@oxy.so/core/inference`, `@oxy.so/core/logger`
34
- - `@oxy.so/core/server` — `OxyServer` (workload service tokens, `middleware.*`, `withUser` / `asUser`, `appPermissionsFor`, `accountAccessFor`, `applicationAccessFor`) and the Express helpers (`createOxyRateLimit`, `createOxyAuthMiddleware`, `getOxyUserId`, `getRequiredOxyUserId`, `createOxyCors`, `createOxySocketCors`, `createOxySecurityHeaders`, `buildOxyCspDirectives`, `safeFetch`, `verifySecret`, and request types)
34
+ - `@oxy.so/core/server` — `OxyServer` (workload service tokens, `middleware.*`, `withUser` / `asUser`, `isFirstPartyServiceUrl`, `appPermissionsFor`, `accountAccessFor`, `applicationAccessFor`) and the Express helpers (`createOxyRateLimit`, `createOxyAuthMiddleware`, `getOxyUserId`, `getRequiredOxyUserId`, `createOxyCors`, `createOxySocketCors`, `createOxySecurityHeaders`, `buildOxyCspDirectives`, `safeFetch`, `verifySecret`, and request types)
35
35
 
36
36
  Heavy pieces have their own entries so an app ships only what it uses; React Native and browser bundles never import Express. The full API map is [docs/api.mdx](./docs/api.mdx).
37
37
 
@@ -49,6 +49,7 @@ const OxyServices_1 = require("../OxyServices");
49
49
  const HttpService_1 = require("../HttpService");
50
50
  const authenticatedFetch_1 = require("../authenticatedFetch");
51
51
  const requestSignal_1 = require("../utils/requestSignal");
52
+ const officialOrigins_1 = require("../utils/officialOrigins");
52
53
  const errorUtils_1 = require("../utils/errorUtils");
53
54
  const logger_1 = require("../logger");
54
55
  const workloadIdentity_1 = require("./workloadIdentity");
@@ -193,6 +194,34 @@ class OxyServer extends OxyServices_1.OxyServices {
193
194
  applicationAccessFor: (req, applicationId, options) => this.applicationAccessFor(req, applicationId, options),
194
195
  });
195
196
  }
197
+ /**
198
+ * Whether `url` names Oxy's own API — the first-party service this server is
199
+ * configured for (`baseURL`), when that origin is an official Oxy origin.
200
+ *
201
+ * For a URL a caller chose (a customer webhook endpoint, say) that may name
202
+ * Oxy itself: `true` sends it through this server
203
+ * (`createServiceClient({ baseURL: oxy.baseURL, audience: 'oxy-api' }).fetch`),
204
+ * `false` through `safeFetch`. Inside the VPC `api.oxy.so` resolves to a
205
+ * private address, which `safeFetch` refuses by design.
206
+ *
207
+ * The comparison is by exact origin (scheme, host and port): a look-alike
208
+ * (`api.oxy.so.evil.com`), another official host, a credential-carrying URL
209
+ * or anything unparseable is `false`, and so goes on to `safeFetch`'s
210
+ * checks. Never decide first-party by hostname suffix or branding.
211
+ */
212
+ isFirstPartyServiceUrl(url) {
213
+ let target;
214
+ try {
215
+ target = new URL(String(url));
216
+ }
217
+ catch {
218
+ return false;
219
+ }
220
+ if (!['http:', 'https:'].includes(target.protocol) || target.username || target.password)
221
+ return false;
222
+ const own = new URL(this.baseURL).origin;
223
+ return target.origin === own && (0, officialOrigins_1.isOfficialWebOrigin)(own);
224
+ }
196
225
  /** A product SDK transport authenticated only by this process's workload. */
197
226
  createServiceClient(options) {
198
227
  const origin = new URL(options.baseURL);
@@ -46,6 +46,7 @@ import { OxyServices } from '../OxyServices.js';
46
46
  import { HttpService } from '../HttpService.js';
47
47
  import { createAuthenticatedFetch } from '../authenticatedFetch.js';
48
48
  import { awaitRequestSignal } from '../utils/requestSignal.js';
49
+ import { isOfficialWebOrigin } from '../utils/officialOrigins.js';
49
50
  import { createCancelledError, extractErrorStatus } from '../utils/errorUtils.js';
50
51
  import { logger } from '../logger/index.js';
51
52
  import { canAttestWorkloadIdentity, requestWorkloadServiceToken, } from './workloadIdentity.js';
@@ -189,6 +190,34 @@ export class OxyServer extends OxyServices {
189
190
  applicationAccessFor: (req, applicationId, options) => this.applicationAccessFor(req, applicationId, options),
190
191
  });
191
192
  }
193
+ /**
194
+ * Whether `url` names Oxy's own API — the first-party service this server is
195
+ * configured for (`baseURL`), when that origin is an official Oxy origin.
196
+ *
197
+ * For a URL a caller chose (a customer webhook endpoint, say) that may name
198
+ * Oxy itself: `true` sends it through this server
199
+ * (`createServiceClient({ baseURL: oxy.baseURL, audience: 'oxy-api' }).fetch`),
200
+ * `false` through `safeFetch`. Inside the VPC `api.oxy.so` resolves to a
201
+ * private address, which `safeFetch` refuses by design.
202
+ *
203
+ * The comparison is by exact origin (scheme, host and port): a look-alike
204
+ * (`api.oxy.so.evil.com`), another official host, a credential-carrying URL
205
+ * or anything unparseable is `false`, and so goes on to `safeFetch`'s
206
+ * checks. Never decide first-party by hostname suffix or branding.
207
+ */
208
+ isFirstPartyServiceUrl(url) {
209
+ let target;
210
+ try {
211
+ target = new URL(String(url));
212
+ }
213
+ catch {
214
+ return false;
215
+ }
216
+ if (!['http:', 'https:'].includes(target.protocol) || target.username || target.password)
217
+ return false;
218
+ const own = new URL(this.baseURL).origin;
219
+ return target.origin === own && isOfficialWebOrigin(own);
220
+ }
192
221
  /** A product SDK transport authenticated only by this process's workload. */
193
222
  createServiceClient(options) {
194
223
  const origin = new URL(options.baseURL);
@@ -196,6 +196,22 @@ export declare class OxyServer extends OxyServices {
196
196
  */
197
197
  readonly middleware: OxyMiddleware;
198
198
  constructor(config: OxyServerConfig);
199
+ /**
200
+ * Whether `url` names Oxy's own API — the first-party service this server is
201
+ * configured for (`baseURL`), when that origin is an official Oxy origin.
202
+ *
203
+ * For a URL a caller chose (a customer webhook endpoint, say) that may name
204
+ * Oxy itself: `true` sends it through this server
205
+ * (`createServiceClient({ baseURL: oxy.baseURL, audience: 'oxy-api' }).fetch`),
206
+ * `false` through `safeFetch`. Inside the VPC `api.oxy.so` resolves to a
207
+ * private address, which `safeFetch` refuses by design.
208
+ *
209
+ * The comparison is by exact origin (scheme, host and port): a look-alike
210
+ * (`api.oxy.so.evil.com`), another official host, a credential-carrying URL
211
+ * or anything unparseable is `false`, and so goes on to `safeFetch`'s
212
+ * checks. Never decide first-party by hostname suffix or branding.
213
+ */
214
+ isFirstPartyServiceUrl(url: string | URL): boolean;
199
215
  /** A product SDK transport authenticated only by this process's workload. */
200
216
  createServiceClient(options: ServiceClientOptions): ServiceHttpClient;
201
217
  /** Live service authority; human sessions and keys have a separate resource contract. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxy.so/core",
3
- "version": "7.22.0",
3
+ "version": "7.23.0",
4
4
  "description": "OxyHQ SDK Foundation — API client, authentication, cryptographic identity, and shared utilities",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -66,6 +66,7 @@ import { OxyServices, type OxyConfig } from '../OxyServices';
66
66
  import { HttpService } from '../HttpService';
67
67
  import { createAuthenticatedFetch } from '../authenticatedFetch';
68
68
  import { awaitRequestSignal } from '../utils/requestSignal';
69
+ import { isOfficialWebOrigin } from '../utils/officialOrigins';
69
70
  import { createCancelledError, extractErrorStatus } from '../utils/errorUtils';
70
71
  import type { HttpMethod, ServiceLane } from '../client/context';
71
72
  import type { RequestOptions, ResponsePayload } from '../HttpService';
@@ -410,6 +411,34 @@ export class OxyServer extends OxyServices {
410
411
  });
411
412
  }
412
413
 
414
+ /**
415
+ * Whether `url` names Oxy's own API — the first-party service this server is
416
+ * configured for (`baseURL`), when that origin is an official Oxy origin.
417
+ *
418
+ * For a URL a caller chose (a customer webhook endpoint, say) that may name
419
+ * Oxy itself: `true` sends it through this server
420
+ * (`createServiceClient({ baseURL: oxy.baseURL, audience: 'oxy-api' }).fetch`),
421
+ * `false` through `safeFetch`. Inside the VPC `api.oxy.so` resolves to a
422
+ * private address, which `safeFetch` refuses by design.
423
+ *
424
+ * The comparison is by exact origin (scheme, host and port): a look-alike
425
+ * (`api.oxy.so.evil.com`), another official host, a credential-carrying URL
426
+ * or anything unparseable is `false`, and so goes on to `safeFetch`'s
427
+ * checks. Never decide first-party by hostname suffix or branding.
428
+ */
429
+ isFirstPartyServiceUrl(url: string | URL): boolean {
430
+ let target: URL;
431
+ try {
432
+ target = new URL(String(url));
433
+ } catch {
434
+ return false;
435
+ }
436
+ if (!['http:', 'https:'].includes(target.protocol) || target.username || target.password)
437
+ return false;
438
+ const own = new URL(this.baseURL).origin;
439
+ return target.origin === own && isOfficialWebOrigin(own);
440
+ }
441
+
413
442
  /** A product SDK transport authenticated only by this process's workload. */
414
443
  createServiceClient(options: ServiceClientOptions): ServiceHttpClient {
415
444
  const origin = new URL(options.baseURL);