@molecule/api-media-streaming-hls 1.0.1 → 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/README.md CHANGED
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
3
  Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
4
  Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
5
  To change this document, edit the module-level JSDoc in src/index.ts.
6
- Generated: 2026-08-04T01:48:38.551Z
6
+ Generated: 2026-09-18T06:08:25.367Z
7
7
  -->
8
8
 
9
9
  # @molecule/api-media-streaming-hls
@@ -218,6 +218,16 @@ Peer dependencies:
218
218
 
219
219
  - `@molecule/api-media-streaming`
220
220
 
221
+ - **String inputs must be local absolute file paths.** `createStream()` and
222
+ `transcode()` accept `Buffer | string`; a string is passed straight to
223
+ ffmpeg's `-i`, and ffmpeg natively fetches `http(s)`, `tcp`, `tls`, and
224
+ more — so an unvalidated string would make the bond an SSRF / file-read
225
+ primitive acting with your server's network position. Strings that
226
+ contain `://`, carry a `scheme:` prefix, or are not absolute paths are
227
+ rejected with a thrown error. For remote media, fetch the bytes yourself
228
+ (with your own SSRF guard) and pass a `Buffer`. As defense-in-depth the
229
+ ffmpeg protocol whitelist is trimmed to local protocols (`file,crypto`) —
230
+ network protocols are never enabled.
221
231
  - **Requires the `ffmpeg` binary on the host** (resolved via PATH, or set
222
232
  `createProvider({ ffmpegPath })`). A missing binary fails at first
223
233
  `createStream()`/`transcode()` call with `spawn ffmpeg ENOENT` — verify
package/dist/index.d.ts CHANGED
@@ -20,6 +20,16 @@
20
20
  * ```
21
21
  *
22
22
  * @remarks
23
+ * - **String inputs must be local absolute file paths.** `createStream()` and
24
+ * `transcode()` accept `Buffer | string`; a string is passed straight to
25
+ * ffmpeg's `-i`, and ffmpeg natively fetches `http(s)`, `tcp`, `tls`, and
26
+ * more — so an unvalidated string would make the bond an SSRF / file-read
27
+ * primitive acting with your server's network position. Strings that
28
+ * contain `://`, carry a `scheme:` prefix, or are not absolute paths are
29
+ * rejected with a thrown error. For remote media, fetch the bytes yourself
30
+ * (with your own SSRF guard) and pass a `Buffer`. As defense-in-depth the
31
+ * ffmpeg protocol whitelist is trimmed to local protocols (`file,crypto`) —
32
+ * network protocols are never enabled.
23
33
  * - **Requires the `ffmpeg` binary on the host** (resolved via PATH, or set
24
34
  * `createProvider({ ffmpegPath })`). A missing binary fails at first
25
35
  * `createStream()`/`transcode()` call with `spawn ffmpeg ENOENT` — verify
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,cAAc,oBAAoB,CAAA;AAClC,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAEH,cAAc,oBAAoB,CAAA;AAClC,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA"}
package/dist/index.js CHANGED
@@ -20,6 +20,16 @@
20
20
  * ```
21
21
  *
22
22
  * @remarks
23
+ * - **String inputs must be local absolute file paths.** `createStream()` and
24
+ * `transcode()` accept `Buffer | string`; a string is passed straight to
25
+ * ffmpeg's `-i`, and ffmpeg natively fetches `http(s)`, `tcp`, `tls`, and
26
+ * more — so an unvalidated string would make the bond an SSRF / file-read
27
+ * primitive acting with your server's network position. Strings that
28
+ * contain `://`, carry a `scheme:` prefix, or are not absolute paths are
29
+ * rejected with a thrown error. For remote media, fetch the bytes yourself
30
+ * (with your own SSRF guard) and pass a `Buffer`. As defense-in-depth the
31
+ * ffmpeg protocol whitelist is trimmed to local protocols (`file,crypto`) —
32
+ * network protocols are never enabled.
23
33
  * - **Requires the `ffmpeg` binary on the host** (resolved via PATH, or set
24
34
  * `createProvider({ ffmpegPath })`). A missing binary fails at first
25
35
  * `createStream()`/`transcode()` call with `spawn ffmpeg ENOENT` — verify
@@ -1 +1 @@
1
- {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,OAAO,KAAK,EACV,iBAAiB,EAOlB,MAAM,+BAA+B,CAAA;AAGtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAwD3C;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,SAAQ,SAAc,KAAG,iBA8JvD,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,iBAAoC,CAAA"}
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,OAAO,KAAK,EACV,iBAAiB,EAOlB,MAAM,+BAA+B,CAAA;AAGtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAoG3C;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,SAAQ,SAAc,KAAG,iBAmKvD,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,iBAAoC,CAAA"}
package/dist/provider.js CHANGED
@@ -10,11 +10,48 @@
10
10
  import { execFile } from 'node:child_process';
11
11
  import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
12
12
  import { tmpdir } from 'node:os';
13
- import { join } from 'node:path';
13
+ import { isAbsolute, join } from 'node:path';
14
14
  import { promisify } from 'node:util';
15
15
  import { generateMasterPlaylist, generateMediaPlaylist } from './m3u8.js';
16
16
  import { assertSafePathComponent, assertSegmentIndex, resolveWithinBase } from './validate.js';
17
17
  const execFileAsync = promisify(execFile);
18
+ /**
19
+ * Matches a URL scheme prefix (`scheme:`) the way ffmpeg's own protocol
20
+ * handler resolution does — including scheme-only forms like `http:host/x`
21
+ * that carry no `//`.
22
+ */
23
+ const URL_SCHEME_PREFIX = /^[a-z][a-z0-9+.-]*:/i;
24
+ /**
25
+ * Asserts that a caller-supplied string input is a LOCAL ABSOLUTE FILE PATH.
26
+ *
27
+ * String inputs are passed verbatim as ffmpeg's `-i` argument, and ffmpeg
28
+ * natively speaks `http`, `https`, `tcp`, `tls`, `concat`, `gopher`, and
29
+ * more. Forwarding an unvalidated string therefore turns this bond into an
30
+ * SSRF / file-read primitive: `http://169.254.169.254/…` (cloud metadata),
31
+ * `http://intra-host/…`, or any other URL the host can reach, executed by
32
+ * ffmpeg with the server's network position. We reject:
33
+ *
34
+ * - anything containing `://` (absolute URLs),
35
+ * - anything with a `scheme:` prefix (ffmpeg accepts `http:host/x` too),
36
+ * - anything that is not an absolute path (`/…`) — a relative path would
37
+ * resolve against ffmpeg's CWD, not the caller's.
38
+ *
39
+ * Applications that need remote media must fetch the bytes themselves (with
40
+ * their own SSRF guard) and pass a `Buffer`.
41
+ *
42
+ * Module-private: not part of the package's public export surface.
43
+ *
44
+ * @param inputPath - The caller-supplied string input.
45
+ * @returns The validated path, unchanged.
46
+ * @throws {Error} When the string is not a local absolute file path.
47
+ */
48
+ const assertLocalInputPath = (inputPath) => {
49
+ if (inputPath.includes('://') || URL_SCHEME_PREFIX.test(inputPath) || !isAbsolute(inputPath)) {
50
+ throw new Error(`Invalid media input: string inputs must be local absolute file paths (got ${JSON.stringify(inputPath)}). ` +
51
+ 'Fetch remote media yourself (with an SSRF guard) and pass a Buffer instead — ffmpeg URLs are rejected to prevent server-side request forgery.');
52
+ }
53
+ return inputPath;
54
+ };
18
55
  let streamCounter = 0;
19
56
  /**
20
57
  * Generates a unique stream identifier.
@@ -34,7 +71,10 @@ const generateStreamId = () => {
34
71
  */
35
72
  const prepareInput = async (input, dir) => {
36
73
  if (typeof input === 'string') {
37
- return input;
74
+ // SSRF guard: a string is treated as a local absolute FILE path only —
75
+ // ffmpeg speaks http/tcp/… natively, so an unvalidated string is a
76
+ // fetch-anything primitive. Remote media must arrive as a Buffer.
77
+ return assertLocalInputPath(input);
38
78
  }
39
79
  const inputPath = join(dir, 'input.tmp');
40
80
  await writeFile(inputPath, input);
@@ -78,10 +118,13 @@ export const createProvider = (config = {}) => {
78
118
  await mkdir(outputDir, { recursive: true });
79
119
  const inputPath = await prepareInput(input, outputDir);
80
120
  await execFileAsync(ffmpegPath, [
81
- // Restrict ffmpeg to safe protocols so a string input can't abuse dangerous
82
- // ones (concat/gopher/ftp/subfile/unix) for LFI/SSRF escalation. [P5BONDS DiD]
121
+ // Defense-in-depth behind the string-input SSRF guard: restrict ffmpeg
122
+ // to LOCAL protocols only (file, crypto). Network protocols (http,
123
+ // https, tcp, tls) are deliberately NOT whitelisted — a string input
124
+ // must be a local absolute file path (see assertLocalInputPath), so
125
+ // nothing legitimate ever needs network access here. [P5BONDS DiD]
83
126
  '-protocol_whitelist',
84
- 'file,crypto,data,http,https,tcp,tls',
127
+ 'file,crypto',
85
128
  '-i',
86
129
  inputPath,
87
130
  '-codec',
@@ -128,9 +171,11 @@ export const createProvider = (config = {}) => {
128
171
  await mkdir(profileDir, { recursive: true });
129
172
  const codec = profile.codec ?? 'h264';
130
173
  const args = [
131
- // Restrict ffmpeg to safe protocols (no concat/gopher/ftp/subfile). [P5BONDS DiD]
174
+ // Defense-in-depth behind the string-input SSRF guard: local
175
+ // protocols only — see the createStream whitelist comment.
176
+ // [P5BONDS DiD]
132
177
  '-protocol_whitelist',
133
- 'file,crypto,data,http,https,tcp,tls',
178
+ 'file,crypto',
134
179
  '-i',
135
180
  inputPath,
136
181
  '-c:v',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@molecule/api-media-streaming-hls",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "HLS media streaming provider for molecule.dev — ffmpeg-based segmentation, transcoding, and M3U8 playlist generation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -37,7 +37,7 @@
37
37
  "@molecule/api-media-streaming": "1.0.1",
38
38
  "@types/node": "26.1.2",
39
39
  "typescript": "6.0.3",
40
- "vitest": "4.1.10"
40
+ "vitest": "4.1.11"
41
41
  },
42
42
  "repository": {
43
43
  "type": "git",