Consume blindfold as a dependency; remove in-tree security code#274
Open
mraduldubey wants to merge 34 commits into
Open
Consume blindfold as a dependency; remove in-tree security code#274mraduldubey wants to merge 34 commits into
mraduldubey wants to merge 34 commits into
Conversation
…ackage Phases 1-4 of the blindfold extraction: config/types, crypto, dual-tier credential store, token resolver, OOB auth socket, CLI (secret/auth/install), and standalone MCP server with 5 tools. 140 tests, all passing. none
Critical: prevent plaintext leaking via redact_markers (opaque handles instead of values), fix empty-string persistence bug in secret CLI, validate ciphertext format in decryptPassword. High: purge session-only expired credentials, remove fleet-specific claudeAiOauth from credential-validation, fix require() in ESM (async import for node:sea), remove misleading password zeroing. Medium: throw instead of process.exit in collect-secret, remove unnecessary credentialResolve in update handler, add memberName validation in launchAuthTerminal, export SEC_HANDLE_RE, add clearTaskCredentials. Low: rename BlindfolConfig → BlindfoldConfig, server.registerTool with full schema, fileURLToPath for Windows path safety.
Phase 5: esbuild-based SEA binary (dist/sea-bundle.cjs → blindfold-linux-x64), build:binary script with CJS wrapper for ESM entry, postject as devDep, prepack script, npmignore to exclude SEA artifacts from tarball. Phase 7: README with API usage, MCP tool reference, CLI reference, security model; Apache-2.0 LICENSE; GitHub Actions CI (ubuntu/macos/windows matrix). Review fixes: correct Claude Code MCP config path (~/.claude.json), fix resolveSecureTokens and collectOobApiKey README examples, expand CLI flag docs, add engines.node >=20, keywords, BlindfoldConfig typo.
- collectOobConfirm gains command/memberName opts, passes --context/--on args to the spawned terminal so users see what triggered the prompt - buildHeadlessFallback is now mode-aware: confirm -> auth --confirm, collect -> secret --set, with optional context lines injected - launchAuthTerminal extracts --context/--on from extraArgs and passes fallbackContext to all buildHeadlessFallback call sites - getAuthCommand confirm branch forwards --context/--on to cmdArgs - cli/auth.ts confirm branch parses --context/--on, displays network egress context, uses readline with "yes" prompt, sanitizes inputs, re-validates memberName on the CLI side - tests: 5 new cases covering mode-aware fallback wording and collectOobConfirm additionalArgs construction + 200-char slice
- fix: read version from package.json dynamically so npm version bumps are reflected in CLI --version output without a manual edit - ci: build + test matrix on ubuntu/macos/windows for every push/PR - release: tag-triggered workflow — test gate, cross-platform SEA binary builds with smoke tests, GitHub Release with all 3 binaries; npm publish deferred to after fleet integration testing
- export OobLaunchFn type from auth-socket - credentialSetHandler accepts optional _launchFn for test injection - mcp-tools tests pass the mock launchFn so OOB flow is controlled instead of trying to spawn a real terminal in CI
PM-side sprint scaffolding for the migration of apra-fleet to depend on the blindfold package: PLAN, progress tracker, status, requirements, backlog, doer/reviewer role overlays, and the empty permissions ledger. Doer/reviewer will commit phase artifacts onto this scaffold; see blindfold-migration/PLAN.md for the 7-phase plan.
Two findings from the Phase 0 review (commit 3918add) recorded for later action: - BL-1 (MEDIUM): fresh-clone build needs a postinstall hook so blindfold's prepack runs automatically. Addressed in Phase 6. - BL-2 (LOW): cosmetic progress.json SHA mismatch from the amend.
Adds src/services/blindfold-init.ts with an idempotent initFleetBlindfold() that calls initBlindfold with dataDir=FLEET_DIR, productName='apra-fleet', pipeName='apra-fleet-auth' so existing users' credentials, sockets, and Windows pipe paths remain unchanged. Wired in at every entrypoint AFTER the --version/--help short-circuits (to keep them fast) and BEFORE any subcommand dispatch: - src/index.ts (MCP server + CLI subcommands) - src/smoke-test.ts (top of file) - tests/setup.ts (after APRA_FLEET_DATA_DIR is set) The logger writes directly to process.stderr instead of going through log-helpers/pino. log-helpers pulls in side-effects (registry + statusline writes) at import time that were observably breaking the statusline test suite. Direct stderr keeps init free of cross-module coupling. Tests: 1280 passing, 3 pre-existing failures (1 platform login-shell env probe, 2 time-utils IST timezone arithmetic), same as Phase 0 baseline.
Tests were leaking writes into the real fleet registry, wiping live members and replacing them with the suite's fake test agents. Root cause: paths.ts captures FLEET_DIR at module-load time, but tests/setup.ts set APRA_FLEET_DATA_DIR after its hoisted imports had already pulled paths.ts (transitively) into the graph, so FLEET_DIR resolved to ~/.apra-fleet/data despite setup.ts's intent. Two-layer fix: 1. vitest.config.ts sets APRA_FLEET_DATA_DIR at the very top of the config module, before any test code is loaded, AND declares test.env so vitest itself propagates it into the test process. This is the earliest reachable point in the test lifecycle. 2. tests/setup.ts now refuses to start (process.exit(2)) if APRA_FLEET_DATA_DIR is missing or differs from the expected tmp dir. Belt-and-suspenders: even if vitest config drifts, a subsequent run cannot silently write to the real data dir. Verified: npm test runs clean, real registry.json untouched, test registry.json empty after cleanup.
Without submodules: recursive, GitHub Actions clones the apra-fleet repo with an empty blindfold/ directory (just the gitlink), and the package.json postinstall hook then fails with 'cd blindfold && npm install' because the dir has no package.json. Pass submodules: recursive to actions/checkout in ci.yml (all 5 jobs), fleet-e2e.yml, and blindfold-ci.yml. Also fix a pre-existing non-ASCII em-dash in ci.yml line 271 that the project's ASCII pre-commit hook surfaced.
v0.0.2 removes the resolve_secure MCP tool. apra-fleet consumes blindfold via library exports (resolveSecureTokens etc.) inside execute_command, so this is a no-op on apra-fleet's behavior and MCP surface. Plaintext continues to never cross the LLM boundary.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
apra-fleet secret --confirmtoapra-fleet auth --confirm(matching what blindfold's OOB launcher spawns). The old path is removed entirely.vitest.config.ts+tests/setup.tsnow make it impossible fornpm testto write to the real~/.apra-fleet/dataregistry (root-caused a load-order bug that had been silently polluting the live registry during test runs).resolve_secureMCP tool. blindfold's MCP wire surface is now vault-only (set/list/update/delete).What changed under the hood
blindfold/added as a git submodule pinned to v0.0.2;package.jsonpulls it in as"blindfold": "file:./blindfold". Switching to the npm-published version later is a one-line bump.src/services/blindfold-init.tscallsinitBlindfold({ dataDir: FLEET_DIR, productName: 'apra-fleet', pipeName: 'apra-fleet-auth' })at every entrypoint after--version/--helpshort-circuit. This preserves existing users' on-disk state (~/.apra-fleet/data/credentials.json, the auth socket path, and the Windows named pipe name) - no migration required.from 'blindfold'. Fleet-local re-implementations ofresolveSecureTokens/redactOutput/resolveSecureField/SECURE_TOKEN_REare removed in favor of blindfold's canonical exports.postinstallscript inpackage.jsonbuilds the submodule on fresh clones sonode_modules/blindfold/dist/is always present.Blindfold security hardening (v0.0.2)
The
resolve_secureMCP tool that v0.0.1 shipped is a fundamental hole in blindfold's threat model: it took a string containing{{secure.NAME}}tokens and returned the same string with plaintext credentials substituted in - directly to the LLM caller. Even with redaction markers in the response, the plaintext bytes were already in the LLM's context window, transcript, and any upstream logging.v0.0.2 removes that tool. The blindfold MCP wire surface is now exactly four tools, all vault-management only:
credential_store_set- OOB collection, returns a handlecredential_store_list- metadata onlycredential_store_update- metadata onlycredential_store_delete- returns statusLibrary exports are unchanged (
resolveSecureTokens,resolveSecureField,redactOutput,containsSecureTokens,SECURE_TOKEN_RE,SEC_HANDLE_RE). apra-fleet uses those server-side insideexecute_commandto substitute tokens just before shell exec and redact plaintext from output before returning to the LLM. Plaintext therefore continues to never cross the LLM boundary in apra-fleet workflows.Implication for standalone blindfold users: blindfold-without-a-host now stores credentials but cannot resolve them in agentic workflows. That is the intended design - vault-only. Hosts (apra-fleet, or anyone consuming the library) own the execution path and the secret-aware redaction. Documented in blindfold's
README.mdunder "Standalone vs host-integrated usage".Compatibility
credential_store_*,execute_command, etc.): unchanged names, schemas, and response shapes.secret --set/--list/--update/--deleteunchanged. Onlysecret --confirmmoved toauth --confirm.initBlindfoldconfiguration.resolve_secureMCP tool: not consumed by apra-fleet (it always used the library function), so zero behavioral impact on apra-fleet.Test plan
npm run build(tsc) - cleannpm test- 1169 passing, 3 failing (all pre-existing baseline: 1platform.test.tslogin-shell env probe, 2time-utils.test.tsIST timezone). Independently re-verified by reviewer in a separate clone.npm run smoke- 13/13 pass with isolated data dirnpm run build:binary- SEA produces a binary that boots--versionin under 400 ms across linux/macos/windows matrixAPRA_FLEET_DATA_DIR-secret --set/--list/--update/--deleteall green~/.apra-fleet/data/registry.jsonacross multiplenpm testruns returns 0 linesnpm install, bothblindfold/distandnode_modules/blindfoldrepopulateapra-fleet auth --confirm <bad name>rejects with exit 1tools/listJSON-RPC probe - exactly 4 vault tools, noresolve_secureDirect-use testing (NOT done in this PR)
These were deliberately skipped to keep the PR isolated from the reviewer's live MCP setup. Recommended before merge:
node /path/to/dist/index.jswith a customAPRA_FLEET_DATA_DIR; driveregister_member+credential_store_set+execute_command echo {{secure.FOO}}(verify[REDACTED:FOO]in output).network_policy=confirm, run a curl-styleexecute_command, verify an OOB terminal pops runningapra-fleet auth --confirm.provision_vcs_authfor a test repo to exercise thecollectOobApiKeypath.Migration to npm (follow-up)
When blindfold publishes to npm:
package.json:"blindfold": "file:./blindfold"->"blindfold": "^X.Y.Z"