@ainyc/canonry 4.74.0 → 4.75.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.
Files changed (26) hide show
  1. package/README.md +1 -1
  2. package/assets/agent-workspace/skills/aero/references/orchestration.md +1 -1
  3. package/assets/agent-workspace/skills/canonry/SKILL.md +1 -1
  4. package/assets/assets/{BacklinksPage-ClgP7CUd.js → BacklinksPage-CwAplOLo.js} +1 -1
  5. package/assets/assets/{ChartPrimitives-PGDrQBXP.js → ChartPrimitives-EGp5HFxn.js} +1 -1
  6. package/assets/assets/{ProjectPage-xfLeh2vB.js → ProjectPage-C-zhkBKK.js} +1 -1
  7. package/assets/assets/{RunRow-DL-lUm35.js → RunRow-YFN2PwH-.js} +1 -1
  8. package/assets/assets/{RunsPage-BCL_lU-R.js → RunsPage-DlKS8zaS.js} +1 -1
  9. package/assets/assets/{SettingsPage-D67UQYJa.js → SettingsPage-Q0OZKjMD.js} +1 -1
  10. package/assets/assets/{TrafficPage-DVRcPxCk.js → TrafficPage-BbySUnhy.js} +1 -1
  11. package/assets/assets/{TrafficSourceDetailPage-JzX1fhGQ.js → TrafficSourceDetailPage-BGzuvTYp.js} +1 -1
  12. package/assets/assets/{extract-error-message-Cia_CilL.js → extract-error-message-Czt2jFxA.js} +1 -1
  13. package/assets/assets/{index-DHg9_-PB.js → index-DYsYdWV8.js} +36 -36
  14. package/assets/assets/{server-traffic-GBmLS3L7.js → server-traffic-BzIFKqGS.js} +1 -1
  15. package/assets/assets/{trash-2-Bk7PYGBN.js → trash-2-DKCkbZUb.js} +1 -1
  16. package/assets/index.html +1 -1
  17. package/dist/{chunk-A7JX3FZB.js → chunk-JNAKRK77.js} +9 -3
  18. package/dist/{chunk-ZRZHIS22.js → chunk-JUWU2DV6.js} +20 -10
  19. package/dist/{chunk-MRC4JMIH.js → chunk-QY5WZWU4.js} +38 -30
  20. package/dist/{chunk-W6GBIRFA.js → chunk-WFMEK34V.js} +1 -1
  21. package/dist/cli.js +5 -5
  22. package/dist/index.d.ts +10 -0
  23. package/dist/index.js +4 -4
  24. package/dist/{intelligence-service-GPO2VMEC.js → intelligence-service-L2A5MFB4.js} +2 -2
  25. package/dist/mcp.js +2 -2
  26. package/package.json +11 -11
@@ -1 +1 @@
1
- import{c as d,j as i,c4 as S,b_ as l,c5 as v,ay as y,l as t,c6 as m,b$ as a,c7 as T,c8 as h,c9 as p,ca as C}from"./index-DHg9_-PB.js";import{u as c,r as g,n as s,o as u}from"./vendor-tanstack-Dq7p98wZ.js";const w=[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]],P=d("refresh-cw",w);function k(e){switch(e){case a.connected:return"positive";case a.paused:return"caution";case a.error:return"negative";case a.archived:return"neutral"}}function q(e){const r={};return e.kind&&e.kind!=="all"&&(r.kind=e.kind),e.sourceId&&(r.sourceId=e.sourceId),e.sinceMinutes!==void 0&&(r.since=new Date(Date.now()-e.sinceMinutes*6e4).toISOString()),e.limit!==void 0&&(r.limit=String(e.limit)),r}function o(e){e.invalidateQueries({predicate:r=>{const n=r.queryKey[0];return typeof n?._id=="string"&&n._id.startsWith("getApiV1ProjectsByNameTraffic")}})}function F(e){return c({...S({client:t,path:{name:e??""}}),enabled:!!e,staleTime:i})}function V(e){return c({...C({client:t,path:{name:e??""}}),enabled:!!e,staleTime:i})}function b(e,r){return c({...l({client:t,path:{name:e??"",id:r??""}}),enabled:!!(e&&r),staleTime:i})}function A(e,r){const n=g.useMemo(()=>q(r),[r.kind,r.sourceId,r.sinceMinutes,r.limit]);return c({...v({client:t,path:{name:e??""},query:n}),enabled:!!e,staleTime:i})}function E(e){const r=s();return u({mutationFn:n=>{if(!e)throw new Error("Project is required to connect a Cloud Run source");return p(e,n)},onSuccess:()=>{e&&o(r)}})}function I(e){const r=s();return u({mutationFn:n=>{if(!e)throw new Error("Project is required to connect a WordPress source");return T(e,n)},onSuccess:()=>{e&&o(r)}})}function Q(e){const r=s();return u({mutationFn:n=>{if(!e)throw new Error("Project is required to connect a Vercel source");return h(e,n)},onSuccess:()=>{e&&o(r)}})}function R(e,r){const n=s();return u({mutationFn:f=>{if(!e||!r)throw new Error("Project and sourceId are required to sync");return m(e,r,f??void 0)},onSuccess:()=>{e&&(o(n),n.invalidateQueries({queryKey:y({client:t})}))}})}export{P as R,I as a,Q as b,E as c,F as d,b as e,A as f,R as g,k as t,V as u};
1
+ import{c as d,j as i,c4 as S,b_ as l,c5 as v,ay as y,l as t,c6 as m,b$ as a,c7 as T,c8 as h,c9 as p,ca as C}from"./index-DYsYdWV8.js";import{u as c,r as g,n as s,o as u}from"./vendor-tanstack-Dq7p98wZ.js";const w=[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]],P=d("refresh-cw",w);function k(e){switch(e){case a.connected:return"positive";case a.paused:return"caution";case a.error:return"negative";case a.archived:return"neutral"}}function q(e){const r={};return e.kind&&e.kind!=="all"&&(r.kind=e.kind),e.sourceId&&(r.sourceId=e.sourceId),e.sinceMinutes!==void 0&&(r.since=new Date(Date.now()-e.sinceMinutes*6e4).toISOString()),e.limit!==void 0&&(r.limit=String(e.limit)),r}function o(e){e.invalidateQueries({predicate:r=>{const n=r.queryKey[0];return typeof n?._id=="string"&&n._id.startsWith("getApiV1ProjectsByNameTraffic")}})}function F(e){return c({...S({client:t,path:{name:e??""}}),enabled:!!e,staleTime:i})}function V(e){return c({...C({client:t,path:{name:e??""}}),enabled:!!e,staleTime:i})}function b(e,r){return c({...l({client:t,path:{name:e??"",id:r??""}}),enabled:!!(e&&r),staleTime:i})}function A(e,r){const n=g.useMemo(()=>q(r),[r.kind,r.sourceId,r.sinceMinutes,r.limit]);return c({...v({client:t,path:{name:e??""},query:n}),enabled:!!e,staleTime:i})}function E(e){const r=s();return u({mutationFn:n=>{if(!e)throw new Error("Project is required to connect a Cloud Run source");return p(e,n)},onSuccess:()=>{e&&o(r)}})}function I(e){const r=s();return u({mutationFn:n=>{if(!e)throw new Error("Project is required to connect a WordPress source");return T(e,n)},onSuccess:()=>{e&&o(r)}})}function Q(e){const r=s();return u({mutationFn:n=>{if(!e)throw new Error("Project is required to connect a Vercel source");return h(e,n)},onSuccess:()=>{e&&o(r)}})}function R(e,r){const n=s();return u({mutationFn:f=>{if(!e||!r)throw new Error("Project and sourceId are required to sync");return m(e,r,f??void 0)},onSuccess:()=>{e&&(o(n),n.invalidateQueries({queryKey:y({client:t})}))}})}export{P as R,I as a,Q as b,E as c,F as d,b as e,A as f,R as g,k as t,V as u};
@@ -1 +1 @@
1
- import{c}from"./index-DHg9_-PB.js";const a=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],h=c("circle-check",a);const e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],n=c("circle-question-mark",e);const o=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],s=c("download",o);const t=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],r=c("trash-2",t);export{h as C,s as D,r as T,n as a};
1
+ import{c}from"./index-DYsYdWV8.js";const a=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],h=c("circle-check",a);const e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],n=c("circle-question-mark",e);const o=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],s=c("download",o);const t=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],r=c("trash-2",t);export{h as C,s as D,r as T,n as a};
package/assets/index.html CHANGED
@@ -12,7 +12,7 @@
12
12
  <link rel="icon" type="image/png" sizes="32x32" href="./favicon-32.png" />
13
13
  <link rel="apple-touch-icon" href="./apple-touch-icon.png" />
14
14
  <title>Canonry</title>
15
- <script type="module" crossorigin src="./assets/index-DHg9_-PB.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-DYsYdWV8.js"></script>
16
16
  <link rel="modulepreload" crossorigin href="./assets/vendor-tanstack-Dq7p98wZ.js">
17
17
  <link rel="modulepreload" crossorigin href="./assets/vendor-radix-B57xfQbP.js">
18
18
  <link rel="modulepreload" crossorigin href="./assets/vendor-recharts-ClRVR6aX.js">
@@ -35,8 +35,8 @@ function notFound(entity, id) {
35
35
  function validationError(message, details) {
36
36
  return new AppError("VALIDATION_ERROR", message, 400, details);
37
37
  }
38
- function authRequired() {
39
- return new AppError("AUTH_REQUIRED", "Authentication required", 401);
38
+ function authRequired(message = "Authentication required") {
39
+ return new AppError("AUTH_REQUIRED", message, 401);
40
40
  }
41
41
  function authInvalid() {
42
42
  return new AppError("AUTH_INVALID", "Invalid API key", 401);
@@ -4283,6 +4283,11 @@ var AI_PROVIDER_INFRA_DOMAINS = [
4283
4283
  VERTEX_AI_SEARCH_PROXY_DOMAIN
4284
4284
  ];
4285
4285
 
4286
+ // ../contracts/src/sql-like.ts
4287
+ function escapeLikePattern(value) {
4288
+ return value.replace(/[\\%_]/g, (match) => `\\${match}`);
4289
+ }
4290
+
4286
4291
  export {
4287
4292
  __export,
4288
4293
  apiKeyDtoSchema,
@@ -4546,5 +4551,6 @@ export {
4546
4551
  AI_ENGINE_DOMAINS,
4547
4552
  AI_ENGINE_SELF_DOMAINS,
4548
4553
  VERTEX_AI_SEARCH_PROXY_DOMAIN,
4549
- AI_PROVIDER_INFRA_DOMAINS
4554
+ AI_PROVIDER_INFRA_DOMAINS,
4555
+ escapeLikePattern
4550
4556
  };
@@ -101,6 +101,7 @@ import {
101
101
  effectiveBrandNames,
102
102
  effectiveDomains,
103
103
  emptyCitationVisibility,
104
+ escapeLikePattern,
104
105
  extractAnswerMentions,
105
106
  findDuplicateLocationLabels,
106
107
  forbidden,
@@ -229,7 +230,7 @@ import {
229
230
  wordpressSchemaDeployResultDtoSchema,
230
231
  wordpressSchemaStatusResultDtoSchema,
231
232
  wordpressStatusDtoSchema
232
- } from "./chunk-A7JX3FZB.js";
233
+ } from "./chunk-JNAKRK77.js";
233
234
 
234
235
  // src/intelligence-service.ts
235
236
  import { eq as eq33, desc as desc17, asc as asc4, and as and24, ne as ne5, or as or5, inArray as inArray12, gte as gte6, lte as lte3 } from "drizzle-orm";
@@ -5029,7 +5030,7 @@ var SKIP_PATHS = ["/health"];
5029
5030
  function shouldSkipAuth(url) {
5030
5031
  if (SKIP_PATHS.includes(url)) return true;
5031
5032
  if (url.endsWith("/openapi.json")) return true;
5032
- if (url.includes("/google/callback")) return true;
5033
+ if (url.endsWith("/google/callback")) return true;
5033
5034
  if (url.endsWith("/session") || url.endsWith("/session/setup")) return true;
5034
5035
  return false;
5035
5036
  }
@@ -7473,10 +7474,10 @@ function computeBuckets(snapshots, projectRuns, bucketDays, queryCreatedAt) {
7473
7474
  const latest = new Date(projectRuns[projectRuns.length - 1].createdAt);
7474
7475
  const buckets = [];
7475
7476
  let start = new Date(earliest);
7476
- start.setHours(0, 0, 0, 0);
7477
+ start.setUTCHours(0, 0, 0, 0);
7477
7478
  while (start <= latest) {
7478
7479
  const end = new Date(start);
7479
- end.setDate(end.getDate() + bucketDays);
7480
+ end.setUTCDate(end.getUTCDate() + bucketDays);
7480
7481
  const startISO = start.toISOString();
7481
7482
  const endISO = end.toISOString();
7482
7483
  const inBucket = snapshots.filter((s) => s.createdAt >= startISO && s.createdAt < endISO);
@@ -12575,9 +12576,6 @@ function clampSearchLimit(raw) {
12575
12576
  if (parsed > SEARCH_HIT_HARD_LIMIT) return SEARCH_HIT_HARD_LIMIT;
12576
12577
  return parsed;
12577
12578
  }
12578
- function escapeLikePattern(value) {
12579
- return value.replace(/[\\%_]/g, (match) => `\\${match}`);
12580
- }
12581
12579
  function summarizeRun(run) {
12582
12580
  return {
12583
12581
  id: run.id,
@@ -19400,8 +19398,8 @@ async function googleRoutes(app, opts) {
19400
19398
  if (startDate) conditions.push(sql8`${gscSearchData.date} >= ${startDate}`);
19401
19399
  else if (cutoffDate) conditions.push(sql8`${gscSearchData.date} >= ${cutoffDate}`);
19402
19400
  if (endDate) conditions.push(sql8`${gscSearchData.date} <= ${endDate}`);
19403
- if (query) conditions.push(sql8`${gscSearchData.query} LIKE ${"%" + query + "%"}`);
19404
- if (page) conditions.push(sql8`${gscSearchData.page} LIKE ${"%" + page + "%"}`);
19401
+ if (query) conditions.push(sql8`${gscSearchData.query} LIKE ${"%" + escapeLikePattern(query) + "%"} ESCAPE '\\'`);
19402
+ if (page) conditions.push(sql8`${gscSearchData.page} LIKE ${"%" + escapeLikePattern(page) + "%"} ESCAPE '\\'`);
19405
19403
  const limitVal = Math.max(parseInt(limit ?? "500", 10) || 0, 1);
19406
19404
  const offsetVal = Math.max(parseInt(offset ?? "0", 10) || 0, 0);
19407
19405
  const rows = app.db.select().from(gscSearchData).where(and13(...conditions)).orderBy(desc10(gscSearchData.date)).limit(limitVal).offset(offsetVal).all();
@@ -23015,10 +23013,17 @@ async function withWordpressErrorHandling(handler) {
23015
23013
  }
23016
23014
  }
23017
23015
  async function wordpressRoutes(app, opts) {
23016
+ const allowLoopback = opts.allowLoopbackWebhooks === true;
23018
23017
  function requireStore() {
23019
23018
  if (opts.wordpressConnectionStore) return opts.wordpressConnectionStore;
23020
23019
  throw validationError("WordPress connection storage is not configured for this deployment");
23021
23020
  }
23021
+ async function assertWordpressUrlAllowed(rawUrl, field) {
23022
+ const check = await resolveWebhookTarget(rawUrl, { allowLoopback });
23023
+ if (!check.ok) {
23024
+ throw validationError(`${field} ${check.message.replace(/^"url" /, "")}`);
23025
+ }
23026
+ }
23022
23027
  function requireConnection(store, projectName) {
23023
23028
  const connection = store.getConnection(projectName);
23024
23029
  if (!connection) {
@@ -23038,6 +23043,8 @@ async function wordpressRoutes(app, opts) {
23038
23043
  if (defaultEnv === "staging" && !stagingUrl) {
23039
23044
  throw validationError('defaultEnv "staging" requires stagingUrl');
23040
23045
  }
23046
+ await assertWordpressUrlAllowed(url, "url");
23047
+ if (stagingUrl) await assertWordpressUrlAllowed(stagingUrl, "stagingUrl");
23041
23048
  const now = (/* @__PURE__ */ new Date()).toISOString();
23042
23049
  const existing = store.getConnection(project.name);
23043
23050
  const nextConnection = {
@@ -23366,6 +23373,8 @@ async function wordpressRoutes(app, opts) {
23366
23373
  if (defaultEnv === "staging" && !stagingUrl) {
23367
23374
  throw validationError('defaultEnv "staging" requires stagingUrl');
23368
23375
  }
23376
+ await assertWordpressUrlAllowed(url, "url");
23377
+ if (stagingUrl) await assertWordpressUrlAllowed(stagingUrl, "stagingUrl");
23369
23378
  const steps = [];
23370
23379
  let connection = null;
23371
23380
  let pageUrls = [];
@@ -32107,7 +32116,8 @@ async function apiRoutes(app, opts) {
32107
32116
  });
32108
32117
  await api.register(wordpressRoutes, {
32109
32118
  wordpressConnectionStore: opts.wordpressConnectionStore,
32110
- routePrefix: opts.routePrefix ?? "/api/v1"
32119
+ routePrefix: opts.routePrefix ?? "/api/v1",
32120
+ allowLoopbackWebhooks: opts.allowLoopbackWebhooks
32111
32121
  });
32112
32122
  await api.register(cdpRoutes, {
32113
32123
  getCdpStatus: opts.getCdpStatus,
@@ -9,7 +9,7 @@ import {
9
9
  loadConfig,
10
10
  loadConfigRaw,
11
11
  saveConfigPatch
12
- } from "./chunk-W6GBIRFA.js";
12
+ } from "./chunk-WFMEK34V.js";
13
13
  import {
14
14
  CC_CACHE_DIR,
15
15
  DUCKDB_SPEC,
@@ -97,7 +97,7 @@ import {
97
97
  siteAuditPages,
98
98
  siteAuditSnapshots,
99
99
  usageCounters
100
- } from "./chunk-ZRZHIS22.js";
100
+ } from "./chunk-JUWU2DV6.js";
101
101
  import {
102
102
  AGENT_MEMORY_VALUE_MAX_BYTES,
103
103
  AGENT_PROVIDER_IDS,
@@ -127,6 +127,7 @@ import {
127
127
  agentMemoryDeleteRequestSchema,
128
128
  agentMemoryUpsertRequestSchema,
129
129
  authInvalid,
130
+ authRequired,
130
131
  buildRunErrorFromMessages,
131
132
  classifySkillFile,
132
133
  coerceSkillManifest,
@@ -148,7 +149,7 @@ import {
148
149
  validationError,
149
150
  winnabilityClassLabel,
150
151
  withRetry
151
- } from "./chunk-A7JX3FZB.js";
152
+ } from "./chunk-JNAKRK77.js";
152
153
 
153
154
  // src/telemetry.ts
154
155
  import crypto from "crypto";
@@ -4068,31 +4069,10 @@ import { eq as eq4, and as and4 } from "drizzle-orm";
4068
4069
  var log4 = createLogger("SitemapParser");
4069
4070
  var LOC_REGEX = /<loc>([^<]+)<\/loc>/gi;
4070
4071
  var SITEMAP_TAG_REGEX = /<sitemap>[\s\S]*?<\/sitemap>/gi;
4071
- var PRIVATE_IP_PATTERNS = [
4072
- /^169\.254\./,
4073
- // link-local (AWS metadata endpoint etc.)
4074
- /^10\./,
4075
- // private class A
4076
- /^172\.(1[6-9]|2\d|3[01])\./,
4077
- // private class B
4078
- /^192\.168\./
4079
- // private class C
4080
- ];
4081
- function validateSitemapUrl(url) {
4082
- let parsed;
4083
- try {
4084
- parsed = new URL(url);
4085
- } catch {
4086
- throw new Error(`Invalid sitemap URL: ${url}`);
4087
- }
4088
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
4089
- throw new Error(`Sitemap URL must use http or https protocol: ${url}`);
4090
- }
4091
- const host = parsed.hostname.toLowerCase();
4092
- for (const pattern of PRIVATE_IP_PATTERNS) {
4093
- if (pattern.test(host)) {
4094
- throw new Error(`Sitemap URL points to a private or reserved IP range: ${url}`);
4095
- }
4072
+ async function validateSitemapUrl(url) {
4073
+ const check = await resolveWebhookTarget(url, { allowLoopback: true });
4074
+ if (!check.ok) {
4075
+ throw new Error(`Sitemap URL rejected: ${check.message.replace(/^"url" /, "")} (${url})`);
4096
4076
  }
4097
4077
  }
4098
4078
  async function readSitemapBody(res) {
@@ -4122,9 +4102,9 @@ async function parseSitemapRecursive(url, urls, visited, depth, isChild) {
4122
4102
  if (depth > 3) return;
4123
4103
  if (visited.has(url)) return;
4124
4104
  visited.add(url);
4125
- validateSitemapUrl(url);
4126
4105
  let res;
4127
4106
  try {
4107
+ await validateSitemapUrl(url);
4128
4108
  res = await fetch(url);
4129
4109
  } catch (err) {
4130
4110
  if (!isChild) throw err;
@@ -5176,6 +5156,12 @@ function toHomepageUrl(canonicalDomain) {
5176
5156
  const trimmed = canonicalDomain.trim().replace(/\/+$/, "");
5177
5157
  return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
5178
5158
  }
5159
+ async function assertSiteAuditUrlAllowed(rawUrl, field) {
5160
+ const check = await resolveWebhookTarget(rawUrl);
5161
+ if (!check.ok) {
5162
+ throw new Error(`${field} ${check.message.replace(/^"url" /, "")}`);
5163
+ }
5164
+ }
5179
5165
  function clampSiteAuditLimit(limit) {
5180
5166
  if (limit == null || !Number.isFinite(limit)) return SITE_AUDIT_DEFAULT_PAGE_LIMIT;
5181
5167
  return Math.max(1, Math.min(SITE_AUDIT_MAX_PAGE_LIMIT, Math.floor(limit)));
@@ -5233,6 +5219,8 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
5233
5219
  const homepageUrl = toHomepageUrl(project.canonicalDomain);
5234
5220
  const limit = clampSiteAuditLimit(opts.limit);
5235
5221
  log11.info("start", { runId, projectId, homepageUrl, sitemapUrl: opts.sitemapUrl ?? null, limit });
5222
+ await assertSiteAuditUrlAllowed(homepageUrl, "canonicalDomain");
5223
+ if (opts.sitemapUrl) await assertSiteAuditUrlAllowed(opts.sitemapUrl, "sitemapUrl");
5236
5224
  const report = await runSitemapAudit(homepageUrl, { sitemapUrl: opts.sitemapUrl, limit });
5237
5225
  const successCount = report.pages.filter((page) => page.status === "success").length;
5238
5226
  const pagesErrored = report.pages.filter((page) => page.status === "error").length;
@@ -5773,7 +5761,7 @@ function readStoredGroundingSources(rawResponse) {
5773
5761
  return result;
5774
5762
  }
5775
5763
  async function backfillInsightsCommand(project, opts) {
5776
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-GPO2VMEC.js");
5764
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-L2A5MFB4.js");
5777
5765
  const config = loadConfig();
5778
5766
  const db = createClient(config.database);
5779
5767
  migrate(db);
@@ -9592,6 +9580,13 @@ function applyLegacyCredentials(rows, config) {
9592
9580
  log18.info("credentials.migrated", { type: "ga4", count: migratedGa4 });
9593
9581
  }
9594
9582
  }
9583
+ function isLoopbackBindHost(host) {
9584
+ if (host == null || host === "") return true;
9585
+ const normalized = host.trim().toLowerCase().replace(/^\[|\]$/g, "");
9586
+ if (normalized === "localhost" || normalized === "::1") return true;
9587
+ if (/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(normalized)) return true;
9588
+ return false;
9589
+ }
9595
9590
  async function createServer(opts) {
9596
9591
  const logger = opts.logger === false ? false : process.stdout.isTTY ? {
9597
9592
  transport: {
@@ -9994,6 +9989,15 @@ async function createServer(opts) {
9994
9989
  }));
9995
9990
  return true;
9996
9991
  };
9992
+ const boundToLoopback = isLoopbackBindHost(opts.host);
9993
+ const requestHasValidApiKey = (request) => {
9994
+ const header = request.headers.authorization;
9995
+ if (!header) return false;
9996
+ const parts = header.split(" ");
9997
+ if (parts.length !== 2 || parts[0] !== "Bearer") return false;
9998
+ const key = opts.db.select().from(apiKeys).where(eq18(apiKeys.keyHash, hashApiKey(parts[1]))).get();
9999
+ return Boolean(key && !key.revokedAt);
10000
+ };
9997
10001
  app.get(apiPrefix + "/session", async (request, reply) => {
9998
10002
  const sessionId = parseCookies(request.headers.cookie)[SESSION_COOKIE_NAME];
9999
10003
  return reply.send({
@@ -10002,6 +10006,10 @@ async function createServer(opts) {
10002
10006
  });
10003
10007
  });
10004
10008
  app.post(apiPrefix + "/session/setup", async (request, reply) => {
10009
+ if (!boundToLoopback && !requestHasValidApiKey(request)) {
10010
+ const err = authRequired("This server is network-reachable; setting the dashboard password requires a valid API key.");
10011
+ return reply.status(err.statusCode).send(err.toJSON());
10012
+ }
10005
10013
  if (opts.config.dashboardPasswordHash) {
10006
10014
  const err = validationError("Dashboard password is already configured");
10007
10015
  return reply.status(err.statusCode).send(err.toJSON());
@@ -22,7 +22,7 @@ import {
22
22
  trafficConnectVercelRequestSchema,
23
23
  trafficConnectWordpressRequestSchema,
24
24
  trafficEventKindSchema
25
- } from "./chunk-A7JX3FZB.js";
25
+ } from "./chunk-JNAKRK77.js";
26
26
 
27
27
  // src/config.ts
28
28
  import fs from "fs";
package/dist/cli.js CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  setTelemetrySource,
28
28
  showFirstRunNotice,
29
29
  trackEvent
30
- } from "./chunk-MRC4JMIH.js";
30
+ } from "./chunk-QY5WZWU4.js";
31
31
  import {
32
32
  CliError,
33
33
  EXIT_SYSTEM_ERROR,
@@ -44,7 +44,7 @@ import {
44
44
  saveConfig,
45
45
  saveConfigPatch,
46
46
  usageError
47
- } from "./chunk-W6GBIRFA.js";
47
+ } from "./chunk-WFMEK34V.js";
48
48
  import {
49
49
  apiKeys,
50
50
  createClient,
@@ -52,7 +52,7 @@ import {
52
52
  projects,
53
53
  queries,
54
54
  renderReportHtml
55
- } from "./chunk-ZRZHIS22.js";
55
+ } from "./chunk-JUWU2DV6.js";
56
56
  import {
57
57
  CcReleaseSyncStatuses,
58
58
  CheckScopes,
@@ -72,7 +72,7 @@ import {
72
72
  providerQuotaPolicySchema,
73
73
  resolveProviderInput,
74
74
  winnabilityClassSchema
75
- } from "./chunk-A7JX3FZB.js";
75
+ } from "./chunk-JNAKRK77.js";
76
76
 
77
77
  // src/cli.ts
78
78
  import { pathToFileURL } from "url";
@@ -10376,7 +10376,7 @@ async function serveCommand(format = "text") {
10376
10376
  process.stderr.write(`warning: ai-referral-paths backfill skipped: ${msg}
10377
10377
  `);
10378
10378
  }
10379
- const app = await createServer({ config, db });
10379
+ const app = await createServer({ config, db, host });
10380
10380
  let shuttingDown = false;
10381
10381
  const shutdown = (signal) => {
10382
10382
  if (shuttingDown) return;
package/dist/index.d.ts CHANGED
@@ -196,6 +196,16 @@ declare function createServer(opts: {
196
196
  db: DatabaseClient;
197
197
  open?: boolean;
198
198
  logger?: boolean;
199
+ /**
200
+ * The network interface the server will bind to (from `canonry serve`).
201
+ * Used to gate the unauthenticated first-run dashboard password setup: on a
202
+ * loopback bind only local processes can reach `/session/setup`, so claiming
203
+ * the initial password without the API key is safe. On a non-loopback bind
204
+ * (`0.0.0.0`, a LAN IP) the setup endpoint additionally requires a valid
205
+ * bearer key so a remote first-visitor cannot mint a full-access session.
206
+ * Defaults to loopback when unset (programmatic/test callers).
207
+ */
208
+ host?: string;
199
209
  }): Promise<FastifyInstance>;
200
210
 
201
211
  export { type CanonryConfig, createServer, loadConfig };
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-MRC4JMIH.js";
3
+ } from "./chunk-QY5WZWU4.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-W6GBIRFA.js";
7
- import "./chunk-ZRZHIS22.js";
8
- import "./chunk-A7JX3FZB.js";
6
+ } from "./chunk-WFMEK34V.js";
7
+ import "./chunk-JUWU2DV6.js";
8
+ import "./chunk-JNAKRK77.js";
9
9
  export {
10
10
  createServer,
11
11
  loadConfig
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-ZRZHIS22.js";
4
- import "./chunk-A7JX3FZB.js";
3
+ } from "./chunk-JUWU2DV6.js";
4
+ import "./chunk-JNAKRK77.js";
5
5
  export {
6
6
  IntelligenceService
7
7
  };
package/dist/mcp.js CHANGED
@@ -3,8 +3,8 @@ import {
3
3
  PACKAGE_VERSION,
4
4
  canonryMcpTools,
5
5
  createApiClient
6
- } from "./chunk-W6GBIRFA.js";
7
- import "./chunk-A7JX3FZB.js";
6
+ } from "./chunk-WFMEK34V.js";
7
+ import "./chunk-JNAKRK77.js";
8
8
 
9
9
  // src/mcp/cli.ts
10
10
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "4.74.0",
3
+ "version": "4.75.0",
4
4
  "type": "module",
5
5
  "description": "Agent-first open-source AEO operating platform - track how answer engines cite your domain",
6
6
  "license": "FSL-1.1-ALv2",
@@ -63,25 +63,25 @@
63
63
  "tsup": "^8.5.1",
64
64
  "tsx": "^4.19.0",
65
65
  "@ainyc/canonry-api-routes": "0.0.0",
66
- "@ainyc/canonry-contracts": "0.0.0",
66
+ "@ainyc/canonry-config": "0.0.0",
67
67
  "@ainyc/canonry-db": "0.0.0",
68
- "@ainyc/canonry-integration-cloud-run": "0.0.0",
68
+ "@ainyc/canonry-contracts": "0.0.0",
69
69
  "@ainyc/canonry-integration-bing": "0.0.0",
70
- "@ainyc/canonry-config": "0.0.0",
71
- "@ainyc/canonry-integration-google": "0.0.0",
70
+ "@ainyc/canonry-api-client": "0.0.0",
71
+ "@ainyc/canonry-integration-cloud-run": "0.0.0",
72
72
  "@ainyc/canonry-integration-commoncrawl": "0.0.0",
73
73
  "@ainyc/canonry-integration-google-business-profile": "0.0.0",
74
- "@ainyc/canonry-integration-google-places": "0.0.0",
74
+ "@ainyc/canonry-integration-google": "0.0.0",
75
75
  "@ainyc/canonry-integration-traffic": "0.0.0",
76
76
  "@ainyc/canonry-integration-wordpress": "0.0.0",
77
- "@ainyc/canonry-provider-cdp": "0.0.0",
78
77
  "@ainyc/canonry-intelligence": "0.0.0",
79
- "@ainyc/canonry-api-client": "0.0.0",
78
+ "@ainyc/canonry-provider-cdp": "0.0.0",
79
+ "@ainyc/canonry-integration-google-places": "0.0.0",
80
80
  "@ainyc/canonry-provider-claude": "0.0.0",
81
- "@ainyc/canonry-provider-gemini": "0.0.0",
82
- "@ainyc/canonry-provider-local": "0.0.0",
83
81
  "@ainyc/canonry-provider-openai": "0.0.0",
84
- "@ainyc/canonry-provider-perplexity": "0.0.0"
82
+ "@ainyc/canonry-provider-gemini": "0.0.0",
83
+ "@ainyc/canonry-provider-perplexity": "0.0.0",
84
+ "@ainyc/canonry-provider-local": "0.0.0"
85
85
  },
86
86
  "scripts": {
87
87
  "build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",