@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
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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