polygram 0.8.0-rc.67 → 0.8.0-rc.68

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "polygram",
4
- "version": "0.8.0-rc.67",
4
+ "version": "0.8.0-rc.68",
5
5
  "description": "Telegram integration for Claude Code that preserves the OpenClaw per-chat session model. Migration target for OpenClaw users. Multi-bot, multi-chat, per-topic isolation; SQLite transcripts; inline-keyboard approvals. Bundles /polygram:status|logs|pair-code|approvals admin commands plus history (transcript queries) and polygram-send (out-of-turn IPC sends with file-upload validation) skills.",
6
6
  "keywords": [
7
7
  "telegram",
@@ -6,6 +6,20 @@
6
6
  * No count cap: per-file (10 MB) and total-size (20 MB) bound resource
7
7
  * usage already; an additional count limit just produces "skipped: max
8
8
  * count" surprises on Telegram albums (up to 10 photos in one send).
9
+ *
10
+ * rc.68 — widened scope:
11
+ * - archives (zip + alt zip MIME some Telegram clients send): containers
12
+ * the agent unpacks via Bash + unzip when downstream tools are gated
13
+ * in. Size caps remain the binding control.
14
+ * - markup the agent reads natively but was silently being denied unless
15
+ * the client happened to ship it as text/plain (markdown, html, yaml,
16
+ * xml). Closes the consistency gap.
17
+ * - extension-fallback path for missing/octet-stream MIME. Telegram's
18
+ * server-side detection degrades to octet-stream (or omits MIME) for
19
+ * extensions it doesn't sniff; the fallback trusts the filename when
20
+ * extension is on the same well-known list. Defense-in-depth: explicit
21
+ * denylisted MIME (e.g. application/x-msdownload) still wins over
22
+ * extension — the fallback only kicks in when MIME is unhelpful.
9
23
  */
10
24
 
11
25
  const MAX_FILE_BYTES = 10 * 1024 * 1024;
@@ -16,12 +30,42 @@ const MIME_ALLOW = [
16
30
  /^application\/msword$/, /^application\/vnd\.openxmlformats-/,
17
31
  /^application\/vnd\.ms-excel$/, /^application\/json$/,
18
32
  /^text\/csv$/,
33
+ // rc.68: archives + markup formats Claude reads natively.
34
+ /^application\/zip$/, /^application\/x-zip-compressed$/,
35
+ /^text\/markdown$/,
36
+ /^text\/html$/,
37
+ /^text\/yaml$/, /^application\/yaml$/, /^application\/x-yaml$/,
38
+ /^application\/xml$/, /^text\/xml$/,
19
39
  ];
20
40
 
41
+ // rc.68: extensions trusted when MIME is missing or generic
42
+ // (application/octet-stream). Same set the explicit MIME list covers, so
43
+ // the fallback is "trust the filename when MIME is unhelpful" — not "any
44
+ // extension goes." A file named foo.exe with empty MIME stays rejected.
45
+ const EXTENSION_ALLOW = new Set([
46
+ // archives
47
+ 'zip',
48
+ // text / structured data
49
+ 'txt', 'md', 'csv', 'json', 'yaml', 'yml', 'xml', 'html',
50
+ // documents
51
+ 'pdf', 'doc', 'docx', 'xls', 'xlsx',
52
+ ]);
53
+ // MIME values that mean "I have no idea what this is" — fall back to
54
+ // extension match for these.
55
+ const FALLBACK_MIMES = new Set(['', 'application/octet-stream']);
56
+
57
+ function extensionOf(name) {
58
+ if (!name) return '';
59
+ const dot = String(name).lastIndexOf('.');
60
+ if (dot < 0 || dot === name.length - 1) return '';
61
+ return name.slice(dot + 1).toLowerCase();
62
+ }
63
+
21
64
  function filterAttachments(attachments, opts = {}) {
22
65
  const maxFileBytes = opts.maxFileBytes ?? MAX_FILE_BYTES;
23
66
  const maxTotalBytes = opts.maxTotalBytes ?? MAX_TOTAL_BYTES;
24
67
  const mimeAllow = opts.mimeAllow ?? MIME_ALLOW;
68
+ const extensionAllow = opts.extensionAllow ?? EXTENSION_ALLOW;
25
69
 
26
70
  const accepted = [];
27
71
  const rejected = [];
@@ -29,7 +73,15 @@ function filterAttachments(attachments, opts = {}) {
29
73
 
30
74
  for (const a of attachments || []) {
31
75
  const mime = a.mime_type || '';
32
- if (!mimeAllow.some((re) => re.test(mime))) {
76
+ const mimeOk = mimeAllow.some((re) => re.test(mime));
77
+ // rc.68: extension fallback only when MIME is unhelpful (empty or
78
+ // octet-stream). An explicit MIME — even one we don't allow — wins
79
+ // over the extension; that keeps malware.zip with mime
80
+ // application/x-msdownload from sneaking through via the .zip suffix.
81
+ const fallbackOk = !mimeOk
82
+ && FALLBACK_MIMES.has(mime)
83
+ && extensionAllow.has(extensionOf(a.name));
84
+ if (!mimeOk && !fallbackOk) {
33
85
  rejected.push({ att: a, reason: `mime not allowed (${mime || 'unknown'})` });
34
86
  continue;
35
87
  }
@@ -55,4 +107,11 @@ function filterAttachments(attachments, opts = {}) {
55
107
  return { accepted, rejected, totalBytes };
56
108
  }
57
109
 
58
- module.exports = { filterAttachments, MAX_FILE_BYTES, MAX_TOTAL_BYTES, MIME_ALLOW };
110
+ module.exports = {
111
+ filterAttachments,
112
+ MAX_FILE_BYTES,
113
+ MAX_TOTAL_BYTES,
114
+ MIME_ALLOW,
115
+ EXTENSION_ALLOW,
116
+ FALLBACK_MIMES,
117
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygram",
3
- "version": "0.8.0-rc.67",
3
+ "version": "0.8.0-rc.68",
4
4
  "description": "Telegram daemon for Claude Code that preserves the OpenClaw per-chat session model. Migration path for OpenClaw users moving to Claude Code.",
5
5
  "main": "lib/ipc-client.js",
6
6
  "bin": {