urma-mcp 0.1.0 → 0.2.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.
Files changed (49) hide show
  1. package/README.md +13 -13
  2. package/dist/src/acquisition/frames.d.ts +1 -1
  3. package/dist/src/acquisition/frames.js +8 -19
  4. package/dist/src/acquisition/frames.js.map +1 -1
  5. package/dist/src/acquisition/media.js +3 -2
  6. package/dist/src/acquisition/media.js.map +1 -1
  7. package/dist/src/acquisition/remote-budget.js +2 -2
  8. package/dist/src/acquisition/remote-budget.js.map +1 -1
  9. package/dist/src/acquisition/transcript.js +70 -12
  10. package/dist/src/acquisition/transcript.js.map +1 -1
  11. package/dist/src/acquisition/video-timing.d.ts +2 -0
  12. package/dist/src/acquisition/video-timing.js +9 -0
  13. package/dist/src/acquisition/video-timing.js.map +1 -1
  14. package/dist/src/core/coverage.d.ts +14 -0
  15. package/dist/src/core/coverage.js +15 -0
  16. package/dist/src/core/coverage.js.map +1 -1
  17. package/dist/src/core/diagnostics.d.ts +1 -1
  18. package/dist/src/core/diagnostics.js +1 -1
  19. package/dist/src/core/diagnostics.js.map +1 -1
  20. package/dist/src/core/search-limits.d.ts +1 -1
  21. package/dist/src/core/search-limits.js +1 -1
  22. package/dist/src/core/search-limits.js.map +1 -1
  23. package/dist/src/distribution/manifest.js +15 -15
  24. package/dist/src/distribution/manifest.js.map +1 -1
  25. package/dist/src/evidence/service.d.ts +65 -0
  26. package/dist/src/evidence/service.js +9 -2
  27. package/dist/src/evidence/service.js.map +1 -1
  28. package/dist/src/mcp/projection.d.ts +1 -1
  29. package/dist/src/mcp/projection.js +1 -1
  30. package/dist/src/mcp/projection.js.map +1 -1
  31. package/dist/src/mcp/schemas.d.ts +160 -0
  32. package/dist/src/mcp/schemas.js +12 -0
  33. package/dist/src/mcp/schemas.js.map +1 -1
  34. package/dist/src/mcp/server.js +22 -18
  35. package/dist/src/mcp/server.js.map +1 -1
  36. package/dist/src/store/sqlite-store.js +25 -5
  37. package/dist/src/store/sqlite-store.js.map +1 -1
  38. package/dist/src/store/store.d.ts +1 -0
  39. package/dist/src/subprocess/runner.js +116 -23
  40. package/dist/src/subprocess/runner.js.map +1 -1
  41. package/dist/src/subprocess/ytdlp.d.ts +2 -2
  42. package/dist/src/subprocess/ytdlp.js +2 -2
  43. package/dist/src/subprocess/ytdlp.js.map +1 -1
  44. package/package.json +6 -7
  45. package/docs/ARCHITECTURE.md +0 -242
  46. package/docs/CONTRIBUTING.md +0 -118
  47. package/docs/EVIDENCE_MODEL.md +0 -350
  48. package/docs/PRODUCT_SCOPE.md +0 -69
  49. package/docs/SECURITY.md +0 -151
@@ -1,350 +0,0 @@
1
- # Evidence model
2
-
3
- <p align="center">
4
- <img src="https://raw.githubusercontent.com/sajidurdev/urma/main/docs/assets/evidence-model.png" alt="Urma evidence model" width="100%" />
5
- </p>
6
-
7
- Urma exposes five tools over MCP. Call `inspect_video` to open a source, then
8
- use its `investigationRef` to request captions or images from that snapshot.
9
-
10
- - [Inspect a video](#inspect_video)
11
- - [Search or read captions](#caption-evidence)
12
- - [Get a visual overview](#overview-semantics)
13
- - [Get frames or a panel](#exact-frames-and-panels)
14
- - [Sample at a fixed cadence](#fixed-cadence-schedules)
15
- - [Read saved resources](#resources-and-state)
16
-
17
- ## Times and references
18
-
19
- Times are nonnegative integer milliseconds from the start of the source.
20
- An interval `[startMs, endMs)` includes its start and excludes its end. It must
21
- satisfy `startMs < endMs`, with `endMs` no later than the video duration. A
22
- point timestamp must be earlier than the duration.
23
-
24
- A source reference identifies a local or remote video. An investigation
25
- reference identifies the snapshot opened by inspection. An artifact reference
26
- identifies saved content, such as a JPEG, by its SHA-256 hash. Treat these
27
- references and pagination cursors as opaque values returned by the server.
28
-
29
- The examples below use placeholder references. Replace them with values from
30
- your calls. Examples with time ranges assume a video at least 30 seconds long.
31
-
32
- ## `inspect_video`
33
-
34
- Open a URL, an allowed local file, or a previously returned `sourceRef`:
35
-
36
- ```json
37
- { "source": "/absolute/path/to/demo.mp4" }
38
- ```
39
-
40
- | Argument | Required | Description |
41
- | --- | --- | --- |
42
- | `source` | Yes | HTTP(S) URL, local path, or `sourceRef`; up to 4,096 characters. |
43
- | `freshness` | No | `reuse` (default) or `refresh`. |
44
-
45
- Inspection returns `sourceRef`, `investigationRef`, source metadata and
46
- validated duration, capabilities, caption tracks, cache information, and
47
- `stateResource`. A source must describe one finite video; see
48
- [Product scope](PRODUCT_SCOPE.md#source-admission) for accepted inputs.
49
-
50
- For a known remote URL or a `sourceRef`, `reuse` opens the latest saved
51
- snapshot. `refresh` resolves the source again. Passing a local path reads its
52
- video and selected caption sidecar again, so changed bytes produce a new
53
- revision. Passing a local `sourceRef` with `reuse` opens its saved snapshot
54
- without rereading the path.
55
-
56
- Each inspection creates a new investigation. Later calls remain tied to its
57
- snapshot, even if another inspection refreshes the source. Caption reads and
58
- overviews are optional; a frame request can follow inspection directly.
59
-
60
- ## Caption evidence
61
-
62
- Urma uses source-provided captions and local sidecars. Supported formats are
63
- JSON3, WebVTT, and SRT. It does not transcribe audio, merge tracks, translate
64
- captions, or correct automatic captions.
65
-
66
- Both caption tools accept an optional `trackRef` from `inspect_video`. When
67
- omitted, Urma prefers manual captions, then local sidecars, automatic captions,
68
- and tracks of unknown kind. Within each kind it prefers the original language,
69
- then English, then stable lexical order. Responses identify the selected track
70
- and its language, kind, display name, and provider identifier when available.
71
-
72
- If no usable track is available, the call fails with `CAPTIONS_UNAVAILABLE`.
73
- That differs from a successful search that returns no matches. Tracks marked
74
- `unknown` may be listed by inspection but cannot be acquired without a known
75
- origin.
76
-
77
- ### `search_transcript`
78
-
79
- Find a phrase in the selected caption track:
80
-
81
- ```json
82
- {
83
- "investigationRef": "urma:investigation:0123456789abcdef0123456789abcdef",
84
- "query": "dark mode"
85
- }
86
- ```
87
-
88
- | Argument | Required | Description |
89
- | --- | --- | --- |
90
- | `investigationRef` | Yes | Reference returned by inspection. |
91
- | `query` or `queries` | Yes | Exactly one query string, or an array of 1–20 query strings. Each query must contain non-whitespace text and be at most 256 characters. |
92
- | `trackRef` | No | Caption track to search. |
93
- | `mode` | No | `phrase` (default) or `terms`. |
94
- | `limit` | No | 1–20 results; default 5. For a batch, applies to each query before merging. |
95
-
96
- Search normalizes Unicode with NFKC, ignores case, and treats separators as
97
- spaces. `phrase` finds a normalized substring. `terms` requires every
98
- normalized term to occur as a substring in the same cue. Matching is literal,
99
- without semantic ranking.
100
-
101
- A single-query hit includes the matching cue's text and timestamps, plus up to
102
- one preceding and one following cue as context. For several queries, use
103
- `queries` instead of `query`:
104
-
105
- ```json
106
- {
107
- "investigationRef": "urma:investigation:0123456789abcdef0123456789abcdef",
108
- "queries": ["dark mode", "settings"],
109
- "limit": 10
110
- }
111
- ```
112
-
113
- Batch searches match each query independently, merge overlapping spans, and
114
- return hits in timestamp order. Each hit includes `matchedQueries`. A batch
115
- returns at most 20 merged hits and 16,000 caption characters.
116
-
117
- Search scans at most 10,000 segments per query and can stop earlier at its
118
- candidate limit. The result reports whether it found and returned every match:
119
-
120
- | Field | Meaning |
121
- | --- | --- |
122
- | `candidateHitCount` | Number of candidates observed. |
123
- | `candidateCountComplete` | Whether the candidate count is complete. |
124
- | `omittedHits` | Exact number omitted when the count is complete; otherwise `null`. |
125
- | `partial` | `true` if the scan was incomplete or result limits omitted matches. |
126
-
127
- `partial: false` means all matches under the selected track's matching rules
128
- were returned. A complete search with no hits means the track contained no
129
- matching cue. It does not rule out uncaptioned speech or a visual event.
130
-
131
- ### `read_transcript`
132
-
133
- Read captions overlapping a time range:
134
-
135
- ```json
136
- {
137
- "investigationRef": "urma:investigation:0123456789abcdef0123456789abcdef",
138
- "startMs": 10000,
139
- "endMs": 20000
140
- }
141
- ```
142
-
143
- | Argument | Required | Description |
144
- | --- | --- | --- |
145
- | `investigationRef` | Yes | Reference returned by inspection. |
146
- | `startMs`, `endMs` | Yes | Half-open time range to read. |
147
- | `trackRef` | No | Caption track to read. |
148
- | `cursor` | No | `nextCursor` from a previous page with the same investigation, range, and track. |
149
-
150
- The result includes `requestedRange`, `returnedRange`, the selected `track`,
151
- and timestamped `segments`. Overlapping cues retain their full timestamps, so
152
- `returnedRange` can extend beyond the requested range.
153
-
154
- A page contains at most 200 segments or 16,000 caption characters. If more
155
- segments remain, `partial` is `true` and `nextCursor` identifies the next page.
156
- Repeat the original arguments with that cursor:
157
-
158
- ```json
159
- {
160
- "investigationRef": "urma:investigation:0123456789abcdef0123456789abcdef",
161
- "startMs": 10000,
162
- "endMs": 20000,
163
- "cursor": "<nextCursor>"
164
- }
165
- ```
166
-
167
- Replace `<nextCursor>` with the returned value. If you selected `trackRef`,
168
- include it again. A cursor cannot be reused for another range or track.
169
-
170
- ## Overview semantics
171
-
172
- ### `get_overview`
173
-
174
- Get a visual overview of a time range:
175
-
176
- ```json
177
- {
178
- "investigationRef": "urma:investigation:0123456789abcdef0123456789abcdef",
179
- "startMs": 10000,
180
- "endMs": 30000
181
- }
182
- ```
183
-
184
- `investigationRef` is required. `startMs` defaults to `0` and `endMs` defaults
185
- to the source duration; omit both for the whole video.
186
-
187
- Urma requests 12 temporally distributed points. A short interval can produce
188
- fewer distinct samples. It prefers a source's native storyboard and otherwise
189
- decodes a copy of the video for navigation.
190
-
191
- The response includes an image panel, its resource link, and a `cells` array
192
- with each sample's timestamp and provenance. `requestedInterval` records the
193
- range requested. `observedCoverage.sampleTimestampsMs` records the returned
194
- samples, and `adjacentSpacingMs` records the gaps between them. Coverage is
195
- `sample-points-only`, with `continuous: false`.
196
-
197
- Current overview timestamps are nominal sample times. An overview is useful
198
- for finding a likely scene; requesting exact frames at its timestamps is a
199
- separate operation. Cell spacing does not guarantee observation of the video
200
- between those cells.
201
-
202
- Repeated overviews report their relationship in `sampling.sampleReuse`:
203
-
204
- | `relation` | Meaning |
205
- | --- | --- |
206
- | `not-scoped` | Whole-source overview. |
207
- | `no-prior-overview` | No comparable overview has been presented in this investigation. |
208
- | `same-samples` | The underlying samples match an earlier overview. |
209
- | `different-subset` | A scoped storyboard selected another subset of the same underlying samples. |
210
- | `new-decoded-samples` | Navigation media produced a new set of decoded samples. |
211
-
212
- `reusedUnderlyingSamples` concerns the images themselves. Reusing downloaded
213
- media alone does not mean an overview shows the same samples.
214
-
215
- ## Exact frames and panels
216
-
217
- ### `get_frames`
218
-
219
- Request specific timestamps:
220
-
221
- ```json
222
- {
223
- "investigationRef": "urma:investigation:0123456789abcdef0123456789abcdef",
224
- "request": { "kind": "points", "timesMs": [10000, 11000] }
225
- }
226
- ```
227
-
228
- Or request evenly spaced samples from an interval:
229
-
230
- ```json
231
- {
232
- "investigationRef": "urma:investigation:0123456789abcdef0123456789abcdef",
233
- "request": { "kind": "burst", "startMs": 10000, "endMs": 12000, "count": 4 },
234
- "presentation": "panel"
235
- }
236
- ```
237
-
238
- | Argument | Required | Description |
239
- | --- | --- | --- |
240
- | `investigationRef` | Yes | Reference returned by inspection. |
241
- | `request` | Yes, for a new request | A `points`, `burst`, or `cadence` request. |
242
- | `presentation` | No | `individual` (default) or `panel`. |
243
- | `cursor` | For cadence continuation | Returned `nextCursor`; omit `request` when using it. |
244
- | `pageSize` | No; cadence only | Targets per page, up to the configured server maximum. |
245
- | `maxTargets` | No; cadence only | Caller limit on the total schedule size, subject to the server limit. |
246
-
247
- `points` accepts 1–12 unique timestamps and preserves their request order.
248
- `burst` accepts a half-open interval and `count` from 2 to 12, returning its
249
- samples in time order. [Cadence requests](#fixed-cadence-schedules) can span
250
- several pages.
251
-
252
- Urma selects the first decodable presentation frame at or after each requested
253
- time. A target need not coincide with a video's frame boundary. Returned
254
- `atMs`, `timestampMs`, and `requestedAtMs` values record the requested times;
255
- they are not measured decoder presentation timestamps.
256
-
257
- With `individual`, the result contains each JPEG and its resource link. With
258
- `panel`, the result contains one combined JPEG with timestamp labels and links
259
- to the individual frame artifacts. A panel preserves request order and is
260
- created only after all requested explicit frames succeed.
261
-
262
- ## Fixed-cadence schedules
263
-
264
- To sample once per second over 30 seconds:
265
-
266
- ```json
267
- {
268
- "investigationRef": "urma:investigation:0123456789abcdef0123456789abcdef",
269
- "request": { "kind": "cadence", "startMs": 0, "endMs": 30000, "cadenceMs": 1000 },
270
- "pageSize": 12
271
- }
272
- ```
273
-
274
- `cadenceMs` must be a positive integer. Targets are `startMs + i * cadenceMs`
275
- for zero-based index `i`, while the target is earlier than `endMs`. This example
276
- has 30 targets, from 0 through 29 seconds. Urma checks the total count before
277
- acquiring frames.
278
-
279
- The default server limits are 12 targets per page and 120 per schedule. Set
280
- `pageSize` or `maxTargets` to impose smaller limits. Server limits are
281
- [configurable](../README.md#acquisition-limits), but pages cannot exceed 12.
282
-
283
- Each page reports the schedule, page indexes, slots, status counts,
284
- `scheduleComplete`, and `nextCursor`. Slots retain their zero-based schedule
285
- index and requested timestamp:
286
-
287
- | Status | Meaning |
288
- | --- | --- |
289
- | `success` | The frame was acquired and returned. |
290
- | `error` | The target failed; `error` contains `code`, `retryable`, and `detail`. |
291
- | `unfinished` | Work for that target did not finish in this page. |
292
-
293
- To continue, pass the investigation and returned cursor without a new request:
294
-
295
- ```json
296
- {
297
- "investigationRef": "urma:investigation:0123456789abcdef0123456789abcdef",
298
- "cursor": "<nextCursor>"
299
- }
300
- ```
301
-
302
- Include `presentation: "panel"` on each call if you want panels. A cadence page
303
- gets a panel only when every slot on that page succeeds. Error and unfinished
304
- slots remain visible in the result and have no frame artifact.
305
-
306
- Cursors are signed for the running server process. A cursor from another
307
- investigation or incompatible snapshot is rejected. After a server restart,
308
- start again with the original cadence request.
309
-
310
- A cadence schedule observes discrete moments. Even a completed schedule can
311
- miss events between targets.
312
-
313
- ## Resources and state
314
-
315
- Successful results provide resource links for saved artifacts. Their URI form
316
- is:
317
-
318
- ```text
319
- urma://investigation/<investigationId>/artifact/<artifactHash>
320
- ```
321
-
322
- An artifact is readable only after it has been returned to that investigation.
323
- Reading the resource returns the same validated bytes. It does not acquire
324
- more frames or add observations. A panel and its individual frames have
325
- separate resources. Missing or corrupt artifacts fail to read; resource reads
326
- do not reacquire them.
327
-
328
- Each tool also returns `stateResource`, with this URI form:
329
-
330
- ```text
331
- urma://investigation/<investigationId>/state
332
- ```
333
-
334
- It reports the investigation's saved source, acquisition, cache, artifact,
335
- and presentation state. Source data can be cached across investigations,
336
- while each investigation records its own returned evidence. A state resource
337
- that exceeds the 32 MiB read limit fails rather than returning truncated JSON.
338
-
339
- ## Errors
340
-
341
- Tool execution failures return `isError: true` with a JSON text object
342
- containing `code`, `retryable`, and `detail`. Cadence requests can report a
343
- failure in one slot while returning successful frames for others.
344
-
345
- Common conditions include disabled local access (`LOCAL_SOURCE_DISABLED`), a
346
- file outside configured roots (`LOCAL_PATH_OUTSIDE_ROOT`), unavailable captions
347
- (`CAPTIONS_UNAVAILABLE`), and an exceeded output limit (`OUTPUT_LIMIT_EXCEEDED`).
348
- Check the error detail and the [configuration](../README.md#configuration)
349
- before retrying. For installation or runtime problems, run
350
- [doctor](../README.md#cli-and-maintenance).
@@ -1,69 +0,0 @@
1
- # Product scope
2
-
3
- <p align="center">
4
- <img src="https://raw.githubusercontent.com/sajidurdev/urma/main/docs/assets/product-scope.png" alt="Urma product scope" width="100%" />
5
- </p>
6
-
7
- Urma retrieves captions and selected frames from finite videos through MCP.
8
- It supports caption search, caption reads, visual overviews, and frame requests
9
- at specific times. The [tool reference](EVIDENCE_MODEL.md) describes those
10
- operations, and the [README](../README.md) covers installation.
11
-
12
- ## Source admission
13
-
14
- ### Remote videos
15
-
16
- Urma uses yt-dlp to resolve public video URLs. A source must resolve to one
17
- video with a positive, finite duration. Supported media forms include
18
- progressive files, finite HLS playlists, and static DASH timelines.
19
-
20
- Compatibility checks cover public fixtures from X / Twitter,
21
- Facebook Reel, Instagram Reel, Loom, Streamable, and Mux-hosted HLS. This is
22
- fixture-based coverage; provider access and delivery formats can change.
23
-
24
- Other providers, including YouTube single-video URLs, are best effort. yt-dlp
25
- support by itself does not guarantee that Urma can inspect or retrieve frames
26
- from a source.
27
-
28
- Urma rejects live and upcoming streams, playlists and other multi-video
29
- results, private videos, sources requiring login or payment, and DRM-protected
30
- media. Remote URLs must use HTTP or HTTPS on the default port, without embedded
31
- credentials, and pass the [network checks](SECURITY.md#remote-url-and-network-boundary).
32
-
33
- ### Local videos
34
-
35
- Local videos must be regular files inside a directory allowed by
36
- `URMA_LOCAL_ROOTS`. Access is disabled when no roots are configured. Resolved
37
- symlink and junction targets must also remain inside an allowed root.
38
-
39
- For a video such as `demo.mp4`, Urma checks for `demo.vtt`, then `demo.srt` in
40
- the same directory. The selected sidecar uses the same access checks as the
41
- video. UNC inputs require `URMA_ALLOW_UNC` as well as an allowed root.
42
-
43
- ## Captions and visual results
44
-
45
- Captions come from the source or a local sidecar. Urma does not transcribe
46
- audio, translate captions, or repair automatic captions. Search performs
47
- literal phrase or term matching in one selected track; it does not use
48
- semantic search.
49
-
50
- Overviews contain up to 12 visual samples. Frame requests return JPEGs at
51
- chosen times, as an ordered burst, or as a paged fixed-cadence schedule.
52
- Sampling can miss events between frames. Urma does not run OCR, object or
53
- action recognition, or model-based video analysis.
54
-
55
- ## Runtime and storage
56
-
57
- Urma runs as a local stdio MCP server on native Node.js 22.16+ (22.x), 24.x,
58
- or 26.x. Supported platforms are Windows x64/arm64, macOS x64/arm64, and Linux
59
- x64/arm64 with glibc. Setup supplies the required FFmpeg, ffprobe, and yt-dlp
60
- versions.
61
-
62
- The installation, SQLite database, and media cache live in a user-owned local
63
- data directory. Network data directories are unsupported. Installed versions
64
- are kept separately so updates can be checked before activation and a retained
65
- previous version can be restored.
66
-
67
- Urma is designed for a single local user and does not provide hosted multi-user
68
- isolation. MCP clients control how the returned text and images are processed,
69
- including whether they are sent to a model service.
package/docs/SECURITY.md DELETED
@@ -1,151 +0,0 @@
1
- # Security
2
-
3
- <p align="center">
4
- <img src="https://raw.githubusercontent.com/sajidurdev/urma/main/docs/assets/security.png" alt="Urma security" width="100%" />
5
- </p>
6
-
7
- Urma is intended for one trusted local user. Its controls restrict file access,
8
- remote destinations, and native-tool execution within the application. They
9
- assume a trusted operating system, Node.js, FFmpeg, ffprobe, and yt-dlp. Urma
10
- is not an operating-system sandbox for a compromised executable.
11
-
12
- ## Local file boundary
13
-
14
- Local input is disabled until `URMA_LOCAL_ROOTS` contains an allowed directory.
15
- For each video or caption sidecar, Urma resolves the file and configured roots
16
- to canonical paths, checks that the file remains inside a root, and requires
17
- a regular file. Symlinks and junctions are checked against their resolved
18
- targets.
19
-
20
- UNC inputs require `URMA_ALLOW_UNC` set to `1` or `true` and must still pass
21
- the root check. This setting applies to source files only. The installation
22
- and persistent data directory must be local and accessible to the current
23
- user. On macOS and Linux, setup also checks that the directory, or its nearest
24
- existing parent, is owned by that user. Urma does not detect network storage
25
- behind mapped drives or mount points.
26
-
27
- Caption sidecars are checked beside the video as `<basename>.vtt`, then
28
- `<basename>.srt`. The selected file is pinned with the video content and uses
29
- the same path checks. The MCP response exposes an opaque source reference
30
- rather than the canonical local path.
31
-
32
- ## Installation integrity
33
-
34
- Setup verifies native archive hashes before extraction. Extraction rejects
35
- absolute and traversal paths, unsafe names, duplicate or case-colliding
36
- entries, and symlink escapes.
37
-
38
- The runtime is staged on the installation filesystem. Before activation, it
39
- must pass native-tool qualification, receipt checks, and a persisted-runtime
40
- MCP smoke test. The receipt records installed component hashes, platform and
41
- policy data, and qualification results.
42
-
43
- Setup uses an installation lock and writes each version to a separate
44
- immutable generation. It writes and flushes a temporary active selector before
45
- atomically replacing `ACTIVE.json`. A failed update leaves the previous
46
- selection in use.
47
-
48
- `rollback` checks the retained installation's integrity and compatibility with
49
- persistent state before selecting it. `recover` restores the selector saved
50
- in `ACTIVE.backup.json`. See the [maintenance commands](../README.md#cli-and-maintenance).
51
-
52
- ## Runtime integrity
53
-
54
- The launcher validates the Node.js version and platform, active selector,
55
- generation receipt, state schema, and recorded executable paths. Runtime and
56
- native-tool paths must identify regular files inside the selected generation;
57
- the recorded Node executable and execution architecture are checked as well.
58
-
59
- Packaged runtimes use the selected generation's FFmpeg, ffprobe, and yt-dlp
60
- paths. Before a native tool is first used in a process, Urma checks its file
61
- against the receipt hash and caches that result for the process. A missing or
62
- modified executable fails the operation; Urma does not fall back to a binary
63
- found on `PATH`.
64
-
65
- ## Remote URL and network boundary
66
-
67
- Remote sources and their subresources must use HTTP or HTTPS on the default
68
- port, without embedded credentials. Urma rejects local or non-public hostnames
69
- and addresses, including private, loopback, link-local, metadata, multicast,
70
- and reserved ranges. DNS results undergo the same address checks as literal
71
- IP addresses.
72
-
73
- The process-local Safe Proxy listens on `127.0.0.1` at an ephemeral port. It
74
- supports HTTP forwarding and HTTPS `CONNECT` without terminating TLS. For
75
- each connection, it checks the URL, resolves the hostname, validates every
76
- returned address, and dials a selected validated address directly.
77
-
78
- Redirects, delivery URLs, manifests, media fragments, captions, and storyboards
79
- all use these checks. Accepting a hostname once does not authorize a different
80
- address on a later resolution. Provider delivery URLs are temporary acquisition
81
- data and are omitted from public source and artifact records.
82
-
83
- Source admission separately checks that the result is one finite video and
84
- rejects live, upcoming, authentication-required, paywalled, DRM-protected, and
85
- multi-entry sources. See [Product scope](PRODUCT_SCOPE.md#source-admission).
86
-
87
- These checks provide an application-level server-side request forgery (SSRF)
88
- boundary. A compromised trusted executable could open a connection outside
89
- that path. Preventing such connections requires operating-system or
90
- infrastructure isolation.
91
-
92
- ## Subprocess controls
93
-
94
- Native tools run with argument arrays, `shell: false`, hidden child windows
95
- on Windows, and an allowlisted environment. Children do not inherit arbitrary
96
- application state, credentials, browser profiles, proxy settings, or runtime
97
- hooks.
98
-
99
- The yt-dlp invocation disables ambient configuration, plugins, cookies,
100
- external execution, cache, remote components, updates, playlist expansion,
101
- and uncontrolled retries or concurrency. Its JavaScript runtime and FFmpeg
102
- location are supplied from the selected installation.
103
-
104
- Remote FFmpeg and ffprobe inputs use an explicit Safe Proxy and the protocol
105
- list `http,https,tcp,tls,httpproxy`. Caller arguments cannot disable the proxy.
106
- For filesystem-backed media, Urma opens a read-only regular file and supplies
107
- a seekable `fd:` input with `-protocol_whitelist fd`. This prevents a local
108
- manifest from resolving nested file or network references through those
109
- wrappers. Setup qualifies seeking and frame extraction, including a check
110
- that a nested DASH file reference is rejected.
111
-
112
- The subprocess runner bounds stdout and stderr and enforces deadlines.
113
- Timeouts and cancellation terminate the process tree. Nonzero exits and
114
- output-limit breaches fail the operation.
115
-
116
- ## Acquisition and cache safety
117
-
118
- Downloads have separate limits for targeted media, navigation media, reusable
119
- media, and wall time. Exceeding a limit stops the process tree and removes
120
- temporary acquisition data.
121
-
122
- Media is checked for video streams, duration, timing, and size before storage.
123
- JPEG output is checked for valid markers. Captions are parsed before their
124
- database transaction; a cue larger than the response ceiling is rejected.
125
- Failed or cancelled acquisition does not create a successful cache record.
126
-
127
- Artifacts are stored by SHA-256. Writes use temporary files and atomic
128
- promotion; reads verify the expected path, size, and hash. A missing or corrupt
129
- artifact fails a resource read. Tool acquisition can replace invalid cached
130
- content, but reading a resource does not trigger reacquisition.
131
-
132
- ## MCP integration
133
-
134
- Caption text, metadata, and images are untrusted source content. Integrations
135
- must keep that content separate from instructions when passing results to a
136
- model. Network validation checks where content comes from; it does not make
137
- captions truthful or source content appropriate to display.
138
-
139
- Tool execution errors include a normalized code, retryability flag, and
140
- redacted detail. URL-shaped diagnostics and local path patterns are redacted
141
- from model-facing error text. Public projections omit raw resolver metadata
142
- and internal transport fields.
143
-
144
- Artifact resources are available only after the artifact has been returned to
145
- that investigation. A guessed hash, cached content, or presentation to another
146
- investigation does not grant access. These checks track returned evidence;
147
- they do not authenticate users or provide multi-user isolation.
148
-
149
- `URMA_DEBUG` and `URMA_DEBUG_FILE` enable diagnostic logs. Treat those logs as
150
- sensitive operational data, protect the selected file, and review logs before
151
- sharing them.