@canonry/canonry 7.9.0 → 7.9.1

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.
@@ -3543,6 +3543,7 @@ import { createRequire as createRequire3 } from "module";
3543
3543
  import crypto29 from "crypto";
3544
3544
  import fs9 from "fs";
3545
3545
  import path8 from "path";
3546
+ import { isIP } from "net";
3546
3547
  import { fileURLToPath as fileURLToPath3 } from "url";
3547
3548
  import { and as and23, eq as eq29 } from "drizzle-orm";
3548
3549
 
@@ -7872,7 +7873,7 @@ function nonBlank(value) {
7872
7873
  return trimmed ? trimmed : void 0;
7873
7874
  }
7874
7875
  function resolveBuildCommit(env = process.env) {
7875
- const embedded = true ? "c54a2244dca1abcb4657e792eaa6f89bb0d230d5" : void 0;
7876
+ const embedded = true ? "1ef30fafb9a5781918fcb963ade6280231796cc9" : void 0;
7876
7877
  return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
7877
7878
  }
7878
7879
  function resolveInstanceIdentity(env = process.env) {
@@ -21280,6 +21281,28 @@ function isLoopbackBindHost(host) {
21280
21281
  if (/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(normalized)) return true;
21281
21282
  return false;
21282
21283
  }
21284
+ function isAllowedHost(host, allowedHosts) {
21285
+ const normalized = host.trim().toLowerCase().replace(/^\[|\]$/g, "");
21286
+ return allowedHosts.has(normalized) || isIP(normalized) !== 0;
21287
+ }
21288
+ function buildAllowedHosts(bindHost, ...configuredUrls) {
21289
+ const allowed = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
21290
+ if (bindHost) {
21291
+ const normalized = bindHost.trim().toLowerCase().replace(/^\[|\]$/g, "");
21292
+ if (normalized && !isLoopbackBindHost(normalized)) {
21293
+ allowed.add(normalized);
21294
+ }
21295
+ }
21296
+ for (const configuredUrl of configuredUrls) {
21297
+ if (!configuredUrl) continue;
21298
+ try {
21299
+ const url = new URL(configuredUrl.trim());
21300
+ if (url.hostname) allowed.add(url.hostname.toLowerCase().replace(/^\[|\]$/g, ""));
21301
+ } catch {
21302
+ }
21303
+ }
21304
+ return allowed;
21305
+ }
21283
21306
  function resolveGooglePublicUrl(config, basePath) {
21284
21307
  const configured = config.publicUrl?.trim();
21285
21308
  if (configured) return configured;
@@ -21432,6 +21455,45 @@ async function createServer(opts) {
21432
21455
  genReqId: () => crypto29.randomUUID(),
21433
21456
  trustProxy
21434
21457
  });
21458
+ const allowedHosts = buildAllowedHosts(opts.host, opts.config.apiUrl, opts.config.publicUrl);
21459
+ const embedAllowedOrigins = new Set(
21460
+ (embed.allowedOrigins ?? []).map((o) => {
21461
+ try {
21462
+ return new URL(o).origin;
21463
+ } catch {
21464
+ return null;
21465
+ }
21466
+ }).filter((h) => h !== null)
21467
+ );
21468
+ app.addHook("onRequest", async (req, reply) => {
21469
+ const reject = (message) => {
21470
+ const error = forbidden(message);
21471
+ return reply.status(error.statusCode).send(error.toJSON());
21472
+ };
21473
+ const rawHostHeader = req.headers.host?.toLowerCase() ?? "";
21474
+ let requestUrl;
21475
+ try {
21476
+ requestUrl = new URL(`http://${rawHostHeader}`);
21477
+ } catch {
21478
+ return reject("Unexpected Host header");
21479
+ }
21480
+ if (!isAllowedHost(requestUrl.hostname, allowedHosts)) {
21481
+ return reject("Unexpected Host header");
21482
+ }
21483
+ const origin = req.headers.origin;
21484
+ if (origin) {
21485
+ let originUrl;
21486
+ try {
21487
+ originUrl = new URL(origin);
21488
+ } catch {
21489
+ return reject("Invalid Origin header");
21490
+ }
21491
+ const requestHost = req.headers.host?.toLowerCase() ?? "";
21492
+ if (originUrl.host !== requestHost && !embedAllowedOrigins.has(originUrl.origin)) {
21493
+ return reject("Cross-origin request refused");
21494
+ }
21495
+ }
21496
+ });
21435
21497
  const registry = new ProviderRegistry();
21436
21498
  const providers = opts.config.providers ?? {};
21437
21499
  if (opts.config.geminiApiKey && !providers.gemini) {
package/dist/cli.js CHANGED
@@ -32,7 +32,7 @@ import {
32
32
  trackCliCommandFinished,
33
33
  trackEvent,
34
34
  waitForServerRuntimeStartup
35
- } from "./chunk-NU62UT2S.js";
35
+ } from "./chunk-4BKRZGLC.js";
36
36
  import {
37
37
  autoSyncSkills,
38
38
  formatAutoSyncNotice
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  createGoogleMarketingCredentialStore,
4
4
  createGoogleMarketingRuntime,
5
5
  createServer
6
- } from "./chunk-NU62UT2S.js";
6
+ } from "./chunk-4BKRZGLC.js";
7
7
  import {
8
8
  loadConfig
9
9
  } from "./chunk-LO56QXPO.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonry/canonry",
3
- "version": "7.9.0",
3
+ "version": "7.9.1",
4
4
  "type": "module",
5
5
  "description": "Self-hosted AI visibility (AEO) platform: track how ChatGPT, Claude, Gemini, and Perplexity cite your domain, join it with Search Console, GA4, server-side traffic, and paid media, and fix what you find through agent tools (CLI, REST, MCP). Local SQLite.",
6
6
  "keywords": [
@@ -86,31 +86,31 @@
86
86
  "tsup": "^8.5.1",
87
87
  "tsx": "^4.19.0",
88
88
  "@ainyc/canonry-web": "0.0.0",
89
+ "@ainyc/canonry-api-routes": "0.0.0",
90
+ "@ainyc/canonry-config": "0.0.0",
89
91
  "@ainyc/canonry-api-client": "0.0.0",
90
92
  "@ainyc/canonry-contracts": "0.0.0",
91
- "@ainyc/canonry-config": "0.0.0",
92
93
  "@ainyc/canonry-db": "0.0.0",
93
- "@ainyc/canonry-api-routes": "0.0.0",
94
94
  "@ainyc/canonry-integration-bing": "0.0.0",
95
- "@ainyc/canonry-integration-cloudflare-queue": "0.0.0",
96
- "@ainyc/canonry-integration-cloudflare-worker": "0.0.0",
97
95
  "@ainyc/canonry-integration-cloud-run": "0.0.0",
96
+ "@ainyc/canonry-integration-cloudflare-worker": "0.0.0",
97
+ "@ainyc/canonry-integration-cloudflare-queue": "0.0.0",
98
98
  "@ainyc/canonry-integration-commoncrawl": "0.0.0",
99
+ "@ainyc/canonry-integration-google": "0.0.0",
99
100
  "@ainyc/canonry-integration-google-ads": "0.0.0",
100
- "@ainyc/canonry-integration-google-business-profile": "0.0.0",
101
101
  "@ainyc/canonry-integration-google-tag-manager": "0.0.0",
102
- "@ainyc/canonry-integration-google-places": "0.0.0",
102
+ "@ainyc/canonry-integration-google-business-profile": "0.0.0",
103
103
  "@ainyc/canonry-integration-openai-ads": "0.0.0",
104
104
  "@ainyc/canonry-integration-traffic": "0.0.0",
105
105
  "@ainyc/canonry-integration-wordpress": "0.0.0",
106
106
  "@ainyc/canonry-intelligence": "0.0.0",
107
+ "@ainyc/canonry-integration-google-places": "0.0.0",
107
108
  "@ainyc/canonry-provider-cdp": "0.0.0",
108
109
  "@ainyc/canonry-provider-gemini": "0.0.0",
109
- "@ainyc/canonry-integration-google": "0.0.0",
110
- "@ainyc/canonry-provider-local": "0.0.0",
111
- "@ainyc/canonry-provider-muse": "0.0.0",
112
110
  "@ainyc/canonry-provider-claude": "0.0.0",
111
+ "@ainyc/canonry-provider-local": "0.0.0",
113
112
  "@ainyc/canonry-provider-openai": "0.0.0",
113
+ "@ainyc/canonry-provider-muse": "0.0.0",
114
114
  "@ainyc/canonry-provider-perplexity": "0.0.0",
115
115
  "@ainyc/canonry-integration-typesafe": "0.0.0"
116
116
  },