@stravica-ai/digital-operator-pa 0.1.37 → 0.1.38

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.
@@ -393,14 +393,24 @@ const server = createServer(async (req, res) => {
393
393
  // outer pass has already validated the Host header, so this call is
394
394
  // effectively the state-changing-method sweep.
395
395
  if (pathname.startsWith('/__api/') || pathname.startsWith('/api/')) {
396
- const guard = checkRequestGuards(req, ALLOWED_HOSTS, ALLOWED_ORIGINS);
397
- if (!guard.ok) {
398
- warn('[workspace] guard refused ' + req.method + ' ' + pathname + ' (' + guard.reason + ')');
399
- res.writeHead(guard.status, { 'Content-Type': 'text/plain; charset=utf-8' });
400
- res.end('Forbidden');
401
- return;
402
- }
396
+ // Extension routes may opt into cross-origin acceptance via
397
+ // addRoute(..., { crossOrigin: true }). Look the route up before the
398
+ // state-changing sweep so an opted-in route can accept a POST from a
399
+ // foreign origin (e.g. the Dave Review bookmarklet firing on any web
400
+ // page). The outer per-request pass has already validated the Host
401
+ // header, so a crossOrigin route is still protected against DNS
402
+ // rebinding; it takes over Origin/CSRF policy through its own auth
403
+ // (a per-install token, in the case of the review submit route).
403
404
  const extRoute = extRoutes.find(r => r.method === req.method && r.pathname === pathname);
405
+ if (!extRoute || extRoute.crossOrigin !== true) {
406
+ const guard = checkRequestGuards(req, ALLOWED_HOSTS, ALLOWED_ORIGINS);
407
+ if (!guard.ok) {
408
+ warn('[workspace] guard refused ' + req.method + ' ' + pathname + ' (' + guard.reason + ')');
409
+ res.writeHead(guard.status, { 'Content-Type': 'text/plain; charset=utf-8' });
410
+ res.end('Forbidden');
411
+ return;
412
+ }
413
+ }
404
414
  if (extRoute) { extRoute.handler(req, res, url, REPO_ROOT); return; }
405
415
 
406
416
  // GET /__api/nav -- merged core + extension nav manifest, team-scoped to
@@ -577,13 +587,22 @@ async function loadExtensions() {
577
587
  try {
578
588
  const extApi = await import(extApiPath);
579
589
  if (typeof extApi.register === 'function') {
580
- extApi.register(function addRoute(method, pathname, handler) {
590
+ extApi.register(function addRoute(method, pathname, handler, options) {
591
+ // options.crossOrigin === true opts this route out of the API-branch
592
+ // Origin/Sec-Fetch-Site sweep. The outer Host allowlist still runs
593
+ // on every request, so a crossOrigin route accepts requests from a
594
+ // foreign browser Origin as long as the Host addresses this
595
+ // workspace via one of its own allowlisted names. The extension
596
+ // owns auth (a per-install token, say) and its own CORS response.
597
+ // Default is unchanged: undefined/false keeps the full guard.
598
+ const opts = (options && typeof options === 'object') ? options : {};
599
+ const crossOrigin = opts.crossOrigin === true;
581
600
  const shadows = extRoutes.some(r => r.method === method && r.pathname === pathname);
582
601
  if (!shadows) {
583
602
  // Check if it shadows a core route (informational only).
584
- log('[workspace] Extension route registered: ' + method + ' ' + pathname);
603
+ log('[workspace] Extension route registered: ' + method + ' ' + pathname + (crossOrigin ? ' [crossOrigin]' : ''));
585
604
  }
586
- extRoutes.push({ method: method, pathname: pathname, handler: handler });
605
+ extRoutes.push({ method: method, pathname: pathname, handler: handler, crossOrigin: crossOrigin });
587
606
  });
588
607
  }
589
608
  } catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stravica-ai/digital-operator-pa",
3
- "version": "0.1.37",
3
+ "version": "0.1.38",
4
4
  "description": "Digital Operator PA — single-bundled public package surface (bins + library) for the platform.",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",