@getbastionai/gatepost 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bastion
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,135 @@
1
+ ![Gatepost Banner](Image.png)
2
+
3
+ # Gatepost
4
+
5
+ **Gatepost** wraps your package managers and scans every install for malicious packages — before they touch your machine.
6
+
7
+ Every install is checked against three layers of protection:
8
+
9
+ | Check | What it catches |
10
+ |---|---|
11
+ | **Blocklist** | Known malicious packages by name |
12
+ | **Typosquat detection** | Lookalikes of popular packages (e.g. `lodahs` → `lodash`) |
13
+ | **CVE scanning** | Live vulnerability lookup via the [OSV.dev](https://osv.dev) API |
14
+
15
+ If a package is flagged, the install is blocked. If it's clean, Gatepost steps aside — zero friction, zero overhead.
16
+
17
+ ---
18
+
19
+ ## Supported package managers
20
+
21
+ | Ecosystem | Managers |
22
+ |---|---|
23
+ | **Node / JS** | `npm`, `npx`, `yarn`, `pnpm`, `pnpx`, `bun`, `bunx` |
24
+ | **Python** | `pip`, `pip3`, `uv`, `poetry`, `pipx` |
25
+
26
+ ---
27
+
28
+ ## Installation
29
+
30
+ ```sh
31
+ npm install -g @getbastionai/gatepost
32
+ ```
33
+
34
+ That's it. Shell aliases are set up automatically — restart your terminal and every package manager command is protected.
35
+
36
+ ---
37
+
38
+ ## Usage
39
+
40
+ After install, use your package managers exactly as you normally would:
41
+
42
+ ```sh
43
+ npm install lodash
44
+ yarn add axios
45
+ pip install requests
46
+ bun add hono
47
+ npx create-react-app my-app
48
+ uv add fastapi
49
+ ```
50
+
51
+ Gatepost runs silently when everything is clean. Output only appears when something is flagged.
52
+
53
+ ### Blocked install
54
+
55
+ ```
56
+ gatepost: install blocked
57
+
58
+ blocked event-stream Known malicious package
59
+ ```
60
+
61
+ The install exits with code 1. Nothing was installed.
62
+
63
+ ### Warning (install proceeds)
64
+
65
+ ```
66
+ gatepost: warning
67
+
68
+ warn lodahs Possible typosquat of "lodash"
69
+ ```
70
+
71
+ Warnings are shown but the install is not blocked — you decide.
72
+
73
+ ---
74
+
75
+ ## Manual check
76
+
77
+ Scan packages without installing them:
78
+
79
+ ```sh
80
+ gatepost check express axios lodash
81
+ ```
82
+
83
+ ```
84
+ ok express
85
+ ok axios
86
+ ok lodash
87
+
88
+ All packages look clean.
89
+ ```
90
+
91
+ ---
92
+
93
+ ## Commands
94
+
95
+ | Command | Description |
96
+ |---|---|
97
+ | `gatepost setup` | Add shell aliases (run once after install) |
98
+ | `gatepost remove` | Remove shell aliases |
99
+ | `gatepost check <pkg...>` | Scan packages without installing |
100
+ | `gatepost <manager> [args]` | Run any manager with protection |
101
+
102
+ ---
103
+
104
+ ## How it works
105
+
106
+ 1. Shell aliases silently redirect `npm install foo` → `gatepost npm install foo`
107
+ 2. Gatepost extracts package names from the command arguments
108
+ 3. Three checks run in parallel: blocklist lookup, typosquat similarity, OSV CVE query
109
+ 4. Blocked packages exit with code 1 — nothing installs
110
+ 5. Warnings print to stderr but allow the install to continue
111
+ 6. Clean packages pass straight through to the real package manager
112
+
113
+ If the OSV network request fails (offline or timeout), Gatepost warns and proceeds — it never blocks a legitimate workflow.
114
+
115
+ ---
116
+
117
+ ## Uninstall
118
+
119
+ ```sh
120
+ gatepost remove
121
+ npm uninstall -g @getbastionai/gatepost
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Requirements
127
+
128
+ - Node.js 16+
129
+ - npm (for global install)
130
+
131
+ ---
132
+
133
+ ## License
134
+
135
+ MIT
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@getbastionai/gatepost",
3
+ "version": "1.0.0",
4
+ "description": "Wraps npm, npx, yarn, pnpm, and pnpx to block malicious packages in real-time",
5
+ "scripts": {
6
+ "postinstall": "node ./src/index.js setup"
7
+ },
8
+ "bin": {
9
+ "gatepost": "./src/index.js"
10
+ },
11
+ "files": [
12
+ "src/"
13
+ ],
14
+ "keywords": [
15
+ "security",
16
+ "supply-chain",
17
+ "npm",
18
+ "malware",
19
+ "package-manager",
20
+ "typosquatting"
21
+ ],
22
+ "license": "MIT",
23
+ "engines": {
24
+ "node": ">=16"
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/GetBastion/gatepost.git"
29
+ }
30
+ }
@@ -0,0 +1,80 @@
1
+ 'use strict'
2
+
3
+ // Known malicious packages sourced from npm security advisories,
4
+ // GitHub Security Advisories, and public threat intelligence reports.
5
+ const BLOCKLIST = new Set([
6
+ // Compromised/backdoored packages
7
+ 'event-stream',
8
+ 'flatmap-stream',
9
+ 'node-ipc',
10
+ 'ua-parser-js',
11
+ 'coa',
12
+ 'rc',
13
+ 'colors',
14
+ 'faker',
15
+
16
+ // Typosquatting - targeting popular packages
17
+ 'crossenv',
18
+ 'cross-env.js',
19
+ 'ffmmpeg',
20
+ 'babelcli',
21
+ 'nodecaffe',
22
+ 'nodefabric',
23
+ 'node-fabric',
24
+ 'nodeffmpeg',
25
+ 'nodemysql',
26
+ 'node-opencv',
27
+ 'node-openssl',
28
+ 'node-os',
29
+ 'node-paint',
30
+ 'node-pentest',
31
+ 'node-sqlite',
32
+ 'node-tkinter',
33
+ 'nodecrypto',
34
+ 'nodeftp',
35
+ 'nodemailer-js',
36
+ 'nodemailer.js',
37
+ 'socketio',
38
+ 'socket.io.js',
39
+ 'discordie',
40
+ 'mongose',
41
+ 'mssql-node',
42
+ 'mysqljs',
43
+ 'node-sass-middleware',
44
+ 'gruntcli',
45
+ 'jquey',
46
+ 'jquery.js',
47
+ 'd3.js',
48
+ 'require',
49
+ 'loadash',
50
+ 'momnet',
51
+ 'expres',
52
+ 'expresss',
53
+ 'reakt',
54
+ 'reactt',
55
+ 'vue.js',
56
+ 'vuejs',
57
+ 'angularjs',
58
+ 'lodahs',
59
+ 'lodash.js',
60
+ 'requets',
61
+ 'requset',
62
+ 'underscore.js',
63
+ 'underscorejs',
64
+ 'webpak',
65
+ 'webpackk',
66
+ 'axio',
67
+ 'axxios',
68
+ 'dotenv.js',
69
+
70
+ // Known credential stealers (public reports)
71
+ 'electron-native-notify',
72
+ 'getcookies',
73
+ 'http-fetch-client',
74
+ 'aws-sdk-config',
75
+ 'node-browserify',
76
+ 'nodecookies',
77
+ 'xss-payloads',
78
+ ])
79
+
80
+ module.exports = { BLOCKLIST }
package/src/check.js ADDED
@@ -0,0 +1,142 @@
1
+ 'use strict'
2
+
3
+ const https = require('https')
4
+ const { BLOCKLIST } = require('./blocklist')
5
+ const { POPULAR_PACKAGES } = require('./typosquat')
6
+
7
+ // Levenshtein distance — measures edit distance between two strings
8
+ function levenshtein(a, b) {
9
+ const m = a.length, n = b.length
10
+ const dp = []
11
+ for (let i = 0; i <= m; i++) {
12
+ dp[i] = [i]
13
+ for (let j = 1; j <= n; j++) {
14
+ dp[i][j] = i === 0
15
+ ? j
16
+ : a[i - 1] === b[j - 1]
17
+ ? dp[i - 1][j - 1]
18
+ : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1])
19
+ }
20
+ }
21
+ return dp[m][n]
22
+ }
23
+
24
+ // Parse the canonical package name from an install arg
25
+ // Handles: lodash, lodash@4.x, @types/node, @types/node@18.0.0
26
+ function parsePkgName(arg) {
27
+ if (arg.startsWith('@')) {
28
+ const match = arg.match(/^(@[^/]+\/[^@]+)/)
29
+ return match ? match[1] : null
30
+ }
31
+ const name = arg.split('@')[0]
32
+ return name || null
33
+ }
34
+
35
+ // Skip non-package args (URLs, file paths, git refs)
36
+ function isPackageName(arg) {
37
+ if (!arg || arg.startsWith('-')) return false
38
+ if (arg.startsWith('git+') || arg.startsWith('github:') ||
39
+ arg.startsWith('gitlab:') || arg.startsWith('bitbucket:') ||
40
+ arg.startsWith('file:') || arg.startsWith('http') ||
41
+ arg.startsWith('.') || arg.startsWith('/')) return false
42
+ return true
43
+ }
44
+
45
+ // Check for typosquatting against popular packages
46
+ function checkTyposquat(pkgName, ecosystem = 'npm') {
47
+ const { POPULAR_PYTHON_PACKAGES } = require('./typosquat')
48
+ const pool = ecosystem === 'PyPI' ? POPULAR_PYTHON_PACKAGES : POPULAR_PACKAGES
49
+
50
+ // Use the local name for scoped npm packages: @types/node -> node
51
+ const localName = pkgName.startsWith('@')
52
+ ? pkgName.split('/')[1] || ''
53
+ : pkgName
54
+
55
+ const lower = localName.toLowerCase()
56
+ if (lower.length < 4) return null
57
+
58
+ for (const popular of pool) {
59
+ if (lower === popular.toLowerCase()) return null // exact match, not a typosquat
60
+ const dist = levenshtein(lower, popular.toLowerCase())
61
+ if (dist >= 1 && dist <= 2) return popular
62
+ }
63
+ return null
64
+ }
65
+
66
+ // Query the OSV.dev API for known vulnerabilities
67
+ function queryOSV(pkgName, ecosystem = 'npm') {
68
+ return new Promise((resolve) => {
69
+ const body = JSON.stringify({
70
+ package: { name: pkgName, ecosystem },
71
+ })
72
+
73
+ const req = https.request({
74
+ hostname: 'api.osv.dev',
75
+ path: '/v1/query',
76
+ method: 'POST',
77
+ headers: {
78
+ 'Content-Type': 'application/json',
79
+ 'Content-Length': Buffer.byteLength(body),
80
+ },
81
+ timeout: 5000,
82
+ }, (res) => {
83
+ let data = ''
84
+ res.on('data', chunk => { data += chunk })
85
+ res.on('end', () => {
86
+ try {
87
+ const json = JSON.parse(data)
88
+ resolve(json.vulns && json.vulns.length > 0 ? json.vulns : null)
89
+ } catch {
90
+ resolve(null)
91
+ }
92
+ })
93
+ })
94
+
95
+ req.on('error', () => resolve(null))
96
+ req.on('timeout', () => { req.destroy(); resolve(null) })
97
+ req.write(body)
98
+ req.end()
99
+ })
100
+ }
101
+
102
+ // Check a single package — returns { pkg, issues[] }
103
+ async function checkPackage(arg, ecosystem = 'npm') {
104
+ const pkgName = ecosystem === 'npm' ? parsePkgName(arg) : arg.trim()
105
+ if (!pkgName) return { pkg: arg, issues: [] }
106
+
107
+ const issues = []
108
+
109
+ // 1. Hard blocklist
110
+ if (BLOCKLIST.has(pkgName)) {
111
+ issues.push({ type: 'blocklist', severity: 'block', message: 'Known malicious package' })
112
+ }
113
+
114
+ // 2. Typosquatting
115
+ const similar = checkTyposquat(pkgName, ecosystem)
116
+ if (similar) {
117
+ issues.push({ type: 'typosquat', severity: 'warn', message: `Possible typosquat of "${similar}"` })
118
+ }
119
+
120
+ // 3. OSV vulnerability database
121
+ const vulns = await queryOSV(pkgName, ecosystem)
122
+ if (vulns) {
123
+ const count = vulns.length
124
+ issues.push({
125
+ type: 'vuln',
126
+ severity: 'warn',
127
+ message: `${count} known vulnerabilit${count === 1 ? 'y' : 'ies'} found in OSV database`,
128
+ vulns,
129
+ })
130
+ }
131
+
132
+ return { pkg: pkgName, issues }
133
+ }
134
+
135
+ // Check multiple packages in parallel
136
+ async function checkPackages(args, ecosystem = 'npm') {
137
+ const packageArgs = args.filter(isPackageName)
138
+ if (packageArgs.length === 0) return []
139
+ return Promise.all(packageArgs.map(a => checkPackage(a, ecosystem)))
140
+ }
141
+
142
+ module.exports = { checkPackages, parsePkgName, isPackageName }
package/src/index.js ADDED
@@ -0,0 +1,222 @@
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+
4
+ const { spawnSync } = require('child_process')
5
+ const { checkPackages } = require('./check')
6
+ const { setup, remove } = require('./setup')
7
+
8
+ const MANAGERS = ['npm', 'npx', 'yarn', 'pnpm', 'pnpx', 'bun', 'bunx', 'pip', 'pip3', 'uv', 'poetry', 'pipx']
9
+
10
+ // Ecosystem per manager — used for OSV API queries
11
+ const ECOSYSTEMS = {
12
+ npm: 'npm', npx: 'npm', yarn: 'npm', pnpm: 'npm', pnpx: 'npm',
13
+ bun: 'npm', bunx: 'npm',
14
+ pip: 'PyPI', pip3: 'PyPI', uv: 'PyPI', poetry: 'PyPI', pipx: 'PyPI',
15
+ }
16
+
17
+ // Subcommands that trigger a package install (null = the command itself is the package)
18
+ const INSTALL_SUBCMDS = {
19
+ npm: ['install', 'i', 'add', 'ci'],
20
+ yarn: ['add'],
21
+ pnpm: ['add', 'install', 'i'],
22
+ npx: null,
23
+ pnpx: null,
24
+ bun: ['add', 'install', 'i'],
25
+ bunx: null,
26
+ pip: ['install'],
27
+ pip3: ['install'],
28
+ uv: ['add', 'pip install'], // handled specially below
29
+ poetry: ['add'],
30
+ pipx: ['install', 'run'],
31
+ }
32
+
33
+ // ANSI colors
34
+ const c = {
35
+ red: s => `\x1b[31m${s}\x1b[0m`,
36
+ yellow: s => `\x1b[33m${s}\x1b[0m`,
37
+ green: s => `\x1b[32m${s}\x1b[0m`,
38
+ bold: s => `\x1b[1m${s}\x1b[0m`,
39
+ dim: s => `\x1b[2m${s}\x1b[0m`,
40
+ }
41
+
42
+ function printHelp() {
43
+ console.log(`
44
+ ${c.bold('gatepost')} — secure your package installs
45
+
46
+ ${c.bold('Usage:')}
47
+ gatepost setup Install shell aliases (run once)
48
+ gatepost remove Remove shell aliases
49
+ gatepost check <pkg...> Manually check packages
50
+ gatepost <manager> [args] Run a package manager with protection
51
+
52
+ ${c.bold('Examples:')}
53
+ gatepost setup
54
+ gatepost npm install lodash
55
+ gatepost check express axios
56
+
57
+ ${c.bold('Supported managers:')}
58
+ npm, npx, yarn, pnpm, pnpx, bun, bunx
59
+ pip, pip3, uv, poetry, pipx
60
+
61
+ After running ${c.bold('gatepost setup')}, your package managers are
62
+ automatically protected — no need to type "gatepost" each time.
63
+ `)
64
+ }
65
+
66
+ // Run the real package manager, stripping our shim dir from PATH to avoid recursion
67
+ function passThrough(manager, args) {
68
+ const result = spawnSync(manager, args, {
69
+ stdio: 'inherit',
70
+ env: {
71
+ ...process.env,
72
+ // Remove ~/.gatepost from PATH if present (shim recursion guard)
73
+ PATH: (process.env.PATH || '').split(':')
74
+ .filter(p => !p.includes('.gatepost'))
75
+ .join(':'),
76
+ },
77
+ })
78
+ process.exit(result.status ?? 0)
79
+ }
80
+
81
+ // Extract package names from install args (skip flags and subcommand)
82
+ function extractPackages(manager, args) {
83
+ // Single-package executors — first non-flag arg is the package
84
+ if (['npx', 'pnpx', 'bunx'].includes(manager)) {
85
+ const pkg = args.find(a => !a.startsWith('-'))
86
+ return pkg ? [pkg] : []
87
+ }
88
+
89
+ // uv has two install forms: `uv add pkg` and `uv pip install pkg`
90
+ if (manager === 'uv') {
91
+ const rest = args[0] === 'pip' ? args.slice(2) : args.slice(1)
92
+ return rest.filter(a => !a.startsWith('-')).map(stripPythonVersion)
93
+ }
94
+
95
+ // pip/pip3/poetry/pipx — skip subcommand, grab non-flag args
96
+ if (['pip', 'pip3', 'poetry', 'pipx'].includes(manager)) {
97
+ const rest = args.slice(1)
98
+ return rest.filter(a => !a.startsWith('-')).map(stripPythonVersion)
99
+ }
100
+
101
+ // npm/yarn/pnpm/bun — skip subcommand, grab non-flag args
102
+ const rest = args.slice(1)
103
+ return rest.filter(a => !a.startsWith('-'))
104
+ }
105
+
106
+ // Strip Python version specifiers: requests==2.28.0 -> requests, flask[async] -> flask
107
+ function stripPythonVersion(arg) {
108
+ return arg.replace(/[=<>!~^].*/,'').replace(/\[.*\]/, '').trim()
109
+ }
110
+
111
+ async function runWrapped(manager, args) {
112
+ const installSubcmds = INSTALL_SUBCMDS[manager]
113
+ const subCmd = args[0]
114
+
115
+ // Not an install command — pass straight through
116
+ const isInstall = installSubcmds === null
117
+ ? true
118
+ : installSubcmds.includes(subCmd)
119
+
120
+ if (!isInstall) {
121
+ return passThrough(manager, args)
122
+ }
123
+
124
+ const pkgs = extractPackages(manager, args)
125
+
126
+ // No explicit packages — installing from lockfile, pass through
127
+ if (pkgs.length === 0) {
128
+ return passThrough(manager, args)
129
+ }
130
+
131
+ const ecosystem = ECOSYSTEMS[manager] || 'npm'
132
+ process.stderr.write(c.dim(`gatepost: checking ${pkgs.join(', ')}...\n`))
133
+
134
+ let results
135
+ try {
136
+ results = await checkPackages(pkgs, ecosystem)
137
+ } catch {
138
+ // Network failure — warn and proceed
139
+ process.stderr.write(c.yellow('gatepost: security check failed (network error), proceeding anyway\n'))
140
+ return passThrough(manager, args)
141
+ }
142
+
143
+ const blocked = results.filter(r => r.issues.some(i => i.severity === 'block'))
144
+ const warned = results.filter(r => r.issues.some(i => i.severity === 'warn') && !blocked.find(b => b.pkg === r.pkg))
145
+
146
+ if (blocked.length > 0) {
147
+ console.error(c.red(c.bold('\ngatepost: install blocked\n')))
148
+ for (const r of blocked) {
149
+ for (const issue of r.issues) {
150
+ console.error(` ${c.red('blocked')} ${c.bold(r.pkg)} ${issue.message}`)
151
+ }
152
+ }
153
+ console.error('')
154
+ process.exit(1)
155
+ }
156
+
157
+ if (warned.length > 0) {
158
+ console.error(c.yellow(c.bold('\ngatepost: warning\n')))
159
+ for (const r of warned) {
160
+ for (const issue of r.issues) {
161
+ console.error(` ${c.yellow('warn')} ${c.bold(r.pkg)} ${issue.message}`)
162
+ }
163
+ }
164
+ console.error('')
165
+ }
166
+
167
+ passThrough(manager, args)
168
+ }
169
+
170
+ async function manualCheck(pkgs) {
171
+ if (pkgs.length === 0) {
172
+ console.error('Usage: gatepost check <package...>')
173
+ process.exit(1)
174
+ }
175
+
176
+ console.log(c.dim(`Checking ${pkgs.length} package(s) against blocklist, typosquatting, and OSV database...\n`))
177
+
178
+ const results = await checkPackages(pkgs)
179
+
180
+ let allClear = true
181
+ for (const r of results) {
182
+ if (r.issues.length === 0) {
183
+ console.log(` ${c.green('ok')} ${c.bold(r.pkg)}`)
184
+ } else {
185
+ allClear = false
186
+ for (const issue of r.issues) {
187
+ const label = issue.severity === 'block' ? c.red('blocked') : c.yellow('warn')
188
+ console.log(` ${label} ${c.bold(r.pkg)} ${issue.message}`)
189
+ }
190
+ }
191
+ }
192
+
193
+ if (allClear) {
194
+ console.log(c.green('\nAll packages look clean.'))
195
+ }
196
+ }
197
+
198
+ // --- CLI router ---
199
+ const args = process.argv.slice(2)
200
+ const command = args[0]
201
+
202
+ if (!command || command === '--help' || command === '-h') {
203
+ printHelp()
204
+ } else if (command === 'setup') {
205
+ setup()
206
+ } else if (command === 'remove' || command === 'uninstall') {
207
+ remove()
208
+ } else if (command === 'check') {
209
+ manualCheck(args.slice(1)).catch(err => {
210
+ console.error('Error:', err.message)
211
+ process.exit(1)
212
+ })
213
+ } else if (MANAGERS.includes(command)) {
214
+ runWrapped(command, args.slice(1)).catch(err => {
215
+ console.error('gatepost error:', err.message)
216
+ process.exit(1)
217
+ })
218
+ } else {
219
+ console.error(`Unknown command: ${command}`)
220
+ printHelp()
221
+ process.exit(1)
222
+ }
package/src/setup.js ADDED
@@ -0,0 +1,75 @@
1
+ 'use strict'
2
+
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+ const os = require('os')
6
+
7
+ const MANAGERS = ['npm', 'npx', 'yarn', 'pnpm', 'pnpx', 'bun', 'bunx', 'pip', 'pip3', 'uv', 'poetry', 'pipx']
8
+ const MARKER_START = '# gatepost-start'
9
+ const MARKER_END = '# gatepost-end'
10
+
11
+ function buildAliasBlock() {
12
+ const aliases = MANAGERS.map(m => `alias ${m}='gatepost ${m}'`).join('\n')
13
+ return `\n${MARKER_START}\n${aliases}\n${MARKER_END}\n`
14
+ }
15
+
16
+ function getShellConfigs() {
17
+ const home = os.homedir()
18
+ return [
19
+ path.join(home, '.zshrc'),
20
+ path.join(home, '.bashrc'),
21
+ path.join(home, '.bash_profile'),
22
+ path.join(home, '.profile'),
23
+ ].filter(f => fs.existsSync(f))
24
+ }
25
+
26
+ function setup() {
27
+ const block = buildAliasBlock()
28
+ const configs = getShellConfigs()
29
+
30
+ if (configs.length === 0) {
31
+ console.log('No shell config file found. Add these aliases manually:\n')
32
+ console.log(block)
33
+ return
34
+ }
35
+
36
+ let updated = 0
37
+ for (const config of configs) {
38
+ const contents = fs.readFileSync(config, 'utf8')
39
+ if (contents.includes(MARKER_START)) {
40
+ console.log(` Already configured: ${config}`)
41
+ continue
42
+ }
43
+ fs.appendFileSync(config, block)
44
+ console.log(` Updated: ${config}`)
45
+ updated++
46
+ }
47
+
48
+ if (updated > 0) {
49
+ console.log('\nGatepost is set up. Restart your terminal or run:')
50
+ console.log(' source ~/.zshrc\n')
51
+ console.log('After that, npm, npx, yarn, pnpm, and pnpx will be protected automatically.')
52
+ }
53
+ }
54
+
55
+ function remove() {
56
+ const configs = getShellConfigs()
57
+ const re = new RegExp(`\\n${MARKER_START}[\\s\\S]*?${MARKER_END}\\n`, 'g')
58
+
59
+ let removed = 0
60
+ for (const config of configs) {
61
+ const contents = fs.readFileSync(config, 'utf8')
62
+ if (!contents.includes(MARKER_START)) continue
63
+ fs.writeFileSync(config, contents.replace(re, '\n'))
64
+ console.log(` Removed aliases from: ${config}`)
65
+ removed++
66
+ }
67
+
68
+ if (removed === 0) {
69
+ console.log('No Gatepost aliases found to remove.')
70
+ } else {
71
+ console.log('\nGatepost removed. Restart your terminal to apply.')
72
+ }
73
+ }
74
+
75
+ module.exports = { setup, remove }
@@ -0,0 +1,51 @@
1
+ 'use strict'
2
+
3
+ // Top npm packages by download count — used for typosquatting detection.
4
+ // If a package name is within edit distance 1-2 of one of these, it's flagged.
5
+ const POPULAR_PACKAGES = [
6
+ 'lodash', 'express', 'react', 'chalk', 'commander', 'axios', 'moment',
7
+ 'webpack', 'babel', 'typescript', 'eslint', 'prettier', 'jest', 'mocha',
8
+ 'dotenv', 'mongoose', 'sequelize', 'socket.io', 'nodemailer', 'passport',
9
+ 'bcrypt', 'jsonwebtoken', 'cors', 'helmet', 'morgan', 'body-parser',
10
+ 'uuid', 'underscore', 'async', 'request', 'superagent', 'got', 'node-fetch',
11
+ 'inquirer', 'yargs', 'minimist', 'glob', 'rimraf', 'mkdirp', 'fs-extra',
12
+ 'path', 'events', 'stream', 'buffer', 'util', 'crypto', 'http', 'https',
13
+ 'vue', 'angular', 'svelte', 'next', 'nuxt', 'gatsby',
14
+ 'react-dom', 'react-router', 'redux', 'mobx', 'zustand',
15
+ 'vite', 'rollup', 'parcel', 'esbuild',
16
+ 'prettier', 'husky', 'lint-staged',
17
+ 'prisma', 'typeorm', 'knex', 'redis', 'ioredis',
18
+ 'sharp', 'jimp', 'multer', 'formidable',
19
+ 'ws', 'uws', 'fastify', 'koa', 'hapi', 'restify',
20
+ 'pm2', 'nodemon', 'concurrently',
21
+ 'cheerio', 'puppeteer', 'playwright',
22
+ 'aws-sdk', 'firebase', 'supabase',
23
+ 'tailwindcss', 'postcss', 'autoprefixer',
24
+ 'zod', 'joi', 'yup', 'ajv',
25
+ 'date-fns', 'luxon', 'dayjs',
26
+ 'ramda', 'immer', 'immutable',
27
+ 'rxjs', 'bluebird', 'p-limit',
28
+ 'semver', 'acorn', 'terser',
29
+ ]
30
+
31
+ // Top PyPI packages by download count
32
+ const POPULAR_PYTHON_PACKAGES = [
33
+ 'requests', 'numpy', 'pandas', 'flask', 'django', 'fastapi', 'sqlalchemy',
34
+ 'pytest', 'boto3', 'pydantic', 'celery', 'redis', 'pillow', 'scipy',
35
+ 'matplotlib', 'tensorflow', 'torch', 'scikit-learn', 'transformers',
36
+ 'cryptography', 'paramiko', 'fabric', 'ansible', 'click', 'typer',
37
+ 'httpx', 'aiohttp', 'uvicorn', 'gunicorn', 'starlette', 'fastapi',
38
+ 'alembic', 'marshmallow', 'attrs', 'pyyaml', 'toml', 'dotenv',
39
+ 'python-dotenv', 'rich', 'loguru', 'arrow', 'pendulum', 'dateutil',
40
+ 'six', 'packaging', 'setuptools', 'wheel', 'pip', 'virtualenv',
41
+ 'black', 'mypy', 'flake8', 'pylint', 'isort', 'bandit',
42
+ 'docker', 'kubernetes', 'google-cloud', 'azure', 'openai', 'anthropic',
43
+ 'langchain', 'opentelemetry', 'prometheus-client', 'psutil',
44
+ 'psycopg2', 'pymongo', 'motor', 'elasticsearch', 'stripe',
45
+ 'twilio', 'sendgrid', 'jinja2', 'werkzeug', 'itsdangerous',
46
+ 'lxml', 'beautifulsoup4', 'selenium', 'playwright', 'scrapy',
47
+ 'PyJWT', 'bcrypt', 'passlib', 'authlib',
48
+ 'tqdm', 'tabulate', 'openpyxl', 'xlrd',
49
+ ]
50
+
51
+ module.exports = { POPULAR_PACKAGES, POPULAR_PYTHON_PACKAGES }