@uploadista/flow-security-nodes 0.0.20 → 0.1.0-beta.5
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/.turbo/turbo-build.log +23 -22
- package/.turbo/turbo-check.log +6 -0
- package/.turbo/turbo-test.log +126 -5
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -9
- package/tests/scan-virus-node.test.ts +68 -52
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
[34mℹ[39m
|
|
7
|
-
[34mℹ[39m
|
|
8
|
-
[34mℹ[39m
|
|
9
|
-
[34mℹ[39m
|
|
10
|
-
[34mℹ[39m
|
|
11
|
-
[34mℹ[39m
|
|
12
|
-
[34mℹ[39m [33m[CJS][39m
|
|
13
|
-
[34mℹ[39m [
|
|
14
|
-
[34mℹ[39m [34m[ESM][39m [2mdist/[
|
|
15
|
-
[34mℹ[39m [34m[ESM][39m [2mdist/[22mindex.
|
|
16
|
-
[34mℹ[39m [34m[ESM][39m [2mdist/[
|
|
17
|
-
[34mℹ[39m [34m[ESM][39m
|
|
18
|
-
[
|
|
19
|
-
[
|
|
20
|
-
[34mℹ[39m [33m[CJS][39m [2mdist/[
|
|
21
|
-
[34mℹ[39m [33m[CJS][39m
|
|
22
|
-
[
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
> @uploadista/flow-security-nodes@0.1.0-beta.4 build /Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/security/nodes
|
|
4
|
+
> tsc --noEmit && tsdown
|
|
5
|
+
|
|
6
|
+
[34mℹ[39m tsdown [2mv0.19.0[22m powered by rolldown [2mv1.0.0-beta.59[22m
|
|
7
|
+
[34mℹ[39m config file: [4m/Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/security/nodes/tsdown.config.ts[24m
|
|
8
|
+
[34mℹ[39m entry: [34msrc/index.ts[39m
|
|
9
|
+
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
10
|
+
[34mℹ[39m Build start
|
|
11
|
+
[34mℹ[39m Cleaning 7 files
|
|
12
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22m[1mindex.cjs[22m [2m1.23 kB[22m [2m│ gzip: 0.69 kB[22m
|
|
13
|
+
[34mℹ[39m [33m[CJS][39m 1 files, total: 1.23 kB
|
|
14
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[1mindex.mjs[22m [2m1.26 kB[22m [2m│ gzip: 0.73 kB[22m
|
|
15
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22mindex.mjs.map [2m5.64 kB[22m [2m│ gzip: 2.19 kB[22m
|
|
16
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22mindex.d.mts.map [2m0.54 kB[22m [2m│ gzip: 0.29 kB[22m
|
|
17
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[32m[1mindex.d.mts[22m[39m [2m3.29 kB[22m [2m│ gzip: 1.21 kB[22m
|
|
18
|
+
[34mℹ[39m [34m[ESM][39m 4 files, total: 10.73 kB
|
|
19
|
+
[32m✔[39m Build complete in [32m6293ms[39m
|
|
20
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22mindex.d.cts.map [2m0.54 kB[22m [2m│ gzip: 0.29 kB[22m
|
|
21
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22m[32m[1mindex.d.cts[22m[39m [2m3.29 kB[22m [2m│ gzip: 1.20 kB[22m
|
|
22
|
+
[34mℹ[39m [33m[CJS][39m 2 files, total: 3.83 kB
|
|
23
|
+
[32m✔[39m Build complete in [32m6315ms[39m
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
> @uploadista/flow-security-nodes@0.1.0-beta.2 check /Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/security/nodes
|
|
4
|
+
> biome check --write ./src
|
|
5
|
+
|
|
6
|
+
[0m[34mChecked [0m[0m[34m2[0m[0m[34m [0m[0m[34mfiles[0m[0m[34m in [0m[0m[34m79[0m[0m[2m[34mms[0m[0m[34m.[0m[0m[34m No fixes applied.[0m
|
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,5 +1,126 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
>
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
> @uploadista/flow-security-nodes@0.1.0-beta.2 test /Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/security/nodes
|
|
4
|
+
> vitest run
|
|
5
|
+
|
|
6
|
+
[?25l
|
|
7
|
+
[1m[46m RUN [49m[22m [36mv4.0.17 [39m[90m/Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/security/nodes[39m
|
|
8
|
+
|
|
9
|
+
[?2026h
|
|
10
|
+
[2m Test Files [22m[1m[32m0 passed[39m[22m[90m (1)[39m
|
|
11
|
+
[2m Tests [22m[1m[32m0 passed[39m[22m[90m (0)[39m
|
|
12
|
+
[2m Start at [22m16:11:08
|
|
13
|
+
[2m Duration [22m959ms
|
|
14
|
+
[?2026l[?2026h[K[1A[K[1A[K[1A[K[1A[K[1A[K
|
|
15
|
+
[1m[33m ❯ [39m[22mtests/scan-virus-node.test.ts[2m [queued][22m
|
|
16
|
+
|
|
17
|
+
[2m Test Files [22m[1m[32m0 passed[39m[22m[90m (1)[39m
|
|
18
|
+
[2m Tests [22m[1m[32m0 passed[39m[22m[90m (0)[39m
|
|
19
|
+
[2m Start at [22m16:11:08
|
|
20
|
+
[2m Duration [22m1.16s
|
|
21
|
+
[?2026l[?2026h[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K
|
|
22
|
+
[1m[33m ❯ [39m[22mtests/scan-virus-node.test.ts[2m [queued][22m
|
|
23
|
+
|
|
24
|
+
[2m Test Files [22m[1m[32m0 passed[39m[22m[90m (1)[39m
|
|
25
|
+
[2m Tests [22m[1m[32m0 passed[39m[22m[90m (0)[39m
|
|
26
|
+
[2m Start at [22m16:11:08
|
|
27
|
+
[2m Duration [22m1.90s
|
|
28
|
+
[?2026l[?2026h[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K
|
|
29
|
+
[1m[33m ❯ [39m[22mtests/scan-virus-node.test.ts[2m [queued][22m
|
|
30
|
+
|
|
31
|
+
[2m Test Files [22m[1m[32m0 passed[39m[22m[90m (1)[39m
|
|
32
|
+
[2m Tests [22m[1m[32m0 passed[39m[22m[90m (0)[39m
|
|
33
|
+
[2m Start at [22m16:11:08
|
|
34
|
+
[2m Duration [22m2.91s
|
|
35
|
+
[?2026l[?2026h[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K
|
|
36
|
+
[1m[33m ❯ [39m[22mtests/scan-virus-node.test.ts[2m [queued][22m
|
|
37
|
+
|
|
38
|
+
[2m Test Files [22m[1m[32m0 passed[39m[22m[90m (1)[39m
|
|
39
|
+
[2m Tests [22m[1m[32m0 passed[39m[22m[90m (0)[39m
|
|
40
|
+
[2m Start at [22m16:11:08
|
|
41
|
+
[2m Duration [22m3.97s
|
|
42
|
+
[?2026l[?2026h[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K
|
|
43
|
+
[1m[33m ❯ [39m[22mtests/scan-virus-node.test.ts[2m 0/16[22m
|
|
44
|
+
|
|
45
|
+
[2m Test Files [22m[1m[32m0 passed[39m[22m[90m (1)[39m
|
|
46
|
+
[2m Tests [22m[1m[32m0 passed[39m[22m[90m (16)[39m
|
|
47
|
+
[2m Start at [22m16:11:08
|
|
48
|
+
[2m Duration [22m4.59s
|
|
49
|
+
[?2026l[?2026h[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K
|
|
50
|
+
[1m[33m ❯ [39m[22mtests/scan-virus-node.test.ts[2m 1/16[22m
|
|
51
|
+
|
|
52
|
+
[2m Test Files [22m[1m[32m0 passed[39m[22m[90m (1)[39m
|
|
53
|
+
[2m Tests [22m[1m[32m1 passed[39m[22m[90m (16)[39m
|
|
54
|
+
[2m Start at [22m16:11:08
|
|
55
|
+
[2m Duration [22m4.89s
|
|
56
|
+
[?2026l[?2026h[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K
|
|
57
|
+
[1m[33m ❯ [39m[22mtests/scan-virus-node.test.ts[2m 2/16[22m
|
|
58
|
+
|
|
59
|
+
[2m Test Files [22m[1m[32m0 passed[39m[22m[90m (1)[39m
|
|
60
|
+
[2m Tests [22m[1m[32m2 passed[39m[22m[90m (16)[39m
|
|
61
|
+
[2m Start at [22m16:11:08
|
|
62
|
+
[2m Duration [22m4.99s
|
|
63
|
+
[?2026l[?2026h[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K
|
|
64
|
+
[1m[33m ❯ [39m[22mtests/scan-virus-node.test.ts[2m 4/16[22m
|
|
65
|
+
|
|
66
|
+
[2m Test Files [22m[1m[32m0 passed[39m[22m[90m (1)[39m
|
|
67
|
+
[2m Tests [22m[1m[32m4 passed[39m[22m[90m (16)[39m
|
|
68
|
+
[2m Start at [22m16:11:08
|
|
69
|
+
[2m Duration [22m5.20s
|
|
70
|
+
[?2026l[?2026h[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K
|
|
71
|
+
[1m[33m ❯ [39m[22mtests/scan-virus-node.test.ts[2m 6/16[22m
|
|
72
|
+
|
|
73
|
+
[2m Test Files [22m[1m[32m0 passed[39m[22m[90m (1)[39m
|
|
74
|
+
[2m Tests [22m[1m[32m6 passed[39m[22m[90m (16)[39m
|
|
75
|
+
[2m Start at [22m16:11:08
|
|
76
|
+
[2m Duration [22m5.30s
|
|
77
|
+
[?2026l[?2026h[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K
|
|
78
|
+
[1m[33m ❯ [39m[22mtests/scan-virus-node.test.ts[2m 7/16[22m
|
|
79
|
+
|
|
80
|
+
[2m Test Files [22m[1m[32m0 passed[39m[22m[90m (1)[39m
|
|
81
|
+
[2m Tests [22m[1m[32m7 passed[39m[22m[90m (16)[39m
|
|
82
|
+
[2m Start at [22m16:11:08
|
|
83
|
+
[2m Duration [22m5.50s
|
|
84
|
+
[?2026l[?2026h[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K
|
|
85
|
+
[1m[33m ❯ [39m[22mtests/scan-virus-node.test.ts[2m 9/16[22m
|
|
86
|
+
|
|
87
|
+
[2m Test Files [22m[1m[32m0 passed[39m[22m[90m (1)[39m
|
|
88
|
+
[2m Tests [22m[1m[32m9 passed[39m[22m[90m (16)[39m
|
|
89
|
+
[2m Start at [22m16:11:08
|
|
90
|
+
[2m Duration [22m5.60s
|
|
91
|
+
[?2026l[?2026h[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K [32m✓[39m tests/scan-virus-node.test.ts [2m([22m[2m16 tests[22m[2m)[22m[33m 1141[2mms[22m[39m
|
|
92
|
+
[32m✓[39m Scan Virus Node [2m(16)[22m
|
|
93
|
+
[32m✓[39m Node Creation [2m(2)[22m
|
|
94
|
+
[32m✓[39m should create scan virus node with correct properties[32m 226[2mms[22m[39m
|
|
95
|
+
[32m✓[39m should create node with default parameters[32m 46[2mms[22m[39m
|
|
96
|
+
[32m✓[39m Clean File Scanning [2m(3)[22m
|
|
97
|
+
[32m✓[39m should pass clean file through unchanged[32m 57[2mms[22m[39m
|
|
98
|
+
[32m✓[39m should add virus scan metadata to clean file[32m 45[2mms[22m[39m
|
|
99
|
+
[32m✓[39m should preserve existing file metadata[32m 116[2mms[22m[39m
|
|
100
|
+
[32m✓[39m Infected File Scanning - Fail Action [2m(3)[22m
|
|
101
|
+
[32m✓[39m should fail flow when virus detected with fail action[32m 147[2mms[22m[39m
|
|
102
|
+
[32m✓[39m should include virus names in error message[32m 48[2mms[22m[39m
|
|
103
|
+
[32m✓[39m should include scan metadata in error details[32m 111[2mms[22m[39m
|
|
104
|
+
[32m✓[39m Infected File Scanning - Pass Action [2m(3)[22m
|
|
105
|
+
[32m✓[39m should continue flow when virus detected with pass action[32m 11[2mms[22m[39m
|
|
106
|
+
[32m✓[39m should add virus detection metadata when passing infected file[32m 82[2mms[22m[39m
|
|
107
|
+
[32m✓[39m should preserve file bytes when passing infected file[32m 33[2mms[22m[39m
|
|
108
|
+
[32m✓[39m Parameter Validation [2m(4)[22m
|
|
109
|
+
[32m✓[39m should accept valid fail action[32m 5[2mms[22m[39m
|
|
110
|
+
[32m✓[39m should accept valid pass action[32m 40[2mms[22m[39m
|
|
111
|
+
[32m✓[39m should apply default timeout[32m 10[2mms[22m[39m
|
|
112
|
+
[32m✓[39m should accept custom timeout within limits[32m 26[2mms[22m[39m
|
|
113
|
+
[32m✓[39m Engine Version [2m(1)[22m
|
|
114
|
+
[32m✓[39m should include engine version in scan metadata[32m 14[2mms[22m[39m
|
|
115
|
+
|
|
116
|
+
[2m Test Files [22m[1m[32m1 passed[39m[22m[90m (1)[39m
|
|
117
|
+
[2m Tests [22m[1m[32m16 passed[39m[22m[90m (16)[39m
|
|
118
|
+
[2m Start at [22m16:11:08
|
|
119
|
+
[2m Duration [22m5.70s
|
|
120
|
+
[?2026l[K[1A[K[1A[K[1A[K[1A[K[1A[K
|
|
121
|
+
[2m Test Files [22m [1m[32m1 passed[39m[22m[90m (1)[39m
|
|
122
|
+
[2m Tests [22m [1m[32m16 passed[39m[22m[90m (16)[39m
|
|
123
|
+
[2m Start at [22m 16:11:08
|
|
124
|
+
[2m Duration [22m 5.78s[2m (transform 755ms, setup 0ms, import 3.43s, tests 1.14s, environment 0ms)[22m
|
|
125
|
+
|
|
126
|
+
[?25h
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/scan-virus-node.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;cAaa,YAAU,CAAA,CAAA;;;AAAvB,CAAA,CAAA;AACY,KAAA,UAAA,GAAa,CAAA,CAAE,KAAa,CAAA,OAAA,UAAR,CAAA;AAKhC;;;cAAa,iBAAe,CAAA,CAAA;;;;EAAA,CAAA,CAAA,CAAA;EAAA,OAAA,cAAA,cAAA,YAAA,CAAA,CAAA;AAgB5B,CAAA,eAAY,CAAA;AA8BI,KA9BJ,eAAA,GAAkB,CAAA,CAAE,KA8BG,CAAA,OA9BU,eA8BV,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAnB,mBAAA,sBAEN;;IAC0B,MAAA,CAAA,OAD0B,sBAAA,CAC1B,YAAA;yBAAA,uBAAA,CAAA,UAAA;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/scan-virus-node.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;cAaa,YAAU,CAAA,CAAA;;;AAAvB,CAAA,CAAA;AACY,KAAA,UAAA,GAAa,CAAA,CAAE,KAAa,CAAA,OAAA,UAAR,CAAA;AAKhC;;;cAAa,iBAAe,CAAA,CAAA;;;;EAAA,CAAA,CAAA,CAAA;EAAA,OAAA,cAAA,cAAA,YAAA,CAAA,CAAA;AAgB5B,CAAA,eAAY,CAAA;AA8BI,KA9BJ,eAAA,GAAkB,CAAA,CAAE,KA8BG,CAAA,OA9BU,eA8BV,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAnB,mBAAA,sBAEN;;IAC0B,MAAA,CAAA,OAD0B,sBAAA,CAC1B,YAAA;yBAAA,uBAAA,CAAA,UAAA;;;;;;;aAqEik5M;;iEAAA,uBAAA,CAAA,UAAA"}
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/scan-virus-node.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;cAaa,YAAU,CAAA,CAAA;;;AAAvB,CAAA,CAAA;AACY,KAAA,UAAA,GAAa,CAAA,CAAE,KAAa,CAAA,OAAA,UAAR,CAAA;AAKhC;;;cAAa,iBAAe,CAAA,CAAA;;;;EAAA,CAAA,CAAA,CAAA;EAAA,OAAA,cAAA,cAAA,YAAA,CAAA,CAAA;AAgB5B,CAAA,eAAY,CAAA;AA8BI,KA9BJ,eAAA,GAAkB,CAAA,CAAE,KA8BG,CAAA,OA9BU,eA8BV,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAnB,mBAAA,sBAEN;;IAC0B,MAAA,CAAA,OAD0B,sBAAA,CAC1B,YAAA;yBAAA,uBAAA,CAAA,UAAA;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/scan-virus-node.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;cAaa,YAAU,CAAA,CAAA;;;AAAvB,CAAA,CAAA;AACY,KAAA,UAAA,GAAa,CAAA,CAAE,KAAa,CAAA,OAAA,UAAR,CAAA;AAKhC;;;cAAa,iBAAe,CAAA,CAAA;;;;EAAA,CAAA,CAAA,CAAA;EAAA,OAAA,cAAA,cAAA,YAAA,CAAA,CAAA;AAgB5B,CAAA,eAAY,CAAA;AA8BI,KA9BJ,eAAA,GAAkB,CAAA,CAAE,KA8BG,CAAA,OA9BU,eA8BV,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAnB,mBAAA,sBAEN;;IAC0B,MAAA,CAAA,OAD0B,sBAAA,CAC1B,YAAA;yBAAA,uBAAA,CAAA,UAAA;;;;;;;aAqEik5M;;iEAAA,uBAAA,CAAA,UAAA"}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/scan-virus-node.ts"],"sourcesContent":["import { httpFailure } from \"@uploadista/core/errors\";\nimport {\n createTransformNode,\n type ScanMetadata,\n STORAGE_OUTPUT_TYPE_ID,\n VirusScanPlugin,\n} from \"@uploadista/core/flow\";\nimport { Effect } from \"effect\";\nimport { z } from \"zod\";\n\n/**\n * Scan action to take when a virus is detected\n */\nexport const ScanAction = z.enum([\"fail\", \"pass\"]);\nexport type ScanAction = z.infer<typeof ScanAction>;\n\n/**\n * Parameters for the Scan Virus node\n */\nexport const ScanVirusParams = z.object({\n /**\n * Action to take when a virus is detected:\n * - \"fail\": Mark flow task as FAILED and stop processing\n * - \"pass\": Continue processing but add virus metadata to file\n */\n action: ScanAction.default(\"fail\"),\n\n /**\n * Maximum time to wait for scan completion (in milliseconds)\n * Default: 60000ms (60 seconds)\n * Max: 300000ms (5 minutes)\n */\n timeout: z.number().min(1000).max(300000).optional().default(60000),\n});\n\nexport type ScanVirusParams = z.infer<typeof ScanVirusParams>;\n\n/**\n * Creates a Scan Virus node for malware detection\n *\n * Scans files for viruses and malware using the configured VirusScanPlugin\n * (typically ClamAV). Supports configurable actions on detection:\n * - \"fail\": Stop flow execution when virus detected\n * - \"pass\": Continue flow with detection metadata\n *\n * All scan results are stored in file.metadata.virusScan for downstream nodes.\n *\n * @param id - Unique node identifier\n * @param params - Configuration parameters for scan behavior\n * @returns Effect that resolves to the configured node\n *\n * @example\n * ```typescript\n * // Fail on virus detection (default)\n * const failNode = yield* createScanVirusNode(\"scan-1\", {\n * action: \"fail\"\n * });\n *\n * // Pass through with metadata\n * const passNode = yield* createScanVirusNode(\"scan-2\", {\n * action: \"pass\",\n * timeout: 120000 // 2 minutes\n * });\n * ```\n */\nexport function createScanVirusNode(\n id: string,\n params: ScanVirusParams = { action: \"fail\", timeout: 60000 },\n options?: { keepOutput?: boolean },\n) {\n return Effect.gen(function* () {\n const virusScanService = yield* VirusScanPlugin;\n\n // Validate params\n const validatedParams = ScanVirusParams.parse(params);\n\n return yield* createTransformNode({\n id,\n name: \"Scan Virus\",\n description: \"Scans files for viruses and malware using ClamAV\",\n nodeTypeId: \"scan-virus\",\n outputTypeId: STORAGE_OUTPUT_TYPE_ID,\n keepOutput: options?.keepOutput,\n // External service - enable circuit breaker with fail fallback (don't skip security scans)\n circuitBreaker: {\n enabled: true,\n failureThreshold: 5,\n resetTimeout: 60000,\n fallback: { type: \"fail\" },\n },\n transform: (inputBytes, file) =>\n Effect.gen(function* () {\n // Perform virus scan\n const scanResult = yield* virusScanService.scan(inputBytes);\n\n // Get engine version for metadata\n const engineVersion = yield* virusScanService.getVersion();\n\n // Build comprehensive scan metadata\n const scanMetadata: ScanMetadata = {\n scanned: true,\n isClean: scanResult.isClean,\n detectedViruses: scanResult.detectedViruses,\n scanDate: new Date().toISOString(),\n engineVersion,\n definitionsDate: new Date().toISOString(), // TODO: Get actual definitions date from plugin\n };\n\n // Check if virus was detected\n if (!scanResult.isClean) {\n // Build error message with detected viruses\n const virusList = scanResult.detectedViruses.join(\", \");\n const message = `Virus detected: ${virusList}`;\n\n // Handle based on configured action\n if (validatedParams.action === \"fail\") {\n // Fail the flow task\n return yield* httpFailure(\"VIRUS_DETECTED\", {\n body: message,\n details: { scanMetadata },\n });\n }\n // action === \"pass\": Continue with metadata (handled below)\n }\n\n // Return file with scan metadata (clean or pass action)\n return {\n bytes: inputBytes, // Pass through original bytes unchanged\n metadata: {\n ...file.metadata,\n virusScan: scanMetadata,\n },\n };\n }),\n });\n });\n}\n"],"mappings":"2NAaA,MAAa,EAAa,EAAE,KAAK,CAAC,OAAQ,OAAO,CAAC,CAMrC,EAAkB,EAAE,OAAO,CAMtC,OAAQ,EAAW,QAAQ,OAAO,CAOlC,QAAS,EAAE,QAAQ,CAAC,IAAI,IAAK,CAAC,IAAI,IAAO,CAAC,UAAU,CAAC,QAAQ,IAAM,CACpE,CAAC,CAgCF,SAAgB,EACd,EACA,EAA0B,CAAE,OAAQ,OAAQ,QAAS,IAAO,CAC5D,EACA,CACA,OAAO,EAAO,IAAI,WAAa,CAC7B,IAAM,EAAmB,MAAO,EAG1B,EAAkB,EAAgB,MAAM,EAAO,CAErD,OAAO,MAAO,EAAoB,CAChC,KACA,KAAM,aACN,YAAa,mDACb,WAAY,aACZ,aAAc,EACd,WAAY,GAAS,WAErB,eAAgB,CACd,QAAS,GACT,iBAAkB,EAClB,aAAc,IACd,SAAU,CAAE,KAAM,OAAQ,CAC3B,CACD,WAAY,EAAY,IACtB,EAAO,IAAI,WAAa,CAEtB,IAAM,EAAa,MAAO,EAAiB,KAAK,EAAW,CAGrD,EAAgB,MAAO,EAAiB,YAAY,CAGpD,EAA6B,CACjC,QAAS,GACT,QAAS,EAAW,QACpB,gBAAiB,EAAW,gBAC5B,SAAU,IAAI,MAAM,CAAC,aAAa,CAClC,gBACA,gBAAiB,IAAI,MAAM,CAAC,aAAa,CAC1C,CAGD,GAAI,CAAC,EAAW,QAAS,CAGvB,IAAM,EAAU,mBADE,EAAW,gBAAgB,KAAK,KAAK,GAIvD,GAAI,EAAgB,SAAW,OAE7B,OAAO,MAAO,EAAY,iBAAkB,CAC1C,KAAM,EACN,QAAS,CAAE,eAAc,CAC1B,CAAC,CAMN,MAAO,CACL,MAAO,EACP,SAAU,CACR,GAAG,EAAK,SACR,UAAW,EACZ,CACF,EACD,CACL,CAAC,EACF"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uploadista/flow-security-nodes",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.1.0-beta.5",
|
|
5
5
|
"description": "Security processing nodes for Uploadista Flow",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Uploadista",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@uploadista/core": "0.0.
|
|
17
|
+
"@uploadista/core": "0.1.0-beta.5"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
20
|
"effect": "^3.0.0",
|
|
@@ -22,19 +22,19 @@
|
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@effect/vitest": "0.27.0",
|
|
25
|
-
"@types/node": "24.10.
|
|
26
|
-
"effect": "3.19.
|
|
27
|
-
"tsdown": "0.
|
|
28
|
-
"vitest": "4.0.
|
|
29
|
-
"zod": "4.
|
|
30
|
-
"@uploadista/typescript-config": "0.0.
|
|
25
|
+
"@types/node": "24.10.8",
|
|
26
|
+
"effect": "3.19.14",
|
|
27
|
+
"tsdown": "0.19.0",
|
|
28
|
+
"vitest": "4.0.17",
|
|
29
|
+
"zod": "4.3.5",
|
|
30
|
+
"@uploadista/typescript-config": "0.1.0-beta.5"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"build": "tsc --noEmit && tsdown",
|
|
34
34
|
"format": "biome format --write ./src",
|
|
35
35
|
"lint": "biome lint --write ./src",
|
|
36
36
|
"check": "biome check --write ./src",
|
|
37
|
-
"test": "vitest",
|
|
37
|
+
"test": "vitest run",
|
|
38
38
|
"test:run": "vitest run",
|
|
39
39
|
"test:watch": "vitest watch"
|
|
40
40
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from "@effect/vitest";
|
|
2
2
|
import { UploadistaError } from "@uploadista/core/errors";
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
TestUploadEngine,
|
|
5
5
|
TestVirusScanPlugin,
|
|
6
6
|
} from "@uploadista/core/testing";
|
|
7
7
|
import type { UploadFile } from "@uploadista/core/types";
|
|
@@ -56,7 +56,7 @@ const createInfectedFileBytes = (): Uint8Array => {
|
|
|
56
56
|
/**
|
|
57
57
|
* Test layer combining all mocks
|
|
58
58
|
*/
|
|
59
|
-
const TestLayer = Layer.mergeAll(TestVirusScanPlugin,
|
|
59
|
+
const TestLayer = Layer.mergeAll(TestVirusScanPlugin, TestUploadEngine);
|
|
60
60
|
|
|
61
61
|
describe("Scan Virus Node", () => {
|
|
62
62
|
describe("Node Creation", () => {
|
|
@@ -64,6 +64,7 @@ describe("Scan Virus Node", () => {
|
|
|
64
64
|
Effect.gen(function* () {
|
|
65
65
|
const node = yield* createScanVirusNode("scan-1", {
|
|
66
66
|
action: "fail",
|
|
67
|
+
timeout: 60000,
|
|
67
68
|
});
|
|
68
69
|
|
|
69
70
|
expect(node.id).toBe("scan-1");
|
|
@@ -89,6 +90,7 @@ describe("Scan Virus Node", () => {
|
|
|
89
90
|
Effect.gen(function* () {
|
|
90
91
|
const node = yield* createScanVirusNode("scan-clean", {
|
|
91
92
|
action: "fail",
|
|
93
|
+
timeout: 60000,
|
|
92
94
|
});
|
|
93
95
|
|
|
94
96
|
const testFile = createTestUploadFile();
|
|
@@ -117,6 +119,7 @@ describe("Scan Virus Node", () => {
|
|
|
117
119
|
Effect.gen(function* () {
|
|
118
120
|
const node = yield* createScanVirusNode("scan-metadata", {
|
|
119
121
|
action: "fail",
|
|
122
|
+
timeout: 60000,
|
|
120
123
|
});
|
|
121
124
|
|
|
122
125
|
const testFile = createTestUploadFile();
|
|
@@ -147,6 +150,7 @@ describe("Scan Virus Node", () => {
|
|
|
147
150
|
Effect.gen(function* () {
|
|
148
151
|
const node = yield* createScanVirusNode("scan-preserve", {
|
|
149
152
|
action: "fail",
|
|
153
|
+
timeout: 60000,
|
|
150
154
|
});
|
|
151
155
|
|
|
152
156
|
const testFile = createTestUploadFile({
|
|
@@ -182,41 +186,43 @@ describe("Scan Virus Node", () => {
|
|
|
182
186
|
});
|
|
183
187
|
|
|
184
188
|
describe("Infected File Scanning - Fail Action", () => {
|
|
185
|
-
it.effect(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
});
|
|
189
|
+
it.effect("should fail flow when virus detected with fail action", () =>
|
|
190
|
+
Effect.gen(function* () {
|
|
191
|
+
const node = yield* createScanVirusNode("scan-fail", {
|
|
192
|
+
action: "fail",
|
|
193
|
+
timeout: 60000,
|
|
194
|
+
});
|
|
192
195
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
)
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
196
|
+
// Use file ID containing "infected" to trigger EICAR content from mock
|
|
197
|
+
const testFile = createTestUploadFile({ id: "infected-file-1" });
|
|
198
|
+
|
|
199
|
+
const result = yield* Effect.either(
|
|
200
|
+
node.run({
|
|
201
|
+
data: testFile,
|
|
202
|
+
jobId: "test-job",
|
|
203
|
+
flowId: "test-flow",
|
|
204
|
+
storageId: "test-storage",
|
|
205
|
+
clientId: "test-client",
|
|
206
|
+
}),
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
expect(result._tag).toBe("Left");
|
|
210
|
+
if (result._tag === "Left") {
|
|
211
|
+
expect(result.left).toBeInstanceOf(UploadistaError);
|
|
212
|
+
expect(result.left.code).toBe("VIRUS_DETECTED");
|
|
213
|
+
}
|
|
214
|
+
}).pipe(Effect.provide(TestLayer)),
|
|
211
215
|
);
|
|
212
216
|
|
|
213
217
|
it.effect("should include virus names in error message", () =>
|
|
214
218
|
Effect.gen(function* () {
|
|
215
219
|
const node = yield* createScanVirusNode("scan-names", {
|
|
216
220
|
action: "fail",
|
|
221
|
+
timeout: 60000,
|
|
217
222
|
});
|
|
218
223
|
|
|
219
|
-
|
|
224
|
+
// Use file ID containing "infected" to trigger EICAR content from mock
|
|
225
|
+
const testFile = createTestUploadFile({ id: "infected-file-2" });
|
|
220
226
|
|
|
221
227
|
const result = yield* Effect.either(
|
|
222
228
|
node.run({
|
|
@@ -241,9 +247,11 @@ describe("Scan Virus Node", () => {
|
|
|
241
247
|
Effect.gen(function* () {
|
|
242
248
|
const node = yield* createScanVirusNode("scan-details", {
|
|
243
249
|
action: "fail",
|
|
250
|
+
timeout: 60000,
|
|
244
251
|
});
|
|
245
252
|
|
|
246
|
-
|
|
253
|
+
// Use file ID containing "infected" to trigger EICAR content from mock
|
|
254
|
+
const testFile = createTestUploadFile({ id: "infected-file-3" });
|
|
247
255
|
|
|
248
256
|
const result = yield* Effect.either(
|
|
249
257
|
node.run({
|
|
@@ -262,35 +270,35 @@ describe("Scan Virus Node", () => {
|
|
|
262
270
|
expect(error.details?.scanMetadata).toBeDefined();
|
|
263
271
|
expect(error.details?.scanMetadata.isClean).toBe(false);
|
|
264
272
|
expect(error.details?.scanMetadata.detectedViruses).toBeDefined();
|
|
265
|
-
expect(
|
|
266
|
-
|
|
267
|
-
);
|
|
273
|
+
expect(
|
|
274
|
+
error.details?.scanMetadata.detectedViruses.length,
|
|
275
|
+
).toBeGreaterThan(0);
|
|
268
276
|
}
|
|
269
277
|
}).pipe(Effect.provide(TestLayer)),
|
|
270
278
|
);
|
|
271
279
|
});
|
|
272
280
|
|
|
273
281
|
describe("Infected File Scanning - Pass Action", () => {
|
|
274
|
-
it.effect(
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
});
|
|
282
|
+
it.effect("should continue flow when virus detected with pass action", () =>
|
|
283
|
+
Effect.gen(function* () {
|
|
284
|
+
const node = yield* createScanVirusNode("scan-pass", {
|
|
285
|
+
action: "pass",
|
|
286
|
+
timeout: 60000,
|
|
287
|
+
});
|
|
281
288
|
|
|
282
|
-
|
|
289
|
+
// Use file ID containing "infected" to trigger EICAR content from mock
|
|
290
|
+
const testFile = createTestUploadFile({ id: "infected-file-pass-1" });
|
|
283
291
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
292
|
+
const result = yield* node.run({
|
|
293
|
+
data: testFile,
|
|
294
|
+
jobId: "test-job",
|
|
295
|
+
flowId: "test-flow",
|
|
296
|
+
storageId: "test-storage",
|
|
297
|
+
clientId: "test-client",
|
|
298
|
+
});
|
|
291
299
|
|
|
292
|
-
|
|
293
|
-
|
|
300
|
+
expect(result.type).toBe("complete");
|
|
301
|
+
}).pipe(Effect.provide(TestLayer)),
|
|
294
302
|
);
|
|
295
303
|
|
|
296
304
|
it.effect(
|
|
@@ -299,9 +307,11 @@ describe("Scan Virus Node", () => {
|
|
|
299
307
|
Effect.gen(function* () {
|
|
300
308
|
const node = yield* createScanVirusNode("scan-pass-metadata", {
|
|
301
309
|
action: "pass",
|
|
310
|
+
timeout: 60000,
|
|
302
311
|
});
|
|
303
312
|
|
|
304
|
-
|
|
313
|
+
// Use file ID containing "infected" to trigger EICAR content from mock
|
|
314
|
+
const testFile = createTestUploadFile({ id: "infected-file-pass-2" });
|
|
305
315
|
|
|
306
316
|
const result = yield* node.run({
|
|
307
317
|
data: testFile,
|
|
@@ -327,9 +337,11 @@ describe("Scan Virus Node", () => {
|
|
|
327
337
|
Effect.gen(function* () {
|
|
328
338
|
const node = yield* createScanVirusNode("scan-pass-bytes", {
|
|
329
339
|
action: "pass",
|
|
340
|
+
timeout: 60000,
|
|
330
341
|
});
|
|
331
342
|
|
|
332
|
-
|
|
343
|
+
// Use file ID containing "infected" to trigger EICAR content from mock
|
|
344
|
+
const testFile = createTestUploadFile({ id: "infected-file-pass-3" });
|
|
333
345
|
|
|
334
346
|
const result = yield* node.run({
|
|
335
347
|
data: testFile,
|
|
@@ -342,7 +354,9 @@ describe("Scan Virus Node", () => {
|
|
|
342
354
|
expect(result.type).toBe("complete");
|
|
343
355
|
if (result.type === "complete") {
|
|
344
356
|
expect(result.data).toBeDefined();
|
|
345
|
-
|
|
357
|
+
// The transform creates a new upload with a new ID, but the file is processed
|
|
358
|
+
expect(result.data.id).toBeDefined();
|
|
359
|
+
expect(typeof result.data.id).toBe("string");
|
|
346
360
|
}
|
|
347
361
|
}).pipe(Effect.provide(TestLayer)),
|
|
348
362
|
);
|
|
@@ -375,6 +389,7 @@ describe("Scan Virus Node", () => {
|
|
|
375
389
|
Effect.gen(function* () {
|
|
376
390
|
const node = yield* createScanVirusNode("scan-default-timeout", {
|
|
377
391
|
action: "fail",
|
|
392
|
+
timeout: 60000,
|
|
378
393
|
});
|
|
379
394
|
|
|
380
395
|
expect(node.id).toBe("scan-default-timeout");
|
|
@@ -398,6 +413,7 @@ describe("Scan Virus Node", () => {
|
|
|
398
413
|
Effect.gen(function* () {
|
|
399
414
|
const node = yield* createScanVirusNode("scan-version", {
|
|
400
415
|
action: "fail",
|
|
416
|
+
timeout: 60000,
|
|
401
417
|
});
|
|
402
418
|
|
|
403
419
|
const testFile = createTestUploadFile();
|