fileport-gateway 0.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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -0
  3. package/THIRD_PARTY_NOTICES.md +12 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +87 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/client.d.ts +1 -0
  8. package/dist/client.js +22 -0
  9. package/dist/client.js.map +1 -0
  10. package/dist/config.d.ts +2 -0
  11. package/dist/config.js +30 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/duration.d.ts +1 -0
  14. package/dist/duration.js +14 -0
  15. package/dist/duration.js.map +1 -0
  16. package/dist/gateway.d.ts +20 -0
  17. package/dist/gateway.js +188 -0
  18. package/dist/gateway.js.map +1 -0
  19. package/dist/paths.d.ts +8 -0
  20. package/dist/paths.js +88 -0
  21. package/dist/paths.js.map +1 -0
  22. package/dist/proxy.d.ts +2 -0
  23. package/dist/proxy.js +29 -0
  24. package/dist/proxy.js.map +1 -0
  25. package/dist/registry.d.ts +19 -0
  26. package/dist/registry.js +93 -0
  27. package/dist/registry.js.map +1 -0
  28. package/dist/save.d.ts +36 -0
  29. package/dist/save.js +165 -0
  30. package/dist/save.js.map +1 -0
  31. package/dist/types.d.ts +18 -0
  32. package/dist/types.js +2 -0
  33. package/dist/types.js.map +1 -0
  34. package/dist/ui.d.ts +2 -0
  35. package/dist/ui.js +20 -0
  36. package/dist/ui.js.map +1 -0
  37. package/dist/workers.d.ts +23 -0
  38. package/dist/workers.js +110 -0
  39. package/dist/workers.js.map +1 -0
  40. package/docs/CURRENT_ARCHITECTURE.md +69 -0
  41. package/docs/DUFS_COMPATIBILITY.md +38 -0
  42. package/docs/MIGRATION_PLAN.md +53 -0
  43. package/package.json +69 -0
  44. package/scripts/dufs-compatibility.mjs +32 -0
  45. package/scripts/install-dufs.sh +15 -0
  46. package/vendor/LICENSE-DUFS-APACHE +201 -0
  47. package/vendor/LICENSE-DUFS-MIT +21 -0
  48. package/vendor/dufs +0 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fileport contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # Fileport
2
+
3
+ Fileport is a clean-room Agent Artifact Gateway backed by the official [Dufs](https://github.com/sigoden/dufs) filesystem engine.
4
+
5
+ It turns a local file or directory into an opaque capability URL while keeping Dufs workers on Unix sockets and enforcing the share boundary again at the Gateway.
6
+
7
+ ## Phase 1 status
8
+
9
+ Implemented:
10
+
11
+ - official Dufs 0.46.0 pin + compatibility contract;
12
+ - persistent capability shares;
13
+ - read-only Unix Socket workers with root reuse and crash restart;
14
+ - reverse proxy with GET/HEAD/Range;
15
+ - traversal, encoded-path and symlink-escape protection;
16
+ - CLI and browser preview shell;
17
+ - explicit RO/RW shares, expiry and strict existing-file-only atomic saves.
18
+
19
+ See [current architecture](docs/CURRENT_ARCHITECTURE.md), [Dufs compatibility](docs/DUFS_COMPATIBILITY.md), and [migration plan](docs/MIGRATION_PLAN.md).
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ npm install --global fileport-gateway
25
+ fileport share /absolute/path/to/report.md --readonly
26
+ ```
27
+
28
+ The npm package includes the checksum-verified official Dufs 0.46.0 Linux x86_64 binary. Source development uses `npm ci && npm run vendor:dufs && npm run build`.
29
+
30
+ ## Run
31
+
32
+ ```bash
33
+ export FILEPORT_PUBLIC_BASE_URL="http://<devbox-ip>:7892"
34
+ fileport serve
35
+ ```
36
+
37
+ In another shell:
38
+
39
+ ```bash
40
+ fileport share /absolute/path/to/report.md
41
+ fileport ls
42
+ fileport revoke <share-id>
43
+ ```
44
+
45
+ Fileport provides HTTP only; `FILEPORT_PUBLIC_BASE_URL` rejects `https://`. The public service defaults to `0.0.0.0:7892`. Agent control is available only through `$FILEPORT_STATE_DIR/control.sock` (0600).
46
+
47
+ ## Verify
48
+
49
+ ```bash
50
+ npm run typecheck
51
+ FILEPORT_TEST_DUFS=vendor/dufs npm test
52
+ FILEPORT_DUFS_BIN=vendor/dufs node scripts/dufs-compatibility.mjs
53
+ ```
54
+
55
+ ## Security boundary
56
+
57
+ Dufs always remains read-only and never receives network exposure or write flags. RO is the default. An explicit RW share can modify only an existing allowlisted text file through Fileport's versioned, atomic Save API. Fileport rejects Home/filesystem-root/common credential targets; capability requests are canonicalized and checked against the exact share scope before proxying.
@@ -0,0 +1,12 @@
1
+ # Third-party notices
2
+
3
+ ## Dufs
4
+
5
+ Fileport Gateway packages the official Linux x86_64 musl binary of [Dufs](https://github.com/sigoden/dufs), version 0.46.0.
6
+
7
+ - Copyright: Dufs contributors
8
+ - License: Apache License 2.0 or MIT, at your option
9
+ - Source tag: https://github.com/sigoden/dufs/tree/v0.46.0
10
+ - Release archive SHA256: `817769f726613194bcff9d0e3e481eaccc86ac11208857614f36a8c02f410977`
11
+
12
+ The corresponding license texts are included in `vendor/LICENSE-DUFS-APACHE` and `vendor/LICENSE-DUFS-MIT`.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { controlRequest } from "./client.js";
7
+ import { loadConfig } from "./config.js";
8
+ import { Gateway } from "./gateway.js";
9
+ import { parseDuration } from "./duration.js";
10
+ const config = loadConfig();
11
+ const controlSocket = path.join(config.stateDir, "control.sock");
12
+ async function waitForControl() { const deadline = Date.now() + 5000; while (Date.now() < deadline) {
13
+ try {
14
+ await controlRequest(controlSocket, "GET", "/shares");
15
+ return;
16
+ }
17
+ catch {
18
+ await new Promise((r) => setTimeout(r, 50));
19
+ }
20
+ } throw new Error("gateway did not become ready"); }
21
+ async function ensureServer() {
22
+ try {
23
+ await controlRequest(controlSocket, "GET", "/shares");
24
+ return;
25
+ }
26
+ catch { }
27
+ await fs.mkdir(config.stateDir, { recursive: true, mode: 0o700 });
28
+ const executable = fileURLToPath(import.meta.url);
29
+ const child = spawn(process.execPath, [executable, "serve"], { detached: true, stdio: "ignore", env: process.env });
30
+ child.unref();
31
+ await waitForControl();
32
+ }
33
+ async function main() {
34
+ const [command, ...args] = process.argv.slice(2);
35
+ if (command === "serve") {
36
+ const gateway = new Gateway(config);
37
+ await gateway.start();
38
+ console.log(`fileport listening on ${config.host}:${config.port}`);
39
+ const stop = async () => { await gateway.close(); process.exit(0); };
40
+ process.on("SIGINT", stop);
41
+ process.on("SIGTERM", stop);
42
+ return;
43
+ }
44
+ if (command === "share") {
45
+ let permission = "ro";
46
+ let expire;
47
+ const positional = [];
48
+ for (let index = 0; index < args.length; index++) {
49
+ const arg = args[index];
50
+ if (arg === "--rw")
51
+ permission = "rw";
52
+ else if (arg === "--readonly" || arg === "--ro")
53
+ permission = "ro";
54
+ else if (arg === "--expire") {
55
+ expire = args[++index];
56
+ if (!expire)
57
+ throw new Error("--expire requires a duration");
58
+ }
59
+ else if (arg.startsWith("-"))
60
+ throw new Error(`unknown option: ${arg}`);
61
+ else
62
+ positional.push(arg);
63
+ }
64
+ if (positional.length !== 1)
65
+ throw new Error("usage: fileport share <path> [--readonly|--rw] [--expire 2h]");
66
+ await ensureServer();
67
+ const result = await controlRequest(controlSocket, "POST", "/shares", { path: positional[0], permission, expiresAt: expire ? parseDuration(expire) : undefined });
68
+ console.log(result.url);
69
+ return;
70
+ }
71
+ if (command === "ls") {
72
+ await ensureServer();
73
+ console.log(JSON.stringify(await controlRequest(controlSocket, "GET", "/shares"), null, 2));
74
+ return;
75
+ }
76
+ if (command === "revoke") {
77
+ if (!args[0])
78
+ throw new Error("usage: fileport revoke <share-id>");
79
+ await ensureServer();
80
+ await controlRequest(controlSocket, "DELETE", `/shares/${encodeURIComponent(args[0])}`);
81
+ return;
82
+ }
83
+ console.error("usage: fileport <serve|share|ls|revoke>");
84
+ process.exitCode = 2;
85
+ }
86
+ main().catch((error) => { console.error(error instanceof Error ? error.message : error); process.exitCode = 1; });
87
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;AAAC,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;AAC9F,KAAK,UAAU,cAAc,KAAoB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;IAAC,IAAI,CAAC;QAAC,MAAM,cAAc,CAAC,aAAa,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;QAAC,OAAO;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IAAC,CAAC;AAAC,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC,CAAC,CAAC;AACvS,KAAK,UAAU,YAAY;IACzB,IAAI,CAAC;QAAC,MAAM,cAAc,CAAC,aAAa,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;QAAC,OAAO;IAAC,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;IAC/E,MAAM,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAClE,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAAC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,UAAU,EAAE,OAAO,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IAAC,MAAM,cAAc,EAAE,CAAC;AAChN,CAAC;AACD,KAAK,UAAU,IAAI;IACjB,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACjD,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;QAAC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;QAAC,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QAAC,OAAO,CAAC,GAAG,CAAC,yBAAyB,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QAAC,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QAAC,OAAO;IAAC,CAAC;IACnS,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;QACxB,IAAI,UAAU,GAAgB,IAAI,CAAC;QAAC,IAAI,MAA0B,CAAC;QAAC,MAAM,UAAU,GAAa,EAAE,CAAC;QACpG,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;YACjD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;YACxB,IAAI,GAAG,KAAK,MAAM;gBAAE,UAAU,GAAG,IAAI,CAAC;iBACjC,IAAI,GAAG,KAAK,YAAY,IAAI,GAAG,KAAK,MAAM;gBAAE,UAAU,GAAG,IAAI,CAAC;iBAC9D,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;gBAAC,MAAM,GAAG,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC;gBAAC,IAAI,CAAC,MAAM;oBAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;YAAC,CAAC;iBACjH,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,GAAG,EAAE,CAAC,CAAC;;gBACnE,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;QACD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAC;QAC7G,MAAM,YAAY,EAAE,CAAC;QAAC,MAAM,MAAM,GAAG,MAAM,cAAc,CAAe,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;QAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAAC,OAAO;IACzO,CAAC;IACD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QAAC,MAAM,YAAY,EAAE,CAAC;QAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,cAAc,CAAC,aAAa,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAAC,OAAO;IAAC,CAAC;IACpJ,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;QAAC,MAAM,YAAY,EAAE,CAAC;QAAC,MAAM,cAAc,CAAC,aAAa,EAAE,QAAQ,EAAE,WAAW,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAAC,OAAO;IAAC,CAAC;IACxN,OAAO,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAAC,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACjF,CAAC;AACD,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function controlRequest<T>(socketPath: string, method: string, path: string, body?: unknown): Promise<T>;
package/dist/client.js ADDED
@@ -0,0 +1,22 @@
1
+ import http from "node:http";
2
+ export function controlRequest(socketPath, method, path, body) {
3
+ return new Promise((resolve, reject) => {
4
+ const data = body === undefined ? undefined : JSON.stringify(body);
5
+ const req = http.request({ socketPath, method, path, headers: data ? { "content-type": "application/json", "content-length": Buffer.byteLength(data) } : undefined }, (res) => {
6
+ const chunks = [];
7
+ res.on("data", (chunk) => chunks.push(chunk));
8
+ res.on("end", () => { const text = Buffer.concat(chunks).toString("utf8"); if ((res.statusCode || 500) >= 400)
9
+ return reject(new Error(text)); try {
10
+ resolve(JSON.parse(text));
11
+ }
12
+ catch (error) {
13
+ reject(error);
14
+ } });
15
+ });
16
+ req.on("error", reject);
17
+ if (data)
18
+ req.write(data);
19
+ req.end();
20
+ });
21
+ }
22
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,UAAU,cAAc,CAAI,UAAkB,EAAE,MAAc,EAAE,IAAY,EAAE,IAAc;IAChG,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,IAAI,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACnE,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE;YAC5K,MAAM,MAAM,GAAa,EAAE,CAAC;YAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;YAAC,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,IAAI,GAAG;gBAAE,OAAO,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;gBAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAM,CAAC,CAAC;YAAC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACzS,CAAC,CAAC,CAAC;QAAC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAAC,IAAI,IAAI;YAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAAC,GAAG,CAAC,GAAG,EAAE,CAAC;IACpE,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { FileportConfig } from "./types.js";
2
+ export declare function loadConfig(env?: NodeJS.ProcessEnv): FileportConfig;
package/dist/config.js ADDED
@@ -0,0 +1,30 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ function integer(value, fallback) {
5
+ if (!value)
6
+ return fallback;
7
+ const result = Number(value);
8
+ if (!Number.isInteger(result) || result < 1 || result > 65535)
9
+ throw new Error(`invalid port: ${value}`);
10
+ return result;
11
+ }
12
+ export function loadConfig(env = process.env) {
13
+ const stateHome = env.XDG_STATE_HOME || path.join(os.homedir(), ".local", "state");
14
+ let publicBaseUrl = env.FILEPORT_PUBLIC_BASE_URL;
15
+ if (publicBaseUrl) {
16
+ const parsed = new URL(publicBaseUrl);
17
+ if (parsed.protocol !== "http:")
18
+ throw new Error("Fileport supports HTTP only; FILEPORT_PUBLIC_BASE_URL must use http://");
19
+ publicBaseUrl = publicBaseUrl.replace(/\/$/, "");
20
+ }
21
+ const packagedDufs = fileURLToPath(new URL("../vendor/dufs", import.meta.url));
22
+ return {
23
+ stateDir: path.resolve(env.FILEPORT_STATE_DIR || path.join(stateHome, "fileport")),
24
+ host: env.FILEPORT_HOST || "0.0.0.0",
25
+ port: integer(env.FILEPORT_PORT, 7892),
26
+ dufsBin: path.resolve(env.FILEPORT_DUFS_BIN || packagedDufs),
27
+ publicBaseUrl,
28
+ };
29
+ }
30
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,SAAS,OAAO,CAAC,KAAyB,EAAE,QAAgB;IAC1D,IAAI,CAAC,KAAK;QAAE,OAAO,QAAQ,CAAC;IAC5B,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,IAAI,MAAM,GAAG,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,KAAK,EAAE,CAAC,CAAC;IACzG,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAyB,OAAO,CAAC,GAAG;IAC7D,MAAM,SAAS,GAAG,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IACnF,IAAI,aAAa,GAAG,GAAG,CAAC,wBAAwB,CAAC;IACjD,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,aAAa,CAAC,CAAC;QACtC,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,wEAAwE,CAAC,CAAC;QAC3H,aAAa,GAAG,aAAa,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACnD,CAAC;IACD,MAAM,YAAY,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,gBAAgB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/E,OAAO;QACL,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;QAClF,IAAI,EAAE,GAAG,CAAC,aAAa,IAAI,SAAS;QACpC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC;QACtC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,YAAY,CAAC;QAC5D,aAAa;KACd,CAAC;AACJ,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function parseDuration(value: string, now?: number): string;
@@ -0,0 +1,14 @@
1
+ export function parseDuration(value, now = Date.now()) {
2
+ const match = value.match(/^(\d+)(m|h|d|w)$/);
3
+ if (!match)
4
+ throw new Error("invalid duration; use e.g. 30m, 2h, 7d");
5
+ const amount = Number(match[1]);
6
+ if (!Number.isSafeInteger(amount) || amount <= 0)
7
+ throw new Error("duration must be positive");
8
+ const units = { m: 60_000, h: 3_600_000, d: 86_400_000, w: 604_800_000 };
9
+ const expires = now + amount * units[match[2]];
10
+ if (!Number.isSafeInteger(expires))
11
+ throw new Error("duration too large");
12
+ return new Date(expires).toISOString();
13
+ }
14
+ //# sourceMappingURL=duration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duration.js","sourceRoot":"","sources":["../src/duration.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,aAAa,CAAC,KAAa,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IAC3D,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;IAC9C,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IACtE,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAChC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC/F,MAAM,KAAK,GAA2B,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC;IACjG,MAAM,OAAO,GAAG,GAAG,GAAG,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/C,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;IAC1E,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;AACzC,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { ShareRegistry } from "./registry.js";
2
+ import { SaveManager } from "./save.js";
3
+ import type { FileportConfig } from "./types.js";
4
+ import { DufsWorkerManager } from "./workers.js";
5
+ export declare class Gateway {
6
+ readonly config: FileportConfig;
7
+ readonly registry: ShareRegistry;
8
+ readonly workers: DufsWorkerManager;
9
+ readonly saves: SaveManager;
10
+ private publicServer?;
11
+ private controlServer?;
12
+ readonly controlSocket: string;
13
+ private lockFile?;
14
+ private readonly lockPath;
15
+ constructor(config: FileportConfig);
16
+ start(): Promise<void>;
17
+ private handleControl;
18
+ private handlePublic;
19
+ close(): Promise<void>;
20
+ }
@@ -0,0 +1,188 @@
1
+ import fs from "node:fs/promises";
2
+ import http from "node:http";
3
+ import path from "node:path";
4
+ import { authorizeSharePath } from "./paths.js";
5
+ import { proxyToUnixSocket } from "./proxy.js";
6
+ import { ShareRegistry } from "./registry.js";
7
+ import { fileVersion, isEditableText, MAX_SAVE_REQUEST_BYTES, SaveError, SaveManager } from "./save.js";
8
+ import { previewHtml } from "./ui.js";
9
+ import { DufsWorkerManager } from "./workers.js";
10
+ function json(res, status, body) { const data = JSON.stringify(body); res.writeHead(status, { "content-type": "application/json; charset=utf-8", "content-length": Buffer.byteLength(data), "cache-control": "no-store", "referrer-policy": "no-referrer", "x-content-type-options": "nosniff" }); res.end(data); }
11
+ async function body(req, limit = 65536) { const chunks = []; let size = 0; for await (const chunk of req) {
12
+ size += chunk.length;
13
+ if (size > limit)
14
+ throw new SaveError(413, "request_too_large");
15
+ chunks.push(chunk);
16
+ } try {
17
+ return JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
18
+ }
19
+ catch {
20
+ throw new SaveError(400, "invalid_json");
21
+ } }
22
+ export class Gateway {
23
+ config;
24
+ registry;
25
+ workers;
26
+ saves = new SaveManager();
27
+ publicServer;
28
+ controlServer;
29
+ controlSocket;
30
+ lockFile;
31
+ lockPath;
32
+ constructor(config) {
33
+ this.config = config;
34
+ this.registry = new ShareRegistry(config.stateDir);
35
+ this.workers = new DufsWorkerManager(config.stateDir, config.dufsBin);
36
+ this.controlSocket = path.join(config.stateDir, "control.sock");
37
+ this.lockPath = path.join(config.stateDir, "gateway.lock");
38
+ }
39
+ async start() {
40
+ await fs.mkdir(this.config.stateDir, { recursive: true, mode: 0o700 });
41
+ await fs.chmod(this.config.stateDir, 0o700);
42
+ try {
43
+ this.lockFile = await fs.open(this.lockPath, "wx", 0o600);
44
+ }
45
+ catch (error) {
46
+ if (error.code !== "EEXIST")
47
+ throw error;
48
+ let active = false;
49
+ try {
50
+ await new Promise((resolve, reject) => { const request = http.request({ socketPath: this.controlSocket, path: "/shares", method: "GET" }, (response) => { response.resume(); response.once("end", () => response.statusCode === 200 ? resolve() : reject(new Error("invalid control response"))); }); request.once("error", reject); request.end(); });
51
+ active = true;
52
+ }
53
+ catch { }
54
+ if (active)
55
+ throw new Error("Fileport Gateway is already running");
56
+ await fs.unlink(this.lockPath);
57
+ this.lockFile = await fs.open(this.lockPath, "wx", 0o600);
58
+ }
59
+ await this.lockFile.writeFile(`${process.pid}\n`);
60
+ await this.lockFile.sync();
61
+ await this.registry.load();
62
+ try {
63
+ await fs.unlink(this.controlSocket);
64
+ }
65
+ catch (error) {
66
+ if (error.code !== "ENOENT")
67
+ throw error;
68
+ }
69
+ this.publicServer = http.createServer((req, res) => { void this.handlePublic(req, res).catch((error) => { if (!res.headersSent)
70
+ json(res, 500, { error: "internal_error" });
71
+ else
72
+ res.destroy(); console.error(error); }); });
73
+ this.controlServer = http.createServer((req, res) => { void this.handleControl(req, res).catch((error) => json(res, error instanceof SaveError ? error.status : 400, { error: error instanceof SaveError ? error.code : error instanceof Error ? error.message : String(error) })); });
74
+ await Promise.all([
75
+ new Promise((resolve, reject) => { this.publicServer.once("error", reject); this.publicServer.listen(this.config.port, this.config.host, resolve); }),
76
+ new Promise((resolve, reject) => { this.controlServer.once("error", reject); this.controlServer.listen(this.controlSocket, resolve); }),
77
+ ]);
78
+ await fs.chmod(this.controlSocket, 0o600);
79
+ }
80
+ async handleControl(req, res) {
81
+ const url = new URL(req.url || "/", "http://localhost");
82
+ if (req.method === "POST" && url.pathname === "/shares") {
83
+ const input = await body(req);
84
+ const share = await this.registry.create(input.path, { permission: input.permission, expiresAt: input.expiresAt });
85
+ const base = this.config.publicBaseUrl || `http://127.0.0.1:${this.config.port}`;
86
+ return json(res, 201, { share, url: `${base}/s/${share.id}/` });
87
+ }
88
+ if (req.method === "GET" && url.pathname === "/shares")
89
+ return json(res, 200, { shares: this.registry.list() });
90
+ const revoke = url.pathname.match(/^\/shares\/([A-Za-z0-9_-]+)$/);
91
+ if (req.method === "DELETE" && revoke)
92
+ return json(res, (await this.registry.revoke(revoke[1])) ? 200 : 404, { ok: true });
93
+ json(res, 404, { error: "not_found" });
94
+ }
95
+ async handlePublic(req, res) {
96
+ const raw = req.url || "/";
97
+ const queryAt = raw.indexOf("?");
98
+ const rawPath = queryAt >= 0 ? raw.slice(0, queryAt) : raw;
99
+ const query = queryAt >= 0 ? raw.slice(queryAt) : "";
100
+ if (rawPath === "/healthz")
101
+ return json(res, 200, { status: "ok", workers: this.workers.count() });
102
+ const match = rawPath.match(/^\/s\/([A-Za-z0-9_-]+)(?:\/(.*))?$/);
103
+ if (!match)
104
+ return json(res, 404, { error: "not_found" });
105
+ const share = this.registry.get(match[1]);
106
+ if (!share)
107
+ return json(res, 404, { error: "share_not_found" });
108
+ const tail = match[2] || "";
109
+ const editMatch = tail.match(/^api\/edit\/(.*)$/);
110
+ if (editMatch && req.method === "GET") {
111
+ try {
112
+ return json(res, 200, await this.saves.read(share, editMatch[1]));
113
+ }
114
+ catch (error) {
115
+ return json(res, error instanceof SaveError ? error.status : 403, { error: error instanceof SaveError ? error.code : "scope_denied" });
116
+ }
117
+ }
118
+ const metaMatch = tail.match(/^api\/meta\/(.*)$/);
119
+ if (metaMatch && req.method === "GET") {
120
+ try {
121
+ const upstream = await authorizeSharePath(share, metaMatch[1]);
122
+ const target = path.resolve(share.root, ...upstream.split("/").filter(Boolean).map(decodeURIComponent));
123
+ const stat = await fs.stat(target, { bigint: true });
124
+ if (!stat.isFile())
125
+ return json(res, 400, { error: "not_a_file" });
126
+ return json(res, 200, { version: fileVersion(stat), size: Number(stat.size), editable: share.permission === "rw" && isEditableText(target), permission: share.permission });
127
+ }
128
+ catch (error) {
129
+ return json(res, error instanceof SaveError ? error.status : 403, { error: error instanceof SaveError ? error.code : "scope_denied" });
130
+ }
131
+ }
132
+ const saveMatch = tail.match(/^api\/save\/(.*)$/);
133
+ if (saveMatch && req.method === "PUT") {
134
+ try {
135
+ const input = await body(req, MAX_SAVE_REQUEST_BYTES);
136
+ return json(res, 200, await this.saves.save(share, saveMatch[1], input.content, input.expectedVersion));
137
+ }
138
+ catch (error) {
139
+ return json(res, error instanceof SaveError ? error.status : 403, { error: error instanceof SaveError ? error.code : error instanceof Error ? error.message : "save_failed" });
140
+ }
141
+ }
142
+ if (tail === "" && (req.method === "GET" || req.method === "HEAD")) {
143
+ const html = previewHtml(share);
144
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8", "content-length": Buffer.byteLength(html), "cache-control": "no-store", "referrer-policy": "no-referrer", "x-content-type-options": "nosniff", "content-security-policy": "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src 'self' blob: data:; connect-src 'self'; frame-src blob:" });
145
+ res.end(req.method === "HEAD" ? undefined : html);
146
+ return;
147
+ }
148
+ if (!tail.startsWith("fs/") || !["GET", "HEAD"].includes(req.method || ""))
149
+ return json(res, 405, { error: "read_only" });
150
+ const relative = tail.slice(3);
151
+ let upstreamRelative;
152
+ try {
153
+ upstreamRelative = await authorizeSharePath(share, relative);
154
+ }
155
+ catch (error) {
156
+ return json(res, error instanceof Error && error.message === "target not found" ? 404 : 403, { error: "scope_denied" });
157
+ }
158
+ const authorizedPath = path.resolve(share.root, ...upstreamRelative.split("/").filter(Boolean).map(decodeURIComponent));
159
+ if (!rawPath.endsWith("/") && (await fs.stat(authorizedPath)).isDirectory()) {
160
+ res.writeHead(308, { location: `${rawPath}/${query}`, "cache-control": "no-store", "referrer-policy": "no-referrer" });
161
+ res.end();
162
+ return;
163
+ }
164
+ const worker = await this.workers.get(share.root);
165
+ await proxyToUnixSocket(req, res, worker.socket, `/${upstreamRelative}${query}`, `/s/${share.id}/fs/`);
166
+ }
167
+ async close() {
168
+ const close = (server) => server ? new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())) : Promise.resolve();
169
+ await Promise.all([close(this.publicServer), close(this.controlServer)]);
170
+ await this.workers.close();
171
+ try {
172
+ await fs.unlink(this.controlSocket);
173
+ }
174
+ catch { }
175
+ try {
176
+ await this.lockFile?.close();
177
+ }
178
+ catch { }
179
+ finally {
180
+ this.lockFile = undefined;
181
+ }
182
+ try {
183
+ await fs.unlink(this.lockPath);
184
+ }
185
+ catch { }
186
+ }
187
+ }
188
+ //# sourceMappingURL=gateway.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway.js","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { Share, ShareKind } from "./types.js";
2
+ export declare function canonicalTarget(input: string): Promise<{
3
+ target: string;
4
+ root: string;
5
+ kind: ShareKind;
6
+ }>;
7
+ export declare function decodeRelativePath(raw: string): string;
8
+ export declare function authorizeSharePath(share: Share, rawRelative: string): Promise<string>;
package/dist/paths.js ADDED
@@ -0,0 +1,88 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ const sensitiveSegments = new Set([".ssh", ".gnupg", ".aws", ".kube", ".config/gcloud"]);
5
+ const sensitiveNames = new Set([".env", ".netrc", "credentials", "id_rsa", "id_ed25519"]);
6
+ function isSensitiveRelative(relative) {
7
+ const normalized = relative.split(path.sep).join("/").toLowerCase();
8
+ const parts = normalized.split("/").filter(Boolean);
9
+ if (parts.some((part) => [".ssh", ".gnupg", ".aws", ".kube"].includes(part)))
10
+ return true;
11
+ const name = parts.at(-1) || "";
12
+ return sensitiveNames.has(name) || /^\.env(?:\.|$)/.test(name) || /^id_(?:rsa|dsa|ecdsa|ed25519)(?:\.pub)?$/.test(name) || /\.(?:pem|p12|pfx)$/.test(name);
13
+ }
14
+ async function assertSafeTarget(target) {
15
+ const home = await fs.realpath(os.homedir());
16
+ if (target === home || target === path.parse(target).root)
17
+ throw new Error("refusing to share Home or filesystem root");
18
+ const relativeHome = path.relative(home, target).split(path.sep).join("/");
19
+ for (const segment of sensitiveSegments)
20
+ if (relativeHome === segment || relativeHome.startsWith(`${segment}/`))
21
+ throw new Error("sensitive path denied");
22
+ if (isSensitiveRelative(target))
23
+ throw new Error("sensitive path denied");
24
+ }
25
+ export async function canonicalTarget(input) {
26
+ const absolute = path.resolve(input);
27
+ const target = await fs.realpath(absolute);
28
+ await assertSafeTarget(target);
29
+ const stat = await fs.stat(target);
30
+ if (!stat.isFile() && !stat.isDirectory())
31
+ throw new Error("share target must be a regular file or directory");
32
+ return { target, root: stat.isFile() ? path.dirname(target) : target, kind: stat.isFile() ? "file" : "directory" };
33
+ }
34
+ function decodeSegment(segment) {
35
+ if (/%2f|%5c/i.test(segment))
36
+ throw new Error("encoded path separator denied");
37
+ let decoded;
38
+ try {
39
+ decoded = decodeURIComponent(segment);
40
+ }
41
+ catch {
42
+ throw new Error("malformed path encoding");
43
+ }
44
+ if (decoded === "." || decoded === ".." || decoded.includes("/") || decoded.includes("\\") || /%[0-9a-f]{2}/i.test(decoded)) {
45
+ throw new Error("path traversal denied");
46
+ }
47
+ return decoded;
48
+ }
49
+ export function decodeRelativePath(raw) {
50
+ if (!raw)
51
+ return "";
52
+ return raw.split("/").filter(Boolean).map(decodeSegment).join(path.sep);
53
+ }
54
+ function inside(parent, child) {
55
+ const rel = path.relative(parent, child);
56
+ return rel === "" || (!rel.startsWith(`..${path.sep}`) && rel !== ".." && !path.isAbsolute(rel));
57
+ }
58
+ export async function authorizeSharePath(share, rawRelative) {
59
+ const relative = decodeRelativePath(rawRelative);
60
+ if (share.kind === "file") {
61
+ const expected = path.basename(share.target);
62
+ if (relative !== expected)
63
+ throw new Error("outside file share scope");
64
+ }
65
+ if (isSensitiveRelative(relative))
66
+ throw new Error("sensitive path denied");
67
+ const candidate = path.resolve(share.root, relative || ".");
68
+ if (!inside(share.root, candidate))
69
+ throw new Error("outside share root");
70
+ let real;
71
+ try {
72
+ real = await fs.realpath(candidate);
73
+ }
74
+ catch {
75
+ throw new Error("target not found");
76
+ }
77
+ try {
78
+ await fs.access(real, fs.constants.R_OK);
79
+ }
80
+ catch {
81
+ throw new Error("target not readable");
82
+ }
83
+ const scope = share.target;
84
+ if (share.kind === "file" ? real !== scope : !inside(scope, real))
85
+ throw new Error("symlink escape denied");
86
+ return relative.split(path.sep).map(encodeURIComponent).join("/");
87
+ }
88
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { type IncomingMessage, type ServerResponse } from "node:http";
2
+ export declare function proxyToUnixSocket(req: IncomingMessage, res: ServerResponse, socketPath: string, upstreamPath: string, publicPrefix: string): Promise<void>;