fvtt-world-cli 1.1.0 → 1.1.2
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 +37 -36
- package/dist/index.js +12 -2
- package/docs/README.md +5 -61
- package/docs/architecture.md +53 -209
- package/docs/commands.md +166 -444
- package/docs/compatibility.md +13 -22
- package/docs/getting-started.md +76 -74
- package/docs/images/module-settings.png +0 -0
- package/docs/protocol.md +228 -187
- package/docs/security.md +149 -322
- package/docs/skill.md +25 -30
- package/package.json +17 -6
- package/skills/foundry-world-editor/SKILL.md +8 -0
- package/skills/foundry-world-editor/SKILL.md.sha256 +1 -1
- package/bin/sync-installed-skill.mjs +0 -89
- package/docs/images/icon.svg +0 -16
- package/docs/images/mark.svg +0 -15
package/docs/security.md
CHANGED
|
@@ -1,366 +1,193 @@
|
|
|
1
1
|
# Security
|
|
2
2
|
|
|
3
|
-
fvtt-world-cli is a local administration tool.
|
|
4
|
-
|
|
5
|
-
boundary described here.
|
|
3
|
+
`fvtt-world-cli` is a local administration tool. An authenticated caller acts with the connected
|
|
4
|
+
GM client's authority, limited by the commands, permissions, and file rules described here.
|
|
6
5
|
|
|
7
6
|
## Trust boundary
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
authenticates and routes requests but does not grant Foundry permissions. The bridge validates
|
|
12
|
-
requests again and executes them as the connected Foundry user.
|
|
8
|
+
Requests travel through the authenticated loopback daemon to a Foundry GM browser. The daemon
|
|
9
|
+
routes requests; the bridge validates them and executes through Foundry's APIs as the connected GM.
|
|
13
10
|
|
|
14
|
-
The design assumes a trusted local machine and
|
|
15
|
-
|
|
11
|
+
The design assumes a trusted local machine and GM session. Processes running as the same OS user
|
|
12
|
+
may be able to read the CLI credential. A compromised GM browser or installed module is already
|
|
13
|
+
inside the trusted runtime.
|
|
14
|
+
|
|
15
|
+
Reads and search can expose any content the connected GM can see, including material hidden from
|
|
16
|
+
players.
|
|
16
17
|
|
|
17
18
|
## Authentication
|
|
18
19
|
|
|
19
|
-
- The daemon
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- The browser label attached to a pairing is constrained by the protocol schema to 1 to 64
|
|
37
|
-
characters, with whitespace-only values rejected along with control, zero-width,
|
|
38
|
-
bidirectional-override, and Unicode tag characters; a violating request is refused rather than
|
|
39
|
-
repaired. Rejecting a whitespace-only label removes the simplest way to send a label that reads
|
|
40
|
-
as nothing; it is not a guarantee that a label renders visibly, because blank-rendering filler
|
|
41
|
-
characters pass the pattern.
|
|
42
|
-
The client identifier printed beside the label is what reliably distinguishes two browsers, including
|
|
43
|
-
two that share one label. The pairing socket is unauthenticated, so any local process can send a
|
|
44
|
-
pairing request, and an unfiltered label would reach a terminal as an escape-sequence injection or as
|
|
45
|
-
hidden text in agent output. Both approval prompts, the one `auth approve` shows and the one the
|
|
46
|
-
`auth` pairing wait shows, print it as raw text, and JSON output escapes C0 control characters but
|
|
47
|
-
not C1, zero-width, bidirectional-override, or tag ones. The constraint covers the label field only;
|
|
48
|
-
the identity fields printed beside it are described under Known risks.
|
|
49
|
-
- The module removes those same characters from the label it collects in the Authorization window, and
|
|
50
|
-
also normalizes it to NFC, trims it, and caps its length, before the pairing request goes out. That
|
|
51
|
-
sanitizer is a usability layer, not the boundary: it keeps an honest browser from being refused over
|
|
52
|
-
a stray paste, while the schema check in the daemon remains the enforcement point, so a request from
|
|
53
|
-
any other local process is still refused rather than repaired.
|
|
54
|
-
- Browser pairing and bridge sockets require a syntactically valid HTTP(S) Origin, matched exactly
|
|
55
|
-
after pairing. Credentials never appear in URLs, command lines, environment variables, or output.
|
|
56
|
-
- The bridge starts only for a configured GM client. The module hides its settings and category from
|
|
57
|
-
users below the Assistant GM role, and only gamemasters can open its windows. The settings remain
|
|
58
|
-
registered in that browser profile for a later GM session.
|
|
59
|
-
- Another pairing cannot displace the active bridge. A same-pairing reconnect may take over after a
|
|
60
|
-
reload; requests already forwarded to the displaced socket fail immediately as indeterminate.
|
|
61
|
-
Abnormal close retains a 30-second lease against other pairings, while intentional goodbye,
|
|
62
|
-
release, and revocation do not create a lease. Release uses a dedicated terminal close code so the
|
|
63
|
-
released browser cannot automatically reclaim the slot.
|
|
64
|
-
- Only the exact active authenticated bridge socket can send `bridge.goodbye` or revoke its own
|
|
65
|
-
pairing. A socket is not assigned the bridge role or pairing identity until the complete handshake
|
|
66
|
-
has authenticated.
|
|
67
|
-
- Browser Unpair deletes its clear credential only after a correlated successful revocation response.
|
|
68
|
-
On failure it retains the credential for retry; Forget local is a separate action that deliberately
|
|
69
|
-
leaves the daemon-side profile active.
|
|
70
|
-
- Protocol-version or authentication rejection stops that bridge load until the Foundry client is
|
|
71
|
-
reloaded. `BRIDGE_BUSY` is reported separately, preserves the credential, and permits manual Retry
|
|
72
|
-
after the current owner is released.
|
|
73
|
-
- A bridge that loses live GM authority returns correlated `PERMISSION_DENIED` before it closes and
|
|
74
|
-
does not route the denied command, preserving a determinate no-mutation outcome.
|
|
75
|
-
|
|
76
|
-
Loopback reduces exposure but does not replace authentication. Other processes running as the same
|
|
77
|
-
local user may be able to read local configuration or connect to local ports. The daemon accepts only
|
|
78
|
-
the exact normalized Host authority configured for its listener; post-bind publication of a numeric
|
|
79
|
-
runtime address does not broaden that authority or invalidate an explicitly configured `localhost`.
|
|
20
|
+
- The daemon refuses non-loopback listen hosts and validates the exact configured HTTP Host.
|
|
21
|
+
Loopback does not replace authentication.
|
|
22
|
+
- Local clients authenticate with a random credential stored in the per-user configuration.
|
|
23
|
+
The daemon validates the first frame before assigning a socket's role.
|
|
24
|
+
- Each Origin/world/GM/browser pairing has its own bridge credential. The daemon stores only its
|
|
25
|
+
SHA-256 digest; the clear credential stays in client-scoped Foundry storage. Credentials do not
|
|
26
|
+
appear in URLs, command lines, or output.
|
|
27
|
+
- Browser sockets require a valid HTTP(S) Origin that matches the paired Origin exactly. The
|
|
28
|
+
hello must also match the stored browser client id.
|
|
29
|
+
- The bridge requires GM authority. Losing that authority causes command rejection before
|
|
30
|
+
dispatch and ends the connection.
|
|
31
|
+
- Another pairing cannot displace the active bridge. Only the authenticated active socket may
|
|
32
|
+
release ownership or revoke its own pairing.
|
|
33
|
+
|
|
34
|
+
Unpair deletes the browser's credential only after the daemon confirms revocation. *Forget local*
|
|
35
|
+
removes that local credential without revoking the daemon record. Local commands can revoke a
|
|
36
|
+
pairing or rotate the CLI credential; see [Authorization commands](commands.md#authorization-commands).
|
|
80
37
|
|
|
81
38
|
## Command boundary
|
|
82
39
|
|
|
83
|
-
The bridge accepts
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
Mutations use Foundry Document APIs or a reviewed typed Foundry action. Raw live world databases are
|
|
88
|
-
outside the command surface.
|
|
40
|
+
The bridge accepts explicit, schema-validated commands advertised during the handshake. Document
|
|
41
|
+
mutations use Foundry Document APIs; action commands call fixed, reviewed methods. There is no
|
|
42
|
+
generic RPC, arbitrary JavaScript evaluation, or direct access to live world databases.
|
|
43
|
+
Compendium commands cannot edit packs; imports create world documents.
|
|
89
44
|
|
|
90
|
-
|
|
45
|
+
### Validation and protected metadata
|
|
91
46
|
|
|
92
|
-
Closed document
|
|
93
|
-
|
|
47
|
+
Closed document schemas enumerate writable fields. Open schemas allow system and module data but
|
|
48
|
+
sanitize protected metadata on every write and preview. Identity, statistics, authorship, and raw
|
|
49
|
+
ownership cannot be set through ordinary content payloads. Foundry also applies its own DataModel
|
|
50
|
+
and permission checks.
|
|
94
51
|
|
|
95
|
-
Document
|
|
96
|
-
payloads cannot set them. Supported ownership changes use dedicated commands with GM and schema
|
|
97
|
-
checks.
|
|
52
|
+
### Document ownership
|
|
98
53
|
|
|
99
|
-
|
|
100
|
-
|
|
54
|
+
Dedicated `<family>.ownership.set` commands change supported default or per-user access levels.
|
|
55
|
+
Embedded documents often inherit access from their parent.
|
|
101
56
|
|
|
102
57
|
## Permissions and destructive actions
|
|
103
58
|
|
|
104
|
-
The
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
The
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
`COMMAND_DENIED`. Changing it between allow and approve does not create a second approval. A denied,
|
|
135
|
-
expired, or confirmed-cancelled request never runs.
|
|
136
|
-
|
|
137
|
-
The module returns a random 128-bit `approvalId` only to the original caller. Reading or cancelling
|
|
138
|
-
the decision requires that identifier.
|
|
139
|
-
|
|
140
|
-
The GM-only Command Approval window shows the command, remaining time, target documents or managed
|
|
141
|
-
paths, and parameters. It reports binary upload content by size instead of rendering the payload,
|
|
142
|
-
and any text longer than 16,384 characters, including a macro body, by its character count for the
|
|
143
|
-
same reason. A macro long enough to cross that line is therefore approved on its name and origin
|
|
144
|
-
rather than on its code, and `macro.create` runs by itself under the shipped defaults, so a GM who
|
|
145
|
-
wants every body reviewable moves `macro.create` and `macro.update` to approve as well. The command
|
|
146
|
-
envelope has no caller identity, so the window cannot name the requester. The GM approves the
|
|
147
|
-
displayed invocation, not a person or process.
|
|
148
|
-
|
|
149
|
-
## Document ownership
|
|
150
|
-
|
|
151
|
-
Ownership is access policy rather than ordinary document content. Raw `ownership` is excluded from
|
|
152
|
-
normal create and update payloads. Dedicated `<family>.ownership.set` commands change the default or
|
|
153
|
-
per-user level on supported families.
|
|
154
|
-
|
|
155
|
-
Read projections expose ownership only where the public command contract intentionally includes it.
|
|
156
|
-
Embedded documents often derive access from a parent and do not provide an independent ownership
|
|
157
|
-
surface. What each family exposes is described by its own schema and result, not by another
|
|
158
|
-
family's behavior.
|
|
59
|
+
The GM browser assigns commands one of three modes: allow, approve, or deny. Its policy covers
|
|
60
|
+
reads, writes, bulk commands, and dry runs. Permissions belong to that browser profile; a second
|
|
61
|
+
paired browser can have a different policy.
|
|
62
|
+
|
|
63
|
+
By default, code execution, setting writes, and user role or permission changes are denied.
|
|
64
|
+
Destructive operations, GM-client reload, and user creation require approval. The Command permissions
|
|
65
|
+
window shows the full current policy. State checks and internal approval controls remain available;
|
|
66
|
+
daemon-side `auth` commands are outside this policy.
|
|
67
|
+
|
|
68
|
+
The module enforces command permissions at dispatch. An approved request passes the normal guards
|
|
69
|
+
again and cannot run if its command was changed to deny while waiting. A force flag acknowledges
|
|
70
|
+
a command's specific guard but does not bypass permissions, hooks, validation, or file containment.
|
|
71
|
+
|
|
72
|
+
Denial, expiry, or confirmed cancellation prevents execution. Approval does not lock world state,
|
|
73
|
+
and the browser stores decisions only in memory. A disconnected caller's request can remain
|
|
74
|
+
actionable until a decision or expiry. Choose an approval timeout that gives the GM enough time
|
|
75
|
+
without leaving forgotten requests open. See [Approval flow](protocol.md#approval-flow) for
|
|
76
|
+
cancellation and lost-state handling.
|
|
77
|
+
|
|
78
|
+
### What the approval window shows
|
|
79
|
+
|
|
80
|
+
The GM-only window shows the command, targets, parameters, and remaining time. Binary uploads
|
|
81
|
+
appear by size. Text over 16,384 characters, including macro bodies, is replaced by its character
|
|
82
|
+
count. For `macro.execute`, inspect longer bodies in Foundry's macro editor before approving.
|
|
83
|
+
Proposed `macro.create` or `macro.update` content may not exist there yet; inspect it separately
|
|
84
|
+
or decline if the window does not show enough to review.
|
|
85
|
+
|
|
86
|
+
The request carries no caller identity. The GM approves the displayed invocation, but the window
|
|
87
|
+
cannot identify the local person or process that requested it.
|
|
88
|
+
Reading or cancelling a decision requires its opaque approval identifier.
|
|
159
89
|
|
|
160
90
|
## Executable content
|
|
161
91
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
through ordinary writes.
|
|
165
|
-
|
|
166
|
-
- `macro.execute` is the one way to run code, and it runs only stored world macros. It is denied by
|
|
167
|
-
default; a GM who enables it can keep it on approve, where the Command Approval window shows the
|
|
168
|
-
macro's type and its command body, up to the length cap described above, before anything runs.
|
|
169
|
-
The `macro.create → macro.execute → macro.delete` chain is the sanctioned path for ad-hoc code,
|
|
170
|
-
so a GM who wants only vetted macros to run sets `macro.create` and `macro.update` to approve or
|
|
171
|
-
deny while `macro.execute` stays enabled. A script macro that throws fails the command with a
|
|
172
|
-
structured error naming what the macro raised, and the failure is partial by nature: whatever
|
|
173
|
-
the macro changed before it threw stays changed. A macro that catches its own errors still
|
|
174
|
-
reports a `null` return, so results do not prove success and effects deserve a read-back.
|
|
175
|
-
- Macro bodies and chat content written by ordinary document commands are stored, not executed.
|
|
176
|
-
- Action commands invoke only their fixed typed Foundry methods.
|
|
177
|
-
- Ordinary region-behavior writes reject core script- or macro-executing types through a shared
|
|
178
|
-
guard, including on nested behaviors supplied with a region write. The dedicated
|
|
179
|
-
`scene.region.behavior.executable` commands, denied by default, accept exactly the `executeMacro`
|
|
180
|
-
type and require the referenced world macro to exist; the approval window names the macro, the
|
|
181
|
-
trigger events, and whether the behavior fires for everyone. Those commands accept a behavior's
|
|
182
|
-
`system` only in a spelling the guard and that window can both read: a create takes only the plain
|
|
183
|
-
`system` object, an update or clone also accepts single-field `system.<field>` paths, and no route
|
|
184
|
-
accepts both spellings at once, a path deeper than one field (`system.events.<n>`), a
|
|
185
|
-
forced-replacement or forced-deletion operator key, or a read field carrying the wrong type (a
|
|
186
|
-
non-array `events`, a non-boolean `everyone`). Each of those could store a macro reference or a
|
|
187
|
-
trigger set that differs from the one the macro check validated and the approval window showed. An
|
|
188
|
-
`executeMacro` behavior triggers later on player-driven region events, with `everyone: true`
|
|
189
|
-
running the macro on every connected client.
|
|
190
|
-
- `executeScript` behaviors are not supported on any route: Foundry runs their source in every
|
|
191
|
-
connected player's browser with no per-user execution check, which no popup can make reviewable.
|
|
192
|
-
|
|
193
|
-
Installed systems and modules remain trusted Foundry code. They can register behavior types, hooks,
|
|
194
|
-
ActiveEffect interpretations, or other data-driven features whose effects the bridge cannot classify
|
|
195
|
-
universally. Operators must review system/module semantics when authoring content those extensions
|
|
196
|
-
interpret.
|
|
197
|
-
|
|
198
|
-
A typed Foundry action can also trigger existing GM-authored automation. Combat changes, table draws,
|
|
199
|
-
card actions, document hooks, region triggers, and game-system workflows can cause secondary writes
|
|
200
|
-
or chat output after the direct command.
|
|
92
|
+
Code execution requires the GM to enable dedicated commands. Ordinary document writes store macro
|
|
93
|
+
bodies without executing them and reject core executable region behavior types.
|
|
201
94
|
|
|
202
|
-
|
|
95
|
+
### World macros
|
|
203
96
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
module's own secret-bearing settings are redacted from every read.
|
|
207
|
-
|
|
208
|
-
`setting.set` and `setting.set-many` exist because module configuration is a legitimate
|
|
209
|
-
administration task, and they are denied by default because settings can alter global security and
|
|
210
|
-
runtime behavior and frequently invoke module callbacks: a write to `core.permissions` or
|
|
211
|
-
`core.moduleConfiguration` changes what the GM client itself can do or load. Writes go through
|
|
212
|
-
Foundry's own registration and DataField validation, and only registered settings are writable. The
|
|
213
|
-
approval behavior shows the stored value next to the proposed one before a write runs.
|
|
214
|
-
|
|
215
|
-
The command policy stays beyond the CLI's reach by construction: the write commands refuse this
|
|
216
|
-
module's namespace with a structured error in every mode, real, dry-run, and per bulk element
|
|
217
|
-
alike, so no policy setting, credential, or approval timeout can be changed from the surface the
|
|
218
|
-
policy governs. The client-scoped `commandPolicy` and `approvalTimeoutMinutes` settings remain readable
|
|
219
|
-
because they contain no secrets, and only a GM using Foundry changes them: the Command permissions
|
|
220
|
-
window writes the policy, and the main Module Settings form writes the timeout and enforces its
|
|
221
|
-
bounds. Another browser profile or machine has its own values.
|
|
97
|
+
`macro.execute` runs a stored world macro and is denied by default. Approve mode lets the GM review
|
|
98
|
+
each invocation within the window's [text display limit](#what-the-approval-window-shows).
|
|
222
99
|
|
|
223
|
-
|
|
100
|
+
The module captures the macro body and type when approval is requested. The window shows that
|
|
101
|
+
capture, and execution checks it against the stored macro immediately before dispatch. An edit,
|
|
102
|
+
deletion, or missing capture returns `APPROVAL_STALE` without running the macro. Read the current
|
|
103
|
+
macro before requesting fresh approval; follow the [retry rules](protocol.md#delivery-states-and-retries).
|
|
224
104
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
- No command reads or writes `password` or `passwordSalt`. Foundry transmits a set password in
|
|
228
|
-
clear text and hashes it server-side, so a password path through the bridge would expose secrets
|
|
229
|
-
in transcripts and logs; password changes stay in Foundry's own UI.
|
|
230
|
-
- `user.update` edits profile fields and is allowed by default because none of them grant
|
|
231
|
-
authority; the fields are name, color, pronouns, avatar, assigned character, and flags.
|
|
232
|
-
- `user.create` and `user.delete` ask for approval by default. `user.role.set` and
|
|
233
|
-
`user.permissions.set` are denied by default because they change who can do what.
|
|
234
|
-
- A `user.create` carries whatever role it asks for, up to the caller's own, so an approved one can
|
|
235
|
-
mint a gamemaster. That is why it asks for approval rather than running by itself: the approval
|
|
236
|
-
window names the account and the role, including the player role Foundry gives when the command
|
|
237
|
-
asks for none, and the GM reading it is the review point. A GM who does not want that decision
|
|
238
|
-
in the loop at all sets `user.create` to deny.
|
|
239
|
-
- The bridge GM's own account is self-protected: `user.role.set`, `user.permissions.set`, and
|
|
240
|
-
`user.delete` aimed at the user holding the bridge are refused with a structured error, so
|
|
241
|
-
automation cannot demote, remove, or strip the restricted menus from the account it runs through.
|
|
242
|
-
A second GM account carries no such guard; deciding about it is exactly what enabling the command
|
|
243
|
-
means.
|
|
244
|
-
- Foundry's server-side limits stay in force underneath: a role cannot be raised above the caller's
|
|
245
|
-
own, and the last gamemaster account can be neither demoted nor deleted. Those refusals surface
|
|
246
|
-
as permission errors with Foundry's own message.
|
|
247
|
-
|
|
248
|
-
## Search
|
|
249
|
-
|
|
250
|
-
`world.search` indexes selected authored world and optional compendium content in the connected GM
|
|
251
|
-
client. Search results can reveal content visible to that GM, including text not visible to ordinary
|
|
252
|
-
players.
|
|
105
|
+
Enabling execution also permits a caller to create a macro, execute it, then delete it. To restrict
|
|
106
|
+
execution to vetted macros, set `macro.create` and `macro.update` to approve or deny as well.
|
|
253
107
|
|
|
254
|
-
|
|
255
|
-
|
|
108
|
+
A macro timeout does not stop the macro. A thrown error does not undo earlier changes, and a
|
|
109
|
+
macro that catches its errors may return normally. Verify effects before retrying.
|
|
256
110
|
|
|
257
|
-
|
|
111
|
+
### Executable region behaviors
|
|
258
112
|
|
|
259
|
-
|
|
113
|
+
Ordinary behavior writes, including behaviors nested inside a region, reject core executable
|
|
114
|
+
types. The separate `scene.region.behavior.executable` commands are denied by default and accept
|
|
115
|
+
only `executeMacro` with an existing world macro. The approval window shows the macro reference,
|
|
116
|
+
events, and whether it runs for everyone. Payload guards reject ambiguous representations of
|
|
117
|
+
those fields so validation and review refer to the same values.
|
|
260
118
|
|
|
261
|
-
|
|
262
|
-
|
|
119
|
+
These behaviors run on future region events. With `everyone: true`, the macro runs on every
|
|
120
|
+
connected client. Approval authorizes that reference and its future triggers, with no new decision
|
|
121
|
+
for each event. It does not freeze the macro body.
|
|
263
122
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
- `packs/` and all descendants.
|
|
123
|
+
`executeScript` is unsupported on every route because Foundry executes it in player browsers
|
|
124
|
+
without a per-user execution check.
|
|
267
125
|
|
|
268
|
-
|
|
269
|
-
dispatch. Path normalization rejects a segment whose literal or percent-decoded form is `.` or `..`,
|
|
270
|
-
and a segment whose decoded form contains a path separator (so a percent-encoded `/` or `\` cannot
|
|
271
|
-
smuggle traversal inside one segment). Absolute host paths, traversal, sibling-prefix tricks, and
|
|
272
|
-
percent-encoded traversal (`%2e%2e`, `..%2f..`) therefore cannot cross the boundary; this applies to
|
|
273
|
-
every managed-path caller, including `image show`.
|
|
126
|
+
### Existing Foundry automation
|
|
274
127
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
128
|
+
Systems and modules can interpret ActiveEffects, register behavior types, and attach hooks to
|
|
129
|
+
ordinary document changes. Typed actions such as combat transitions, table draws, and card deals
|
|
130
|
+
can trigger existing automation and secondary writes. The bridge cannot classify or undo every
|
|
131
|
+
side effect of trusted Foundry code. This also applies to content imported from compendiums.
|
|
278
132
|
|
|
279
|
-
|
|
280
|
-
command so their intent and permissions remain visible.
|
|
133
|
+
## Settings
|
|
281
134
|
|
|
282
|
-
|
|
135
|
+
Setting writes can change security, loaded modules, or runtime behavior and invoke callbacks.
|
|
136
|
+
Approval shows the stored and proposed values.
|
|
283
137
|
|
|
284
|
-
|
|
285
|
-
|
|
138
|
+
Writes to this module's own namespace are forbidden, including in dry runs and bulk operations.
|
|
139
|
+
A command therefore cannot enable itself, change its approval timeout, or overwrite credentials.
|
|
140
|
+
A GM changes these settings in Foundry, separately for each browser profile.
|
|
286
141
|
|
|
287
|
-
|
|
288
|
-
content can still carry data-driven behavior; importing is not a security audit of the source pack.
|
|
289
|
-
The bridge does not expose arbitrary compendium writes.
|
|
142
|
+
Reads redact this module's secret-bearing settings. Non-secret permission settings remain readable.
|
|
290
143
|
|
|
291
|
-
##
|
|
144
|
+
## Users
|
|
145
|
+
|
|
146
|
+
- Passwords and password salts cannot be read or written. Change passwords in Foundry's UI.
|
|
147
|
+
- Creating a user can grant any role up to the caller's own, including GM. Review the role shown
|
|
148
|
+
in the approval window; set `user.create` to deny if automation should not create accounts.
|
|
149
|
+
- The account holding the bridge cannot delete itself or change its own role or permissions.
|
|
150
|
+
Other GM accounts do not have this bridge-specific protection.
|
|
151
|
+
- Foundry also prevents raising a role above the caller's and demoting or deleting the last GM.
|
|
152
|
+
|
|
153
|
+
## File write boundary
|
|
154
|
+
|
|
155
|
+
File commands use Foundry's managed `data` source. Reads may address managed assets; writes stay
|
|
156
|
+
under the active world's `worlds/<worldId>/` tree and exclude `world.json`, `data/**`, and `packs/**`.
|
|
157
|
+
|
|
158
|
+
Containment checks run before payload decoding and capability dispatch. They examine path segments
|
|
159
|
+
and their percent-decoded forms, rejecting traversal, encoded separators, absolute host paths, and
|
|
160
|
+
sibling-prefix tricks. Managed local paths used by `image show` follow the same normalization rules.
|
|
292
161
|
|
|
293
|
-
The
|
|
294
|
-
|
|
295
|
-
|
|
162
|
+
The CLI may read an explicitly supplied local upload source. It sends bytes and a managed-data
|
|
163
|
+
relative destination; the module cannot read arbitrary operator-machine files. File mutations do
|
|
164
|
+
not rewrite document references. Reference changes require a separate document command.
|
|
165
|
+
|
|
166
|
+
`image show` also accepts off-host HTTP(S) URLs. These make the targeted players' browsers fetch
|
|
167
|
+
and display the remote image; they are outside the managed-file boundary.
|
|
168
|
+
|
|
169
|
+
## Availability and resource limits
|
|
296
170
|
|
|
297
|
-
The module
|
|
298
|
-
|
|
171
|
+
The daemon and module bound uploads, transport frames, search work, batches, and approval queues.
|
|
172
|
+
Oversized requests return structured errors where possible.
|
|
299
173
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
multi-user scheduling.
|
|
174
|
+
These limits do not prevent an authorized caller from exhausting browser resources through many
|
|
175
|
+
expensive operations. The tool assumes cooperative local automation.
|
|
303
176
|
|
|
304
177
|
## Known risks
|
|
305
178
|
|
|
306
|
-
- A stolen
|
|
307
|
-
|
|
308
|
-
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
the rendered identity rather than answer the prompt by reflex. Both commands approve only the request
|
|
322
|
-
whose identity they displayed.
|
|
323
|
-
- A compromised GM browser session or installed Foundry module is already inside the trusted runtime.
|
|
324
|
-
- Foundry, systems, and modules can attach hooks and side effects to otherwise ordinary writes.
|
|
325
|
-
- Timeouts and disconnects can leave delivery indeterminate; a mutation may have committed.
|
|
326
|
-
- Native Foundry batch operations are not transactional and can partially apply.
|
|
327
|
-
- Search and read commands can expose GM-visible world content to the local caller.
|
|
328
|
-
- Large but permitted content can consume browser memory and processing time.
|
|
329
|
-
- An approved command executes when the GM decides, so the world may have changed while it waited.
|
|
330
|
-
Approval does not lock world state.
|
|
331
|
-
- Decisions waiting for a GM live only in that browser session. Reloading the GM client or ending
|
|
332
|
-
its bridge session discards them. Waiting callers receive an indeterminate result.
|
|
333
|
-
- A caller that disappears without a confirmed cancellation leaves its request actionable on the
|
|
334
|
-
GM's screen until the GM decides or the timeout expires. The command envelope carries no client
|
|
335
|
-
identity, so the module cannot tell that the caller is gone.
|
|
336
|
-
- The daemon keeps reservations, approval links, and lost-in-flight idempotency keys in one bounded
|
|
337
|
-
store shared by its clients. It returns `IDEMPOTENCY_STORE_FULL` instead of evicting an
|
|
338
|
-
indeterminate key. Capacity returns when earlier keys settle or expire, or when a daemon restart,
|
|
339
|
-
world switch, or pairing switch clears the store.
|
|
340
|
-
- Command permissions belong to a browser profile. A second paired browser, or the same browser with
|
|
341
|
-
a fresh profile, holds its own permissions, and whichever client holds the bridge is the one whose
|
|
342
|
-
permissions apply.
|
|
343
|
-
- Declarative content can reference existing executable or module-interpreted content.
|
|
344
|
-
- `image show` accepts an arbitrary off-host `http(s)` URL as its source and pushes it to the
|
|
345
|
-
targeted players' clients unchanged, so an enabled caller can make those browsers fetch and display
|
|
346
|
-
a remote image the operator did not host. Only local sources are confined to the managed data
|
|
347
|
-
boundary; a URL source is a display of external content, not a file read.
|
|
348
|
-
- An enabled `macro.execute` or executable-behavior command makes the GM's enablement and approval
|
|
349
|
-
discipline the effective code-review boundary; an enabled `setting.set` can change core settings
|
|
350
|
-
that alter the GM client's own capabilities or take it down until a reload.
|
|
351
|
-
|
|
352
|
-
## Operator guidance
|
|
353
|
-
|
|
354
|
-
- Keep the daemon on loopback, protect the per-user config, and review Origin/world/GM before approval.
|
|
355
|
-
- Confirm the connected world and GM identity before mutation.
|
|
356
|
-
- Use JSON output, dry runs, stable idempotency keys, and post-write reads for automation.
|
|
357
|
-
- Review every outcome of a bulk operation.
|
|
358
|
-
- Treat forwarded timeouts and disconnects as potentially committed.
|
|
359
|
-
- Set the approval timeout to the time a GM realistically needs to answer. A long timeout keeps a
|
|
360
|
-
request actionable long after its caller gave up; a short one refuses work the GM would have
|
|
361
|
-
approved. Either way the expiry never executes the command.
|
|
362
|
-
- Configure command permissions in every browser profile that holds the bridge, and review them
|
|
363
|
-
after an update that adds commands.
|
|
364
|
-
- Review installed systems and modules before authoring automation-sensitive data.
|
|
365
|
-
- Back up important worlds before large migrations.
|
|
366
|
-
- Run the live smoke workflow only in a designated test world.
|
|
179
|
+
- A stolen CLI credential permits use of the active bridge. A stolen bridge credential permits
|
|
180
|
+
authentication with the matching pairing identity and Origin.
|
|
181
|
+
- A local process can forge a pairing request's Origin, browser id, and label. Bare `auth` offers
|
|
182
|
+
the earliest pending request, which may precede the operator's own request. Check the displayed
|
|
183
|
+
identity before answering; approval applies only to the request shown.
|
|
184
|
+
- World titles and GM names arrive in unauthenticated pairing requests without terminal-escape
|
|
185
|
+
restrictions. The CLI prints them as raw text in prompts and profile listings, including
|
|
186
|
+
`auth prune`. A forged value can redraw identity lines, and a stored value can do so again later.
|
|
187
|
+
|
|
188
|
+
## Reporting a vulnerability
|
|
189
|
+
|
|
190
|
+
Suspected vulnerabilities are best reported privately through GitHub security advisories:
|
|
191
|
+
<https://github.com/DroiD16/fvtt-world-cli/security/advisories/new>. A private report leaves time
|
|
192
|
+
to publish a fix before the details are public, so the issue tracker is the wrong place for one.
|
|
193
|
+
Fixes land in the latest release; earlier releases are not patched separately.
|