@uploadista/flow-security-nodes 0.0.16-beta.1
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 +22 -0
- package/LICENSE +21 -0
- package/README.md +129 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +86 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +86 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +37 -0
- package/src/index.ts +16 -0
- package/src/scan-virus-node.ts +125 -0
- package/tsconfig.json +14 -0
- package/tsdown.config.ts +11 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
> @uploadista/flow-security-nodes@0.0.15 build /Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/security/nodes
|
|
4
|
+
> tsdown
|
|
5
|
+
|
|
6
|
+
[34mℹ[39m tsdown [2mv0.16.5[22m powered by rolldown [2mv1.0.0-beta.50[22m
|
|
7
|
+
[34mℹ[39m Using tsdown config: [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.05 kB[22m [2m│ gzip: 0.58 kB[22m
|
|
13
|
+
[34mℹ[39m [33m[CJS][39m 1 files, total: 1.05 kB
|
|
14
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[1mindex.mjs[22m [2m1.08 kB[22m [2m│ gzip: 0.61 kB[22m
|
|
15
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22mindex.mjs.map [2m5.11 kB[22m [2m│ gzip: 1.95 kB[22m
|
|
16
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22mindex.d.mts.map [2m0.54 kB[22m [2m│ gzip: 0.28 kB[22m
|
|
17
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[32m[1mindex.d.mts[22m[39m [2m3.18 kB[22m [2m│ gzip: 1.17 kB[22m
|
|
18
|
+
[34mℹ[39m [34m[ESM][39m 4 files, total: 9.91 kB
|
|
19
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22mindex.d.cts.map [2m0.54 kB[22m [2m│ gzip: 0.28 kB[22m
|
|
20
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22m[32m[1mindex.d.cts[22m[39m [2m3.18 kB[22m [2m│ gzip: 1.16 kB[22m
|
|
21
|
+
[34mℹ[39m [33m[CJS][39m 2 files, total: 3.72 kB
|
|
22
|
+
[32m✔[39m Build complete in [32m5257ms[39m
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 uploadista
|
|
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,129 @@
|
|
|
1
|
+
# @uploadista/flow-security-nodes
|
|
2
|
+
|
|
3
|
+
Security processing nodes for Uploadista Flow, including virus scanning and malware detection.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @uploadista/flow-security-nodes
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @uploadista/flow-security-nodes
|
|
11
|
+
# or
|
|
12
|
+
yarn add @uploadista/flow-security-nodes
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- **Virus Scanning**: Scan files for viruses and malware using pluggable antivirus engines
|
|
18
|
+
- **Configurable Actions**: Choose to fail flow or continue with metadata on virus detection
|
|
19
|
+
- **Effect-based**: Built on Effect-TS for type-safe, composable error handling
|
|
20
|
+
- **Plugin Architecture**: Support for multiple antivirus engines (ClamAV, cloud services, etc.)
|
|
21
|
+
|
|
22
|
+
## Available Nodes
|
|
23
|
+
|
|
24
|
+
### Scan Virus Node
|
|
25
|
+
|
|
26
|
+
Scans files for viruses and malware. Requires a `VirusScanPlugin` implementation (e.g., `@uploadista/flow-security-clamscan`).
|
|
27
|
+
|
|
28
|
+
#### Usage
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { createScanVirusNode } from "@uploadista/flow-security-nodes";
|
|
32
|
+
import { Effect } from "effect";
|
|
33
|
+
|
|
34
|
+
const program = Effect.gen(function* () {
|
|
35
|
+
// Create a scan virus node that fails on detection
|
|
36
|
+
const scanNode = yield* createScanVirusNode("virus-scan-1", {
|
|
37
|
+
action: "fail", // Stop flow if virus detected
|
|
38
|
+
timeout: 60000, // 60 second timeout
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Or create a node that passes with metadata
|
|
42
|
+
const auditNode = yield* createScanVirusNode("virus-scan-2", {
|
|
43
|
+
action: "pass", // Continue flow even if virus detected
|
|
44
|
+
timeout: 120000, // 2 minute timeout for large files
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
#### Parameters
|
|
50
|
+
|
|
51
|
+
- `id` (required): Unique node identifier
|
|
52
|
+
- `params` (optional): Configuration object
|
|
53
|
+
- `action`: `"fail"` | `"pass"` (default: `"fail"`)
|
|
54
|
+
- `"fail"`: Mark flow task as FAILED and stop processing when virus detected
|
|
55
|
+
- `"pass"`: Continue processing but add virus metadata to file
|
|
56
|
+
- `timeout`: Maximum scan time in milliseconds (default: 60000, max: 300000)
|
|
57
|
+
|
|
58
|
+
#### Scan Metadata
|
|
59
|
+
|
|
60
|
+
All scan results are stored in `file.metadata.virusScan`:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
type VirusScanMetadata = {
|
|
64
|
+
scanned: boolean; // Whether file was scanned
|
|
65
|
+
isClean: boolean; // Whether file is clean (no viruses)
|
|
66
|
+
detectedViruses: string[]; // Array of detected virus names
|
|
67
|
+
scanDate: string; // ISO 8601 timestamp
|
|
68
|
+
engineVersion: string; // Antivirus engine version
|
|
69
|
+
definitionsDate: string; // Virus definitions date
|
|
70
|
+
};
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
#### Example Flow
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { createFlow } from "@uploadista/core/flow";
|
|
77
|
+
import { createScanVirusNode } from "@uploadista/flow-security-nodes";
|
|
78
|
+
import { ClamScanPluginLayer } from "@uploadista/flow-security-clamscan";
|
|
79
|
+
|
|
80
|
+
const secureUploadFlow = createFlow({
|
|
81
|
+
nodes: [
|
|
82
|
+
// 1. Input node
|
|
83
|
+
createInputNode("input-1"),
|
|
84
|
+
|
|
85
|
+
// 2. Scan for viruses - fail if infected
|
|
86
|
+
createScanVirusNode("scan-1", {
|
|
87
|
+
action: "fail",
|
|
88
|
+
timeout: 60000,
|
|
89
|
+
}),
|
|
90
|
+
|
|
91
|
+
// 3. Process clean files
|
|
92
|
+
createImageResizeNode("resize-1", {
|
|
93
|
+
width: 1920,
|
|
94
|
+
height: 1080,
|
|
95
|
+
}),
|
|
96
|
+
|
|
97
|
+
// 4. Store to S3
|
|
98
|
+
createStorageNode("storage-1", {
|
|
99
|
+
storageId: "my-s3-bucket",
|
|
100
|
+
}),
|
|
101
|
+
],
|
|
102
|
+
edges: [
|
|
103
|
+
{ source: "input-1", target: "scan-1" },
|
|
104
|
+
{ source: "scan-1", target: "resize-1" },
|
|
105
|
+
{ source: "resize-1", target: "storage-1" },
|
|
106
|
+
],
|
|
107
|
+
}).pipe(Effect.provide(ClamScanPluginLayer()));
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Error Codes
|
|
111
|
+
|
|
112
|
+
The scan virus node may return the following error codes:
|
|
113
|
+
|
|
114
|
+
- `VIRUS_DETECTED`: Virus or malware detected in file (when `action: "fail"`)
|
|
115
|
+
- `VIRUS_SCAN_FAILED`: Generic scanning operation failure
|
|
116
|
+
- `CLAMAV_NOT_INSTALLED`: ClamAV or configured antivirus not available
|
|
117
|
+
- `SCAN_TIMEOUT`: Scanning exceeded timeout limit
|
|
118
|
+
|
|
119
|
+
## Requirements
|
|
120
|
+
|
|
121
|
+
This package requires a `VirusScanPlugin` implementation. See [@uploadista/flow-security-clamscan](../clamscan) for ClamAV support.
|
|
122
|
+
|
|
123
|
+
## TypeScript
|
|
124
|
+
|
|
125
|
+
This package is written in TypeScript and includes full type definitions.
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let e=require(`@uploadista/core/errors`),t=require(`@uploadista/core/flow`),n=require(`effect`),r=require(`zod`);const i=r.z.enum([`fail`,`pass`]),a=r.z.object({action:i.default(`fail`),timeout:r.z.number().min(1e3).max(3e5).optional().default(6e4)});function o(r,i={action:`fail`,timeout:6e4}){return n.Effect.gen(function*(){let o=yield*t.VirusScanPlugin,s=a.parse(i);return yield*(0,t.createTransformNode)({id:r,name:`Scan Virus`,description:`Scans files for viruses and malware using ClamAV`,transform:(t,r)=>n.Effect.gen(function*(){let n=yield*o.scan(t),i=yield*o.getVersion(),a={scanned:!0,isClean:n.isClean,detectedViruses:n.detectedViruses,scanDate:new Date().toISOString(),engineVersion:i,definitionsDate:new Date().toISOString()};if(!n.isClean){let t=`Virus detected: ${n.detectedViruses.join(`, `)}`;if(s.action===`fail`)return yield*(0,e.httpFailure)(`VIRUS_DETECTED`,{body:t,details:{scanMetadata:a}})}return{bytes:t,metadata:{...r.metadata,virusScan:a}}})})})}exports.ScanAction=i,exports.ScanVirusParams=a,exports.createScanVirusNode=o;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as _uploadista_core_flow0 from "@uploadista/core/flow";
|
|
2
|
+
import { ScanMetadata, ScanResult, VirusScanPlugin } from "@uploadista/core/flow";
|
|
3
|
+
import * as _uploadista_core_types0 from "@uploadista/core/types";
|
|
4
|
+
import * as _uploadista_core_errors0 from "@uploadista/core/errors";
|
|
5
|
+
import * as _uploadista_core_upload0 from "@uploadista/core/upload";
|
|
6
|
+
import { Effect } from "effect";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
|
|
9
|
+
//#region src/scan-virus-node.d.ts
|
|
10
|
+
/**
|
|
11
|
+
* Scan action to take when a virus is detected
|
|
12
|
+
*/
|
|
13
|
+
declare const ScanAction: z.ZodEnum<{
|
|
14
|
+
fail: "fail";
|
|
15
|
+
pass: "pass";
|
|
16
|
+
}>;
|
|
17
|
+
type ScanAction = z.infer<typeof ScanAction>;
|
|
18
|
+
/**
|
|
19
|
+
* Parameters for the Scan Virus node
|
|
20
|
+
*/
|
|
21
|
+
declare const ScanVirusParams: z.ZodObject<{
|
|
22
|
+
action: z.ZodDefault<z.ZodEnum<{
|
|
23
|
+
fail: "fail";
|
|
24
|
+
pass: "pass";
|
|
25
|
+
}>>;
|
|
26
|
+
timeout: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
27
|
+
}, z.core.$strip>;
|
|
28
|
+
type ScanVirusParams = z.infer<typeof ScanVirusParams>;
|
|
29
|
+
/**
|
|
30
|
+
* Creates a Scan Virus node for malware detection
|
|
31
|
+
*
|
|
32
|
+
* Scans files for viruses and malware using the configured VirusScanPlugin
|
|
33
|
+
* (typically ClamAV). Supports configurable actions on detection:
|
|
34
|
+
* - "fail": Stop flow execution when virus detected
|
|
35
|
+
* - "pass": Continue flow with detection metadata
|
|
36
|
+
*
|
|
37
|
+
* All scan results are stored in file.metadata.virusScan for downstream nodes.
|
|
38
|
+
*
|
|
39
|
+
* @param id - Unique node identifier
|
|
40
|
+
* @param params - Configuration parameters for scan behavior
|
|
41
|
+
* @returns Effect that resolves to the configured node
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* // Fail on virus detection (default)
|
|
46
|
+
* const failNode = yield* createScanVirusNode("scan-1", {
|
|
47
|
+
* action: "fail"
|
|
48
|
+
* });
|
|
49
|
+
*
|
|
50
|
+
* // Pass through with metadata
|
|
51
|
+
* const passNode = yield* createScanVirusNode("scan-2", {
|
|
52
|
+
* action: "pass",
|
|
53
|
+
* timeout: 120000 // 2 minutes
|
|
54
|
+
* });
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
declare function createScanVirusNode(id: string, params?: ScanVirusParams): Effect.Effect<_uploadista_core_flow0.FlowNodeData & {
|
|
58
|
+
inputSchema: z.ZodType<_uploadista_core_types0.UploadFile, unknown, z.core.$ZodTypeInternals<_uploadista_core_types0.UploadFile, unknown>>;
|
|
59
|
+
outputSchema: z.ZodType<_uploadista_core_types0.UploadFile, unknown, z.core.$ZodTypeInternals<_uploadista_core_types0.UploadFile, unknown>>;
|
|
60
|
+
run: (args: {
|
|
61
|
+
data: _uploadista_core_types0.UploadFile;
|
|
62
|
+
jobId: string;
|
|
63
|
+
storageId: string;
|
|
64
|
+
flowId: string;
|
|
65
|
+
inputs?: Record<string, unknown>;
|
|
66
|
+
clientId: string | null;
|
|
67
|
+
}) => Effect.Effect<_uploadista_core_flow0.NodeExecutionResult<_uploadista_core_types0.UploadFile>, _uploadista_core_errors0.UploadistaError, never>;
|
|
68
|
+
condition?: {
|
|
69
|
+
field: string;
|
|
70
|
+
operator: string;
|
|
71
|
+
value: unknown;
|
|
72
|
+
};
|
|
73
|
+
multiInput?: boolean;
|
|
74
|
+
multiOutput?: boolean;
|
|
75
|
+
pausable?: boolean;
|
|
76
|
+
retry?: {
|
|
77
|
+
maxRetries?: number;
|
|
78
|
+
retryDelay?: number;
|
|
79
|
+
exponentialBackoff?: boolean;
|
|
80
|
+
};
|
|
81
|
+
} & {
|
|
82
|
+
type: _uploadista_core_flow0.NodeType;
|
|
83
|
+
}, _uploadista_core_errors0.UploadistaError, VirusScanPlugin | _uploadista_core_upload0.UploadServer>;
|
|
84
|
+
//#endregion
|
|
85
|
+
export { ScanAction, type ScanAction as ScanActionType, type ScanMetadata, type ScanResult, ScanVirusParams, type ScanVirusParams as ScanVirusParamsType, createScanVirusNode };
|
|
86
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/scan-virus-node.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;cAYa,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,kBAAoD,MAAA,CAAA,OAAA,sBAAA,CAAA,YAAA;yBAAA,uBAAA,CAAA,UAAA;;;;;;;aA2DoplI;;iEAAA,uBAAA,CAAA,UAAA"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as _uploadista_core_errors0 from "@uploadista/core/errors";
|
|
2
|
+
import * as _uploadista_core_flow0 from "@uploadista/core/flow";
|
|
3
|
+
import { ScanMetadata, ScanResult, VirusScanPlugin } from "@uploadista/core/flow";
|
|
4
|
+
import { Effect } from "effect";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import * as _uploadista_core_types0 from "@uploadista/core/types";
|
|
7
|
+
import * as _uploadista_core_upload0 from "@uploadista/core/upload";
|
|
8
|
+
|
|
9
|
+
//#region src/scan-virus-node.d.ts
|
|
10
|
+
/**
|
|
11
|
+
* Scan action to take when a virus is detected
|
|
12
|
+
*/
|
|
13
|
+
declare const ScanAction: z.ZodEnum<{
|
|
14
|
+
fail: "fail";
|
|
15
|
+
pass: "pass";
|
|
16
|
+
}>;
|
|
17
|
+
type ScanAction = z.infer<typeof ScanAction>;
|
|
18
|
+
/**
|
|
19
|
+
* Parameters for the Scan Virus node
|
|
20
|
+
*/
|
|
21
|
+
declare const ScanVirusParams: z.ZodObject<{
|
|
22
|
+
action: z.ZodDefault<z.ZodEnum<{
|
|
23
|
+
fail: "fail";
|
|
24
|
+
pass: "pass";
|
|
25
|
+
}>>;
|
|
26
|
+
timeout: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
27
|
+
}, z.core.$strip>;
|
|
28
|
+
type ScanVirusParams = z.infer<typeof ScanVirusParams>;
|
|
29
|
+
/**
|
|
30
|
+
* Creates a Scan Virus node for malware detection
|
|
31
|
+
*
|
|
32
|
+
* Scans files for viruses and malware using the configured VirusScanPlugin
|
|
33
|
+
* (typically ClamAV). Supports configurable actions on detection:
|
|
34
|
+
* - "fail": Stop flow execution when virus detected
|
|
35
|
+
* - "pass": Continue flow with detection metadata
|
|
36
|
+
*
|
|
37
|
+
* All scan results are stored in file.metadata.virusScan for downstream nodes.
|
|
38
|
+
*
|
|
39
|
+
* @param id - Unique node identifier
|
|
40
|
+
* @param params - Configuration parameters for scan behavior
|
|
41
|
+
* @returns Effect that resolves to the configured node
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* // Fail on virus detection (default)
|
|
46
|
+
* const failNode = yield* createScanVirusNode("scan-1", {
|
|
47
|
+
* action: "fail"
|
|
48
|
+
* });
|
|
49
|
+
*
|
|
50
|
+
* // Pass through with metadata
|
|
51
|
+
* const passNode = yield* createScanVirusNode("scan-2", {
|
|
52
|
+
* action: "pass",
|
|
53
|
+
* timeout: 120000 // 2 minutes
|
|
54
|
+
* });
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
declare function createScanVirusNode(id: string, params?: ScanVirusParams): Effect.Effect<_uploadista_core_flow0.FlowNodeData & {
|
|
58
|
+
inputSchema: z.ZodType<_uploadista_core_types0.UploadFile, unknown, z.core.$ZodTypeInternals<_uploadista_core_types0.UploadFile, unknown>>;
|
|
59
|
+
outputSchema: z.ZodType<_uploadista_core_types0.UploadFile, unknown, z.core.$ZodTypeInternals<_uploadista_core_types0.UploadFile, unknown>>;
|
|
60
|
+
run: (args: {
|
|
61
|
+
data: _uploadista_core_types0.UploadFile;
|
|
62
|
+
jobId: string;
|
|
63
|
+
storageId: string;
|
|
64
|
+
flowId: string;
|
|
65
|
+
inputs?: Record<string, unknown>;
|
|
66
|
+
clientId: string | null;
|
|
67
|
+
}) => Effect.Effect<_uploadista_core_flow0.NodeExecutionResult<_uploadista_core_types0.UploadFile>, _uploadista_core_errors0.UploadistaError, never>;
|
|
68
|
+
condition?: {
|
|
69
|
+
field: string;
|
|
70
|
+
operator: string;
|
|
71
|
+
value: unknown;
|
|
72
|
+
};
|
|
73
|
+
multiInput?: boolean;
|
|
74
|
+
multiOutput?: boolean;
|
|
75
|
+
pausable?: boolean;
|
|
76
|
+
retry?: {
|
|
77
|
+
maxRetries?: number;
|
|
78
|
+
retryDelay?: number;
|
|
79
|
+
exponentialBackoff?: boolean;
|
|
80
|
+
};
|
|
81
|
+
} & {
|
|
82
|
+
type: _uploadista_core_flow0.NodeType;
|
|
83
|
+
}, _uploadista_core_errors0.UploadistaError, VirusScanPlugin | _uploadista_core_upload0.UploadServer>;
|
|
84
|
+
//#endregion
|
|
85
|
+
export { ScanAction, type ScanAction as ScanActionType, type ScanMetadata, type ScanResult, ScanVirusParams, type ScanVirusParams as ScanVirusParamsType, createScanVirusNode };
|
|
86
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/scan-virus-node.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;cAYa,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,kBAAoD,MAAA,CAAA,OAAA,sBAAA,CAAA,YAAA;yBAAA,uBAAA,CAAA,UAAA;;;;;;;aA2DoplI;;iEAAA,uBAAA,CAAA,UAAA"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{httpFailure as e}from"@uploadista/core/errors";import{VirusScanPlugin as t,createTransformNode as n}from"@uploadista/core/flow";import{Effect as r}from"effect";import{z as i}from"zod";const a=i.enum([`fail`,`pass`]),o=i.object({action:a.default(`fail`),timeout:i.number().min(1e3).max(3e5).optional().default(6e4)});function s(i,a={action:`fail`,timeout:6e4}){return r.gen(function*(){let s=yield*t,c=o.parse(a);return yield*n({id:i,name:`Scan Virus`,description:`Scans files for viruses and malware using ClamAV`,transform:(t,n)=>r.gen(function*(){let r=yield*s.scan(t),i=yield*s.getVersion(),a={scanned:!0,isClean:r.isClean,detectedViruses:r.detectedViruses,scanDate:new Date().toISOString(),engineVersion:i,definitionsDate:new Date().toISOString()};if(!r.isClean){let t=`Virus detected: ${r.detectedViruses.join(`, `)}`;if(c.action===`fail`)return yield*e(`VIRUS_DETECTED`,{body:t,details:{scanMetadata:a}})}return{bytes:t,metadata:{...n.metadata,virusScan:a}}})})})}export{a as ScanAction,o as ScanVirusParams,s as createScanVirusNode};
|
|
2
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["scanMetadata: ScanMetadata"],"sources":["../src/scan-virus-node.ts"],"sourcesContent":["import { httpFailure } from \"@uploadista/core/errors\";\nimport {\n createTransformNode,\n type ScanMetadata,\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) {\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 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":"+LAYA,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,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,EAAY,IACtB,EAAO,IAAI,WAAa,CAEtB,IAAM,EAAa,MAAO,EAAiB,KAAK,EAAW,CAGrD,EAAgB,MAAO,EAAiB,YAAY,CAGpDA,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
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@uploadista/flow-security-nodes",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.16-beta.1",
|
|
5
|
+
"description": "Security processing nodes for Uploadista Flow",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Uploadista",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.mts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.cjs",
|
|
13
|
+
"default": "./dist/index.mjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"effect": "3.19.4",
|
|
18
|
+
"zod": "4.1.12",
|
|
19
|
+
"@uploadista/core": "0.0.16-beta.1"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@effect/vitest": "0.27.0",
|
|
23
|
+
"@types/node": "24.10.1",
|
|
24
|
+
"tsdown": "0.16.5",
|
|
25
|
+
"vitest": "4.0.8",
|
|
26
|
+
"@uploadista/typescript-config": "0.0.16-beta.1"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsdown",
|
|
30
|
+
"format": "biome format --write ./src",
|
|
31
|
+
"lint": "biome lint --write ./src",
|
|
32
|
+
"check": "biome check --write ./src",
|
|
33
|
+
"test": "vitest",
|
|
34
|
+
"test:run": "vitest run",
|
|
35
|
+
"test:watch": "vitest watch"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Security processing nodes
|
|
2
|
+
|
|
3
|
+
// Import from core packages to ensure proper type resolution in generated declarations
|
|
4
|
+
// These imports force tsdown to create namespace aliases instead of inlining types
|
|
5
|
+
import type {} from "@uploadista/core/types";
|
|
6
|
+
import type {} from "@uploadista/core/upload";
|
|
7
|
+
|
|
8
|
+
// Re-export types from core for convenience
|
|
9
|
+
export type { ScanMetadata, ScanResult } from "@uploadista/core/flow";
|
|
10
|
+
export {
|
|
11
|
+
createScanVirusNode,
|
|
12
|
+
ScanAction,
|
|
13
|
+
type ScanAction as ScanActionType,
|
|
14
|
+
ScanVirusParams,
|
|
15
|
+
type ScanVirusParams as ScanVirusParamsType,
|
|
16
|
+
} from "./scan-virus-node";
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { httpFailure } from "@uploadista/core/errors";
|
|
2
|
+
import {
|
|
3
|
+
createTransformNode,
|
|
4
|
+
type ScanMetadata,
|
|
5
|
+
VirusScanPlugin,
|
|
6
|
+
} from "@uploadista/core/flow";
|
|
7
|
+
import { Effect } from "effect";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Scan action to take when a virus is detected
|
|
12
|
+
*/
|
|
13
|
+
export const ScanAction = z.enum(["fail", "pass"]);
|
|
14
|
+
export type ScanAction = z.infer<typeof ScanAction>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parameters for the Scan Virus node
|
|
18
|
+
*/
|
|
19
|
+
export const ScanVirusParams = z.object({
|
|
20
|
+
/**
|
|
21
|
+
* Action to take when a virus is detected:
|
|
22
|
+
* - "fail": Mark flow task as FAILED and stop processing
|
|
23
|
+
* - "pass": Continue processing but add virus metadata to file
|
|
24
|
+
*/
|
|
25
|
+
action: ScanAction.default("fail"),
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Maximum time to wait for scan completion (in milliseconds)
|
|
29
|
+
* Default: 60000ms (60 seconds)
|
|
30
|
+
* Max: 300000ms (5 minutes)
|
|
31
|
+
*/
|
|
32
|
+
timeout: z.number().min(1000).max(300000).optional().default(60000),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export type ScanVirusParams = z.infer<typeof ScanVirusParams>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Creates a Scan Virus node for malware detection
|
|
39
|
+
*
|
|
40
|
+
* Scans files for viruses and malware using the configured VirusScanPlugin
|
|
41
|
+
* (typically ClamAV). Supports configurable actions on detection:
|
|
42
|
+
* - "fail": Stop flow execution when virus detected
|
|
43
|
+
* - "pass": Continue flow with detection metadata
|
|
44
|
+
*
|
|
45
|
+
* All scan results are stored in file.metadata.virusScan for downstream nodes.
|
|
46
|
+
*
|
|
47
|
+
* @param id - Unique node identifier
|
|
48
|
+
* @param params - Configuration parameters for scan behavior
|
|
49
|
+
* @returns Effect that resolves to the configured node
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* // Fail on virus detection (default)
|
|
54
|
+
* const failNode = yield* createScanVirusNode("scan-1", {
|
|
55
|
+
* action: "fail"
|
|
56
|
+
* });
|
|
57
|
+
*
|
|
58
|
+
* // Pass through with metadata
|
|
59
|
+
* const passNode = yield* createScanVirusNode("scan-2", {
|
|
60
|
+
* action: "pass",
|
|
61
|
+
* timeout: 120000 // 2 minutes
|
|
62
|
+
* });
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export function createScanVirusNode(
|
|
66
|
+
id: string,
|
|
67
|
+
params: ScanVirusParams = { action: "fail", timeout: 60000 },
|
|
68
|
+
) {
|
|
69
|
+
return Effect.gen(function* () {
|
|
70
|
+
const virusScanService = yield* VirusScanPlugin;
|
|
71
|
+
|
|
72
|
+
// Validate params
|
|
73
|
+
const validatedParams = ScanVirusParams.parse(params);
|
|
74
|
+
|
|
75
|
+
return yield* createTransformNode({
|
|
76
|
+
id,
|
|
77
|
+
name: "Scan Virus",
|
|
78
|
+
description: "Scans files for viruses and malware using ClamAV",
|
|
79
|
+
transform: (inputBytes, file) =>
|
|
80
|
+
Effect.gen(function* () {
|
|
81
|
+
// Perform virus scan
|
|
82
|
+
const scanResult = yield* virusScanService.scan(inputBytes);
|
|
83
|
+
|
|
84
|
+
// Get engine version for metadata
|
|
85
|
+
const engineVersion = yield* virusScanService.getVersion();
|
|
86
|
+
|
|
87
|
+
// Build comprehensive scan metadata
|
|
88
|
+
const scanMetadata: ScanMetadata = {
|
|
89
|
+
scanned: true,
|
|
90
|
+
isClean: scanResult.isClean,
|
|
91
|
+
detectedViruses: scanResult.detectedViruses,
|
|
92
|
+
scanDate: new Date().toISOString(),
|
|
93
|
+
engineVersion,
|
|
94
|
+
definitionsDate: new Date().toISOString(), // TODO: Get actual definitions date from plugin
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Check if virus was detected
|
|
98
|
+
if (!scanResult.isClean) {
|
|
99
|
+
// Build error message with detected viruses
|
|
100
|
+
const virusList = scanResult.detectedViruses.join(", ");
|
|
101
|
+
const message = `Virus detected: ${virusList}`;
|
|
102
|
+
|
|
103
|
+
// Handle based on configured action
|
|
104
|
+
if (validatedParams.action === "fail") {
|
|
105
|
+
// Fail the flow task
|
|
106
|
+
return yield* httpFailure("VIRUS_DETECTED", {
|
|
107
|
+
body: message,
|
|
108
|
+
details: { scanMetadata },
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
// action === "pass": Continue with metadata (handled below)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Return file with scan metadata (clean or pass action)
|
|
115
|
+
return {
|
|
116
|
+
bytes: inputBytes, // Pass through original bytes unchanged
|
|
117
|
+
metadata: {
|
|
118
|
+
...file.metadata,
|
|
119
|
+
virusScan: scanMetadata,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}),
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@uploadista/typescript-config/server.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"baseUrl": "./",
|
|
5
|
+
"paths": {
|
|
6
|
+
"@/*": ["./src/*"]
|
|
7
|
+
},
|
|
8
|
+
"outDir": "./dist",
|
|
9
|
+
"rootDir": "./src",
|
|
10
|
+
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
|
11
|
+
"types": []
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}
|