@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 +11 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +52 -7
- package/package.json +2 -2
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-
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
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
|
package/dist/provider.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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
|
-
//
|
|
82
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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.
|
|
40
|
+
"vitest": "4.1.11"
|
|
41
41
|
},
|
|
42
42
|
"repository": {
|
|
43
43
|
"type": "git",
|