wiki-plugin-similarity 0.12.0 → 0.13.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/client/similarity.js +1 -1
- package/package.json +1 -1
- package/pages/about-similarity-plugin +23 -23
- package/pages/semantic-search +83 -8
- package/server/farm-lib.js +32 -5
- package/server/server.js +43 -6
- package/server/trust.js +119 -0
package/client/similarity.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* wiki-plugin-similarity - 0.
|
|
1
|
+
/* wiki-plugin-similarity - 0.13.0 - Tue, 18 Aug 2026 13:37:47 GMT */
|
|
2
2
|
(()=>{var q={high:.78,medium:.68,low:.58},N=q.medium,J=10,_=t=>{let e=[],s=[],i=[],p=null,E=null,f="search",y=!1,L=!1,b=!1,k=null,C=null,S=(a,r)=>a===r||a.startsWith(r)&&/^[\s:]/.test(a.slice(r.length)),w=(a,r)=>a.slice(r.length).replace(/^\s*:?\s*/,"").trim();for(let a of t.split(`
|
|
3
3
|
`)){let r=a.trim();if(!r||r.startsWith("#"))continue;let g=r.toUpperCase();if(S(g,"LIVE")){y=!0;continue}if(S(g,"SUBJECT")){L=!0;continue}if(S(g,"AUTHOR")){!e.length&&f==="search"&&(f="author");continue}if(S(g,"REPORT")){f==="search"&&(f="report");continue}if(S(g,"KEYWORD")){f==="search"&&(f="keyword");continue}if(S(g,"SITES")){f==="search"&&(f="sites");continue}if(S(g,"BUILD")){f==="search"&&(f="build");continue}if(S(g,"FORCE")){b=!0;continue}if(S(g,"GHOST")){k=w(r,"GHOST"),f==="search"&&(f="ghost");continue}if(S(g,"BUTTON")){C=w(r,"BUTTON");continue}if(S(g,"ROSTER")){let v=w(r,"ROSTER");v&&s.push(v);continue}if(S(g,"FARM")){let v=w(r,"FARM");v&&i.push(v);continue}if(S(g,"LIST")){!e.length&&f==="search"&&(f="list");continue}if(S(g,"SIMILAR")){let v=w(g,"SIMILAR").toLowerCase();p=q[v]||N,!e.length&&f==="search"&&(f="similar");continue}if(S(g,"THRESHOLD")){let v=w(r,"THRESHOLD");p=q[v.toLowerCase()]??(parseFloat(v)||N);continue}if(S(g,"LIMIT")){E=parseInt(w(r,"LIMIT"))||J;continue}e.push(["PUBLIC","LOCAL","PRIVATE","GALAXY"].includes(g)?g:r)}return{mode:f,specs:e,rosterRefs:s,farms:i,threshold:p??N,limit:E??J,live:y,subject:L,force:b,ghostUrl:k,label:C,thresholdSet:p!==null}},Y=t=>t.includes("*")||t.includes("?"),W=t=>t==="PUBLIC"||t==="LOCAL"||t==="PRIVATE"||t==="GALAXY";var X=t=>t.toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,""),B=new Map,pt=async(t,e)=>{if(B.has(t))return B.get(t);let s=`${e}/system/indexed-domains.json?pattern=${encodeURIComponent(t)}`,i=await fetch(s);if(!i.ok)throw new Error(`indexed-domains failed: ${i.status}`);let p=await i.json();return B.set(t,p),p},O=async(t,e)=>{t.length||(t=[window.location.hostname]);let s=new Set,i=[];for(let p of t)if(p==="*"||Y(p)||W(p))for(let E of await pt(p,e))s.has(E.domain)||(s.add(E.domain),i.push(E));else s.has(p)||(s.add(p),i.push({domain:p,page_count:null}));return i},gt=/^([a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+|localhost)(:\d+)?$/,ht=/^ROSTER ([A-Za-z0-9.\-:]+\/[a-z0-9-]+)$/,wt=/^REFERENCES ([A-Za-z0-9.\-:]+\/[a-z0-9-]+)$/,Z=async t=>{let e=t.indexOf("/"),s=await fetch(`//${t.slice(0,e)}/${t.slice(e+1)}.json`);if(!s.ok)throw new Error(`roster page ${t} failed: ${s.status}`);return s.json()},Q=async(t,e,s)=>{if(s.has(t))return;s.add(t);let i;try{i=await Z(t)}catch(p){console.warn(p.message);return}for(let p of i.story||[])if(p.type==="roster")for(let E of(p.text||"").split(/\r?\n/)){let f=E.trim();if(!f)continue;let y=f.match(gt);if(y){e.add(y[0]);continue}let L=f.match(ht);if(L){await Q(L[1],e,s);continue}let b=f.match(wt);if(b)try{let k=await Z(b[1]);for(let C of k.story||[])C.type==="reference"&&C.site&&e.add(C.site)}catch(k){console.warn(k.message)}}},yt=async t=>{let e=new Set,s=new Set;for(let i of t)await Q(i,e,s);return[...e]},tt=async(t,e)=>e.length?[...t,...await yt(e)]:t;var G=new Map,$t=t=>`${window.location.origin}/system/semantic-vectors.json?domain=${encodeURIComponent(t)}`,et=async t=>{if(G.has(t))return G.get(t);let e=await fetch($t(t));if(!e.ok)return[];let s=await e.json();return G.set(t,s),s},U=async(t,e)=>{let s=await fetch(`${e}/system/embed.json`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({text:t})});if(!s.ok)throw new Error(`embed failed: ${s.status}`);return(await s.json()).vector},st=async(t,e)=>{let i=(await et(e)).find(p=>p.slug===t);return i?i.vector:null},M=(t,e,{threshold:s,limit:i,excludeSlug:p,excludeDomain:E})=>{let f=[];for(let{domain:y,pages:L}of e)for(let{slug:b,title:k,vector:C}of L){if(b===p&&y===E)continue;let S=0;for(let w=0;w<t.length;w++)S+=t[w]*C[w];S>=s&&f.push({domain:y,slug:b,title:k,score:S})}return f.sort((y,L)=>L.score-y.score),f.slice(0,i)},z=async(t,e)=>{let s=await O(t,e);return(await Promise.all(s.map(async({domain:p})=>({domain:p,pages:await et(p)})))).filter(p=>p.pages.length>0)};var nt=t=>{let e=t.parents(".page:first"),s=e.prev(".page"),i=s.length?s:e,p=(i.attr("id")||"").split("_rev")[0],E=i.data("site")||window.location.hostname,f=i.data("data")||{},y=f.title||i.find(".title").text().trim()||p,L=[y,...(f.story||[]).filter(b=>b.type==="markdown"||b.type==="paragraph").map(b=>(b.text||"").trim()).filter(Boolean)].join(`
|
|
4
4
|
`).slice(0,2e3);return{slug:p,site:E,title:y,text:L,isSelf:!s.length}},T=t=>`Subject: ${t.title}${t.isSelf?" (this page)":` @ ${t.site}`}`;var it=t=>`sim-cache-${t}`,ot=t=>{try{let e=JSON.parse(localStorage.getItem(it(t.id))||"null");return e?.text===(t.text||"")?e:null}catch{return null}},P=(t,e)=>{try{localStorage.setItem(it(t.id),JSON.stringify({text:t.text||"",ts:Date.now(),...e}))}catch{}},at=t=>{let e=Math.floor((Date.now()-t)/1e3);return e<60?`${e}s ago`:e<3600?`${Math.floor(e/60)}m ago`:e<86400?`${Math.floor(e/3600)}h ago`:`${Math.floor(e/86400)}d ago`};var D=`
|
package/package.json
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
{
|
|
5
5
|
"type": "markdown",
|
|
6
6
|
"id": "aec1dfcd5f2bc42b",
|
|
7
|
-
"text": "The Similarity plugin adds search to a Federated Wiki farm
|
|
7
|
+
"text": "The Similarity plugin adds search to a Federated Wiki farm — semantic similarity over prebuilt vector indexes, ranked search reports, galactic keyword search over each site's own MiniSearch index, and semantic search across the wider federation. One item type — **similarity** — with a line-oriented DSL, plus a server component that serves every search same-origin. Works on any host: embedding runs in-process (native ONNX where available, WASM everywhere else)."
|
|
8
8
|
},
|
|
9
9
|
{
|
|
10
10
|
"type": "markdown",
|
|
11
11
|
"id": "28edff984cc5334d",
|
|
12
|
-
"text": "## The DSL\n\nThe first ALL-CAPS keyword sets the mode; other lines are domain specs (globs, explicit domains, or the scope keywords PUBLIC / LOCAL / PRIVATE / GALAXY):\n\n- (no keyword)
|
|
12
|
+
"text": "## The DSL\n\nThe first ALL-CAPS keyword sets the mode; other lines are domain specs (globs, explicit domains, or the scope keywords PUBLIC / LOCAL / PRIVATE / GALAXY):\n\n- (no keyword) — interactive semantic search form\n- `SIMILAR: high|medium|low` — ambient: pages similar to this page\n- `REPORT` — server-side ranked, stub-filtered, fork-bundled report\n- `KEYWORD` — galactic MiniSearch over live site-index.json files\n- `SITES` — which site should this page go on? Per-domain aggregation of the page-vector scan: 0.7 × mean of the top 5 page similarities + 0.3 × site-centroid similarity. Forked pages count for every site that carries them\n- `LIST` — table of indexed domains\n- `AUTHOR` — search results as a draggable ghost page\n- `BUILD` / `FORCE` — proxy to the farm indexer, where configured\n- `GHOST url` / `BUTTON label` — open any page-json as a ghost page\n- `ROSTER site/slug` — add a roster page's sites to the scope (off-farm sites searched via the galaxy cache)\n- `FARM domain` — ask a peer farm to continue the search. The peer answers from exactly the sites whose own [[Federated Farm Search]] page grants your origin (`FROM` lines; `FROM *` needs the peer admin's `WIKI_PEER_FEDERATION=open`)\n- `GALAXY` — search the off-farm federation sites indexed into this host's galaxy tree. A scope, not a mode: `GALAXY` alone answers only from the federation (the farm's own pages are never swept in, and `*` never includes galaxy sites); `*` and `GALAXY` together search farm and federation as one\n- `SUBJECT` — act on the previous page in the lineup, the host a tool page was opened beside\n- `THRESHOLD: 0.72` · `LIMIT: 8` · `LIVE` — tuning"
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"type": "markdown",
|
|
@@ -64,12 +64,12 @@
|
|
|
64
64
|
{
|
|
65
65
|
"type": "markdown",
|
|
66
66
|
"id": "d9a96dfebb9d1692",
|
|
67
|
-
"text": "## Server Routes\n\nAll same-origin, registered by the plugin's server component:\n\n- `GET /system/indexed-domains.json?pattern=`\n- `GET /system/semantic-vectors.json?domain=`\n- `GET /system/embed.json?text=`\n- `POST /system/search-report.json`
|
|
67
|
+
"text": "## Server Routes\n\nAll same-origin, registered by the plugin's server component:\n\n- `GET /system/indexed-domains.json?pattern=`\n- `GET /system/semantic-vectors.json?domain=`\n- `GET /system/embed.json?text=`\n- `POST /system/search-report.json` — accepts an optional `author`: a display name or account username, resolved against this farm's own site ownership (each site's `owner.json`) and intersected with the requested scope. An ambiguous name answers 409 with the competing usernames rather than guessing\n- `POST /system/site-report.json` — which site should this page go on? Per-domain aggregation of the page-vector scan: 0.7 × mean of the top 5 page similarities + 0.3 × site-centroid similarity; forked pages count for every site that carries them\n- `GET /system/farm-search.json?q=&pattern=&limit=`\n- `GET /system/build-index.json?domains=` (proxied to the farm indexer)\n- `GET /system/galaxy-search.json?q=&sites=` — keyword search over off-farm federation sites\n- `GET /system/galaxy-registry.json` — the galaxy site registry: every federation site known to this host, with provenance, liveness, page counts and scraped site owner (read-only; written by the galaxy indexer host)\n- `GET /system/peer-hello.json` — capability probe: version, model, federation status (404 = plugin absent)\n- `GET /system/similarity-health.json` — embedder supervisor state: child-process / semindex / url, breaker status, recent crashes\n- `POST /system/peer-search.json` — answer another farm's search. An optional `author` travels as the plain name asked for, never a resolved username: the answering farm resolves it against its OWN records and reports `applied`, `ignored` (no records kept) or `ambiguous`. Two keys: the admin's `WIKI_PEER_FEDERATION` ceiling (off / grants / open) and each site's Federated Farm Search grants page; scope = union of granting sites; hop limit, replay dedup, IP-keyed + global rate limits; restricted sites always excluded\n\nIndexes are `status/semantic-vectors.json` per domain — built by the farm indexer, distributed by sync, read from farm disk. Keyword search reads each site's per-edit `status/site-index.json`; no separate index is built. Query embedding is BAAI bge-small-en-v1.5, 384-dim, CLS-pooled, unit-normalised — byte-compatible with the index builder.\n\nSince 0.9.0 all query embedding runs in a supervised child process, never in the wiki server itself: a crash in the ONNX runtime fails that one search, and repeated crashes trip a circuit breaker that answers 503 while cooling down. The worst case for a wiki running this plugin is \"semantic search temporarily unavailable\" — never a dead wiki.\n\nSince 0.11.0 a third index tier sits beside the farm: the **galaxy** — a farm-shaped tree of off-farm federation sites, built by a separate indexer and read through a byte-capped vector cache (`WIKI_GALAXY_VECTOR_CACHE_MB`, default 512). Galaxy entries carry their enrichment precomputed, because off-farm pages have no local page JSON to read; the stub filter, fork bundling and scoring then run unchanged, so federation results rank directly against farm results.\n\nSince 0.12.0 the plugin also offers a **Semantic Search** page through the wiki's ☰ menu, declared by a `pages` entry in `factory.json` and shipped in the package.\n\nSince 0.13.0 restricted sites are hidden from **every** route unless the caller is trusted, not only from peer answers. Restricted = the local farm's `wikiDomains` entries marked `restricted`, the globs in `WIKI_RESTRICTED_DOMAINS` (e.g. `*.private.fish`), and extra farms' config files. Trusted = an owner or admin session, a tool on the same host (loopback, no `X-Forwarded-For`), or a client the reverse proxy vouches for from `WIKI_TRUSTED_NETS` (default: the Tailscale ranges and loopback; the header is believed only when the direct peer is loopback). Cross-origin browser requests are never trusted. Untrusted callers get the public view: `semantic-vectors.json` answers 403 for a restricted domain, and the scans simply do not include it. `similarity-health.json` reports the policy and `callerTrusted`."
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
70
|
"type": "markdown",
|
|
71
71
|
"id": "21eb5354d2adb29e",
|
|
72
|
-
"text": "**Version:** 0.12.0\n\n# Links\n\n- [https://github.com/Hitchhikers-Guide-to-the-Galaxy/wiki-plugin-similarity GitHub repository]\n- [https://www.npmjs.com/package/wiki-plugin-similarity npm package]\n- [https://plugin.fedwiki.club/similarity-plugin.html Plugin documentation]\n- [https://search.fedwiki.club/ Search hub]
|
|
72
|
+
"text": "**Version:** 0.12.0\n\n# Links\n\n- [https://github.com/Hitchhikers-Guide-to-the-Galaxy/wiki-plugin-similarity GitHub repository]\n- [https://www.npmjs.com/package/wiki-plugin-similarity npm package]\n- [https://plugin.fedwiki.club/similarity-plugin.html Plugin documentation]\n- [https://search.fedwiki.club/ Search hub] — architecture, indexing, freshness."
|
|
73
73
|
}
|
|
74
74
|
],
|
|
75
75
|
"plugin": "similarity",
|
|
@@ -82,12 +82,12 @@
|
|
|
82
82
|
{
|
|
83
83
|
"type": "markdown",
|
|
84
84
|
"id": "aec1dfcd5f2bc42b",
|
|
85
|
-
"text": "The Similarity plugin adds search to a Federated Wiki farm
|
|
85
|
+
"text": "The Similarity plugin adds search to a Federated Wiki farm — semantic similarity over prebuilt vector indexes, ranked search reports, and galactic keyword search over each site's own MiniSearch index. One item type — **similarity** — with a line-oriented DSL, plus a server component that serves every search same-origin. Works on any host: embedding runs in-process (native ONNX where available, WASM everywhere else)."
|
|
86
86
|
},
|
|
87
87
|
{
|
|
88
88
|
"type": "markdown",
|
|
89
89
|
"id": "28edff984cc5334d",
|
|
90
|
-
"text": "## The DSL\n\nThe first ALL-CAPS keyword sets the mode; other lines are domain specs (globs, explicit domains, or the scope keywords PUBLIC / LOCAL / PRIVATE):\n\n- (no keyword)
|
|
90
|
+
"text": "## The DSL\n\nThe first ALL-CAPS keyword sets the mode; other lines are domain specs (globs, explicit domains, or the scope keywords PUBLIC / LOCAL / PRIVATE):\n\n- (no keyword) — interactive semantic search form\n- `SIMILAR: high|medium|low` — ambient: pages similar to this page\n- `REPORT` — server-side ranked, stub-filtered, fork-bundled report\n- `KEYWORD` — galactic MiniSearch over live site-index.json files\n- `LIST` — table of indexed domains\n- `AUTHOR` — search results as a draggable ghost page\n- `BUILD` / `FORCE` — proxy to the farm indexer, where configured\n- `GHOST url` / `BUTTON label` — open any page-json as a ghost page\n- `THRESHOLD: 0.72` · `LIMIT: 8` · `LIVE` — tuning"
|
|
91
91
|
},
|
|
92
92
|
{
|
|
93
93
|
"type": "markdown",
|
|
@@ -132,17 +132,17 @@
|
|
|
132
132
|
{
|
|
133
133
|
"type": "markdown",
|
|
134
134
|
"id": "d9a96dfebb9d1692",
|
|
135
|
-
"text": "## Server Routes\n\nAll same-origin, registered by the plugin's server component:\n\n- `GET /system/indexed-domains.json?pattern=`\n- `GET /system/semantic-vectors.json?domain=`\n- `GET /system/embed.json?text=`\n- `POST /system/search-report.json`\n- `GET /system/farm-search.json?q=&pattern=&limit=`\n- `GET /system/build-index.json?domains=` (proxied to the farm indexer)\n\nIndexes are `status/semantic-vectors.json` per domain
|
|
135
|
+
"text": "## Server Routes\n\nAll same-origin, registered by the plugin's server component:\n\n- `GET /system/indexed-domains.json?pattern=`\n- `GET /system/semantic-vectors.json?domain=`\n- `GET /system/embed.json?text=`\n- `POST /system/search-report.json`\n- `GET /system/farm-search.json?q=&pattern=&limit=`\n- `GET /system/build-index.json?domains=` (proxied to the farm indexer)\n\nIndexes are `status/semantic-vectors.json` per domain — built by the farm indexer, distributed by sync, read from farm disk. Keyword search reads each site's per-edit `status/site-index.json`; no separate index is built. Query embedding is BAAI bge-small-en-v1.5, 384-dim, CLS-pooled, unit-normalised — byte-compatible with the index builder."
|
|
136
136
|
},
|
|
137
137
|
{
|
|
138
138
|
"type": "markdown",
|
|
139
139
|
"id": "21eb5354d2adb29e",
|
|
140
|
-
"text": "**Version:** 0.5.1\n\n# Links\n\n- [https://github.com/Hitchhikers-Guide-to-the-Galaxy/wiki-plugin-similarity GitHub repository]\n- [https://www.npmjs.com/package/wiki-plugin-similarity npm package]\n- [https://plugin.fedwiki.club/similarity-plugin.html Plugin documentation]\n- [https://search.fedwiki.club/ Search hub]
|
|
140
|
+
"text": "**Version:** 0.5.1\n\n# Links\n\n- [https://github.com/Hitchhikers-Guide-to-the-Galaxy/wiki-plugin-similarity GitHub repository]\n- [https://www.npmjs.com/package/wiki-plugin-similarity npm package]\n- [https://plugin.fedwiki.club/similarity-plugin.html Plugin documentation]\n- [https://search.fedwiki.club/ Search hub] — architecture, indexing, freshness."
|
|
141
141
|
}
|
|
142
142
|
]
|
|
143
143
|
},
|
|
144
144
|
"date": 1783782734556,
|
|
145
|
-
"provenance": "Authored by an AI agent
|
|
145
|
+
"provenance": "Authored by an AI agent — Claude Fable 5 via the [[fedwiki-page]] skill. [https://claude.com/claude-code Claude Code]"
|
|
146
146
|
},
|
|
147
147
|
{
|
|
148
148
|
"type": "edit",
|
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
"item": {
|
|
151
151
|
"type": "markdown",
|
|
152
152
|
"id": "28edff984cc5334d",
|
|
153
|
-
"text": "## The DSL\n\nThe first ALL-CAPS keyword sets the mode; other lines are domain specs (globs, explicit domains, or the scope keywords PUBLIC / LOCAL / PRIVATE):\n\n- (no keyword)
|
|
153
|
+
"text": "## The DSL\n\nThe first ALL-CAPS keyword sets the mode; other lines are domain specs (globs, explicit domains, or the scope keywords PUBLIC / LOCAL / PRIVATE):\n\n- (no keyword) — interactive semantic search form\n- `SIMILAR: high|medium|low` — ambient: pages similar to this page\n- `REPORT` — server-side ranked, stub-filtered, fork-bundled report\n- `KEYWORD` — galactic MiniSearch over live site-index.json files\n- `LIST` — table of indexed domains\n- `AUTHOR` — search results as a draggable ghost page\n- `BUILD` / `FORCE` — proxy to the farm indexer, where configured\n- `GHOST url` / `BUTTON label` — open any page-json as a ghost page\n- `ROSTER site/slug` — add a roster page's sites to the scope (off-farm sites searched via the galaxy cache)\n- `FARM domain` — ask a peer farm to continue the search (experimental; requires the peer's `search-peers` roster to allow you)\n- `THRESHOLD: 0.72` · `LIMIT: 8` · `LIVE` — tuning"
|
|
154
154
|
},
|
|
155
155
|
"date": 1783800718188
|
|
156
156
|
},
|
|
@@ -160,7 +160,7 @@
|
|
|
160
160
|
"item": {
|
|
161
161
|
"type": "markdown",
|
|
162
162
|
"id": "d9a96dfebb9d1692",
|
|
163
|
-
"text": "## Server Routes\n\nAll same-origin, registered by the plugin's server component:\n\n- `GET /system/indexed-domains.json?pattern=`\n- `GET /system/semantic-vectors.json?domain=`\n- `GET /system/embed.json?text=`\n- `POST /system/search-report.json`\n- `GET /system/farm-search.json?q=&pattern=&limit=`\n- `GET /system/build-index.json?domains=` (proxied to the farm indexer)\n- `GET /system/galaxy-search.json?q=&sites=`
|
|
163
|
+
"text": "## Server Routes\n\nAll same-origin, registered by the plugin's server component:\n\n- `GET /system/indexed-domains.json?pattern=`\n- `GET /system/semantic-vectors.json?domain=`\n- `GET /system/embed.json?text=`\n- `POST /system/search-report.json`\n- `GET /system/farm-search.json?q=&pattern=&limit=`\n- `GET /system/build-index.json?domains=` (proxied to the farm indexer)\n- `GET /system/galaxy-search.json?q=&sites=` — keyword search over off-farm federation sites\n- `POST /system/peer-search.json` — answer another farm's search (guarded: allowlist roster, hop limit, replay dedup, rate limit; restricted sites always excluded)\n\nIndexes are `status/semantic-vectors.json` per domain — built by the farm indexer, distributed by sync, read from farm disk. Keyword search reads each site's per-edit `status/site-index.json`; no separate index is built. Query embedding is BAAI bge-small-en-v1.5, 384-dim, CLS-pooled, unit-normalised — byte-compatible with the index builder."
|
|
164
164
|
},
|
|
165
165
|
"date": 1783800718188
|
|
166
166
|
},
|
|
@@ -170,7 +170,7 @@
|
|
|
170
170
|
"item": {
|
|
171
171
|
"type": "markdown",
|
|
172
172
|
"id": "21eb5354d2adb29e",
|
|
173
|
-
"text": "**Version:** 0.6.0\n\n# Links\n\n- [https://github.com/Hitchhikers-Guide-to-the-Galaxy/wiki-plugin-similarity GitHub repository]\n- [https://www.npmjs.com/package/wiki-plugin-similarity npm package]\n- [https://plugin.fedwiki.club/similarity-plugin.html Plugin documentation]\n- [https://search.fedwiki.club/ Search hub]
|
|
173
|
+
"text": "**Version:** 0.6.0\n\n# Links\n\n- [https://github.com/Hitchhikers-Guide-to-the-Galaxy/wiki-plugin-similarity GitHub repository]\n- [https://www.npmjs.com/package/wiki-plugin-similarity npm package]\n- [https://plugin.fedwiki.club/similarity-plugin.html Plugin documentation]\n- [https://search.fedwiki.club/ Search hub] — architecture, indexing, freshness."
|
|
174
174
|
},
|
|
175
175
|
"date": 1783800718188
|
|
176
176
|
},
|
|
@@ -180,7 +180,7 @@
|
|
|
180
180
|
"item": {
|
|
181
181
|
"type": "markdown",
|
|
182
182
|
"id": "28edff984cc5334d",
|
|
183
|
-
"text": "## The DSL\n\nThe first ALL-CAPS keyword sets the mode; other lines are domain specs (globs, explicit domains, or the scope keywords PUBLIC / LOCAL / PRIVATE):\n\n- (no keyword)
|
|
183
|
+
"text": "## The DSL\n\nThe first ALL-CAPS keyword sets the mode; other lines are domain specs (globs, explicit domains, or the scope keywords PUBLIC / LOCAL / PRIVATE):\n\n- (no keyword) — interactive semantic search form\n- `SIMILAR: high|medium|low` — ambient: pages similar to this page\n- `REPORT` — server-side ranked, stub-filtered, fork-bundled report\n- `KEYWORD` — galactic MiniSearch over live site-index.json files\n- `LIST` — table of indexed domains\n- `AUTHOR` — search results as a draggable ghost page\n- `BUILD` / `FORCE` — proxy to the farm indexer, where configured\n- `GHOST url` / `BUTTON label` — open any page-json as a ghost page\n- `ROSTER site/slug` — add a roster page's sites to the scope (off-farm sites searched via the galaxy cache)\n- `FARM domain` — ask a peer farm to continue the search. The peer answers from exactly the sites whose own [[Federated Farm Search]] page grants your origin (`FROM` lines; `FROM *` needs the peer admin's `WIKI_PEER_FEDERATION=open`)\n- `THRESHOLD: 0.72` · `LIMIT: 8` · `LIVE` — tuning"
|
|
184
184
|
},
|
|
185
185
|
"date": 1784591495272
|
|
186
186
|
},
|
|
@@ -190,7 +190,7 @@
|
|
|
190
190
|
"item": {
|
|
191
191
|
"type": "markdown",
|
|
192
192
|
"id": "d9a96dfebb9d1692",
|
|
193
|
-
"text": "## Server Routes\n\nAll same-origin, registered by the plugin's server component:\n\n- `GET /system/indexed-domains.json?pattern=`\n- `GET /system/semantic-vectors.json?domain=`\n- `GET /system/embed.json?text=`\n- `POST /system/search-report.json`\n- `GET /system/farm-search.json?q=&pattern=&limit=`\n- `GET /system/build-index.json?domains=` (proxied to the farm indexer)\n- `GET /system/galaxy-search.json?q=&sites=`
|
|
193
|
+
"text": "## Server Routes\n\nAll same-origin, registered by the plugin's server component:\n\n- `GET /system/indexed-domains.json?pattern=`\n- `GET /system/semantic-vectors.json?domain=`\n- `GET /system/embed.json?text=`\n- `POST /system/search-report.json`\n- `GET /system/farm-search.json?q=&pattern=&limit=`\n- `GET /system/build-index.json?domains=` (proxied to the farm indexer)\n- `GET /system/galaxy-search.json?q=&sites=` — keyword search over off-farm federation sites\n- `GET /system/peer-hello.json` — capability probe: version, model, federation status (404 = plugin absent)\n- `POST /system/peer-search.json` — answer another farm's search. Two keys: the admin's `WIKI_PEER_FEDERATION` ceiling (off / grants / open) and each site's Federated Farm Search grants page; scope = union of granting sites; hop limit, replay dedup, IP-keyed + global rate limits; restricted sites always excluded\n\nIndexes are `status/semantic-vectors.json` per domain — built by the farm indexer, distributed by sync, read from farm disk. Keyword search reads each site's per-edit `status/site-index.json`; no separate index is built. Query embedding is BAAI bge-small-en-v1.5, 384-dim, CLS-pooled, unit-normalised — byte-compatible with the index builder."
|
|
194
194
|
},
|
|
195
195
|
"date": 1784591495272
|
|
196
196
|
},
|
|
@@ -200,7 +200,7 @@
|
|
|
200
200
|
"item": {
|
|
201
201
|
"type": "markdown",
|
|
202
202
|
"id": "21eb5354d2adb29e",
|
|
203
|
-
"text": "**Version:** 0.7.0\n\n# Links\n\n- [https://github.com/Hitchhikers-Guide-to-the-Galaxy/wiki-plugin-similarity GitHub repository]\n- [https://www.npmjs.com/package/wiki-plugin-similarity npm package]\n- [https://plugin.fedwiki.club/similarity-plugin.html Plugin documentation]\n- [https://search.fedwiki.club/ Search hub]
|
|
203
|
+
"text": "**Version:** 0.7.0\n\n# Links\n\n- [https://github.com/Hitchhikers-Guide-to-the-Galaxy/wiki-plugin-similarity GitHub repository]\n- [https://www.npmjs.com/package/wiki-plugin-similarity npm package]\n- [https://plugin.fedwiki.club/similarity-plugin.html Plugin documentation]\n- [https://search.fedwiki.club/ Search hub] — architecture, indexing, freshness."
|
|
204
204
|
},
|
|
205
205
|
"date": 1784591495272
|
|
206
206
|
},
|
|
@@ -210,7 +210,7 @@
|
|
|
210
210
|
"item": {
|
|
211
211
|
"type": "markdown",
|
|
212
212
|
"id": "d9a96dfebb9d1692",
|
|
213
|
-
"text": "## Server Routes\n\nAll same-origin, registered by the plugin's server component:\n\n- `GET /system/indexed-domains.json?pattern=`\n- `GET /system/semantic-vectors.json?domain=`\n- `GET /system/embed.json?text=`\n- `POST /system/search-report.json`\n- `GET /system/farm-search.json?q=&pattern=&limit=`\n- `GET /system/build-index.json?domains=` (proxied to the farm indexer)\n- `GET /system/galaxy-search.json?q=&sites=`
|
|
213
|
+
"text": "## Server Routes\n\nAll same-origin, registered by the plugin's server component:\n\n- `GET /system/indexed-domains.json?pattern=`\n- `GET /system/semantic-vectors.json?domain=`\n- `GET /system/embed.json?text=`\n- `POST /system/search-report.json`\n- `GET /system/farm-search.json?q=&pattern=&limit=`\n- `GET /system/build-index.json?domains=` (proxied to the farm indexer)\n- `GET /system/galaxy-search.json?q=&sites=` — keyword search over off-farm federation sites\n- `GET /system/peer-hello.json` — capability probe: version, model, federation status (404 = plugin absent)\n- `GET /system/similarity-health.json` — embedder supervisor state: child-process / semindex / url, breaker status, recent crashes\n- `POST /system/peer-search.json` — answer another farm's search. Two keys: the admin's `WIKI_PEER_FEDERATION` ceiling (off / grants / open) and each site's Federated Farm Search grants page; scope = union of granting sites; hop limit, replay dedup, IP-keyed + global rate limits; restricted sites always excluded\n\nIndexes are `status/semantic-vectors.json` per domain — built by the farm indexer, distributed by sync, read from farm disk. Keyword search reads each site's per-edit `status/site-index.json`; no separate index is built. Query embedding is BAAI bge-small-en-v1.5, 384-dim, CLS-pooled, unit-normalised — byte-compatible with the index builder.\n\nSince 0.9.0 all query embedding runs in a supervised child process, never in the wiki server itself: a crash in the ONNX runtime fails that one search, and repeated crashes trip a circuit breaker that answers 503 while cooling down. The worst case for a wiki running this plugin is \"semantic search temporarily unavailable\" — never a dead wiki."
|
|
214
214
|
},
|
|
215
215
|
"date": 1784706029671
|
|
216
216
|
},
|
|
@@ -220,7 +220,7 @@
|
|
|
220
220
|
"item": {
|
|
221
221
|
"type": "markdown",
|
|
222
222
|
"id": "21eb5354d2adb29e",
|
|
223
|
-
"text": "**Version:** 0.9.0\n\n# Links\n\n- [https://github.com/Hitchhikers-Guide-to-the-Galaxy/wiki-plugin-similarity GitHub repository]\n- [https://www.npmjs.com/package/wiki-plugin-similarity npm package]\n- [https://plugin.fedwiki.club/similarity-plugin.html Plugin documentation]\n- [https://search.fedwiki.club/ Search hub]
|
|
223
|
+
"text": "**Version:** 0.9.0\n\n# Links\n\n- [https://github.com/Hitchhikers-Guide-to-the-Galaxy/wiki-plugin-similarity GitHub repository]\n- [https://www.npmjs.com/package/wiki-plugin-similarity npm package]\n- [https://plugin.fedwiki.club/similarity-plugin.html Plugin documentation]\n- [https://search.fedwiki.club/ Search hub] — architecture, indexing, freshness."
|
|
224
224
|
},
|
|
225
225
|
"date": 1784706029671
|
|
226
226
|
},
|
|
@@ -230,7 +230,7 @@
|
|
|
230
230
|
"item": {
|
|
231
231
|
"type": "markdown",
|
|
232
232
|
"id": "aec1dfcd5f2bc42b",
|
|
233
|
-
"text": "The Similarity plugin adds search to a Federated Wiki farm
|
|
233
|
+
"text": "The Similarity plugin adds search to a Federated Wiki farm — semantic similarity over prebuilt vector indexes, ranked search reports, galactic keyword search over each site's own MiniSearch index, and semantic search across the wider federation. One item type — **similarity** — with a line-oriented DSL, plus a server component that serves every search same-origin. Works on any host: embedding runs in-process (native ONNX where available, WASM everywhere else)."
|
|
234
234
|
},
|
|
235
235
|
"date": 1785280898931
|
|
236
236
|
},
|
|
@@ -240,7 +240,7 @@
|
|
|
240
240
|
"item": {
|
|
241
241
|
"type": "markdown",
|
|
242
242
|
"id": "28edff984cc5334d",
|
|
243
|
-
"text": "## The DSL\n\nThe first ALL-CAPS keyword sets the mode; other lines are domain specs (globs, explicit domains, or the scope keywords PUBLIC / LOCAL / PRIVATE / GALAXY):\n\n- (no keyword)
|
|
243
|
+
"text": "## The DSL\n\nThe first ALL-CAPS keyword sets the mode; other lines are domain specs (globs, explicit domains, or the scope keywords PUBLIC / LOCAL / PRIVATE / GALAXY):\n\n- (no keyword) — interactive semantic search form\n- `SIMILAR: high|medium|low` — ambient: pages similar to this page\n- `REPORT` — server-side ranked, stub-filtered, fork-bundled report\n- `KEYWORD` — galactic MiniSearch over live site-index.json files\n- `SITES` — which site should this page go on? Per-domain aggregation of the page-vector scan: 0.7 × mean of the top 5 page similarities + 0.3 × site-centroid similarity. Forked pages count for every site that carries them\n- `LIST` — table of indexed domains\n- `AUTHOR` — search results as a draggable ghost page\n- `BUILD` / `FORCE` — proxy to the farm indexer, where configured\n- `GHOST url` / `BUTTON label` — open any page-json as a ghost page\n- `ROSTER site/slug` — add a roster page's sites to the scope (off-farm sites searched via the galaxy cache)\n- `FARM domain` — ask a peer farm to continue the search. The peer answers from exactly the sites whose own [[Federated Farm Search]] page grants your origin (`FROM` lines; `FROM *` needs the peer admin's `WIKI_PEER_FEDERATION=open`)\n- `GALAXY` — search the off-farm federation sites indexed into this host's galaxy tree. A scope, not a mode: `GALAXY` alone answers only from the federation (the farm's own pages are never swept in, and `*` never includes galaxy sites); `*` and `GALAXY` together search farm and federation as one\n- `SUBJECT` — act on the previous page in the lineup, the host a tool page was opened beside\n- `THRESHOLD: 0.72` · `LIMIT: 8` · `LIVE` — tuning"
|
|
244
244
|
},
|
|
245
245
|
"date": 1785280898931
|
|
246
246
|
},
|
|
@@ -250,7 +250,7 @@
|
|
|
250
250
|
"item": {
|
|
251
251
|
"type": "markdown",
|
|
252
252
|
"id": "d9a96dfebb9d1692",
|
|
253
|
-
"text": "## Server Routes\n\nAll same-origin, registered by the plugin's server component:\n\n- `GET /system/indexed-domains.json?pattern=`\n- `GET /system/semantic-vectors.json?domain=`\n- `GET /system/embed.json?text=`\n- `POST /system/search-report.json`\n- `POST /system/site-report.json`
|
|
253
|
+
"text": "## Server Routes\n\nAll same-origin, registered by the plugin's server component:\n\n- `GET /system/indexed-domains.json?pattern=`\n- `GET /system/semantic-vectors.json?domain=`\n- `GET /system/embed.json?text=`\n- `POST /system/search-report.json`\n- `POST /system/site-report.json` — which site should this page go on? Per-domain aggregation of the page-vector scan: 0.7 × mean of the top 5 page similarities + 0.3 × site-centroid similarity; forked pages count for every site that carries them\n- `GET /system/farm-search.json?q=&pattern=&limit=`\n- `GET /system/build-index.json?domains=` (proxied to the farm indexer)\n- `GET /system/galaxy-search.json?q=&sites=` — keyword search over off-farm federation sites\n- `GET /system/galaxy-registry.json` — the galaxy site registry: every federation site known to this host, with provenance, liveness, page counts and scraped site owner (read-only; written by the galaxy indexer host)\n- `GET /system/peer-hello.json` — capability probe: version, model, federation status (404 = plugin absent)\n- `GET /system/similarity-health.json` — embedder supervisor state: child-process / semindex / url, breaker status, recent crashes\n- `POST /system/peer-search.json` — answer another farm's search. Two keys: the admin's `WIKI_PEER_FEDERATION` ceiling (off / grants / open) and each site's Federated Farm Search grants page; scope = union of granting sites; hop limit, replay dedup, IP-keyed + global rate limits; restricted sites always excluded\n\nIndexes are `status/semantic-vectors.json` per domain — built by the farm indexer, distributed by sync, read from farm disk. Keyword search reads each site's per-edit `status/site-index.json`; no separate index is built. Query embedding is BAAI bge-small-en-v1.5, 384-dim, CLS-pooled, unit-normalised — byte-compatible with the index builder.\n\nSince 0.9.0 all query embedding runs in a supervised child process, never in the wiki server itself: a crash in the ONNX runtime fails that one search, and repeated crashes trip a circuit breaker that answers 503 while cooling down. The worst case for a wiki running this plugin is \"semantic search temporarily unavailable\" — never a dead wiki.\n\nSince 0.11.0 a third index tier sits beside the farm: the **galaxy** — a farm-shaped tree of off-farm federation sites, built by a separate indexer and read through a byte-capped vector cache (`WIKI_GALAXY_VECTOR_CACHE_MB`, default 512). Galaxy entries carry their enrichment precomputed, because off-farm pages have no local page JSON to read; the stub filter, fork bundling and scoring then run unchanged, so federation results rank directly against farm results."
|
|
254
254
|
},
|
|
255
255
|
"date": 1785280898931
|
|
256
256
|
},
|
|
@@ -260,7 +260,7 @@
|
|
|
260
260
|
"item": {
|
|
261
261
|
"type": "markdown",
|
|
262
262
|
"id": "21eb5354d2adb29e",
|
|
263
|
-
"text": "**Version:** 0.11.0\n\n# Links\n\n- [https://github.com/Hitchhikers-Guide-to-the-Galaxy/wiki-plugin-similarity GitHub repository]\n- [https://www.npmjs.com/package/wiki-plugin-similarity npm package]\n- [https://plugin.fedwiki.club/similarity-plugin.html Plugin documentation]\n- [https://search.fedwiki.club/ Search hub]
|
|
263
|
+
"text": "**Version:** 0.11.0\n\n# Links\n\n- [https://github.com/Hitchhikers-Guide-to-the-Galaxy/wiki-plugin-similarity GitHub repository]\n- [https://www.npmjs.com/package/wiki-plugin-similarity npm package]\n- [https://plugin.fedwiki.club/similarity-plugin.html Plugin documentation]\n- [https://search.fedwiki.club/ Search hub] — architecture, indexing, freshness."
|
|
264
264
|
},
|
|
265
265
|
"date": 1785280898931
|
|
266
266
|
},
|
|
@@ -270,7 +270,7 @@
|
|
|
270
270
|
"item": {
|
|
271
271
|
"type": "markdown",
|
|
272
272
|
"id": "d9a96dfebb9d1692",
|
|
273
|
-
"text": "## Server Routes\n\nAll same-origin, registered by the plugin's server component:\n\n- `GET /system/indexed-domains.json?pattern=`\n- `GET /system/semantic-vectors.json?domain=`\n- `GET /system/embed.json?text=`\n- `POST /system/search-report.json`
|
|
273
|
+
"text": "## Server Routes\n\nAll same-origin, registered by the plugin's server component:\n\n- `GET /system/indexed-domains.json?pattern=`\n- `GET /system/semantic-vectors.json?domain=`\n- `GET /system/embed.json?text=`\n- `POST /system/search-report.json` — accepts an optional `author`: a display name or account username, resolved against this farm's own site ownership (each site's `owner.json`) and intersected with the requested scope. An ambiguous name answers 409 with the competing usernames rather than guessing\n- `POST /system/site-report.json` — which site should this page go on? Per-domain aggregation of the page-vector scan: 0.7 × mean of the top 5 page similarities + 0.3 × site-centroid similarity; forked pages count for every site that carries them\n- `GET /system/farm-search.json?q=&pattern=&limit=`\n- `GET /system/build-index.json?domains=` (proxied to the farm indexer)\n- `GET /system/galaxy-search.json?q=&sites=` — keyword search over off-farm federation sites\n- `GET /system/galaxy-registry.json` — the galaxy site registry: every federation site known to this host, with provenance, liveness, page counts and scraped site owner (read-only; written by the galaxy indexer host)\n- `GET /system/peer-hello.json` — capability probe: version, model, federation status (404 = plugin absent)\n- `GET /system/similarity-health.json` — embedder supervisor state: child-process / semindex / url, breaker status, recent crashes\n- `POST /system/peer-search.json` — answer another farm's search. An optional `author` travels as the plain name asked for, never a resolved username: the answering farm resolves it against its OWN records and reports `applied`, `ignored` (no records kept) or `ambiguous`. Two keys: the admin's `WIKI_PEER_FEDERATION` ceiling (off / grants / open) and each site's Federated Farm Search grants page; scope = union of granting sites; hop limit, replay dedup, IP-keyed + global rate limits; restricted sites always excluded\n\nIndexes are `status/semantic-vectors.json` per domain — built by the farm indexer, distributed by sync, read from farm disk. Keyword search reads each site's per-edit `status/site-index.json`; no separate index is built. Query embedding is BAAI bge-small-en-v1.5, 384-dim, CLS-pooled, unit-normalised — byte-compatible with the index builder.\n\nSince 0.9.0 all query embedding runs in a supervised child process, never in the wiki server itself: a crash in the ONNX runtime fails that one search, and repeated crashes trip a circuit breaker that answers 503 while cooling down. The worst case for a wiki running this plugin is \"semantic search temporarily unavailable\" — never a dead wiki.\n\nSince 0.11.0 a third index tier sits beside the farm: the **galaxy** — a farm-shaped tree of off-farm federation sites, built by a separate indexer and read through a byte-capped vector cache (`WIKI_GALAXY_VECTOR_CACHE_MB`, default 512). Galaxy entries carry their enrichment precomputed, because off-farm pages have no local page JSON to read; the stub filter, fork bundling and scoring then run unchanged, so federation results rank directly against farm results.\n\nSince 0.12.0 the plugin also offers a **Semantic Search** page through the wiki's ☰ menu, declared by a `pages` entry in `factory.json` and shipped in the package."
|
|
274
274
|
},
|
|
275
275
|
"date": 1785397304700
|
|
276
276
|
},
|
|
@@ -280,7 +280,7 @@
|
|
|
280
280
|
"item": {
|
|
281
281
|
"type": "markdown",
|
|
282
282
|
"id": "21eb5354d2adb29e",
|
|
283
|
-
"text": "**Version:** 0.12.0\n\n# Links\n\n- [https://github.com/Hitchhikers-Guide-to-the-Galaxy/wiki-plugin-similarity GitHub repository]\n- [https://www.npmjs.com/package/wiki-plugin-similarity npm package]\n- [https://plugin.fedwiki.club/similarity-plugin.html Plugin documentation]\n- [https://search.fedwiki.club/ Search hub]
|
|
283
|
+
"text": "**Version:** 0.12.0\n\n# Links\n\n- [https://github.com/Hitchhikers-Guide-to-the-Galaxy/wiki-plugin-similarity GitHub repository]\n- [https://www.npmjs.com/package/wiki-plugin-similarity npm package]\n- [https://plugin.fedwiki.club/similarity-plugin.html Plugin documentation]\n- [https://search.fedwiki.club/ Search hub] — architecture, indexing, freshness."
|
|
284
284
|
},
|
|
285
285
|
"date": 1785397304700
|
|
286
286
|
}
|
package/pages/semantic-search
CHANGED
|
@@ -4,12 +4,37 @@
|
|
|
4
4
|
{
|
|
5
5
|
"type": "paragraph",
|
|
6
6
|
"id": "6a46bc37dd15ddfb",
|
|
7
|
-
"text": "Semantic search across every indexed domain this wiki can reach. Type a phrase \u2014 the plugin embeds it, compares it against the prebuilt page vectors, and ranks the closest pages by meaning rather than by word.
|
|
7
|
+
"text": "Semantic search across every indexed domain this wiki can reach. Type a phrase \u2014 the plugin embeds it, compares it against the prebuilt page vectors, and ranks the closest pages by meaning rather than by word."
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"type": "paragraph",
|
|
11
|
+
"id": "833526e264c6916f",
|
|
12
|
+
"text": "Here we search across the entire [[Farm]]:"
|
|
8
13
|
},
|
|
9
14
|
{
|
|
10
15
|
"type": "similarity",
|
|
11
16
|
"id": "ece1644752fdbfb5",
|
|
12
|
-
"text": "*\nLIMIT: 10"
|
|
17
|
+
"text": "*\nREPORT\nLIMIT: 10"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"type": "markdown",
|
|
21
|
+
"id": "6ceb4b9c37f3c06c",
|
|
22
|
+
"text": "And here we search [[the federation]] (excluding our farm):"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"type": "similarity",
|
|
26
|
+
"id": "e659640772a8340b",
|
|
27
|
+
"text": "GALAXY\nREPORT\nLIMIT: 10"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"type": "markdown",
|
|
31
|
+
"id": "b8569a0e6aef5f8a",
|
|
32
|
+
"text": "You may be interested in finding a suitable [[site]] on the farm, where these pages may live:"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"type": "similarity",
|
|
36
|
+
"id": "3cd8b0ef7d1c10ac",
|
|
37
|
+
"text": "*\nSITES\nLIMIT: 5"
|
|
13
38
|
},
|
|
14
39
|
{
|
|
15
40
|
"type": "paragraph",
|
|
@@ -24,7 +49,7 @@
|
|
|
24
49
|
"title": "Semantic Search",
|
|
25
50
|
"story": []
|
|
26
51
|
},
|
|
27
|
-
"date":
|
|
52
|
+
"date": 1785399758533
|
|
28
53
|
},
|
|
29
54
|
{
|
|
30
55
|
"type": "add",
|
|
@@ -32,9 +57,19 @@
|
|
|
32
57
|
"item": {
|
|
33
58
|
"type": "paragraph",
|
|
34
59
|
"id": "6a46bc37dd15ddfb",
|
|
35
|
-
"text": "Semantic search across every indexed domain this wiki can reach. Type a phrase \u2014 the plugin embeds it, compares it against the prebuilt page vectors, and ranks the closest pages by meaning rather than by word.
|
|
60
|
+
"text": "Semantic search across every indexed domain this wiki can reach. Type a phrase \u2014 the plugin embeds it, compares it against the prebuilt page vectors, and ranks the closest pages by meaning rather than by word."
|
|
61
|
+
},
|
|
62
|
+
"date": 1785399758533
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"type": "add",
|
|
66
|
+
"id": "833526e264c6916f",
|
|
67
|
+
"item": {
|
|
68
|
+
"type": "paragraph",
|
|
69
|
+
"id": "833526e264c6916f",
|
|
70
|
+
"text": "Here we search across the entire [[Farm]]:"
|
|
36
71
|
},
|
|
37
|
-
"date":
|
|
72
|
+
"date": 1785399758533
|
|
38
73
|
},
|
|
39
74
|
{
|
|
40
75
|
"type": "add",
|
|
@@ -42,9 +77,49 @@
|
|
|
42
77
|
"item": {
|
|
43
78
|
"type": "similarity",
|
|
44
79
|
"id": "ece1644752fdbfb5",
|
|
45
|
-
"text": "*\nLIMIT: 10"
|
|
80
|
+
"text": "*\nREPORT\nLIMIT: 10"
|
|
81
|
+
},
|
|
82
|
+
"date": 1785399758533
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"type": "add",
|
|
86
|
+
"id": "6ceb4b9c37f3c06c",
|
|
87
|
+
"item": {
|
|
88
|
+
"type": "markdown",
|
|
89
|
+
"id": "6ceb4b9c37f3c06c",
|
|
90
|
+
"text": "And here we search [[the federation]] (excluding our farm):"
|
|
91
|
+
},
|
|
92
|
+
"date": 1785399758533
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"type": "add",
|
|
96
|
+
"id": "e659640772a8340b",
|
|
97
|
+
"item": {
|
|
98
|
+
"type": "similarity",
|
|
99
|
+
"id": "e659640772a8340b",
|
|
100
|
+
"text": "GALAXY\nREPORT\nLIMIT: 10"
|
|
101
|
+
},
|
|
102
|
+
"date": 1785399758533
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"type": "add",
|
|
106
|
+
"id": "b8569a0e6aef5f8a",
|
|
107
|
+
"item": {
|
|
108
|
+
"type": "markdown",
|
|
109
|
+
"id": "b8569a0e6aef5f8a",
|
|
110
|
+
"text": "You may be interested in finding a suitable [[site]] on the farm, where these pages may live:"
|
|
111
|
+
},
|
|
112
|
+
"date": 1785399758533
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"type": "add",
|
|
116
|
+
"id": "3cd8b0ef7d1c10ac",
|
|
117
|
+
"item": {
|
|
118
|
+
"type": "similarity",
|
|
119
|
+
"id": "3cd8b0ef7d1c10ac",
|
|
120
|
+
"text": "*\nSITES\nLIMIT: 5"
|
|
46
121
|
},
|
|
47
|
-
"date":
|
|
122
|
+
"date": 1785399758533
|
|
48
123
|
},
|
|
49
124
|
{
|
|
50
125
|
"type": "add",
|
|
@@ -54,7 +129,7 @@
|
|
|
54
129
|
"id": "3088fca5e9418d87",
|
|
55
130
|
"text": "More [[About Similarity Plugin]]"
|
|
56
131
|
},
|
|
57
|
-
"date":
|
|
132
|
+
"date": 1785399758533
|
|
58
133
|
}
|
|
59
134
|
]
|
|
60
135
|
}
|
package/server/farm-lib.js
CHANGED
|
@@ -34,11 +34,38 @@ const globMatch = (pattern, str) => {
|
|
|
34
34
|
// explicit opt-in (GALAXY, an explicit domain, or a roster)
|
|
35
35
|
// PUBLIC domains in 'public' farms (Nextcloud mirror)
|
|
36
36
|
// LOCAL domains in the primary ('local') farm
|
|
37
|
-
// PRIVATE
|
|
37
|
+
// PRIVATE domains marked restricted (see loadRestricted) on any farm
|
|
38
38
|
// GALAXY off-farm federation sites in the 'galaxy' tree (see galaxy-vectors.js)
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
// ── Restricted domains ────────────────────────────────────────────────────────
|
|
41
|
+
// A Set of exact names PLUS a list of globs (WIKI_RESTRICTED_DOMAINS), so a
|
|
42
|
+
// whole namespace such as *.private.fish can be restricted with one env line
|
|
43
|
+
// without touching wikiDomains (which would also switch on login-to-view).
|
|
44
|
+
// `has(domain)` answers for both, so every caller's `restricted.has(...)`
|
|
45
|
+
// keeps working unchanged.
|
|
46
|
+
|
|
47
|
+
class RestrictedSet extends Set {
|
|
48
|
+
constructor(names = [], globs = []) {
|
|
49
|
+
super(names)
|
|
50
|
+
this.globs = [...globs]
|
|
51
|
+
}
|
|
52
|
+
has(domain) {
|
|
53
|
+
return super.has(domain) || this.globs.some(g => globMatch(g, domain))
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Sources, all optional and merged:
|
|
58
|
+
// opts.wikiDomains the LOCAL farm's own wikiDomains map — wiki's farm.js
|
|
59
|
+
// hands it to every plugin as argv.wikiDomains, so the
|
|
60
|
+
// primary farm's restricted sites count without any
|
|
61
|
+
// WIKI_EXTRA_FARMS at all
|
|
62
|
+
// opts.globs WIKI_RESTRICTED_DOMAINS globs
|
|
63
|
+
// publicFarms config-*.json files in extra farm roots (the mirror)
|
|
64
|
+
const loadRestricted = (publicFarms, opts = {}) => {
|
|
65
|
+
const restricted = new RestrictedSet([], opts.globs || [])
|
|
66
|
+
for (const [domain, o] of Object.entries(opts.wikiDomains || {})) {
|
|
67
|
+
if (o && o.restricted) restricted.add(domain)
|
|
68
|
+
}
|
|
42
69
|
for (const farm of publicFarms) {
|
|
43
70
|
let files
|
|
44
71
|
try { files = fs.readdirSync(farm) } catch { continue }
|
|
@@ -60,7 +87,7 @@ const matchesAny = (domain, kind, patterns, restricted) =>
|
|
|
60
87
|
if (p === '*') return kind !== 'galaxy'
|
|
61
88
|
if (p === 'PUBLIC') return kind === 'public'
|
|
62
89
|
if (p === 'LOCAL') return kind === 'local'
|
|
63
|
-
if (p === 'PRIVATE') return kind
|
|
90
|
+
if (p === 'PRIVATE') return kind !== 'galaxy' && restricted.has(domain)
|
|
64
91
|
if (p === 'GALAXY') return kind === 'galaxy'
|
|
65
92
|
return globMatch(p, domain)
|
|
66
93
|
})
|
|
@@ -102,4 +129,4 @@ const findInFarms = (farms, domain, relPath) => {
|
|
|
102
129
|
return null
|
|
103
130
|
}
|
|
104
131
|
|
|
105
|
-
module.exports = { globMatch, loadRestricted, matchesAny, listDomains, findInFarms }
|
|
132
|
+
module.exports = { globMatch, RestrictedSet, loadRestricted, matchesAny, listDomains, findInFarms }
|
package/server/server.js
CHANGED
|
@@ -74,6 +74,7 @@ const { galaxyRoot, galaxyCacheStats } = require('./galaxy-vectors')
|
|
|
74
74
|
const { resolveAuthor } = require('./author-index')
|
|
75
75
|
const { ceiling, grantingDomains, guardEnvelope } = require('./peer-guard')
|
|
76
76
|
const { postToPeer, appendPeerSections, makePeerDesk, setModelMeta } = require('./peer')
|
|
77
|
+
const { parseNets, isTrusted } = require('./trust')
|
|
77
78
|
|
|
78
79
|
const MODEL_META = { model: 'BAAI/bge-small-en-v1.5', dim: 384 }
|
|
79
80
|
const PLUGIN_VERSION = (() => {
|
|
@@ -86,6 +87,12 @@ const EMBED_URL = process.env.WIKI_EMBED_URL || null
|
|
|
86
87
|
const INDEXER_URL = process.env.WIKI_INDEXER_URL || null
|
|
87
88
|
// Optional additional farm roots, colon-separated absolute paths.
|
|
88
89
|
const EXTRA_FARMS = (process.env.WIKI_EXTRA_FARMS || '').split(':').filter(Boolean)
|
|
90
|
+
// Restricted-domain globs, comma-separated (e.g. "*.private.fish,*.pi5.private.fish").
|
|
91
|
+
// Restricted sites are hidden from every route unless the caller is trusted —
|
|
92
|
+
// see ./trust.js and WIKI_TRUSTED_NETS.
|
|
93
|
+
const RESTRICTED_GLOBS = (process.env.WIKI_RESTRICTED_DOMAINS || '')
|
|
94
|
+
.split(',').map(s => s.trim()).filter(Boolean)
|
|
95
|
+
const TRUSTED_NETS = parseNets()
|
|
89
96
|
|
|
90
97
|
// ── HTTP helpers ──────────────────────────────────────────────────────────────
|
|
91
98
|
|
|
@@ -157,11 +164,28 @@ const startServer = ({ argv, app }) => {
|
|
|
157
164
|
const farms = [[farmRoot, 'local'], ...EXTRA_FARMS.map(f => [f, 'public'])]
|
|
158
165
|
const galaxyDir = galaxyRoot()
|
|
159
166
|
if (fs.existsSync(galaxyDir)) farms.push([galaxyDir, 'galaxy'])
|
|
160
|
-
|
|
167
|
+
// Restricted = the local farm's own wikiDomains (argv, merged by wiki's
|
|
168
|
+
// farm.js) + WIKI_RESTRICTED_DOMAINS globs + extra farms' config files.
|
|
169
|
+
const restricted = loadRestricted(EXTRA_FARMS,
|
|
170
|
+
{ wikiDomains: argv.wikiDomains, globs: RESTRICTED_GLOBS })
|
|
161
171
|
const ctx = { farms, restricted, embed: embedText }
|
|
162
172
|
|
|
173
|
+
// Who may see restricted sites (./trust.js): owner session, a tool on this
|
|
174
|
+
// host, or a proxied client on a trusted net. Everyone else gets the
|
|
175
|
+
// public view — `ctxFor(req)` carries the exclusion into every scan.
|
|
176
|
+
const ownerFile = path.join(argv.status || '', 'owner.json')
|
|
177
|
+
const trusted = req => isTrusted(req, {
|
|
178
|
+
securityhandler: app.securityhandler,
|
|
179
|
+
ownerFileExists: (() => { try { return fs.existsSync(ownerFile) } catch { return false } })(),
|
|
180
|
+
nets: TRUSTED_NETS,
|
|
181
|
+
})
|
|
182
|
+
const ctxFor = req => trusted(req) ? ctx : { ...ctx, exclude: restricted }
|
|
183
|
+
const visible = (req, domain) => !restricted.has(domain) || trusted(req)
|
|
184
|
+
|
|
163
185
|
console.log('[wiki-plugin-similarity] registering /system routes, farms:',
|
|
164
|
-
farms.map(([f]) => f).join(', ')
|
|
186
|
+
farms.map(([f]) => f).join(', '),
|
|
187
|
+
`| restricted: ${restricted.size} names + ${restricted.globs.length} globs`,
|
|
188
|
+
`| trusted nets: ${TRUSTED_NETS.join(' ')}`)
|
|
165
189
|
|
|
166
190
|
// Warm the embedding model in the background so the first query is fast.
|
|
167
191
|
if (!EMBED_URL) embedder.warm()
|
|
@@ -200,6 +224,7 @@ const startServer = ({ argv, app }) => {
|
|
|
200
224
|
const patterns = raw.split(',').map(p => p.trim()).filter(Boolean)
|
|
201
225
|
const limit = parseInt(req.query.limit) || null
|
|
202
226
|
let results = listDomains(farms, patterns, restricted, 'status/semantic-vectors.json')
|
|
227
|
+
.filter(({ domain }) => visible(req, domain))
|
|
203
228
|
.map(({ farm, domain }) => {
|
|
204
229
|
let pageCount = null
|
|
205
230
|
try {
|
|
@@ -228,6 +253,11 @@ const startServer = ({ argv, app }) => {
|
|
|
228
253
|
app.get('/system/semantic-vectors.json', (req, res) => {
|
|
229
254
|
cors(res)
|
|
230
255
|
const domain = req.query.domain || req.hostname || 'localhost'
|
|
256
|
+
// Restricted vectors are as private as the pages: titles ride in the file
|
|
257
|
+
// and the vectors themselves leak topic structure.
|
|
258
|
+
if (!visible(req, domain)) {
|
|
259
|
+
return res.status(403).json({ error: `${domain} is restricted` })
|
|
260
|
+
}
|
|
231
261
|
const vecFile = findInFarms(farms, domain, 'status/semantic-vectors.json')
|
|
232
262
|
if (!vecFile) {
|
|
233
263
|
return res.status(404).json({ error: `vectors not found for ${domain}` })
|
|
@@ -293,6 +323,11 @@ const startServer = ({ argv, app }) => {
|
|
|
293
323
|
galaxy: fs.existsSync(galaxyDir)
|
|
294
324
|
? { root: galaxyDir, cache: galaxyCacheStats() }
|
|
295
325
|
: null,
|
|
326
|
+
// Restricted-site policy as this server sees it, and whether THIS
|
|
327
|
+
// caller is trusted — the one-line answer to "why can't I see X?".
|
|
328
|
+
restricted: { names: restricted.size, globs: restricted.globs },
|
|
329
|
+
trustedNets: TRUSTED_NETS,
|
|
330
|
+
callerTrusted: trusted(req),
|
|
296
331
|
})
|
|
297
332
|
})
|
|
298
333
|
|
|
@@ -334,7 +369,7 @@ const startServer = ({ argv, app }) => {
|
|
|
334
369
|
}
|
|
335
370
|
|
|
336
371
|
const page = await buildReport(
|
|
337
|
-
body.query, domains, body.limit || 10,
|
|
372
|
+
body.query, domains, body.limit || 10, ctxFor(req),
|
|
338
373
|
body.threshold ?? null, !!body.live, seedOptsFrom(body))
|
|
339
374
|
if (Array.isArray(body.farms) && body.farms.length) {
|
|
340
375
|
const envelope = {
|
|
@@ -364,7 +399,7 @@ const startServer = ({ argv, app }) => {
|
|
|
364
399
|
const body = await readBody(req)
|
|
365
400
|
if (!body.query) return res.status(400).json({ error: 'query required' })
|
|
366
401
|
res.json(await buildSiteReport(
|
|
367
|
-
body.query, body.domains || ['*'], body.limit || 10,
|
|
402
|
+
body.query, body.domains || ['*'], body.limit || 10, ctxFor(req),
|
|
368
403
|
body.format || null, seedOptsFrom(body)))
|
|
369
404
|
} catch (e) {
|
|
370
405
|
console.error('[wiki-plugin-similarity] site-report error:', e.message)
|
|
@@ -384,7 +419,8 @@ const startServer = ({ argv, app }) => {
|
|
|
384
419
|
const patterns = (req.query.pattern || '*').split(',').map(p => p.trim()).filter(Boolean)
|
|
385
420
|
const limit = parseInt(req.query.limit) || 25
|
|
386
421
|
try {
|
|
387
|
-
res.json(findTwins(farms, patterns, restricted, slug, limit)
|
|
422
|
+
res.json(findTwins(farms, patterns, restricted, slug, limit)
|
|
423
|
+
.filter(t => visible(req, t.domain)))
|
|
388
424
|
} catch (e) {
|
|
389
425
|
res.status(500).json({ error: `title-twins failed: ${e.message}` })
|
|
390
426
|
}
|
|
@@ -408,7 +444,8 @@ const startServer = ({ argv, app }) => {
|
|
|
408
444
|
const localPatterns = patterns.filter(p => !galaxySites.includes(p))
|
|
409
445
|
|
|
410
446
|
const outcome = localPatterns.length
|
|
411
|
-
? searchFarm(farms, localPatterns, restricted, q, limit
|
|
447
|
+
? searchFarm(farms, localPatterns, restricted, q, limit,
|
|
448
|
+
trusted(req) ? null : restricted)
|
|
412
449
|
: { results: [], searched: 0, matched: 0 }
|
|
413
450
|
if (galaxySites.length) {
|
|
414
451
|
const remote = await searchGalaxy(galaxySites, q, limit)
|
package/server/trust.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// wiki-plugin-similarity — who may see restricted sites (CommonJS)
|
|
2
|
+
//
|
|
3
|
+
// Restricted (login-to-view / tailnet-only) sites are hidden from every
|
|
4
|
+
// /system route unless the CALLER is trusted. Three ways to be trusted:
|
|
5
|
+
//
|
|
6
|
+
// 1. an owner/admin session on this wiki (app.securityhandler);
|
|
7
|
+
// 2. a direct loopback connection with no X-Forwarded-For — a tool on the
|
|
8
|
+
// same host (wiki-search, an indexer, a healthcheck);
|
|
9
|
+
// 3. a loopback connection carrying X-Forwarded-For whose first hop is in
|
|
10
|
+
// WIKI_TRUSTED_NETS — the reverse proxy in front of us (Caddy) vouching
|
|
11
|
+
// for a tailnet client. The header is believed ONLY when the direct
|
|
12
|
+
// peer is loopback: a LAN client hitting the wiki port directly cannot
|
|
13
|
+
// forge its way in by adding the header.
|
|
14
|
+
//
|
|
15
|
+
// A cross-origin browser request (Origin host ≠ Host) is never trusted, even
|
|
16
|
+
// from a trusted network: otherwise any web page open in a tailnet member's
|
|
17
|
+
// browser could read private results through the permissive CORS header.
|
|
18
|
+
//
|
|
19
|
+
// Default nets: Tailscale CGNAT + Tailscale ULA + loopback.
|
|
20
|
+
|
|
21
|
+
const net = require('node:net')
|
|
22
|
+
|
|
23
|
+
const DEFAULT_NETS = '100.64.0.0/10,fd7a:115c:a1e0::/48,127.0.0.0/8,::1'
|
|
24
|
+
|
|
25
|
+
const parseNets = (spec = process.env.WIKI_TRUSTED_NETS || DEFAULT_NETS) =>
|
|
26
|
+
String(spec).split(',').map(s => s.trim()).filter(Boolean)
|
|
27
|
+
|
|
28
|
+
// Strip IPv4-mapped IPv6 and zone ids: "::ffff:127.0.0.1" → "127.0.0.1".
|
|
29
|
+
const normalizeIp = ip => {
|
|
30
|
+
if (!ip) return ''
|
|
31
|
+
let s = String(ip).trim()
|
|
32
|
+
const zone = s.indexOf('%')
|
|
33
|
+
if (zone !== -1) s = s.slice(0, zone)
|
|
34
|
+
const m = s.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i)
|
|
35
|
+
return m ? m[1] : s
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const ipToBigInt = ip => {
|
|
39
|
+
const kind = net.isIP(ip)
|
|
40
|
+
if (kind === 4) {
|
|
41
|
+
return ip.split('.').reduce((n, o) => (n << 8n) + BigInt(o), 0n)
|
|
42
|
+
}
|
|
43
|
+
if (kind === 6) {
|
|
44
|
+
// expand :: and any embedded dotted-quad tail
|
|
45
|
+
let [head, tail = ''] = ip.split('::')
|
|
46
|
+
const toGroups = part => part ? part.split(':').flatMap(g => {
|
|
47
|
+
if (g.includes('.')) {
|
|
48
|
+
const v = Number(ipToBigInt(g))
|
|
49
|
+
return [(v >>> 16).toString(16), (v & 0xffff).toString(16)]
|
|
50
|
+
}
|
|
51
|
+
return [g]
|
|
52
|
+
}) : []
|
|
53
|
+
const h = toGroups(head), t = toGroups(tail)
|
|
54
|
+
const groups = ip.includes('::')
|
|
55
|
+
? [...h, ...new Array(8 - h.length - t.length).fill('0'), ...t]
|
|
56
|
+
: h
|
|
57
|
+
if (groups.length !== 8) return null
|
|
58
|
+
return groups.reduce((n, g) => (n << 16n) + BigInt(parseInt(g, 16)), 0n)
|
|
59
|
+
}
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ip within CIDR (or exact address when no prefix). Mixed families never match.
|
|
64
|
+
const inCidr = (ip, cidr) => {
|
|
65
|
+
const [base, bitsRaw] = String(cidr).split('/')
|
|
66
|
+
const a = normalizeIp(ip), b = normalizeIp(base)
|
|
67
|
+
const fa = net.isIP(a), fb = net.isIP(b)
|
|
68
|
+
if (!fa || fa !== fb) return false
|
|
69
|
+
const width = fa === 4 ? 32 : 128
|
|
70
|
+
const bits = bitsRaw === undefined ? width : parseInt(bitsRaw, 10)
|
|
71
|
+
if (!(bits >= 0 && bits <= width)) return false
|
|
72
|
+
const x = ipToBigInt(a), y = ipToBigInt(b)
|
|
73
|
+
if (x === null || y === null) return false
|
|
74
|
+
const shift = BigInt(width - bits)
|
|
75
|
+
return (x >> shift) === (y >> shift)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const inAnyNet = (ip, nets) => nets.some(n => inCidr(ip, n))
|
|
79
|
+
|
|
80
|
+
const LOOPBACK = ['127.0.0.0/8', '::1']
|
|
81
|
+
const isLoopback = ip => inAnyNet(ip, LOOPBACK)
|
|
82
|
+
|
|
83
|
+
// The network side of trust: direct peer address + first X-Forwarded-For hop.
|
|
84
|
+
const trustedNetwork = (req, nets = parseNets()) => {
|
|
85
|
+
const direct = normalizeIp(req.socket && req.socket.remoteAddress)
|
|
86
|
+
if (!isLoopback(direct)) return false
|
|
87
|
+
const xff = String(req.headers['x-forwarded-for'] || '').split(',')[0].trim()
|
|
88
|
+
if (!xff) return true // a tool on this very host
|
|
89
|
+
return inAnyNet(normalizeIp(xff), nets) // the proxy vouches for a client
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Cross-origin browser requests are never trusted (see header comment).
|
|
93
|
+
const crossOrigin = req => {
|
|
94
|
+
const origin = req.headers.origin
|
|
95
|
+
if (!origin) return false
|
|
96
|
+
try { return new URL(origin).host !== String(req.headers.host || '') }
|
|
97
|
+
catch { return true }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Owner/admin session on this wiki. isAuthorized() answers true for an
|
|
101
|
+
// UNCLAIMED site under some security modules, so it only counts when the
|
|
102
|
+
// site actually has an owner file to be authorised against.
|
|
103
|
+
const trustedSession = (req, securityhandler, ownerFileExists) => {
|
|
104
|
+
const sh = securityhandler
|
|
105
|
+
if (!sh) return false
|
|
106
|
+
try { if (sh.isAdmin && sh.isAdmin(req)) return true } catch { /* not admin */ }
|
|
107
|
+
try { if (ownerFileExists && sh.isAuthorized && sh.isAuthorized(req)) return true }
|
|
108
|
+
catch { /* not authorised */ }
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const isTrusted = (req, { securityhandler = null, ownerFileExists = false, nets = parseNets() } = {}) => {
|
|
113
|
+
if (crossOrigin(req)) return false
|
|
114
|
+
if (trustedSession(req, securityhandler, ownerFileExists)) return true
|
|
115
|
+
return trustedNetwork(req, nets)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = { DEFAULT_NETS, parseNets, normalizeIp, inCidr, inAnyNet,
|
|
119
|
+
isLoopback, trustedNetwork, crossOrigin, trustedSession, isTrusted }
|