@lunibee/builders 0.2.3 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunibee/builders",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "module": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -8,6 +8,6 @@
8
8
  ".": "./src/index.ts"
9
9
  },
10
10
  "dependencies": {
11
- "@lunibee/types": "^0.2.3"
11
+ "@lunibee/types": "^0.3.0"
12
12
  }
13
13
  }
package/src/attachment.ts CHANGED
@@ -1,10 +1,16 @@
1
- import { readFile } from "node:fs/promises";
2
- import { basename } from "node:path";
1
+ import { readFile, realpath } from "node:fs/promises";
2
+ import { basename, isAbsolute, relative, resolve } from "node:path";
3
3
 
4
4
  export interface AttachmentData {
5
5
  name: string;
6
6
  description?: string;
7
7
  file: Uint8Array | ArrayBuffer | Blob | Buffer | string;
8
+ /**
9
+ * Folder a string `file` must stay inside (symlinks included). Set it
10
+ * whenever the path is not one you wrote yourself, e.g. from a command
11
+ * option: without it, any readable file on the machine can be uploaded.
12
+ */
13
+ root?: string;
8
14
  }
9
15
 
10
16
  /** Builder for message attachments and file uploads. */
@@ -12,6 +18,8 @@ export class CreateAttachment {
12
18
  public name: string;
13
19
  public description?: string;
14
20
  public file: Uint8Array | ArrayBuffer | Blob | Buffer | string;
21
+ /** Folder a string `file` must stay inside; see {@link AttachmentData.root}. */
22
+ public root?: string;
15
23
 
16
24
  public constructor(
17
25
  file: Uint8Array | ArrayBuffer | Blob | Buffer | string,
@@ -25,6 +33,7 @@ export class CreateAttachment {
25
33
  data?.name ??
26
34
  (typeof file === "string" ? basename(file) : "file.bin");
27
35
  this.description = data?.description;
36
+ this.root = data?.root;
28
37
  }
29
38
  }
30
39
 
@@ -51,7 +60,11 @@ export class CreateAttachment {
51
60
  /** Resolves the attachment payload to a binary Uint8Array. */
52
61
  public async toBuffer(): Promise<Uint8Array> {
53
62
  if (typeof this.file === "string") {
54
- const buffer = await readFile(this.file);
63
+ const path =
64
+ this.root === undefined
65
+ ? this.file
66
+ : await insideRoot(this.root, this.file);
67
+ const buffer = await readFile(path);
55
68
  return new Uint8Array(buffer);
56
69
  }
57
70
  if (this.file instanceof Uint8Array) return this.file;
@@ -69,11 +82,14 @@ export class CreateAttachment {
69
82
  }
70
83
  }
71
84
 
72
- // ─── Deprecated names (0.2.2), removed in 0.3.0 ────────────────────────────────
73
- // Builders are now `CreateX` (`ButtonBuilder` → `CreateButton`); `…Style` became
74
- // `…Type` and `…Type` became `…Enum`.
75
-
76
- /** @deprecated Use {@link CreateAttachment}. Removed in 0.3.0. */
77
- export const AttachmentBuilder = CreateAttachment;
78
- /** @deprecated Use {@link CreateAttachment}. Removed in 0.3.0. */
79
- export type AttachmentBuilder = CreateAttachment;
85
+ /** Resolves `file` against `root`, refusing anything outside it (after following symlinks). */
86
+ async function insideRoot(root: string, file: string): Promise<string> {
87
+ const base = await realpath(root);
88
+ const target = await realpath(resolve(base, file)).catch(() => {
89
+ throw new Error("Attachment file was not found inside its root.");
90
+ });
91
+ const rel = relative(base, target);
92
+ if (rel === "" || rel.startsWith("..") || isAbsolute(rel))
93
+ throw new Error("Attachment file is outside its root folder.");
94
+ return target;
95
+ }