Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pc-filebridge",
"version": "0.1.1",
"version": "0.2.1",
"description": "Read files and create new files on explicitly allowed PC folders without overwrite or delete capabilities.",
"author": {
"name": "Gexiro Global Enterprises Ltd."
Expand Down
9 changes: 9 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.git
.github
node_modules
release
test
config/roots.local.json
*.tgz
*.zip
*.bundle
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ body:
id: version
attributes:
label: Release version
placeholder: v0.1.1
placeholder: v0.2.0
validations:
required: true
- type: input
Expand Down
4 changes: 4 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ updates:
day: monday
time: "06:00"
timezone: Europe/Berlin
cooldown:
default-days: 7
open-pull-requests-limit: 5
commit-message:
prefix: deps
Expand All @@ -17,6 +19,8 @@ updates:
day: monday
time: "06:30"
timezone: Europe/Berlin
cooldown:
default-days: 7
open-pull-requests-limit: 5
commit-message:
prefix: ci
138 changes: 121 additions & 17 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,35 +15,90 @@ concurrency:
cancel-in-progress: true

jobs:
verify-linux:
name: Build, test, and security checks
runs-on: ubuntu-latest
timeout-minutes: 15
verify:
name: ${{ matrix.label }}
runs-on: ${{ matrix.os }}
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
node: "22"
label: Ubuntu Node 22
- os: ubuntu-latest
node: "24"
label: Ubuntu Node 24
- os: windows-latest
node: "22"
label: Windows Node 22
- os: windows-latest
node: "24"
label: Windows Node 24
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "24"
node-version: ${{ matrix.node }}
cache: npm
- name: Install locked dependencies
run: npm ci --ignore-scripts
- name: Verify
run: npm run check
- name: Validate package contents
run: npm pack --dry-run --ignore-scripts
- name: Verify cross-platform policy and MCP invariants
run: npm run verify
- name: Verify a real Windows 8.3 fixture without skipping
if: runner.os == 'Windows'
run: npm run test:windows-8dot3
- name: Verify fixed-volume discovery fails closed on Windows
if: runner.os == 'Windows'
run: npm run test:volume-discovery:windows
- name: Verify discovery and ACL helpers under PowerShell 7
if: runner.os == 'Windows'
run: npm run test:volume-discovery:pwsh
- name: Verify failed topology changes roll back to the last-known-good runtime
if: runner.os == 'Windows'
run: npm run test:runtime-rollback:windows
- name: Verify crash-window recovery and monitor singleton
if: runner.os == 'Windows'
run: npm run test:crash-window:windows
- name: Generate and validate CycloneDX SBOM
shell: bash
run: |
npm sbom --sbom-format cyclonedx > pc-filebridge-sbom.cdx.json
shell: pwsh
run: >-
npm sbom --sbom-format cyclonedx |
Out-File -LiteralPath pc-filebridge-sbom.cdx.json -Encoding utf8;
node -e "JSON.parse(require('node:fs').readFileSync('pc-filebridge-sbom.cdx.json','utf8'))"
- name: Audit production dependencies
run: npm audit --omit=dev --audit-level=high

verify-windows:
name: Windows security checks
runs-on: windows-latest
container-runtime:
name: Hardened VPS container
needs: verify
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "24"
cache: npm
- name: Install locked dependencies
run: npm ci --ignore-scripts
- name: Build digest-pinned runtime image
run: docker build --pull -f deploy/docker/Dockerfile -t pc-filebridge:0.2.1 .
- name: Verify container policy, tunnel version, and fail-closed startup
run: npm run check:container
- name: Scan complete runtime image
run: sh scripts/run-image-vulnerability-gate.sh
- name: Validate Compose model
run: docker compose -f deploy/docker/compose.example.yml -f deploy/docker/compose.host-ca.example.yml config --quiet

receiver-simulation:
name: Clean-room receiver simulation
needs: verify
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Check out repository
Expand All @@ -55,5 +110,54 @@ jobs:
cache: npm
- name: Install locked dependencies
run: npm ci --ignore-scripts
- name: Verify Windows path and MCP invariants
run: npm run check
- name: Build receiver artifacts with basename-only checksums
shell: bash
run: |
set -euo pipefail
npm run verify
version="$(node --input-type=commonjs -p "require('./package.json').version")"
tag="v${version}"
rm -rf release-ci
mkdir release-ci
runtime_name="pc-filebridge-${tag}-runtime-npm.tgz"
source_name="pc-filebridge-${tag}-source.tar.gz"
sbom_name="pc-filebridge-${tag}-sbom.cdx.json"
package_file="$(npm pack --ignore-scripts --silent)"
mv -- "${package_file}" "release-ci/${runtime_name}"
git archive --format=tar.gz --prefix="pc-filebridge-${tag}-source/" --output="release-ci/${source_name}" HEAD
npm sbom --sbom-format cyclonedx > "release-ci/${sbom_name}"
(
cd release-ci
sha256sum "${runtime_name}" "${source_name}" "${sbom_name}" > SHA256SUMS
sha256sum -c SHA256SUMS
)
node scripts/verify-release-artifact.mjs --release-dir release-ci --tag "${tag}"

compatibility-build-status:
name: Build, test, and security checks
needs: [verify, container-runtime, receiver-simulation]
if: ${{ always() }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Enforce aggregate build and security result
env:
VERIFY_RESULT: ${{ needs.verify.result }}
CONTAINER_RESULT: ${{ needs.container-runtime.result }}
RECEIVER_RESULT: ${{ needs.receiver-simulation.result }}
run: |
test "$VERIFY_RESULT" = "success"
test "$CONTAINER_RESULT" = "success"
test "$RECEIVER_RESULT" = "success"

compatibility-windows-status:
name: Windows security checks
needs: verify
if: ${{ always() }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Enforce Windows matrix result
env:
VERIFY_RESULT: ${{ needs.verify.result }}
run: test "$VERIFY_RESULT" = "success"
4 changes: 2 additions & 2 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ jobs:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Initialize CodeQL
uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4
uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4
with:
languages: javascript-typescript
- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4
uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4
9 changes: 8 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ jobs:
- name: Verify release source
run: npm run check

- name: Verify hardened VPS container
run: |
docker build --pull -f deploy/docker/Dockerfile -t pc-filebridge:0.2.1 .
npm run check:container
docker compose -f deploy/docker/compose.example.yml -f deploy/docker/compose.host-ca.example.yml config --quiet
sh scripts/run-image-vulnerability-gate.sh

- name: Build release artifacts and checksums
shell: bash
run: |
Expand Down Expand Up @@ -127,7 +134,7 @@ jobs:
git merge-base --is-ancestor "${GITHUB_SHA}" refs/remotes/origin/main

- name: Download verified release bundle
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: pc-filebridge-release-${{ github.ref_name }}
path: release
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
node_modules/
.pc-filebridge-test-*/
.pc-filebridge-8dot3-*/
.pc-filebridge-mcp-smoke-*/
coverage/
*.log
private/
Expand Down
9 changes: 6 additions & 3 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
# Architecture

PC FileBridge is a local stdio MCP server. The MCP client launches `mcp/server.mjs`; the server loads one operator-controlled JSON configuration and exposes seven bounded tools.
PC FileBridge is a stdio MCP server. A local MCP client or the authenticated Secure MCP Tunnel runtime launches `mcp/server.mjs`; the server loads one operator-controlled JSON configuration and exposes seven bounded tools.

## Components

- `.mcp.json` and `.codex-plugin/plugin.json` describe the Codex integration.
- `src/server.ts` defines the fixed MCP tool catalog and translates policy failures into structured MCP errors.
- `src/filePolicy.ts` owns root authorization, path validation, link containment, read bounds, redaction, and exclusive-create writes.
- `config/roots.local.json` is operator data, is ignored by Git, and fails closed when absent.
- Secure MCP Tunnel helper scripts connect a dedicated runtime on the PC to ChatGPT. Secrets remain outside the repository under the current user's local application-data directory.
- Secure MCP Tunnel helper scripts bind separate `pc-local` and `laptop-local` runtimes to exact tunnel names, root IDs, and drive paths. Their operator gates fail closed and remain locked by default. Secrets remain outside the repository under the current user's local application-data directory.
- The optional Linux container owns only the `infrastructure` role, validates its exact tunnel name and the neutral example paths `/data/primary`, `/data/secondary`, and optional `/data/agent`, and packages the same stdio server with a digest-pinned tunnel client. Server data and runtime secrets remain in separate external volumes.
- One tunnel identifier has exactly one active poller. PC, laptop, and infrastructure identifiers are never shared.

## Data flow

Expand All @@ -17,7 +19,7 @@ PC FileBridge is a local stdio MCP server. The MCP client launches `mcp/server.m
3. The policy resolves and canonicalizes the path, rejects sensitive components and link escapes, enforces the operation-specific bounds, and performs the filesystem operation.
4. The server returns bounded metadata or redacted UTF-8 text. Absolute host paths are not returned.

The server initiates no network connection and executes no shell command. Remote access is a separate authenticated transport boundary; it does not change the filesystem policy.
The MCP server initiates no network connection and executes no shell command. In a remote deployment, the separate tunnel-client process makes the outbound authenticated connection; it does not change the filesystem policy.

## Security invariants

Expand All @@ -26,5 +28,6 @@ The server initiates no network connection and executes no shell command. Remote
- The tool catalog has no delete, overwrite, append, move, rename, patch, or link operation.
- Link and reparse-point traversal is rejected before access; read handles are checked again after opening.
- Full-drive roots require both configuration and an exact process-level opt-in.
- Role-bound launchers reject a mismatched tunnel name, root ID, root path, local gate, or duplicate-poller topology before serving files.

Residual risks and operator controls are documented in [THREAT_MODEL.md](THREAT_MODEL.md) and [SECURITY.md](SECURITY.md).
43 changes: 43 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,48 @@
# Changelog

## 0.2.1 - 2026-08-30

- Derive a stable, opaque volume id from host, disk, partition, volume, and serial identity; drive-letter, label, health, filesystem, and bus changes do not change that id.
- Restrict automatic discovery to healthy online NTFS volumes on internal NVMe, SATA, SAS, RAID, Storage Spaces, or SCM buses.
- Require an ACL-protected, explicit operator approval for File Backed Virtual, USB, SD, MMC, 1394, and removable volumes; network, optical, unknown, ReFS, unhealthy, and incomplete or colliding identities fail closed.
- Expose additive non-secret topology metadata through `list_roots` while preserving the exact seven-tool MCP contract.
- Add candidate-only discovery plus approval and revocation helpers.
- Require three matching topology snapshots, a real local MCP probe, transactional last-known-good promotion, rejected-topology suppression, crash-window recovery, and a role-scoped singleton monitor.
- Add Windows regression coverage for stable identity, approval ACLs, identity collisions, runtime rollback, all six crash windows, and singleton enforcement.
- Publish the runtime version through OCI metadata, the container environment, and successful healthcheck output.
- Fix approval-registry ACL validation under PowerShell 7 and execute the discovery regression suite under both Windows PowerShell and PowerShell 7 in CI.

## 0.2.0 - 2026-08-30

- Separate PC-local, laptop-local, and infrastructure access into distinct tunnel roles, runtime aliases, and root-id namespaces.
- Add fail-closed tunnel metadata verification so a role refuses a tunnel created for another backend.
- Add an explicit `armed/locked` operator gate for local ChatGPT connectors while keeping local Codex stdio independent.
- Add PC C/D, laptop C/optional D, and infrastructure root configuration examples without mirroring complete local drives to the VPS.
- Pin the container deployment to the infrastructure tunnel role and require its exact operator-visible tunnel name.
- Add a machine-checked role contract, exact root-path enforcement, container configuration negative tests, split-brain recovery procedure, and truthful device availability documentation.
- Add release-wide version consistency checks, exclude private VPS records from the public runtime package, and refresh production dependencies.
- Add fail-closed Windows volume discovery with bus/filesystem/health/identity policy, automatic role-bound root generation, and a singleton monitor that transactionally reapplies only the affected runtime and rolls back failed topology changes to a private last-known-good configuration.

## 0.1.3 - 2026-08-28

- Update the hardened VPS image to digest-pinned Node.js 24.20.0 LTS, remove the unused npm CLI from the runtime layer, and refresh current MCP, type, build, and test dependencies.
- Replace the local-only readiness probe with a tunnel-aware healthcheck that requires a recent successful command poll.
- Default tunnel logs to warning level and add bounded local log rotation in the Compose deployment.
- Add an optional host-managed CA overlay for Linux environments with TLS inspection without disabling certificate verification.
- Rebuild the exact OpenAI tunnel-client v0.0.13 source revision with fixed OpenTelemetry Go and `x/net` dependencies, after running the upstream Go test suite.
- Add a digest-pinned Syft, Grype, and `govulncheck` image gate with no HIGH/CRITICAL vulnerability allowlist.
- Refresh pinned GitHub Actions used by CodeQL and release artifact download.

## 0.1.2 - 2026-08-27

- Resolve every existing path component to its filesystem-stored name by exact BigInt file identity before authorization.
- Block Windows 8.3 and other alternate spellings while continuing to allow case-only Windows spelling differences.
- Re-apply sensitive-name policy to stored directory entries and fail closed for missing, zero, unstable, or ambiguous identity.
- Block multiply linked regular files and add parent/file identity checks around create-only operations.
- Add a non-skippable real Windows 8.3 fixture covering stat, list, read, search, and create-parent paths.
- Require Ubuntu and Windows CI on Node.js 22 and 24, production-license review, package-content review, SBOM validation, and clean-room receiver simulation.
- Add an always-on hardened VPS deployment with digest-pinned Node and OpenAI tunnel images, external data/secret volumes, non-root execution, a read-only root filesystem, no published ports, and container create-only/fail-closed CI.

## 0.1.1 - 2026-08-26

- Block PuTTY `.ppk`, Java keystores, additional private-key formats, and SSH `id_*` private-key variants.
Expand Down
Loading