@involvex/bun-scanner 1.0.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.
@@ -0,0 +1,26 @@
1
+ name: Test
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - name: Checkout code
15
+ uses: actions/checkout@v4
16
+
17
+ - name: Setup Bun
18
+ uses: oven-sh/setup-bun@v2
19
+ with:
20
+ bun-version: latest
21
+
22
+ - name: Install dependencies
23
+ run: bun install
24
+
25
+ - name: Run tests
26
+ run: bun test
package/.prettierrc ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "$schema": "http://json.schemastore.org/prettierrc",
3
+ "singleQuote": true,
4
+ "semi": true,
5
+ "printWidth": 100,
6
+ "trailingComma": "all",
7
+ "arrowParens": "avoid",
8
+ "bracketSpacing": false,
9
+ "useTabs": true,
10
+ "quoteProps": "consistent"
11
+ }
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ <img src="https://bun.com/logo.png" height="36" />
2
+
3
+ # Bun Security Scanner Template
4
+
5
+ A template for creating a security scanner for Bun's package installation
6
+ process. Security scanners scan packages against your threat intelligence feeds
7
+ and control whether installations proceed based on detected threats.
8
+
9
+ 📚 [**Full documentation**](https://bun.com/docs/install/security-scanner-api)
10
+
11
+ ## How It Works
12
+
13
+ When packages are installed via Bun, your security scanner:
14
+
15
+ 1. **Receives** package information (name, version)
16
+ 2. **Queries** your threat intelligence API
17
+ 3. **Validates** the response data
18
+ 4. **Categorizes** threats by severity
19
+ 5. **Returns** advisories to control installation (empty array if safe)
20
+
21
+ ### Advisory Levels
22
+
23
+ - **Fatal** (`level: 'fatal'`): Installation stops immediately
24
+ - Examples: malware, token stealers, backdoors, critical vulnerabilities
25
+ - **Warning** (`level: 'warn'`): User prompted for confirmation
26
+ - In TTY: User can choose to continue or cancel
27
+ - Non-TTY: Installation automatically cancelled
28
+ - Examples: protestware, adware, deprecated packages
29
+
30
+ All advisories are always displayed to the user regardless of level.
31
+
32
+ ### Error Handling
33
+
34
+ If your `scan` function throws an error, it will be gracefully handled by Bun, but the installation process **will be cancelled** as a defensive precaution.
35
+
36
+ ### Validation
37
+
38
+ When fetching threat feeds over the network, use schema validation
39
+ (e.g., Zod) to ensure data integrity. Invalid responses should fail immediately
40
+ rather than silently returning empty advisories.
41
+
42
+ ```typescript
43
+ import {z} from 'zod';
44
+
45
+ const ThreatFeedItemSchema = z.object({
46
+ package: z.string(),
47
+ version: z.string(),
48
+ url: z.string().nullable(),
49
+ description: z.string().nullable(),
50
+ categories: z.array(z.enum(['backdoor', 'botnet' /* ... */])),
51
+ });
52
+ ```
53
+
54
+ ### Useful Bun APIs
55
+
56
+ Bun provides several built-in APIs that are particularly useful for security scanner:
57
+
58
+ - [**Security scanner API Reference**](https://bun.com/docs/install/security-scanner-api): Complete API documentation for security scanners
59
+ - [**`Bun.semver.satisfies()`**](https://bun.com/docs/api/semver): Essential for checking if package versions match vulnerability ranges. No external dependencies needed.
60
+
61
+ ```typescript
62
+ if (Bun.semver.satisfies(version, '>=1.0.0 <1.2.5')) {
63
+ // Version is vulnerable
64
+ }
65
+ ```
66
+
67
+ - [**`Bun.hash`**](https://bun.com/docs/api/hashing#bun-hash): Fast hashing for package integrity checks
68
+ - [**`Bun.file`**](https://bun.com/docs/api/file-io): Efficient file I/O, could be used for reading local threat databases
69
+
70
+ ## Testing
71
+
72
+ This template includes tests for a known malicious package version.
73
+ Customize the test file as needed.
74
+
75
+ ```bash
76
+ bun test
77
+ ```
78
+
79
+ ## Publishing Your Provider
80
+
81
+ Publish your security scanner to npm:
82
+
83
+ ```bash
84
+ bun publish
85
+ ```
86
+
87
+ Users can now install your provider and add it to their `bunfig.toml` configuration.
88
+
89
+ To test locally before publishing, use [`bun link`](https://bun.sh/docs/cli/link):
90
+
91
+ ```bash
92
+ # In your provider directory
93
+ bun link
94
+
95
+ # In your test project
96
+ bun link @acme/bun # this is the name in package.json of your provider
97
+ ```
98
+
99
+ ## Contributing
100
+
101
+ This is a template repository. Fork it and customize for your organization's
102
+ security requirements.
103
+
104
+ ## Support
105
+
106
+ For docs and questions, see the [Bun documentation](https://bun.com/docs/install/security-scanner-api) or [Join our Discord](https://bun.com/discord).
107
+
108
+ For template issues, please open an issue in this repository.
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@involvex/bun-scanner",
3
+ "description": "Bun security scanner ",
4
+ "version": "1.0.0",
5
+ "author": "involvex",
6
+ "license": "MIT",
7
+ "exports": {
8
+ "./package.json": "./package.json",
9
+ ".": "./src/index.ts"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/involvex/involvex-bun-scan"
14
+ },
15
+ "keywords": [
16
+ "bun",
17
+ "security",
18
+ "provider"
19
+ ],
20
+ "devDependencies": {
21
+ "@types/bun": "^1.3.5",
22
+ "prettier": "^3.7.4",
23
+ "typescript": "^5.9.3"
24
+ }
25
+ }
@@ -0,0 +1,115 @@
1
+ import {expect, test} from 'bun:test';
2
+ import {scanner} from './src/index.ts';
3
+
4
+ /////////////////////////////////////////////////////////////////////////////////////
5
+ // This test file is mostly just here to get you up and running quickly. It's
6
+ // likely you'd want to improve or remove this, and add more coverage for your
7
+ // own code.
8
+ /////////////////////////////////////////////////////////////////////////////////////
9
+
10
+ test('Scanner should warn about known malicious packages', async () => {
11
+ const advisories = await scanner.scan({
12
+ packages: [
13
+ {
14
+ name: 'event-stream',
15
+ version: '3.3.6', // This was a known incident in 2018 - https://blog.npmjs.org/post/180565383195/details-about-the-event-stream-incident
16
+ requestedRange: '^3.3.0',
17
+ tarball: 'https://registry.npmjs.org/event-stream/-/event-stream-3.3.6.tgz',
18
+ },
19
+ ],
20
+ });
21
+
22
+ expect(advisories.length).toBeGreaterThan(0);
23
+ const advisory = advisories[0]!;
24
+ expect(advisory).toBeDefined();
25
+
26
+ expect(advisory).toMatchObject({
27
+ level: 'fatal',
28
+ package: 'event-stream',
29
+ url: expect.any(String),
30
+ description: expect.any(String),
31
+ });
32
+ });
33
+
34
+ test('There should be no advisories if no packages are being installed', async () => {
35
+ const advisories = await scanner.scan({packages: []});
36
+ expect(advisories.length).toBe(0);
37
+ });
38
+
39
+ test('Safe packages should return no advisories', async () => {
40
+ const advisories = await scanner.scan({
41
+ packages: [
42
+ {
43
+ name: 'lodash',
44
+ version: '4.17.21',
45
+ requestedRange: '^4.17.0',
46
+ tarball: 'https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz',
47
+ },
48
+ ],
49
+ });
50
+ expect(advisories.length).toBe(0);
51
+ });
52
+
53
+ test('Should handle multiple packages with mixed security status', async () => {
54
+ const advisories = await scanner.scan({
55
+ packages: [
56
+ {
57
+ name: 'event-stream',
58
+ version: '3.3.6', // malicious
59
+ requestedRange: '^3.3.0',
60
+ tarball: 'https://registry.npmjs.org/event-stream/-/event-stream-3.3.6.tgz',
61
+ },
62
+ {
63
+ name: 'lodash',
64
+ version: '4.17.21', // safe
65
+ requestedRange: '^4.17.0',
66
+ tarball: 'https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz',
67
+ },
68
+ ],
69
+ });
70
+
71
+ expect(advisories.length).toBe(1);
72
+ expect(advisories[0]?.package).toBe('event-stream');
73
+ });
74
+
75
+ test('Should differentiate between versions of the same package', async () => {
76
+ const maliciousVersion = await scanner.scan({
77
+ packages: [
78
+ {
79
+ name: 'event-stream',
80
+ version: '3.3.6', // malicious version
81
+ requestedRange: '3.3.6',
82
+ tarball: 'https://registry.npmjs.org/event-stream/-/event-stream-3.3.6.tgz',
83
+ },
84
+ ],
85
+ });
86
+
87
+ const safeVersion = await scanner.scan({
88
+ packages: [
89
+ {
90
+ name: 'event-stream',
91
+ version: '4.0.0', // safe version
92
+ requestedRange: '4.0.0',
93
+ tarball: 'https://registry.npmjs.org/event-stream/-/event-stream-4.0.0.tgz',
94
+ },
95
+ ],
96
+ });
97
+
98
+ expect(maliciousVersion.length).toBeGreaterThan(0);
99
+ expect(safeVersion.length).toBe(0);
100
+ });
101
+
102
+ test('Should handle scoped packages correctly', async () => {
103
+ const advisories = await scanner.scan({
104
+ packages: [
105
+ {
106
+ name: '@types/node',
107
+ version: '20.0.0',
108
+ requestedRange: '^20.0.0',
109
+ tarball: 'https://registry.npmjs.org/@types/node/-/node-20.0.0.tgz',
110
+ },
111
+ ],
112
+ });
113
+
114
+ expect(advisories.length).toBe(0);
115
+ });
package/src/index.ts ADDED
@@ -0,0 +1,74 @@
1
+ // This is just an example interface of mock data. You can change this to the
2
+ // type of your actual threat feed (or ideally use a good schema validation
3
+ // library to infer your types from).
4
+ interface ThreatFeedItem {
5
+ package: string;
6
+ range: string;
7
+ url: string | null;
8
+ description: string | null;
9
+ categories: Array<'protestware' | 'adware' | 'backdoor' | 'malware' | 'botnet'>;
10
+ }
11
+
12
+ async function fetchThreatFeed(packages: Bun.Security.Package[]): Promise<ThreatFeedItem[]> {
13
+ // In a real provider you would probably replace this mock data with a
14
+ // fetch() to your threat feed, validating it with Zod or a similar library.
15
+
16
+ const myPretendThreatFeed: ThreatFeedItem[] = [
17
+ {
18
+ package: 'event-stream',
19
+ range: '>=3.3.6 <4.0.0', // Matches 3.3.6 and above but less than 4.0.0
20
+ url: 'https://blog.npmjs.org/post/180565383195/details-about-the-event-stream-incident',
21
+ description: 'event-stream is a malicious package',
22
+ categories: ['malware'],
23
+ },
24
+ // ...
25
+ ];
26
+
27
+ return myPretendThreatFeed.filter(item => {
28
+ return packages.some(
29
+ p => p.name === item.package && Bun.semver.satisfies(p.version, item.range),
30
+ );
31
+ });
32
+ }
33
+
34
+ export const scanner: Bun.Security.Scanner = {
35
+ version: '1', // This is the version of Bun security scanner implementation. You should keep this set as '1'
36
+ async scan({packages}) {
37
+ const feed = await fetchThreatFeed(packages);
38
+
39
+ // Iterate over reported threats and return an array of advisories. This
40
+ // could be longer, shorter or equal length of the input packages array.
41
+ // Whatever you return will be shown to the user.
42
+
43
+ const results: Bun.Security.Advisory[] = [];
44
+
45
+ for (const item of feed) {
46
+ // Advisory levels control installation behavior:
47
+ // - All advisories are always shown to the user regardless of level
48
+ // - Fatal: Installation stops immediately (e.g., backdoors, botnets)
49
+ // - Warning: User prompted in TTY, auto-cancelled in non-TTY (e.g., protestware, adware)
50
+
51
+ const isFatal =
52
+ item.categories.includes('malware') ||
53
+ item.categories.includes('backdoor') ||
54
+ item.categories.includes('botnet');
55
+
56
+ const isWarning =
57
+ item.categories.includes('protestware') || item.categories.includes('adware');
58
+
59
+ if (!isFatal && !isWarning) continue;
60
+
61
+ // Besides the .level property, the other properties are just here
62
+ // for display to the user.
63
+ results.push({
64
+ level: isFatal ? 'fatal' : 'warn',
65
+ package: item.package,
66
+ url: item.url,
67
+ description: item.description,
68
+ });
69
+ }
70
+
71
+ // Return an empty array if there are no advisories!
72
+ return results;
73
+ },
74
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["ESNext"],
4
+ "target": "ESNext",
5
+ "module": "Preserve",
6
+ "moduleDetection": "force",
7
+ "jsx": "react-jsx",
8
+ "allowJs": true,
9
+ "moduleResolution": "bundler",
10
+ "allowImportingTsExtensions": true,
11
+ "verbatimModuleSyntax": true,
12
+ "noEmit": true,
13
+ "strict": true,
14
+ "skipLibCheck": true,
15
+ "noFallthroughCasesInSwitch": true,
16
+ "noUncheckedIndexedAccess": true,
17
+ "noImplicitOverride": true,
18
+ "noUnusedLocals": false,
19
+ "noUnusedParameters": false,
20
+ "noPropertyAccessFromIndexSignature": false
21
+ }
22
+ }