diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a76cfd..28bc4cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] - 2026-01-08 + +### Added +- **Shared Docker Guard**: Added `@git-stunts/docker-guard` with `isDockerEnvironment`/`ensureDocker`, exported banner text, and injectable logger/exit hooks so every repo can reuse the same safety net. +### Changed +- **Plumbing Guard Wiring**: `@git-stunts/plumbing` now imports the shared guard via `test/support/ensure-docker.js`, `vitest.config.js`, and `test/deno_entry.js`, removing the in-repo Minecraft `src/infrastructure/DockerGuard.js`. + +## [2.8.0] - 2026-01-07 + +### Added +- **DockerGuard**: Introduced a critical safety service (`src/infrastructure/DockerGuard.js`) that prevents execution on the host machine to protect against unintended system modifications. +- **Dockerized Workflow**: Added `Dockerfile.node`, `Dockerfile.bun`, `Dockerfile.deno`, and `docker-compose.yml` to standardize isolated testing environments. + +### Changed +- **Command Whitelist Expansion**: Added `log` to the `CommandSanitizer` allowed list to support high-performance graph traversals. +- **Strict Host Enforcement**: Updated `package.json` with a `pretest` script that enforces the `GIT_STUNTS_DOCKER` environment variable. + ## [2.7.0] - 2026-01-07 ### Added diff --git a/README.md b/README.md index 61b0915..f32fcdc 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,26 @@ A low-level, robust, and environment-agnostic Git plumbing library for the moder - **OOM Protection**: Integrated safety buffering (`GitStream.collect`) with configurable byte limits. - **Dockerized CI**: Parallel test execution across all runtimes using isolated containers. +## 🛡️ Safety First: Docker Execution + +This library performs low-level Git manipulations. To protect your host system and ensure a reproducible environment, **execution on the host is strictly prohibited.** + +All tests and commands should be run inside the provided Docker containers: + +```bash +docker-compose run --rm node-test +``` + +The system will automatically fail if `GIT_STUNTS_DOCKER=1` is not set. + +We load `@git-stunts/docker-guard` (v0.1.0+) before every suite (`test/support/ensure-docker.js`), so invoking `ensureDocker()` happens automatically for Vitest/Bun/Deno. You can copy the same pattern in other packages: + +```javascript +import { ensureDocker } from '@git-stunts/docker-guard'; + +ensureDocker(); +``` + ## 🏗️ Design Principles 1. **Git as a Subsystem**: Git is treated as an external, untrusted dependency. Every command and environment variable is sanitized. diff --git a/docker-compose.yml b/docker-compose.yml index c7a4c8d..26e0527 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,13 +5,18 @@ services: dockerfile: Dockerfile.node environment: - NODE_ENV=test + - GIT_STUNTS_DOCKER=1 bun-test: build: context: . dockerfile: Dockerfile.bun + environment: + - GIT_STUNTS_DOCKER=1 deno-test: build: context: . dockerfile: Dockerfile.deno + environment: + - GIT_STUNTS_DOCKER=1 diff --git a/package-lock.json b/package-lock.json index 84e3da8..0cff9d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,25 +1,26 @@ { "name": "@git-stunts/plumbing", - "version": "1.0.0", + "version": "2.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@git-stunts/plumbing", - "version": "1.0.0", + "version": "2.7.0", "license": "Apache-2.0", "dependencies": { "zod": "^3.24.1" }, "devDependencies": { "@eslint/js": "^9.17.0", + "@git-stunts/docker-guard": "^0.1.0", "eslint": "^9.17.0", "prettier": "^3.4.2", "vitest": "^3.0.0" }, "engines": { - "bun": ">=1.0.0", - "deno": ">=1.40.0", + "bun": ">=1.3.5", + "deno": ">=2.0.0", "node": ">=20.0.0" } }, @@ -572,30 +573,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, "node_modules/@eslint/js": { "version": "9.39.2", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", @@ -633,6 +610,13 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@git-stunts/docker-guard": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@git-stunts/docker-guard/-/docker-guard-0.1.0.tgz", + "integrity": "sha512-9h2kzMlidbWeoj62VybBzwEMeMySqN/p3vP03rg5enklElkde68KhwfHB3pfaSR/Cx50tnUT27Vfcb7RMcdZkA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1213,6 +1197,23 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -1558,30 +1559,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, "node_modules/espree": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", @@ -1901,6 +1878,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", diff --git a/package.json b/package.json index b987959..3d53b8c 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,8 @@ "@eslint/js": "^9.17.0", "eslint": "^9.17.0", "prettier": "^3.4.2", - "vitest": "^3.0.0" + "vitest": "^3.0.0", + "@git-stunts/docker-guard": "^0.1.0" }, "files": [ "src", @@ -51,8 +52,22 @@ "NOTICE", "SECURITY.md" ], - "repository": { "type": "git", "url": "git+https://github.com/git-stunts/plumbing.git" }, + "repository": { + "type": "git", + "url": "git+https://github.com/git-stunts/plumbing.git" + }, "homepage": "https://github.com/git-stunts/plumbing#readme", - "bugs": { "url": "https://github.com/git-stunts/plumbing/issues" }, - "keywords": ["git", "plumbing", "content-addressable", "dag", "merkle", "node", "deno", "bun"] -} \ No newline at end of file + "bugs": { + "url": "https://github.com/git-stunts/plumbing/issues" + }, + "keywords": [ + "git", + "plumbing", + "content-addressable", + "dag", + "merkle", + "node", + "deno", + "bun" + ] +} diff --git a/src/domain/services/CommandSanitizer.js b/src/domain/services/CommandSanitizer.js index ae60da1..8987e61 100644 --- a/src/domain/services/CommandSanitizer.js +++ b/src/domain/services/CommandSanitizer.js @@ -42,7 +42,8 @@ export default class CommandSanitizer { 'check-ignore', 'check-attr', 'init', - 'config' + 'config', + 'log' ]); /** diff --git a/test.js b/test.js index 17f66bb..171ce32 100644 --- a/test.js +++ b/test.js @@ -2,6 +2,11 @@ * @fileoverview Integration tests for GitPlumbing */ +import { ensureDocker } from '@git-stunts/docker-guard'; + +ensureDocker(); + +import './test/deno_shim.js'; import { mkdtempSync, rmSync } from 'node:fs'; import path from 'node:path'; import os from 'node:os'; diff --git a/test/deno_entry.js b/test/deno_entry.js index a2b8002..e48cf01 100644 --- a/test/deno_entry.js +++ b/test/deno_entry.js @@ -1,3 +1,4 @@ +import "./support/ensure-docker.js"; import "./deno_shim.js"; // Import all tests to run them in one Deno process with the shim @@ -21,4 +22,4 @@ import "./domain/services/GitCommandBuilder.test.js"; import "./domain/services/GitErrorClassifier.test.js"; import "./domain/services/GitPersistenceService.test.js"; import "./domain/value-objects/GitFileMode.test.js"; -import "./domain/value-objects/GitObjectType.test.js"; \ No newline at end of file +import "./domain/value-objects/GitObjectType.test.js"; diff --git a/test/support/ensure-docker.js b/test/support/ensure-docker.js new file mode 100644 index 0000000..1e11c3f --- /dev/null +++ b/test/support/ensure-docker.js @@ -0,0 +1,3 @@ +import { ensureDocker } from '@git-stunts/docker-guard'; + +ensureDocker(); diff --git a/vitest.config.js b/vitest.config.js new file mode 100644 index 0000000..f18d8ab --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + setupFiles: ['test/support/ensure-docker.js'] + } +});