altweb-context 1.0.3 → 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.
@@ -6920,7 +6920,22 @@ 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);
@@ -6972,10 +6987,25 @@ async function decrypt(payload, password) {
6972
6987
  }
6973
6988
 
6974
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
+ }
6975
7002
  async function verify(data, signatureBase64, publicKeyBase64) {
6976
7003
  try {
6977
7004
  const signature = base64urlDecode(signatureBase64);
6978
7005
  const publicKeyData = base64urlDecode(publicKeyBase64);
7006
+ if (!isLowS(signature)) {
7007
+ return false;
7008
+ }
6979
7009
  const publicKey = await crypto.subtle.importKey(
6980
7010
  "spki",
6981
7011
  publicKeyData,
@@ -11196,30 +11226,39 @@ var pako = {
11196
11226
 
11197
11227
  // ../core/src/compression/compress.ts
11198
11228
  var MAX_DECOMPRESSED_BYTES = 16 * 1024 * 1024;
11229
+ var MAX_COMPRESSED_BYTES = 17 * 1024 * 1024;
11230
+ var INFLATE_SLICE_BYTES = 64 * 1024;
11199
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
+ }
11200
11237
  const inflator = new pako.Inflate();
11201
11238
  const chunks = [];
11202
11239
  let total = 0;
11203
- let bombed = false;
11204
11240
  inflator.onData = (chunk) => {
11205
11241
  const bytes = typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);
11206
11242
  total += bytes.length;
11207
- if (total > MAX_DECOMPRESSED_BYTES) {
11208
- bombed = true;
11209
- inflator.err = -2;
11210
- return;
11211
- }
11212
11243
  chunks.push(bytes);
11213
11244
  };
11214
- inflator.onEnd = () => {
11215
- };
11216
- inflator.push(data, true);
11217
- if (bombed) {
11218
- throw new Error(
11219
- `Decompressed payload exceeds the ${MAX_DECOMPRESSED_BYTES} byte limit`
11220
- );
11245
+ if (data.length === 0) {
11246
+ inflator.push(data, true);
11221
11247
  }
11222
- if (inflator.err) {
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) {
11223
11262
  throw new Error(inflator.msg || "invalid deflate stream");
11224
11263
  }
11225
11264
  const result = new Uint8Array(total);
@@ -12775,12 +12814,45 @@ function sanitizePage(page) {
12775
12814
  ...page.meta,
12776
12815
  title: purify.sanitize(page.meta.title, { ALLOWED_TAGS: [] }),
12777
12816
  description: page.meta.description ? purify.sanitize(page.meta.description, { ALLOWED_TAGS: [] }) : void 0,
12778
- 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
12779
12820
  },
12780
12821
  blocks: page.blocks.map(sanitizeBlock),
12781
12822
  style: sanitizeStyle(page.style)
12782
12823
  };
12783
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
+ }
12784
12856
  function sanitizeBlock(block) {
12785
12857
  switch (block.t) {
12786
12858
  case "h":
@@ -27590,6 +27662,14 @@ function validatePageStructure(data) {
27590
27662
  }
27591
27663
 
27592
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
+ }
27593
27673
  var DecryptionError = class extends Error {
27594
27674
  constructor(message) {
27595
27675
  super(message);
@@ -27603,6 +27683,7 @@ var ValidationError = class extends Error {
27603
27683
  }
27604
27684
  };
27605
27685
  async function decodePage(hash2, password) {
27686
+ assertEnvelopeSize(hash2);
27606
27687
  const envelopeBytes = base64urlDecode(hash2);
27607
27688
  const envelopeJson = new TextDecoder().decode(envelopeBytes);
27608
27689
  let envelope;
@@ -27713,6 +27794,7 @@ async function decodePage(hash2, password) {
27713
27794
  }
27714
27795
  function isEncryptedContent(hash2) {
27715
27796
  try {
27797
+ assertEnvelopeSize(hash2);
27716
27798
  const envelopeBytes = base64urlDecode(hash2);
27717
27799
  const envelopeJson = new TextDecoder().decode(envelopeBytes);
27718
27800
  const envelope = JSON.parse(envelopeJson);
@@ -27724,6 +27806,9 @@ function isEncryptedContent(hash2) {
27724
27806
 
27725
27807
  // ../core/src/codec/inspect.ts
27726
27808
  async function inspectArtifact(hash2) {
27809
+ if (hash2.length > MAX_ENVELOPE_CHARS) {
27810
+ throw new ValidationError("Invalid artifact format");
27811
+ }
27727
27812
  let envelope;
27728
27813
  try {
27729
27814
  envelope = JSON.parse(new TextDecoder().decode(base64urlDecode(hash2)));
@@ -29089,10 +29174,28 @@ var HTML_ENTITIES = {
29089
29174
  function decodeHtmlEntities(text2) {
29090
29175
  return text2.replace(/&(?:amp|lt|gt|quot|#39|apos|nbsp);/g, (match) => HTML_ENTITIES[match] || match);
29091
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
+ }
29092
29196
  function stripHtml(html2) {
29093
29197
  if (!html2) return "";
29094
- const stripped = html2.replace(/<[^>]*>/g, "");
29095
- return decodeHtmlEntities(stripped);
29198
+ return decodeHtmlEntities(stripTags(html2));
29096
29199
  }
29097
29200
  function htmlToMarkdown(html2) {
29098
29201
  if (!html2) return "";
@@ -29101,7 +29204,7 @@ function htmlToMarkdown(html2) {
29101
29204
  md = md.replace(/<(?:em|i)>(.*?)<\/(?:em|i)>/gi, "*$1*");
29102
29205
  md = md.replace(/<code>(.*?)<\/code>/gi, "`$1`");
29103
29206
  md = md.replace(/<a\s+href="([^"]*)"[^>]*>(.*?)<\/a>/gi, "[$2]($1)");
29104
- md = md.replace(/<[^>]*>/g, "");
29207
+ md = stripTags(md);
29105
29208
  return decodeHtmlEntities(md);
29106
29209
  }
29107
29210
 
@@ -47385,16 +47488,52 @@ function isPrivateV4(addr) {
47385
47488
  a === 169 && b2 === 254 || // link-local / cloud metadata
47386
47489
  a === 172 && b2 >= 16 && b2 <= 31 || a === 192 && b2 === 168;
47387
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
+ }
47388
47524
  function isPrivateAddress(addr) {
47389
- const lower = addr.toLowerCase();
47390
- if (lower.startsWith("::ffff:")) {
47391
- const embedded = lower.slice(7);
47392
- return isIP(embedded) === 4 ? isPrivateV4(embedded) : true;
47393
- }
47394
47525
  if (isIP(addr) === 4) return isPrivateV4(addr);
47395
- return lower === "::1" || lower === "::" || lower.startsWith("fe80:") || // link-local
47396
- lower.startsWith("fc") || // ULA fc00::/7
47397
- lower.startsWith("fd");
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;
47398
47537
  }
47399
47538
  function guardedLookup(hostname4, options, callback) {
47400
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.3",
5
- "description": "MCP server that loads ALTWEB signed context capsules into AI agents verifies first, refuses unsigned, tampered, or untrusted context by default. Verify before you inject.",
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. ȘOIMU",
8
+ "author": "Daniel C. \u0218OIMU",
9
9
  "homepage": "https://altweb.software",
10
10
  "repository": {
11
11
  "type": "git",