bugproof 0.1.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/CHANGELOG.md +65 -0
- package/README.md +256 -0
- package/assets/icon-16x16.png +0 -0
- package/assets/icon-32x32.png +0 -0
- package/assets/icon-512x512.png +0 -0
- package/dist/capture/engine.d.ts +12 -0
- package/dist/capture/engine.d.ts.map +1 -0
- package/dist/capture/engine.js +129 -0
- package/dist/capture/engine.js.map +1 -0
- package/dist/capture/packager.d.ts +39 -0
- package/dist/capture/packager.d.ts.map +1 -0
- package/dist/capture/packager.js +145 -0
- package/dist/capture/packager.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +538 -0
- package/dist/cli.js.map +1 -0
- package/dist/diff/engine.d.ts +28 -0
- package/dist/diff/engine.d.ts.map +1 -0
- package/dist/diff/engine.js +88 -0
- package/dist/diff/engine.js.map +1 -0
- package/dist/replay/engine.d.ts +41 -0
- package/dist/replay/engine.d.ts.map +1 -0
- package/dist/replay/engine.js +69 -0
- package/dist/replay/engine.js.map +1 -0
- package/dist/replay/sandbox.d.ts +33 -0
- package/dist/replay/sandbox.d.ts.map +1 -0
- package/dist/replay/sandbox.js +167 -0
- package/dist/replay/sandbox.js.map +1 -0
- package/dist/replay/verdict.d.ts +8 -0
- package/dist/replay/verdict.d.ts.map +1 -0
- package/dist/replay/verdict.js +32 -0
- package/dist/replay/verdict.js.map +1 -0
- package/dist/sandbox/bugbox.d.ts +38 -0
- package/dist/sandbox/bugbox.d.ts.map +1 -0
- package/dist/sandbox/bugbox.js +122 -0
- package/dist/sandbox/bugbox.js.map +1 -0
- package/dist/sandbox/capabilities.d.ts +33 -0
- package/dist/sandbox/capabilities.d.ts.map +1 -0
- package/dist/sandbox/capabilities.js +53 -0
- package/dist/sandbox/capabilities.js.map +1 -0
- package/dist/sandbox/filesystem.d.ts +50 -0
- package/dist/sandbox/filesystem.d.ts.map +1 -0
- package/dist/sandbox/filesystem.js +134 -0
- package/dist/sandbox/filesystem.js.map +1 -0
- package/dist/sandbox/network.d.ts +68 -0
- package/dist/sandbox/network.d.ts.map +1 -0
- package/dist/sandbox/network.js +136 -0
- package/dist/sandbox/network.js.map +1 -0
- package/dist/sandbox/process.d.ts +17 -0
- package/dist/sandbox/process.d.ts.map +1 -0
- package/dist/sandbox/process.js +30 -0
- package/dist/sandbox/process.js.map +1 -0
- package/dist/sandbox/resources.d.ts +21 -0
- package/dist/sandbox/resources.d.ts.map +1 -0
- package/dist/sandbox/resources.js +60 -0
- package/dist/sandbox/resources.js.map +1 -0
- package/dist/types/artifact.d.ts +57 -0
- package/dist/types/artifact.d.ts.map +1 -0
- package/dist/types/artifact.js +2 -0
- package/dist/types/artifact.js.map +1 -0
- package/dist/types/failure.d.ts +12 -0
- package/dist/types/failure.d.ts.map +1 -0
- package/dist/types/failure.js +2 -0
- package/dist/types/failure.js.map +1 -0
- package/dist/utils/archive.d.ts +13 -0
- package/dist/utils/archive.d.ts.map +1 -0
- package/dist/utils/archive.js +39 -0
- package/dist/utils/archive.js.map +1 -0
- package/dist/utils/associations.d.ts +10 -0
- package/dist/utils/associations.d.ts.map +1 -0
- package/dist/utils/associations.js +46 -0
- package/dist/utils/associations.js.map +1 -0
- package/dist/utils/exclude.d.ts +12 -0
- package/dist/utils/exclude.d.ts.map +1 -0
- package/dist/utils/exclude.js +42 -0
- package/dist/utils/exclude.js.map +1 -0
- package/dist/utils/fingerprint.d.ts +16 -0
- package/dist/utils/fingerprint.d.ts.map +1 -0
- package/dist/utils/fingerprint.js +72 -0
- package/dist/utils/fingerprint.js.map +1 -0
- package/dist/utils/git.d.ts +13 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +41 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/json-output.d.ts +36 -0
- package/dist/utils/json-output.d.ts.map +1 -0
- package/dist/utils/json-output.js +49 -0
- package/dist/utils/json-output.js.map +1 -0
- package/dist/utils/paths.d.ts +19 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +43 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/secrets.d.ts +13 -0
- package/dist/utils/secrets.d.ts.map +1 -0
- package/dist/utils/secrets.js +52 -0
- package/dist/utils/secrets.js.map +1 -0
- package/dist/utils/security.d.ts +27 -0
- package/dist/utils/security.d.ts.map +1 -0
- package/dist/utils/security.js +75 -0
- package/dist/utils/security.js.map +1 -0
- package/dist/utils/ui.d.ts +31 -0
- package/dist/utils/ui.d.ts.map +1 -0
- package/dist/utils/ui.js +54 -0
- package/dist/utils/ui.js.map +1 -0
- package/package.json +80 -0
- package/scripts/bugproof-file-association-linux.sh +80 -0
- package/scripts/bugproof-file-association-macos.sh +48 -0
- package/scripts/bugproof-file-association-windows.reg +44 -0
- package/scripts/postinstall.cjs +215 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to BugProof will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-05-06
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Capture command** — Record failing commands with full context (source code, environment, output)
|
|
13
|
+
- **Replay command** — Execute captured bug artifacts with sandbox isolation and fingerprint matching
|
|
14
|
+
- **Inspect command** — Examine artifact contents without running code
|
|
15
|
+
- **Diff command** — Compare two artifacts side-by-side for changes
|
|
16
|
+
- **Cross-platform support** — Capture on Windows, replay on Linux/macOS with exact fingerprint matching
|
|
17
|
+
- **Sandbox isolation** — Linux cgroups v2, Windows Job Objects, macOS sandbox-exec isolation
|
|
18
|
+
- **Secret redaction** — Automatic detection and masking of 20+ secret patterns (API keys, tokens, passwords)
|
|
19
|
+
- **File association** — Post-install registration of `.bug` file type (Windows/Linux/macOS)
|
|
20
|
+
- **Install-time checks** — Verify Node.js 18+, Git availability, optional language toolchains
|
|
21
|
+
- **JSON output mode** — Structured output for CI/CD integration
|
|
22
|
+
- **Environment override** — Custom environment variables during replay
|
|
23
|
+
- **Exclude patterns** — Skip files matching patterns during capture
|
|
24
|
+
- **Source snapshots** — Bundle git-tracked files with artifacts
|
|
25
|
+
|
|
26
|
+
### Security
|
|
27
|
+
|
|
28
|
+
- Path traversal prevention via `isPathWithinBoundary()`
|
|
29
|
+
- Git ref injection prevention via `isValidGitRef()`
|
|
30
|
+
- Shell injection prevention (no shell: true in spawn calls)
|
|
31
|
+
- Environment variable hijacking prevention (LD_PRELOAD, NODE_OPTIONS blocklist)
|
|
32
|
+
- Symlink escape prevention in file operations
|
|
33
|
+
|
|
34
|
+
### Testing
|
|
35
|
+
|
|
36
|
+
- 19 test suites with 131 passing tests
|
|
37
|
+
- 60%+ code coverage across all modules
|
|
38
|
+
- Cross-platform validation (Windows ↔ Linux)
|
|
39
|
+
- E2E sandbox isolation tests
|
|
40
|
+
|
|
41
|
+
### Documentation
|
|
42
|
+
|
|
43
|
+
- Comprehensive README with quick start guide
|
|
44
|
+
- CLI help command for discoverability
|
|
45
|
+
- Command reference with examples
|
|
46
|
+
- File association setup instructions
|
|
47
|
+
- Troubleshooting and FAQ sections
|
|
48
|
+
|
|
49
|
+
## [0.2.0] - Planned
|
|
50
|
+
|
|
51
|
+
- npm global install polish
|
|
52
|
+
- Docker sandbox fallback
|
|
53
|
+
- Web UI for artifact inspection
|
|
54
|
+
- Language-specific dependency detection
|
|
55
|
+
|
|
56
|
+
## [0.3.0] - Planned
|
|
57
|
+
|
|
58
|
+
- Artifact cloud storage (push/pull with signing)
|
|
59
|
+
- GitHub issue integration
|
|
60
|
+
- Richer diff visualization
|
|
61
|
+
|
|
62
|
+
## [0.4.0] - Planned
|
|
63
|
+
|
|
64
|
+
- Advanced CI/CD integration
|
|
65
|
+
- Enterprise features (audit logging, team sharing)
|
package/README.md
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# BugProof
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
**Executable bugs, not bug reports.**
|
|
8
|
+
|
|
9
|
+
Capture a backend or CLI failure into a portable `.bug` artifact that another machine can replay.
|
|
10
|
+
|
|
11
|
+
[](https://opensource.org/licenses/MIT)
|
|
12
|
+
[]()
|
|
13
|
+
[]()
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
## What BugProof Captures
|
|
18
|
+
|
|
19
|
+
A `.bug` artifact includes:
|
|
20
|
+
- Source snapshot (git-tracked files, optional untracked)
|
|
21
|
+
- Command, arguments, and working directory
|
|
22
|
+
- Environment schema (secret values redacted)
|
|
23
|
+
- Stdout/stderr and failure fingerprint
|
|
24
|
+
- Capture metadata (OS, architecture, commit, branch)
|
|
25
|
+
|
|
26
|
+
This makes replay deterministic and shareable.
|
|
27
|
+
|
|
28
|
+
## Install (NPM Package)
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install -g bugproof
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Install-time checks (automatic)
|
|
35
|
+
|
|
36
|
+
During installation, BugProof now automatically:
|
|
37
|
+
- Verifies Node.js version (requires 18+)
|
|
38
|
+
- Checks that Git is available
|
|
39
|
+
- Detects optional language toolchains (python/java/gcc/g++/go/rustc)
|
|
40
|
+
- Attempts `.bug` file association and icon registration (best effort, user scope)
|
|
41
|
+
|
|
42
|
+
If association setup fails on your system, run manual scripts:
|
|
43
|
+
- Windows: `scripts/bugproof-file-association-windows.reg`
|
|
44
|
+
- Linux: `scripts/bugproof-file-association-linux.sh`
|
|
45
|
+
- macOS: `scripts/bugproof-file-association-macos.sh`
|
|
46
|
+
|
|
47
|
+
## Requirements
|
|
48
|
+
|
|
49
|
+
Required:
|
|
50
|
+
- Node.js 18+
|
|
51
|
+
- Git
|
|
52
|
+
|
|
53
|
+
Optional (only needed if your captured command uses them):
|
|
54
|
+
- Python / Java / GCC / G++ / Go / Rust toolchains
|
|
55
|
+
|
|
56
|
+
## Quick Start
|
|
57
|
+
|
|
58
|
+
### 1) Capture a failure
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
bugproof capture -- npm test
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 2) Replay it anywhere
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
bugproof replay bug_1778049738215.bug
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 3) Inspect artifact contents
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
bugproof inspect bug_1778049738215.bug
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 4) Diff two artifacts
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
bugproof diff old.bug new.bug
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## CLI Help (Lists Everything)
|
|
83
|
+
|
|
84
|
+
Show full command list and global options:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
bugproof --help
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Show command-specific help:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
bugproof help capture
|
|
94
|
+
bugproof help replay
|
|
95
|
+
bugproof help inspect
|
|
96
|
+
bugproof help diff
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
You can also use:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
bugproof capture --help
|
|
103
|
+
bugproof replay --help
|
|
104
|
+
bugproof inspect --help
|
|
105
|
+
bugproof diff --help
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Commands Reference
|
|
109
|
+
|
|
110
|
+
### `bugproof capture [command...]`
|
|
111
|
+
|
|
112
|
+
Capture a command execution as a `.bug` artifact.
|
|
113
|
+
|
|
114
|
+
Examples:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
bugproof capture -- npm test
|
|
118
|
+
bugproof capture -n auth-crash -d "Login fails on expired session" -- node server.js
|
|
119
|
+
bugproof capture --include-untracked -- python app.py
|
|
120
|
+
bugproof capture -e "*.log" -e "*.tmp" -- go test ./...
|
|
121
|
+
bugproof capture --timeout 600000 -- java -cp . Main
|
|
122
|
+
bugproof capture --json -- node script.js
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Options:
|
|
126
|
+
- `--include-untracked` Include untracked files (`git ls-files -o`)
|
|
127
|
+
- `--skip-secrets` Skip environment secret scan
|
|
128
|
+
- `--timeout <ms>` Command timeout in milliseconds (default: `300000`)
|
|
129
|
+
- `-n, --name <name>` Human-readable artifact name
|
|
130
|
+
- `-d, --description <desc>` Bug description
|
|
131
|
+
- `-e, --exclude <pattern>` Exclude files matching pattern (repeatable)
|
|
132
|
+
- `--json` Structured JSON output
|
|
133
|
+
|
|
134
|
+
### `bugproof replay <artifact>`
|
|
135
|
+
|
|
136
|
+
Replay a `.bug` artifact and compare failure signature.
|
|
137
|
+
|
|
138
|
+
Examples:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
bugproof replay my-bug.bug
|
|
142
|
+
bugproof replay --version-match strict my-bug.bug
|
|
143
|
+
bugproof replay --version-match branch my-bug.bug
|
|
144
|
+
bugproof replay --sandbox isolated my-bug.bug
|
|
145
|
+
bugproof replay --env API_URL=https://staging.local --env DEBUG=true my-bug.bug
|
|
146
|
+
bugproof replay --json my-bug.bug
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Options:
|
|
150
|
+
- `--version-match <mode>` `strict | current | branch` (default: `current`)
|
|
151
|
+
- `--sandbox <level>` `workspace | isolated | full` (default: `workspace`)
|
|
152
|
+
- `--env <var=value>` Override environment variable (repeatable)
|
|
153
|
+
- `--json` Structured JSON output
|
|
154
|
+
|
|
155
|
+
### `bugproof inspect <artifact>`
|
|
156
|
+
|
|
157
|
+
Inspect artifact metadata and failure details without replaying.
|
|
158
|
+
|
|
159
|
+
Examples:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
bugproof inspect my-bug.bug
|
|
163
|
+
bugproof inspect --json my-bug.bug
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Options:
|
|
167
|
+
- `--json` Structured JSON output
|
|
168
|
+
|
|
169
|
+
### `bugproof diff <left> <right>`
|
|
170
|
+
|
|
171
|
+
Compare two artifacts side-by-side.
|
|
172
|
+
|
|
173
|
+
Examples:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
bugproof diff bug-before.bug bug-after.bug
|
|
177
|
+
bugproof diff --json bug-before.bug bug-after.bug
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Options:
|
|
181
|
+
- `--json` Structured JSON output
|
|
182
|
+
|
|
183
|
+
## File Association and Icon Registration
|
|
184
|
+
|
|
185
|
+
### Windows
|
|
186
|
+
|
|
187
|
+
BugProof installer registers `.bug` under:
|
|
188
|
+
- `HKCU\\Software\\Classes\\.bug`
|
|
189
|
+
- `HKCU\\Software\\Classes\\BugProof.Artifact`
|
|
190
|
+
|
|
191
|
+
with open command pointing to:
|
|
192
|
+
- `node <package>/dist/cli.js replay "%1"`
|
|
193
|
+
|
|
194
|
+
### Linux
|
|
195
|
+
|
|
196
|
+
BugProof installer registers:
|
|
197
|
+
- MIME type `application/x-bugproof`
|
|
198
|
+
- `bugproof.desktop` handler
|
|
199
|
+
- User-level icon entry in `~/.local/share/icons/hicolor/...`
|
|
200
|
+
|
|
201
|
+
### macOS
|
|
202
|
+
|
|
203
|
+
Installer attempts registration via bundled script. If Finder association does not apply, run:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
bash scripts/bugproof-file-association-macos.sh
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Cross-Platform Replay Matrix
|
|
210
|
+
|
|
211
|
+
| Capture \ Replay | Windows | Linux | macOS |
|
|
212
|
+
|---|---|---|---|
|
|
213
|
+
| Windows | Yes | Yes | Yes |
|
|
214
|
+
| Linux | Yes | Yes | Yes |
|
|
215
|
+
| macOS | Yes | Yes | Yes |
|
|
216
|
+
|
|
217
|
+
Notes:
|
|
218
|
+
- Exit codes may differ by OS for signals/crashes.
|
|
219
|
+
- Fingerprint/error-pattern matching is used for reproduction verdict.
|
|
220
|
+
|
|
221
|
+
## Legacy Notes (Kept from Old README)
|
|
222
|
+
|
|
223
|
+
These project principles remain unchanged:
|
|
224
|
+
- Security-first default behavior
|
|
225
|
+
- Language-agnostic command capture
|
|
226
|
+
- Minimal runtime dependencies
|
|
227
|
+
- Reproducibility over screenshots/log snippets
|
|
228
|
+
|
|
229
|
+
## Roadmap
|
|
230
|
+
|
|
231
|
+
- [x] v0.1: CLI core (capture/replay/inspect/diff)
|
|
232
|
+
- [x] v0.1: Cross-platform replay support
|
|
233
|
+
- [x] v0.1: Secret redaction and sandbox layers
|
|
234
|
+
- [ ] v0.2: npm global install polish and Docker sandbox fallback
|
|
235
|
+
- [ ] v0.2: Web UI for artifact inspection
|
|
236
|
+
- [ ] v0.3: Artifact push/pull and signing
|
|
237
|
+
- [ ] v0.3: Language-specific dependency detection
|
|
238
|
+
- [ ] v0.4: GitHub issue integration and richer diff visualization
|
|
239
|
+
|
|
240
|
+
## Development
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
npm install
|
|
244
|
+
npm run build
|
|
245
|
+
npm test
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## CI and Releases
|
|
249
|
+
|
|
250
|
+
- `push` and `pull_request` CI runs only for core paths such as `src/`, `scripts/`, `tests/`, `package.json`, `tsconfig.json`, and `assets/`.
|
|
251
|
+
- Docs-only edits like `README.md` do not start the CI workflow.
|
|
252
|
+
- Publishing to npmjs.com and GitHub Packages runs only on version tags like `v0.1.0`.
|
|
253
|
+
|
|
254
|
+
## License
|
|
255
|
+
|
|
256
|
+
MIT
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { RunConfig } from '../types/artifact.js';
|
|
2
|
+
import { FailureRecord } from '../types/failure.js';
|
|
3
|
+
/**
|
|
4
|
+
* Spawns the command, captures its output, and produces a FailureRecord.
|
|
5
|
+
* Uses streaming to handle large output without memory issues.
|
|
6
|
+
*/
|
|
7
|
+
export declare function executeAndCapture(config: RunConfig): Promise<{
|
|
8
|
+
failure: FailureRecord;
|
|
9
|
+
stdout: string;
|
|
10
|
+
stderr: string;
|
|
11
|
+
}>;
|
|
12
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/capture/engine.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGpD;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAwI9H"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { generateExactFingerprint, extractErrorPatterns } from '../utils/fingerprint.js';
|
|
3
|
+
/**
|
|
4
|
+
* Spawns the command, captures its output, and produces a FailureRecord.
|
|
5
|
+
* Uses streaming to handle large output without memory issues.
|
|
6
|
+
*/
|
|
7
|
+
export async function executeAndCapture(config) {
|
|
8
|
+
return new Promise((resolve) => {
|
|
9
|
+
const startTime = Date.now();
|
|
10
|
+
// Define resolveExecutable first, before using it
|
|
11
|
+
const resolveExecutable = (cmd) => {
|
|
12
|
+
const normalized = cmd.trim().toLowerCase();
|
|
13
|
+
if (normalized === 'node' || normalized === 'node.exe') {
|
|
14
|
+
return process.execPath;
|
|
15
|
+
}
|
|
16
|
+
return cmd;
|
|
17
|
+
};
|
|
18
|
+
const command = resolveExecutable(config.command[0]);
|
|
19
|
+
const args = config.command.slice(1);
|
|
20
|
+
// We keep strings in memory for v0.1 (in v0.2 this would stream directly to disk)
|
|
21
|
+
let stdoutBuffer = '';
|
|
22
|
+
let stderrBuffer = '';
|
|
23
|
+
let stdoutLines = 0;
|
|
24
|
+
let stderrLines = 0;
|
|
25
|
+
// Safety limit to avoid OOM: 1MB per stream
|
|
26
|
+
const MAX_BUFFER_SIZE = 1024 * 1024;
|
|
27
|
+
let isTimeout = false;
|
|
28
|
+
let resolved = false;
|
|
29
|
+
let proc;
|
|
30
|
+
const safeResolve = (value) => {
|
|
31
|
+
if (resolved)
|
|
32
|
+
return;
|
|
33
|
+
resolved = true;
|
|
34
|
+
resolve(value);
|
|
35
|
+
};
|
|
36
|
+
try {
|
|
37
|
+
proc = spawn(command, args, {
|
|
38
|
+
cwd: config.working_directory,
|
|
39
|
+
env: config.environment,
|
|
40
|
+
shell: false // Prevent shell injection
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
// Command not found or spawn error
|
|
45
|
+
const errStr = String(err);
|
|
46
|
+
safeResolve({
|
|
47
|
+
failure: {
|
|
48
|
+
exit_code: 1,
|
|
49
|
+
signal: null,
|
|
50
|
+
stdout_lines: 0,
|
|
51
|
+
stderr_lines: 1,
|
|
52
|
+
stderr_snippet: errStr,
|
|
53
|
+
fingerprint: generateExactFingerprint(errStr),
|
|
54
|
+
error_patterns: extractErrorPatterns(errStr),
|
|
55
|
+
duration_ms: Date.now() - startTime,
|
|
56
|
+
timeout: false
|
|
57
|
+
},
|
|
58
|
+
stdout: '',
|
|
59
|
+
stderr: errStr
|
|
60
|
+
});
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// Set timeout
|
|
64
|
+
const timeoutHandle = setTimeout(() => {
|
|
65
|
+
isTimeout = true;
|
|
66
|
+
proc.kill('SIGKILL');
|
|
67
|
+
}, config.timeout_ms);
|
|
68
|
+
if (config.capture_output) {
|
|
69
|
+
proc.stdout?.on('data', (data) => {
|
|
70
|
+
const chunk = data.toString();
|
|
71
|
+
stdoutLines += (chunk.match(/\n/g) || []).length;
|
|
72
|
+
if (stdoutBuffer.length < MAX_BUFFER_SIZE) {
|
|
73
|
+
stdoutBuffer += chunk;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
proc.stderr?.on('data', (data) => {
|
|
77
|
+
const chunk = data.toString();
|
|
78
|
+
stderrLines += (chunk.match(/\n/g) || []).length;
|
|
79
|
+
if (stderrBuffer.length < MAX_BUFFER_SIZE) {
|
|
80
|
+
stderrBuffer += chunk;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
proc.on('close', (code, signal) => {
|
|
85
|
+
clearTimeout(timeoutHandle);
|
|
86
|
+
const duration = Date.now() - startTime;
|
|
87
|
+
// Determine snippet (last 5 lines of stderr)
|
|
88
|
+
const lines = stderrBuffer.trim().split('\n');
|
|
89
|
+
const snippet = lines.slice(Math.max(0, lines.length - 5)).join('\n');
|
|
90
|
+
const failure = {
|
|
91
|
+
exit_code: code ?? 1,
|
|
92
|
+
signal: signal,
|
|
93
|
+
stdout_lines: stdoutLines,
|
|
94
|
+
stderr_lines: stderrLines,
|
|
95
|
+
stderr_snippet: snippet,
|
|
96
|
+
fingerprint: generateExactFingerprint(stderrBuffer),
|
|
97
|
+
error_patterns: extractErrorPatterns(stderrBuffer),
|
|
98
|
+
duration_ms: duration,
|
|
99
|
+
timeout: isTimeout
|
|
100
|
+
};
|
|
101
|
+
safeResolve({
|
|
102
|
+
failure,
|
|
103
|
+
stdout: stdoutBuffer,
|
|
104
|
+
stderr: stderrBuffer
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
proc.on('error', (err) => {
|
|
108
|
+
clearTimeout(timeoutHandle);
|
|
109
|
+
const errStr = String(err);
|
|
110
|
+
stderrBuffer += errStr;
|
|
111
|
+
safeResolve({
|
|
112
|
+
failure: {
|
|
113
|
+
exit_code: 1,
|
|
114
|
+
signal: null,
|
|
115
|
+
stdout_lines: stdoutLines,
|
|
116
|
+
stderr_lines: stderrLines + 1,
|
|
117
|
+
stderr_snippet: errStr,
|
|
118
|
+
fingerprint: generateExactFingerprint(errStr),
|
|
119
|
+
error_patterns: extractErrorPatterns(errStr),
|
|
120
|
+
duration_ms: Date.now() - startTime,
|
|
121
|
+
timeout: false
|
|
122
|
+
},
|
|
123
|
+
stdout: stdoutBuffer,
|
|
124
|
+
stderr: stderrBuffer
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/capture/engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ArtifactManifest, EnvSchema, RunConfig, ArtifactMetadata } from '../types/artifact.js';
|
|
2
|
+
import { FailureRecord } from '../types/failure.js';
|
|
3
|
+
export interface PackageOptions {
|
|
4
|
+
manifest: ArtifactManifest;
|
|
5
|
+
envSchema: EnvSchema;
|
|
6
|
+
runConfig: RunConfig;
|
|
7
|
+
metadata: ArtifactMetadata;
|
|
8
|
+
failure: FailureRecord;
|
|
9
|
+
stdout: string;
|
|
10
|
+
stderr: string;
|
|
11
|
+
secretKeys: string[];
|
|
12
|
+
includeUntracked?: boolean;
|
|
13
|
+
excludePatterns?: string[];
|
|
14
|
+
}
|
|
15
|
+
export interface FileEntry {
|
|
16
|
+
path: string;
|
|
17
|
+
size: number;
|
|
18
|
+
sha256: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Packages the artifact into the .bug directory format specified in DESIGN.md.
|
|
22
|
+
*
|
|
23
|
+
* Directory layout:
|
|
24
|
+
* manifest.json
|
|
25
|
+
* env.schema.json
|
|
26
|
+
* metadata.json
|
|
27
|
+
* run.json (environment secrets stripped)
|
|
28
|
+
* failure.json
|
|
29
|
+
* logs/stdout.txt
|
|
30
|
+
* logs/stderr.txt
|
|
31
|
+
* logs/fingerprint.json
|
|
32
|
+
* files/... (git-tracked source snapshot)
|
|
33
|
+
*/
|
|
34
|
+
export declare function packageArtifact(artifactPath: string, options: PackageOptions): Promise<{
|
|
35
|
+
filesCount: number;
|
|
36
|
+
totalSize: number;
|
|
37
|
+
fileEntries: FileEntry[];
|
|
38
|
+
}>;
|
|
39
|
+
//# sourceMappingURL=packager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"packager.d.ts","sourceRoot":"","sources":["../../src/capture/packager.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAChG,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,SAAS,CAAC;IACrB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,OAAO,EAAE,aAAa,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAKD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CACnC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,SAAS,EAAE,CAAA;CAAE,CAAC,CA6E9E"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as crypto from 'crypto';
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
import { spawnSync } from 'child_process';
|
|
6
|
+
import { zipDirectory } from '../utils/archive.js';
|
|
7
|
+
import { filterByExcludePatterns } from '../utils/exclude.js';
|
|
8
|
+
import { isPathWithinBoundary } from '../utils/security.js';
|
|
9
|
+
const MAX_ARTIFACT_SIZE = 50 * 1024 * 1024; // 50MB hard limit per DESIGN.md
|
|
10
|
+
const WARN_THRESHOLD = 10 * 1024 * 1024; // 10MB warning per DESIGN.md
|
|
11
|
+
/**
|
|
12
|
+
* Packages the artifact into the .bug directory format specified in DESIGN.md.
|
|
13
|
+
*
|
|
14
|
+
* Directory layout:
|
|
15
|
+
* manifest.json
|
|
16
|
+
* env.schema.json
|
|
17
|
+
* metadata.json
|
|
18
|
+
* run.json (environment secrets stripped)
|
|
19
|
+
* failure.json
|
|
20
|
+
* logs/stdout.txt
|
|
21
|
+
* logs/stderr.txt
|
|
22
|
+
* logs/fingerprint.json
|
|
23
|
+
* files/... (git-tracked source snapshot)
|
|
24
|
+
*/
|
|
25
|
+
export async function packageArtifact(artifactPath, options) {
|
|
26
|
+
// 1. Create a temporary staging directory
|
|
27
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'bugproof-pkg-'));
|
|
28
|
+
try {
|
|
29
|
+
// 2. Copy source files first so we can compute counts and checksums
|
|
30
|
+
const filesDir = path.join(tempDir, 'files');
|
|
31
|
+
fs.mkdirSync(filesDir, { recursive: true });
|
|
32
|
+
const fileEntries = copySourceFiles(filesDir, options.runConfig.working_directory, options.includeUntracked ?? false, options.excludePatterns ?? []);
|
|
33
|
+
const totalSize = fileEntries.reduce((sum, f) => sum + f.size, 0);
|
|
34
|
+
// 3. Create manifest with actual file stats (immutable: don't mutate input)
|
|
35
|
+
const manifestWithStats = {
|
|
36
|
+
...options.manifest,
|
|
37
|
+
files_count: fileEntries.length,
|
|
38
|
+
files_size_bytes: totalSize,
|
|
39
|
+
};
|
|
40
|
+
// 4. Build a sanitized RunConfig (strip secret values from environment)
|
|
41
|
+
const sanitizedEnv = {};
|
|
42
|
+
for (const [key, val] of Object.entries(options.runConfig.environment)) {
|
|
43
|
+
if (options.secretKeys.includes(key)) {
|
|
44
|
+
sanitizedEnv[key] = '<REDACTED>';
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
sanitizedEnv[key] = val;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const safeRunConfig = { ...options.runConfig, environment: sanitizedEnv };
|
|
51
|
+
// 5. Write JSON schema files
|
|
52
|
+
fs.writeFileSync(path.join(tempDir, 'manifest.json'), JSON.stringify(manifestWithStats, null, 2));
|
|
53
|
+
fs.writeFileSync(path.join(tempDir, 'env.schema.json'), JSON.stringify(options.envSchema, null, 2));
|
|
54
|
+
fs.writeFileSync(path.join(tempDir, 'metadata.json'), JSON.stringify(options.metadata, null, 2));
|
|
55
|
+
fs.writeFileSync(path.join(tempDir, 'run.json'), JSON.stringify(safeRunConfig, null, 2));
|
|
56
|
+
fs.writeFileSync(path.join(tempDir, 'failure.json'), JSON.stringify(options.failure, null, 2));
|
|
57
|
+
// 6. Write file manifest with checksums
|
|
58
|
+
fs.writeFileSync(path.join(tempDir, 'files.json'), JSON.stringify(fileEntries, null, 2));
|
|
59
|
+
// 7. Write logs
|
|
60
|
+
const logsDir = path.join(tempDir, 'logs');
|
|
61
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
62
|
+
fs.writeFileSync(path.join(logsDir, 'stdout.txt'), options.stdout);
|
|
63
|
+
fs.writeFileSync(path.join(logsDir, 'stderr.txt'), options.stderr);
|
|
64
|
+
fs.writeFileSync(path.join(logsDir, 'fingerprint.json'), JSON.stringify({
|
|
65
|
+
fingerprint: options.failure.fingerprint,
|
|
66
|
+
error_patterns: options.failure.error_patterns,
|
|
67
|
+
}, null, 2));
|
|
68
|
+
// 8. Compress the temporary directory into the final .bug zip archive
|
|
69
|
+
await zipDirectory(tempDir, artifactPath);
|
|
70
|
+
return { filesCount: fileEntries.length, totalSize, fileEntries };
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
// Cleanup incomplete artifact on failure
|
|
74
|
+
if (fs.existsSync(artifactPath)) {
|
|
75
|
+
fs.rmSync(artifactPath, { force: true });
|
|
76
|
+
}
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
// Always clean up the temporary staging directory
|
|
81
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Copy git-tracked files into the artifact, computing SHA-256 checksums per file.
|
|
86
|
+
*/
|
|
87
|
+
function copySourceFiles(filesDir, workingDir, includeUntracked, excludePatterns = []) {
|
|
88
|
+
const gitArgs = ['ls-files'];
|
|
89
|
+
if (includeUntracked) {
|
|
90
|
+
gitArgs.push('-o', '--exclude-standard');
|
|
91
|
+
}
|
|
92
|
+
const result = spawnSync('git', gitArgs, { cwd: workingDir, encoding: 'utf-8' });
|
|
93
|
+
if (result.status !== 0) {
|
|
94
|
+
throw new Error(`git ls-files failed: ${result.stderr}`);
|
|
95
|
+
}
|
|
96
|
+
let relativePaths = result.stdout
|
|
97
|
+
.split('\n')
|
|
98
|
+
.map((f) => f.trim())
|
|
99
|
+
.filter((f) => f.length > 0);
|
|
100
|
+
// Apply --exclude patterns
|
|
101
|
+
if (excludePatterns.length > 0) {
|
|
102
|
+
relativePaths = filterByExcludePatterns(relativePaths, excludePatterns);
|
|
103
|
+
}
|
|
104
|
+
const entries = [];
|
|
105
|
+
let runningSize = 0;
|
|
106
|
+
for (const relPath of relativePaths) {
|
|
107
|
+
const sourcePath = path.join(workingDir, relPath);
|
|
108
|
+
if (!fs.existsSync(sourcePath))
|
|
109
|
+
continue;
|
|
110
|
+
// Security: validate path stays within boundaries
|
|
111
|
+
if (!isPathWithinBoundary(sourcePath, workingDir)) {
|
|
112
|
+
process.stderr.write(` Skipping ${relPath}: path traversal detected\n`);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const stats = fs.statSync(sourcePath);
|
|
116
|
+
if (!stats.isFile())
|
|
117
|
+
continue;
|
|
118
|
+
runningSize += stats.size;
|
|
119
|
+
if (runningSize > MAX_ARTIFACT_SIZE) {
|
|
120
|
+
throw new Error(`Artifact would exceed the 50 MB limit (currently ${(runningSize / 1024 / 1024).toFixed(1)} MB). ` +
|
|
121
|
+
'Add large files to .gitignore or use --exclude patterns.');
|
|
122
|
+
}
|
|
123
|
+
if (runningSize > WARN_THRESHOLD && entries.length > 0 && entries.length % 50 === 0) {
|
|
124
|
+
process.stderr.write(` Warning: artifact is ${(runningSize / 1024 / 1024).toFixed(1)} MB and growing...\n`);
|
|
125
|
+
}
|
|
126
|
+
const targetPath = path.join(filesDir, relPath);
|
|
127
|
+
// Security: validate target path stays within artifact boundary
|
|
128
|
+
if (!isPathWithinBoundary(targetPath, filesDir)) {
|
|
129
|
+
process.stderr.write(` Skipping ${relPath}: target path escapes artifact\n`);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
133
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
134
|
+
// Compute SHA-256 checksum
|
|
135
|
+
const fileBuffer = fs.readFileSync(sourcePath);
|
|
136
|
+
const hash = crypto.createHash('sha256').update(fileBuffer).digest('hex');
|
|
137
|
+
entries.push({
|
|
138
|
+
path: relPath.replace(/\\/g, '/'), // normalize to forward slashes
|
|
139
|
+
size: stats.size,
|
|
140
|
+
sha256: hash,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return entries;
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=packager.js.map
|