Skip to content

Commit ad282c5

Browse files
mikolalysenkoclaude
andcommitted
Add TypeScript CLI for patch application
Implements a CLI tool for applying security patches to node_modules: - Patch manifest schema with vulnerability tracking - Git-compatible SHA256 hashing utilities with streaming support - File and package verification logic - Atomic patch application (verify all before applying any) - CLI with --cwd, --dry-run, --silent, --manifest-path flags - Lint configuration matching main depscan repo 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 51ea285 commit ad282c5

File tree

14 files changed

+1001
-2
lines changed

14 files changed

+1001
-2
lines changed

.oxlintrc.json

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
{
2+
"$schema": "./node_modules/oxlint/configuration_schema.json",
3+
"ignorePatterns": ["**/dist/", "**/node_modules/", "**/.git/"],
4+
"plugins": ["typescript", "oxc", "promise", "import"],
5+
"categories": {
6+
"correctness": "warn",
7+
"perf": "warn",
8+
"suspicious": "warn"
9+
},
10+
"rules": {
11+
"no-unused-vars": "allow",
12+
"no-new-array": "allow",
13+
"no-empty-file": "allow",
14+
"no-await-in-loop": "allow",
15+
"consistent-function-scoping": "allow",
16+
"no-new": "allow",
17+
"no-extraneous-class": "allow",
18+
"no-array-index-key": "allow",
19+
"no-unsafe-optional-chaining": "allow",
20+
"no-promise-in-callback": "allow",
21+
"no-callback-in-promise": "allow",
22+
"consistent-type-imports": "deny",
23+
"no-empty-named-blocks": "allow",
24+
"no-unnecessary-parameter-property-assignment": "allow",
25+
"no-unneeded-ternary": "allow",
26+
"no-eq-null": "allow",
27+
"max-lines-per-function": "allow",
28+
"max-depth": "allow",
29+
"no-magic-numbers": "allow",
30+
"no-unassigned-import": "allow",
31+
"promise/always-return": "allow",
32+
"no-unassigned-vars": "deny",
33+
"typescript/no-floating-promises": "deny",
34+
"typescript/no-misused-promises": "deny",
35+
"typescript/return-await": "allow",
36+
"typescript/await-thenable": "allow",
37+
"typescript/consistent-type-imports": "allow",
38+
"typescript/no-base-to-string": "allow",
39+
"typescript/no-duplicate-type-constituents": "allow",
40+
"typescript/no-for-in-array": "allow",
41+
"typescript/no-meaningless-void-operator": "allow",
42+
"typescript/no-misused-spread": "allow",
43+
"typescript/no-redundant-type-constituents": "allow",
44+
"typescript/no-unnecessary-boolean-literal-compare": "allow",
45+
"typescript/no-unnecessary-template-expression": "allow",
46+
"typescript/no-unnecessary-type-arguments": "allow",
47+
"typescript/no-unnecessary-type-assertion": "allow",
48+
"typescript/no-unsafe-enum-comparison": "allow",
49+
"typescript/no-unsafe-type-assertion": "allow",
50+
"typescript/require-array-sort-compare": "allow",
51+
"typescript/restrict-template-expressions": "allow",
52+
"typescript/triple-slash-reference": "allow",
53+
"typescript/unbound-method": "allow"
54+
},
55+
"overrides": [
56+
{
57+
"files": ["**/*.test.ts", "**/*.test.js", "**/*.spec.ts", "**/*.spec.js"],
58+
"rules": {
59+
"typescript/no-floating-promises": "allow",
60+
"typescript/no-misused-promises": "allow"
61+
}
62+
}
63+
]
64+
}

README.md

Lines changed: 140 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,140 @@
1-
# socket-patch
2-
Socket patch CLI tool
1+
# Socket Patch CLI
2+
3+
CLI tool for applying security patches to dependencies.
4+
5+
## Setup
6+
7+
```bash
8+
# Install dependencies
9+
npm install
10+
11+
# Build the project
12+
npm run build
13+
```
14+
15+
## Usage
16+
17+
```bash
18+
# Apply patches from manifest (default: .socket/manifest.json)
19+
socket-patch apply
20+
21+
# Apply patches with custom manifest path
22+
socket-patch apply --manifest-path /path/to/manifest.json
23+
24+
# Dry run (verify patches can be applied without modifying files)
25+
socket-patch apply --dry-run
26+
27+
# Silent mode (only output errors)
28+
socket-patch apply --silent
29+
30+
# Custom working directory
31+
socket-patch apply --cwd /path/to/project
32+
```
33+
34+
## Development
35+
36+
```bash
37+
# Watch mode for development
38+
npm run dev
39+
```
40+
41+
## Project Structure
42+
43+
```
44+
src/
45+
├── cli.ts # Main CLI entry point
46+
├── commands/
47+
│ └── apply.ts # Apply patch command
48+
├── schema/
49+
│ └── manifest-schema.ts # Patch manifest schema (Zod)
50+
├── hash/
51+
│ └── git-sha256.ts # Git-compatible SHA256 hashing
52+
├── patch/
53+
│ ├── file-hash.ts # File hashing utilities
54+
│ └── apply.ts # Core patch application logic
55+
├── types.ts # TypeScript type definitions
56+
├── utils.ts # Utility functions
57+
└── index.ts # Library exports
58+
```
59+
60+
## Commands
61+
62+
### apply
63+
64+
Apply security patches to dependencies from a manifest file.
65+
66+
**Options:**
67+
- `--cwd` - Working directory (default: current directory)
68+
- `-d, --dry-run` - Verify patches can be applied without modifying files
69+
- `-s, --silent` - Only output errors
70+
- `-m, --manifest-path` - Path to patch manifest file (default: `.socket/manifest.json`)
71+
- `-h, --help` - Show help
72+
- `-v, --version` - Show version
73+
74+
**Exit Codes:**
75+
- `0` - Success (patches applied or already applied)
76+
- `1` - Error (manifest not found, verification failed, or patch application failed)
77+
78+
## Manifest Format
79+
80+
The manifest file (`.socket/manifest.json`) contains patch definitions:
81+
82+
```json
83+
{
84+
"patches": {
85+
"pkg:npm/[email protected]": {
86+
"uuid": "unique-patch-id",
87+
"exportedAt": "2024-01-01T00:00:00Z",
88+
"files": {
89+
"path/to/file.js": {
90+
"beforeHash": "git-sha256-before",
91+
"afterHash": "git-sha256-after"
92+
}
93+
},
94+
"vulnerabilities": {
95+
"GHSA-xxxx-xxxx-xxxx": {
96+
"cves": ["CVE-2024-12345"],
97+
"summary": "Vulnerability summary",
98+
"severity": "high",
99+
"description": "Detailed description"
100+
}
101+
},
102+
"description": "Patch description",
103+
"license": "MIT",
104+
"tier": "free"
105+
}
106+
}
107+
}
108+
```
109+
110+
Patched file contents are stored in `.socket/blobs/` directory, named by their Git-compatible SHA256 hash.
111+
112+
## Library Usage
113+
114+
The socket-patch CLI can also be used as a library:
115+
116+
```typescript
117+
import {
118+
PatchManifest,
119+
PatchManifestSchema,
120+
computeGitSHA256FromBuffer,
121+
computeGitSHA256FromChunks,
122+
applyPackagePatch,
123+
findNodeModules,
124+
} from '@socketsecurity/socket-patch-cli'
125+
126+
// Validate manifest
127+
const manifest = PatchManifestSchema.parse(manifestData)
128+
129+
// Compute file hashes
130+
const hash = computeGitSHA256FromBuffer(fileBuffer)
131+
132+
// Apply patches programmatically
133+
const result = await applyPackagePatch(
134+
packageKey,
135+
packagePath,
136+
files,
137+
blobsPath,
138+
dryRun,
139+
)
140+
```

biome.json

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
3+
"files": {
4+
"includes": ["**", "!.git", "!dist", "!node_modules"]
5+
},
6+
"formatter": {
7+
"enabled": true,
8+
"formatWithErrors": false,
9+
"indentStyle": "space",
10+
"indentWidth": 2,
11+
"lineEnding": "lf",
12+
"lineWidth": 80
13+
},
14+
"linter": {
15+
"enabled": false,
16+
"rules": {
17+
"style": {
18+
"noParameterAssign": "error",
19+
"useAsConstAssertion": "error",
20+
"useDefaultParameterLast": "error",
21+
"useEnumInitializers": "error",
22+
"useSelfClosingElements": "error",
23+
"useSingleVarDeclarator": "error",
24+
"noUnusedTemplateLiteral": "error",
25+
"useNumberNamespace": "error",
26+
"noInferrableTypes": "error",
27+
"noUselessElse": "error"
28+
}
29+
}
30+
},
31+
"javascript": {
32+
"formatter": {
33+
"arrowParentheses": "asNeeded",
34+
"semicolons": "asNeeded",
35+
"quoteStyle": "single",
36+
"jsxQuoteStyle": "single",
37+
"trailingCommas": "all"
38+
}
39+
},
40+
"json": {
41+
"formatter": {
42+
"trailingCommas": "none",
43+
"indentStyle": "space",
44+
"indentWidth": 2
45+
}
46+
}
47+
}

package.json

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
{
2+
"name": "@socketsecurity/socket-patch-cli",
3+
"version": "0.1.0",
4+
"description": "CLI tool for applying security patches to dependencies",
5+
"type": "module",
6+
"main": "dist/index.js",
7+
"types": "dist/index.d.ts",
8+
"bin": {
9+
"socket-patch": "dist/cli.js"
10+
},
11+
"exports": {
12+
".": {
13+
"types": "./dist/index.d.ts",
14+
"require": "./dist/index.js",
15+
"import": "./dist/index.js"
16+
},
17+
"./schema": {
18+
"types": "./dist/schema/manifest-schema.d.ts",
19+
"require": "./dist/schema/manifest-schema.js",
20+
"import": "./dist/schema/manifest-schema.js"
21+
},
22+
"./hash": {
23+
"types": "./dist/hash/git-sha256.d.ts",
24+
"require": "./dist/hash/git-sha256.js",
25+
"import": "./dist/hash/git-sha256.js"
26+
},
27+
"./patch": {
28+
"types": "./dist/patch/apply.d.ts",
29+
"require": "./dist/patch/apply.js",
30+
"import": "./dist/patch/apply.js"
31+
}
32+
},
33+
"scripts": {
34+
"build": "tsc",
35+
"dev": "tsc --watch",
36+
"patch": "node dist/cli.js",
37+
"lint": "oxlint -c ./.oxlintrc.json --tsconfig ./tsconfig.json --deny-warnings",
38+
"lint:fix": "pnpm run lint --fix && pnpm run lint:fix:fast",
39+
"lint:fix:fast": "biome format --write"
40+
},
41+
"keywords": [
42+
"security",
43+
"patch",
44+
"cli",
45+
"dependencies"
46+
],
47+
"author": "Socket Security",
48+
"license": "MIT",
49+
"dependencies": {
50+
"yargs": "^17.7.2",
51+
"zod": "^3.24.4"
52+
},
53+
"devDependencies": {
54+
"@biomejs/biome": "^2.1.2",
55+
"@types/node": "^20.0.0",
56+
"@types/yargs": "^17.0.32",
57+
"oxlint": "^1.15.0",
58+
"typescript": "^5.3.0"
59+
},
60+
"engines": {
61+
"node": ">=18.0.0"
62+
}
63+
}

src/cli.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env node
2+
3+
import yargs from 'yargs'
4+
import { hideBin } from 'yargs/helpers'
5+
import { applyCommand } from './commands/apply.js'
6+
7+
async function main(): Promise<void> {
8+
await yargs(hideBin(process.argv))
9+
.scriptName('socket-patch')
10+
.usage('$0 <command> [options]')
11+
.command(applyCommand)
12+
.demandCommand(1, 'You must specify a command')
13+
.help()
14+
.alias('h', 'help')
15+
.version()
16+
.alias('v', 'version')
17+
.strict()
18+
.parse()
19+
}
20+
21+
main().catch((error: Error) => {
22+
console.error('Error:', error.message)
23+
process.exit(1)
24+
})

0 commit comments

Comments
 (0)