Commit 16640c3
feat: FUSE mount for relayfile VFS with permission enforcement (#13)
* feat: add FUSE mount for relayfile VFS with permission enforcement
Adds a FUSE-based filesystem mount backed by the relayfile HTTP API.
Every syscall (open, read, write, readdir, stat) becomes an API call
with a scoped Bearer token. Ignored files return ENOENT, readonly
files return EPERM on write. Includes local LRU cache with TTL and
WebSocket-based cache invalidation for real-time multi-agent sync.
- internal/mountfuse/: fs.go, dir.go, file.go, client.go, cache.go, wsinvalidate.go
- cmd/relayfile-mount: --fuse flag and --mode=fuse support
- Build tag //go:build !nofuse for conditional compilation
- Tests for cache and FUSE operations
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* ci: auto-create v* tag on publish to trigger release workflow
publish.yml now creates both sdk-v* and v* tags. The v* tag
triggers release.yml which builds Go binaries, publishes npm SDK,
and pushes Docker images — matching the relay repo's auto-release pattern.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* sync fixes
* fix: address 4 review finding(s)
wsinvalidate.go: Fix WebSocket endpoint path /fs/events/ws -> /fs/ws
dir.go: Return fuse.FOPEN_KEEP_CACHE instead of raw open flags
file.go: Remove duplicate putFile call before invalidate
file.go: Add RLock/RUnlock in fillEntry for thread safety
Co-Authored-By: My Senior Dev <dev@myseniordev.com>
* acl fixes
* fix: parse ACL permissions from file content when semantics field is empty
The bulk write endpoint doesn't persist the semantics field, so ACL
markers store permissions in the file content as JSON. The aclGetFile
function now falls back to parsing content when semantics.permissions
is empty.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* mount updates
* fix core package
* fix: address 12 review finding(s)
wsinvalidate.go: backoff reset after stable connection, ws/wss scheme support
file.go: writeGen generation counter to prevent flush/Write race condition
webhooks.ts: move nextRevision() after size check to avoid wasting revisions
dir.go, file.go fillEntry, wsinvalidate.go endpoint: previously resolved
Co-Authored-By: My Senior Dev <dev@myseniordev.com>
* publish updates
* feat: add permission denial logging to mount sync client
When a write is denied (403) or a file is reverted, the mount client
now writes to .relay/permissions-denied.log with timestamp, action,
file path, and reason. Agents can check this log for details.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: revert readonly files when agent modifies via chmod bypass
When a readonly file's hash differs from the tracked hash (agent
used chmod to bypass 444 and wrote to it), the sync client now
fetches the original content from relayfile and overwrites the
local file, restoring both content and chmod 444 permissions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update HTTP 429 test expectation to match EAGAIN mapping
The mapError change correctly maps HTTP 429 to syscall.EAGAIN (retryable)
instead of syscall.EIO (fatal), but the test still expected EIO.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address 14 security and reliability findings across 4 groups
Group 1 - HTTP API security/ACL hardening:
- Log warnings when using default dev secrets (server.go)
- Add ACL rule value validation with regex patterns (acl.go)
- Extend aclCheckPath for tree/query_files routes (server.go)
- Document TOCTOU-safe single-snapshot ACL resolution (server.go)
- Add 12 path normalization tests and 18 validation tests (acl_test.go)
Group 2 - SDK/core package security:
- Make webhook signature verification required by default (webhooks.ts)
- Move bearer token from WebSocket URL to auth message (sync.ts)
Group 3 - FUSE mount reliability:
- Add inode cache size limit with eviction (fs.go)
- Add WS read limit, auth error detection, max failure cap (wsinvalidate.go)
- Map HTTP 429 to EAGAIN instead of EIO (client.go)
- Enforce maxFileBytes limit on write buffer (file.go)
Group 4 - Mount command cleanup:
- Cancel derived context on FUSE exit to clean up WS (fuse_mount.go)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update sync test to match token-in-auth-message change
The test expected the bearer token in the WebSocket URL query string,
but commit 1f87f7b moved it to a post-open auth message for security.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: mount syncer scope parsing, ReadOnly latch, .relay exclusion, and root version
- Rewrite scopeGrantsWrite to recognize manage/wildcard scopes
(plane:resource:action:path segments), matching server-side scopeMatches
- Remove ReadOnly OR-latch so permissions reflect fresh scope evaluation
- Add conflicted entry after applyWriteDenied to prevent pullRemote override
- Skip .relay directory in scanLocalFiles walk
- Add version field to root package.json for CI npm version compatibility
Fixes: 3000084339, 3000241814, 3000241892, 3000241978
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: fsnotify, path-aware scopes, canReadPath, agentdeny, full test coverage
- fsnotify file watcher for instant local change detection (replaces polling)
- Path-aware scopeMatchesPath in auth.go (fixes .env leak — token with
relayfile:fs:read:/src/app.ts no longer grants read to all files)
- canReadPath in mount client filters pulls by token scopes
- .agentdeny command filter via shell preexec hook
- Readonly revert in pushSingleFile when hash differs
- Full test coverage: scope matching, read filtering, write rejection,
agentdeny, fsnotify watcher
- Fixed TestWriteRejectionRevertsFile to use read-only scopes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* permission test coverage
* devin feedback
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>1 parent 7210558 commit 16640c3
File tree
35 files changed
+6197
-284
lines changed- .claude
- .github/workflows
- cmd
- mountsync_repro
- relayfile-mount
- internal
- httpapi
- mountfuse
- mountsync
- packages
- core
- src
- sdk/src
35 files changed
+6197
-284
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
6 | 16 | | |
7 | 17 | | |
8 | 18 | | |
| |||
56 | 66 | | |
57 | 67 | | |
58 | 68 | | |
| 69 | + | |
59 | 70 | | |
60 | 71 | | |
61 | 72 | | |
| |||
74 | 85 | | |
75 | 86 | | |
76 | 87 | | |
77 | | - | |
78 | 88 | | |
79 | 89 | | |
80 | 90 | | |
81 | | - | |
82 | 91 | | |
83 | 92 | | |
84 | | - | |
| 93 | + | |
85 | 94 | | |
86 | | - | |
87 | 95 | | |
88 | 96 | | |
89 | 97 | | |
| |||
109 | 117 | | |
110 | 118 | | |
111 | 119 | | |
112 | | - | |
113 | | - | |
114 | | - | |
115 | | - | |
116 | | - | |
117 | | - | |
118 | | - | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
119 | 149 | | |
120 | 150 | | |
121 | 151 | | |
122 | 152 | | |
123 | | - | |
| 153 | + | |
124 | 154 | | |
125 | | - | |
126 | | - | |
127 | | - | |
128 | | - | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
129 | 158 | | |
130 | 159 | | |
131 | | - | |
132 | | - | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
133 | 163 | | |
134 | 164 | | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
135 | 174 | | |
136 | 175 | | |
137 | 176 | | |
| |||
146 | 185 | | |
147 | 186 | | |
148 | 187 | | |
149 | | - | |
150 | | - | |
151 | | - | |
152 | | - | |
153 | | - | |
| 188 | + | |
| 189 | + | |
154 | 190 | | |
155 | 191 | | |
156 | 192 | | |
157 | 193 | | |
158 | | - | |
159 | | - | |
160 | | - | |
161 | | - | |
162 | | - | |
163 | | - | |
164 | | - | |
165 | | - | |
166 | | - | |
167 | | - | |
168 | | - | |
169 | | - | |
170 | | - | |
171 | | - | |
172 | | - | |
173 | | - | |
174 | | - | |
175 | | - | |
176 | | - | |
177 | 194 | | |
178 | 195 | | |
179 | | - | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
180 | 205 | | |
181 | 206 | | |
182 | | - | |
183 | | - | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
184 | 238 | | |
185 | 239 | | |
186 | | - | |
187 | | - | |
| 240 | + | |
| 241 | + | |
188 | 242 | | |
189 | | - | |
| 243 | + | |
190 | 244 | | |
191 | | - | |
| 245 | + | |
192 | 246 | | |
193 | 247 | | |
194 | | - | |
195 | | - | |
| 248 | + | |
196 | 249 | | |
197 | 250 | | |
198 | 251 | | |
199 | | - | |
| 252 | + | |
200 | 253 | | |
201 | | - | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
202 | 258 | | |
203 | 259 | | |
204 | 260 | | |
| |||
210 | 266 | | |
211 | 267 | | |
212 | 268 | | |
213 | | - | |
214 | | - | |
215 | | - | |
216 | | - | |
217 | | - | |
| 269 | + | |
| 270 | + | |
218 | 271 | | |
219 | 272 | | |
220 | 273 | | |
| |||
224 | 277 | | |
225 | 278 | | |
226 | 279 | | |
227 | | - | |
| 280 | + | |
228 | 281 | | |
229 | 282 | | |
230 | 283 | | |
| |||
234 | 287 | | |
235 | 288 | | |
236 | 289 | | |
237 | | - | |
238 | 290 | | |
239 | 291 | | |
240 | 292 | | |
| |||
248 | 300 | | |
249 | 301 | | |
250 | 302 | | |
251 | | - | |
252 | 303 | | |
253 | | - | |
254 | | - | |
| 304 | + | |
| 305 | + | |
255 | 306 | | |
256 | 307 | | |
257 | 308 | | |
258 | 309 | | |
259 | | - | |
| 310 | + | |
260 | 311 | | |
261 | 312 | | |
262 | | - | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
263 | 319 | | |
264 | 320 | | |
265 | 321 | | |
266 | 322 | | |
| 323 | + | |
267 | 324 | | |
268 | 325 | | |
269 | 326 | | |
270 | 327 | | |
271 | 328 | | |
272 | 329 | | |
273 | | - | |
274 | 330 | | |
275 | 331 | | |
276 | 332 | | |
277 | 333 | | |
278 | 334 | | |
279 | 335 | | |
280 | | - | |
| 336 | + | |
281 | 337 | | |
282 | 338 | | |
283 | 339 | | |
| |||
286 | 342 | | |
287 | 343 | | |
288 | 344 | | |
289 | | - | |
| 345 | + | |
290 | 346 | | |
291 | 347 | | |
292 | 348 | | |
293 | 349 | | |
294 | | - | |
295 | 350 | | |
296 | 351 | | |
297 | 352 | | |
298 | 353 | | |
299 | 354 | | |
300 | 355 | | |
301 | | - | |
| 356 | + | |
| 357 | + | |
302 | 358 | | |
303 | | - | |
304 | | - | |
305 | | - | |
306 | | - | |
307 | | - | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
2 | | - | |
3 | | - | |
4 | | - | |
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
0 commit comments