altweb-context 1.0.2 → 1.1.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/dist/altweb-context.mjs +173 -27
- package/package.json +3 -3
package/dist/altweb-context.mjs
CHANGED
|
@@ -6920,12 +6920,33 @@ if (typeof globalThis.window === "undefined") {
|
|
|
6920
6920
|
}
|
|
6921
6921
|
|
|
6922
6922
|
// ../core/src/crypto/encoding.ts
|
|
6923
|
+
var BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
6923
6924
|
function base64urlDecode(str) {
|
|
6925
|
+
if (!/^[A-Za-z0-9_-]*$/.test(str)) {
|
|
6926
|
+
throw new Error("invalid base64url charset");
|
|
6927
|
+
}
|
|
6928
|
+
const rem = str.length % 4;
|
|
6929
|
+
if (rem === 1) {
|
|
6930
|
+
throw new Error("invalid base64url length");
|
|
6931
|
+
}
|
|
6932
|
+
if (rem !== 0) {
|
|
6933
|
+
const lastValue = BASE64URL_ALPHABET.indexOf(str[str.length - 1]);
|
|
6934
|
+
const unusedBits = rem === 2 ? 15 : 3;
|
|
6935
|
+
if ((lastValue & unusedBits) !== 0) {
|
|
6936
|
+
throw new Error("non-canonical base64url encoding");
|
|
6937
|
+
}
|
|
6938
|
+
}
|
|
6924
6939
|
let base644 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
6925
6940
|
while (base644.length % 4) base644 += "=";
|
|
6926
6941
|
const binary = atob(base644);
|
|
6927
6942
|
return Uint8Array.from(binary, (c) => c.charCodeAt(0));
|
|
6928
6943
|
}
|
|
6944
|
+
function concatBytes(a, b2) {
|
|
6945
|
+
const out = new Uint8Array(a.length + b2.length);
|
|
6946
|
+
out.set(a, 0);
|
|
6947
|
+
out.set(b2, a.length);
|
|
6948
|
+
return out;
|
|
6949
|
+
}
|
|
6929
6950
|
|
|
6930
6951
|
// ../core/src/crypto/key-derivation.ts
|
|
6931
6952
|
var PBKDF2_ITERATIONS = 6e5;
|
|
@@ -6966,10 +6987,25 @@ async function decrypt(payload, password) {
|
|
|
6966
6987
|
}
|
|
6967
6988
|
|
|
6968
6989
|
// ../core/src/crypto/signing.ts
|
|
6990
|
+
var P256_N = BigInt(
|
|
6991
|
+
"0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551"
|
|
6992
|
+
);
|
|
6993
|
+
var P256_HALF_N = P256_N >> 1n;
|
|
6994
|
+
function isLowS(signature) {
|
|
6995
|
+
if (signature.length !== 64) return true;
|
|
6996
|
+
let s = 0n;
|
|
6997
|
+
for (let i = 32; i < 64; i++) {
|
|
6998
|
+
s = s << 8n | BigInt(signature[i]);
|
|
6999
|
+
}
|
|
7000
|
+
return s <= P256_HALF_N;
|
|
7001
|
+
}
|
|
6969
7002
|
async function verify(data, signatureBase64, publicKeyBase64) {
|
|
6970
7003
|
try {
|
|
6971
7004
|
const signature = base64urlDecode(signatureBase64);
|
|
6972
7005
|
const publicKeyData = base64urlDecode(publicKeyBase64);
|
|
7006
|
+
if (!isLowS(signature)) {
|
|
7007
|
+
return false;
|
|
7008
|
+
}
|
|
6973
7009
|
const publicKey = await crypto.subtle.importKey(
|
|
6974
7010
|
"spki",
|
|
6975
7011
|
publicKeyData,
|
|
@@ -11190,30 +11226,39 @@ var pako = {
|
|
|
11190
11226
|
|
|
11191
11227
|
// ../core/src/compression/compress.ts
|
|
11192
11228
|
var MAX_DECOMPRESSED_BYTES = 16 * 1024 * 1024;
|
|
11229
|
+
var MAX_COMPRESSED_BYTES = 17 * 1024 * 1024;
|
|
11230
|
+
var INFLATE_SLICE_BYTES = 64 * 1024;
|
|
11193
11231
|
function decompress(data) {
|
|
11232
|
+
if (data.length > MAX_COMPRESSED_BYTES) {
|
|
11233
|
+
throw new Error(
|
|
11234
|
+
`Compressed payload exceeds the ${MAX_COMPRESSED_BYTES} byte limit`
|
|
11235
|
+
);
|
|
11236
|
+
}
|
|
11194
11237
|
const inflator = new pako.Inflate();
|
|
11195
11238
|
const chunks = [];
|
|
11196
11239
|
let total = 0;
|
|
11197
|
-
let bombed = false;
|
|
11198
11240
|
inflator.onData = (chunk) => {
|
|
11199
11241
|
const bytes = typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);
|
|
11200
11242
|
total += bytes.length;
|
|
11201
|
-
if (total > MAX_DECOMPRESSED_BYTES) {
|
|
11202
|
-
bombed = true;
|
|
11203
|
-
inflator.err = -2;
|
|
11204
|
-
return;
|
|
11205
|
-
}
|
|
11206
11243
|
chunks.push(bytes);
|
|
11207
11244
|
};
|
|
11208
|
-
|
|
11209
|
-
|
|
11210
|
-
inflator.push(data, true);
|
|
11211
|
-
if (bombed) {
|
|
11212
|
-
throw new Error(
|
|
11213
|
-
`Decompressed payload exceeds the ${MAX_DECOMPRESSED_BYTES} byte limit`
|
|
11214
|
-
);
|
|
11245
|
+
if (data.length === 0) {
|
|
11246
|
+
inflator.push(data, true);
|
|
11215
11247
|
}
|
|
11216
|
-
|
|
11248
|
+
for (let offset2 = 0; offset2 < data.length; offset2 += INFLATE_SLICE_BYTES) {
|
|
11249
|
+
const last = offset2 + INFLATE_SLICE_BYTES >= data.length;
|
|
11250
|
+
inflator.push(data.subarray(offset2, offset2 + INFLATE_SLICE_BYTES), last);
|
|
11251
|
+
if (total > MAX_DECOMPRESSED_BYTES) {
|
|
11252
|
+
throw new Error(
|
|
11253
|
+
`Decompressed payload exceeds the ${MAX_DECOMPRESSED_BYTES} byte limit`
|
|
11254
|
+
);
|
|
11255
|
+
}
|
|
11256
|
+
if (inflator.err) {
|
|
11257
|
+
throw new Error(inflator.msg || "invalid deflate stream");
|
|
11258
|
+
}
|
|
11259
|
+
if (inflator.ended) break;
|
|
11260
|
+
}
|
|
11261
|
+
if (inflator.err || !inflator.ended) {
|
|
11217
11262
|
throw new Error(inflator.msg || "invalid deflate stream");
|
|
11218
11263
|
}
|
|
11219
11264
|
const result = new Uint8Array(total);
|
|
@@ -12769,12 +12814,45 @@ function sanitizePage(page) {
|
|
|
12769
12814
|
...page.meta,
|
|
12770
12815
|
title: purify.sanitize(page.meta.title, { ALLOWED_TAGS: [] }),
|
|
12771
12816
|
description: page.meta.description ? purify.sanitize(page.meta.description, { ALLOWED_TAGS: [] }) : void 0,
|
|
12772
|
-
author: page.meta.author ? purify.sanitize(page.meta.author, { ALLOWED_TAGS: [] }) : void 0
|
|
12817
|
+
author: page.meta.author ? purify.sanitize(page.meta.author, { ALLOWED_TAGS: [] }) : void 0,
|
|
12818
|
+
header: page.meta.header ? sanitizeHeader(page.meta.header) : void 0,
|
|
12819
|
+
footer: page.meta.footer ? sanitizeFooter(page.meta.footer) : void 0
|
|
12773
12820
|
},
|
|
12774
12821
|
blocks: page.blocks.map(sanitizeBlock),
|
|
12775
12822
|
style: sanitizeStyle(page.style)
|
|
12776
12823
|
};
|
|
12777
12824
|
}
|
|
12825
|
+
function sanitizeHeader(header) {
|
|
12826
|
+
return {
|
|
12827
|
+
...header,
|
|
12828
|
+
logo: header.logo ? sanitizeImageSrc(header.logo) : void 0,
|
|
12829
|
+
customText: header.customText ? purify.sanitize(header.customText, { ALLOWED_TAGS: [] }) : void 0
|
|
12830
|
+
};
|
|
12831
|
+
}
|
|
12832
|
+
function sanitizeFooter(footer) {
|
|
12833
|
+
return {
|
|
12834
|
+
...footer,
|
|
12835
|
+
copyright: footer.copyright ? purify.sanitize(footer.copyright, { ALLOWED_TAGS: [] }) : void 0,
|
|
12836
|
+
customText: footer.customText ? purify.sanitize(footer.customText, { ALLOWED_TAGS: [] }) : void 0,
|
|
12837
|
+
links: footer.links ? footer.links.map((link) => ({
|
|
12838
|
+
label: purify.sanitize(link.label, { ALLOWED_TAGS: [] }),
|
|
12839
|
+
url: sanitizeUrl(link.url)
|
|
12840
|
+
})) : void 0
|
|
12841
|
+
};
|
|
12842
|
+
}
|
|
12843
|
+
function sanitizeImageSrc(src) {
|
|
12844
|
+
if (src.startsWith("data:")) {
|
|
12845
|
+
return validateDataUri(src);
|
|
12846
|
+
}
|
|
12847
|
+
try {
|
|
12848
|
+
const parsed = new URL(src);
|
|
12849
|
+
if (["http:", "https:"].includes(parsed.protocol)) {
|
|
12850
|
+
return src;
|
|
12851
|
+
}
|
|
12852
|
+
} catch {
|
|
12853
|
+
}
|
|
12854
|
+
return "";
|
|
12855
|
+
}
|
|
12778
12856
|
function sanitizeBlock(block) {
|
|
12779
12857
|
switch (block.t) {
|
|
12780
12858
|
case "h":
|
|
@@ -27584,6 +27662,14 @@ function validatePageStructure(data) {
|
|
|
27584
27662
|
}
|
|
27585
27663
|
|
|
27586
27664
|
// ../core/src/codec/decoder.ts
|
|
27665
|
+
var MAX_ENVELOPE_CHARS = 24 * 1024 * 1024;
|
|
27666
|
+
function assertEnvelopeSize(hash2) {
|
|
27667
|
+
if (hash2.length > MAX_ENVELOPE_CHARS) {
|
|
27668
|
+
throw new ValidationError(
|
|
27669
|
+
`Envelope exceeds the ${MAX_ENVELOPE_CHARS} character limit`
|
|
27670
|
+
);
|
|
27671
|
+
}
|
|
27672
|
+
}
|
|
27587
27673
|
var DecryptionError = class extends Error {
|
|
27588
27674
|
constructor(message) {
|
|
27589
27675
|
super(message);
|
|
@@ -27597,6 +27683,7 @@ var ValidationError = class extends Error {
|
|
|
27597
27683
|
}
|
|
27598
27684
|
};
|
|
27599
27685
|
async function decodePage(hash2, password) {
|
|
27686
|
+
assertEnvelopeSize(hash2);
|
|
27600
27687
|
const envelopeBytes = base64urlDecode(hash2);
|
|
27601
27688
|
const envelopeJson = new TextDecoder().decode(envelopeBytes);
|
|
27602
27689
|
let envelope;
|
|
@@ -27632,7 +27719,8 @@ async function decodePage(hash2, password) {
|
|
|
27632
27719
|
let verified2 = false;
|
|
27633
27720
|
let fingerprint2;
|
|
27634
27721
|
if (envelope.s && envelope.pk) {
|
|
27635
|
-
|
|
27722
|
+
const signedBytes = concatBytes(metaCompressed, blocksCompressed);
|
|
27723
|
+
verified2 = await verify(signedBytes, envelope.s, envelope.pk);
|
|
27636
27724
|
if (verified2) {
|
|
27637
27725
|
fingerprint2 = await computeFingerprint(envelope.pk);
|
|
27638
27726
|
}
|
|
@@ -27706,6 +27794,7 @@ async function decodePage(hash2, password) {
|
|
|
27706
27794
|
}
|
|
27707
27795
|
function isEncryptedContent(hash2) {
|
|
27708
27796
|
try {
|
|
27797
|
+
assertEnvelopeSize(hash2);
|
|
27709
27798
|
const envelopeBytes = base64urlDecode(hash2);
|
|
27710
27799
|
const envelopeJson = new TextDecoder().decode(envelopeBytes);
|
|
27711
27800
|
const envelope = JSON.parse(envelopeJson);
|
|
@@ -27717,6 +27806,9 @@ function isEncryptedContent(hash2) {
|
|
|
27717
27806
|
|
|
27718
27807
|
// ../core/src/codec/inspect.ts
|
|
27719
27808
|
async function inspectArtifact(hash2) {
|
|
27809
|
+
if (hash2.length > MAX_ENVELOPE_CHARS) {
|
|
27810
|
+
throw new ValidationError("Invalid artifact format");
|
|
27811
|
+
}
|
|
27720
27812
|
let envelope;
|
|
27721
27813
|
try {
|
|
27722
27814
|
envelope = JSON.parse(new TextDecoder().decode(base64urlDecode(hash2)));
|
|
@@ -29082,10 +29174,28 @@ var HTML_ENTITIES = {
|
|
|
29082
29174
|
function decodeHtmlEntities(text2) {
|
|
29083
29175
|
return text2.replace(/&(?:amp|lt|gt|quot|#39|apos|nbsp);/g, (match) => HTML_ENTITIES[match] || match);
|
|
29084
29176
|
}
|
|
29177
|
+
function stripTags(html2) {
|
|
29178
|
+
let out = "";
|
|
29179
|
+
let i = 0;
|
|
29180
|
+
while (i < html2.length) {
|
|
29181
|
+
const lt = html2.indexOf("<", i);
|
|
29182
|
+
if (lt === -1) {
|
|
29183
|
+
out += html2.slice(i);
|
|
29184
|
+
break;
|
|
29185
|
+
}
|
|
29186
|
+
out += html2.slice(i, lt);
|
|
29187
|
+
const gt = html2.indexOf(">", lt + 1);
|
|
29188
|
+
if (gt === -1) {
|
|
29189
|
+
out += html2.slice(lt);
|
|
29190
|
+
break;
|
|
29191
|
+
}
|
|
29192
|
+
i = gt + 1;
|
|
29193
|
+
}
|
|
29194
|
+
return out;
|
|
29195
|
+
}
|
|
29085
29196
|
function stripHtml(html2) {
|
|
29086
29197
|
if (!html2) return "";
|
|
29087
|
-
|
|
29088
|
-
return decodeHtmlEntities(stripped);
|
|
29198
|
+
return decodeHtmlEntities(stripTags(html2));
|
|
29089
29199
|
}
|
|
29090
29200
|
function htmlToMarkdown(html2) {
|
|
29091
29201
|
if (!html2) return "";
|
|
@@ -29094,7 +29204,7 @@ function htmlToMarkdown(html2) {
|
|
|
29094
29204
|
md = md.replace(/<(?:em|i)>(.*?)<\/(?:em|i)>/gi, "*$1*");
|
|
29095
29205
|
md = md.replace(/<code>(.*?)<\/code>/gi, "`$1`");
|
|
29096
29206
|
md = md.replace(/<a\s+href="([^"]*)"[^>]*>(.*?)<\/a>/gi, "[$2]($1)");
|
|
29097
|
-
md = md
|
|
29207
|
+
md = stripTags(md);
|
|
29098
29208
|
return decodeHtmlEntities(md);
|
|
29099
29209
|
}
|
|
29100
29210
|
|
|
@@ -47378,16 +47488,52 @@ function isPrivateV4(addr) {
|
|
|
47378
47488
|
a === 169 && b2 === 254 || // link-local / cloud metadata
|
|
47379
47489
|
a === 172 && b2 >= 16 && b2 <= 31 || a === 192 && b2 === 168;
|
|
47380
47490
|
}
|
|
47491
|
+
function parseHextets(addr) {
|
|
47492
|
+
let s = addr;
|
|
47493
|
+
const lastColon = s.lastIndexOf(":");
|
|
47494
|
+
const tail = s.slice(lastColon + 1);
|
|
47495
|
+
if (tail.includes(".")) {
|
|
47496
|
+
if (isIP(tail) !== 4) return null;
|
|
47497
|
+
const [a, b2, c, d] = tail.split(".").map(Number);
|
|
47498
|
+
s = s.slice(0, lastColon + 1) + (a << 8 | b2).toString(16) + ":" + (c << 8 | d).toString(16);
|
|
47499
|
+
}
|
|
47500
|
+
const doubleColons = s.split("::");
|
|
47501
|
+
if (doubleColons.length > 2) return null;
|
|
47502
|
+
const parseGroups = (part) => {
|
|
47503
|
+
if (part === "") return [];
|
|
47504
|
+
const groups = [];
|
|
47505
|
+
for (const g2 of part.split(":")) {
|
|
47506
|
+
if (!/^[0-9a-fA-F]{1,4}$/.test(g2)) return null;
|
|
47507
|
+
groups.push(parseInt(g2, 16));
|
|
47508
|
+
}
|
|
47509
|
+
return groups;
|
|
47510
|
+
};
|
|
47511
|
+
const head = parseGroups(doubleColons[0]);
|
|
47512
|
+
const tailGroups = doubleColons.length === 2 ? parseGroups(doubleColons[1]) : [];
|
|
47513
|
+
if (head === null || tailGroups === null) return null;
|
|
47514
|
+
if (doubleColons.length === 2) {
|
|
47515
|
+
const fill = 8 - head.length - tailGroups.length;
|
|
47516
|
+
if (fill < 1) return null;
|
|
47517
|
+
return [...head, ...new Array(fill).fill(0), ...tailGroups];
|
|
47518
|
+
}
|
|
47519
|
+
return head.length === 8 ? head : null;
|
|
47520
|
+
}
|
|
47521
|
+
function embeddedV4(hi, lo) {
|
|
47522
|
+
return `${hi >> 8}.${hi & 255}.${lo >> 8}.${lo & 255}`;
|
|
47523
|
+
}
|
|
47381
47524
|
function isPrivateAddress(addr) {
|
|
47382
|
-
const lower = addr.toLowerCase();
|
|
47383
|
-
if (lower.startsWith("::ffff:")) {
|
|
47384
|
-
const embedded = lower.slice(7);
|
|
47385
|
-
return isIP(embedded) === 4 ? isPrivateV4(embedded) : true;
|
|
47386
|
-
}
|
|
47387
47525
|
if (isIP(addr) === 4) return isPrivateV4(addr);
|
|
47388
|
-
|
|
47389
|
-
|
|
47390
|
-
|
|
47526
|
+
const h = parseHextets(addr);
|
|
47527
|
+
if (h === null) return true;
|
|
47528
|
+
if (h.slice(0, 6).every((x2) => x2 === 0)) return true;
|
|
47529
|
+
if (h.slice(0, 5).every((x2) => x2 === 0) && h[5] === 65535) {
|
|
47530
|
+
return isPrivateV4(embeddedV4(h[6], h[7]));
|
|
47531
|
+
}
|
|
47532
|
+
if ((h[0] & 65472) === 65152) return true;
|
|
47533
|
+
if ((h[0] & 65024) === 64512) return true;
|
|
47534
|
+
if (h[0] === 100 && h[1] === 65435) return true;
|
|
47535
|
+
if (h[0] === 8194) return true;
|
|
47536
|
+
return false;
|
|
47391
47537
|
}
|
|
47392
47538
|
function guardedLookup(hostname4, options, callback) {
|
|
47393
47539
|
lookup(hostname4, { ...options, all: true }, (err2, addresses) => {
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "altweb-context",
|
|
3
3
|
"mcpName": "software.altweb/altweb-context",
|
|
4
|
-
"version": "1.0
|
|
5
|
-
"description": "MCP server that loads ALTWEB signed context capsules into AI agents
|
|
4
|
+
"version": "1.1.0",
|
|
5
|
+
"description": "MCP server that loads ALTWEB signed context capsules into AI agents \u2014 verifies first, refuses unsigned, tampered, or untrusted context by default. Verify before you inject.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "AGPL-3.0-or-later",
|
|
8
|
-
"author": "Daniel C.
|
|
8
|
+
"author": "Daniel C. \u0218OIMU",
|
|
9
9
|
"homepage": "https://altweb.software",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|