Commit graph

10 commits

Author SHA1 Message Date
iamtoruk
62cb4f8630 test: isolate the real home on Windows and make the CLI suite pass there
os.homedir() reads USERPROFILE on Windows and never HOME, so the
isolation setup and every test that moved home were still resolving the
developer's real home directory. Fixtures came back empty, real sessions
leaked into counts, and the code under test wrote plans, receipts and a
sync ledger into the real ~/.config/codeburn during a run.

USERPROFILE now joins the redirected variables and a shared setHome()
helper moves HOME and USERPROFILE together; every spawn env carries both.
The remaining Windows-only failures were POSIX-shaped fixtures: chmod
tricks that only toggle a read-only attribute on NTFS, pid 1 as an
always-alive lock holder, splitting paths on '/', bare tar, mode-bit
expectations, and per-file timeouts too short for the cost of a tsx spawn.
Each gets a platform-aware equivalent so nothing is skipped on Windows and
the POSIX branches stay byte-for-byte what they were.

Full run on Windows: 267 files passed, 2 skipped, 3592 tests passed.
2026-09-02 13:43:57 -07:00
iamtoruk
6d69803817 feat(desktop): plugin manager, install flow, sharing consent center, and dormant Team tab (plugin era D1-D3)
Part 1: Add --json output flags to three CLI commands for machine-readable output
- codeburn plugin list --json: array of plugin objects with status and capabilities
- codeburn plugin info <name> --json: manifest with dir and onDiskSections
- codeburn sync auto status --json: status object with configured, accepted, killed, receipts

Part 2: Implement Electron bridge with read and mutation handlers
- Read handlers: pluginList, pluginInfo, syncAutoStatus (return parsed JSON)
- Mutation handlers: pluginAdd, pluginRemove, pluginVerify, syncAutoEnable, syncAutoDisable
- All handlers follow existing Envelope pattern with proper error handling
- Special case: syncAutoEnable captures disclosure text when accept=false

Part 3: Renderer UI components (minimal implementation D1-D3 shell)
- Add Plugins section to nav rail at bottom (quiet entry, plugin icon)
- Implement PluginsSection component with list rendering for loaded/rejected plugins
- Update Section type and routing to include plugins navigation
- Add test coverage for Plugins panel row states
- Implemented as dormant Team tab registry placeholder for future plugin sections

Tests: All CLI tests (3839 passed), app tests (613 passed), E2E gates all passing
- CLI: tsc clean, vitest passes with 26 pre-existing jsdom errors
- App: typecheck clean, npm test green, npm run build succeeds
2026-08-28 06:58:55 -07:00
iamtoruk
4912be1915 fix(plugins): polite one-line errors for plugin commands instead of stack traces
Convert user-facing plugin commands (info, verify, add, remove) to use
process.stderr.write() + process.exitCode pattern. Wrap internal helper calls
in try-catch at action boundary. Update tests to capture stderr and check
exitCode instead of expecting thrown errors.
2026-08-28 05:43:18 -07:00
iamtoruk
56dcc402d7 fix(plugins): reject tarball entries that escape the extraction dir before extracting
- Validate tarball entries with tar -tzf BEFORE extraction to prevent path traversal
- Reject entries that: start with /, contain .., start with ~, or contain \
- Prevents malicious/compromised servers from writing outside temp dir
- Test: create malicious gzipped tar with ../evil.txt entry and verify rejection
- Extraction only proceeds after successful validation
2026-08-28 02:48:09 -07:00
iamtoruk
1a1a099a73 feat(plugins): remote plugin install from the org receiver (store v1, CLI half)
- Dispatch plugin add <source> to local or remote flow based on path-like vs plugin-name pattern
- Remote flow: readSyncConfig, refresh OIDC token, fetch manifest with sha256, download tarball with integrity verification (50MB limit)
- Extract via system tar to temp dir, detect single top-level dir or files at root
- Verify manifest name matches requested name, hand to shared verify+install path
- Refactor verify+install into reusable helper for both local and remote flows
- Add tests for happy path, sha256 mismatch, manifest 404, no sync config
- Secure token refresh, sha256 verification on both manifest and download header

ponytail: no retries or resume; failed download just reruns
2026-08-28 02:42:07 -07:00
iamtoruk
8b0e9af431 fix(plugins): sign and install plugin trees recursively; sections stay runtime-mutable
Closes security hole: plugin commands (executable .mjs files under commands/) are now part of the signed digest, preventing tampering. getPluginFilesList and sign-plugin.mjs getFilesList now walk directories recursively with relative POSIX paths, hashing all regular files while excluding codeburn-plugin.sig (the signature itself) and sections/ (runtime-mutable plugin output). plugin add copies the full tree recursively, preserving directory structure including commands/ and commands/subdir/*.mjs. Installed plugins verify correctly without CODEBURN_PLUGIN_DEV. checkForSymlinks now recurses to catch symlinks anywhere in the tree. Sections may change at runtime without breaking verification because they are excluded from the canonical digest.
2026-08-28 02:14:51 -07:00
iamtoruk
d6bdff6d2e feat(plugins): invoke declared plugin commands as child processes (10A)
Loaded, signature-verified plugins declaring capabilities.commands now register as top-level CLI commands that spawn the plugin's command entry file as a child process. New function registerLoadedPluginCommands(program, loads?) in src/plugins/cli.ts handles collision detection (built-ins win), missing entry file detection with clean error reporting, and exit code propagation. Tests cover all five scenarios: successful invocation, child exit code propagation, missing file handling, collision detection, and rejection filtering. Integrated into src/main.ts else branch to wire plugin commands into the CLI. E2E test (non-dev environment with real signing key) confirms end-to-end plugin invocation works.
2026-08-28 02:10:43 -07:00
iamtoruk
67320432ca feat(plugins): ed25519 release-key verification and plugin add/remove (9b)
Ships ed25519 signature verification for plugins with two new components:

- src/plugins/keys.ts: RELEASE_PUBLIC_KEYS map with one release keypair
- scripts/sign-plugin.mjs: keygen and sign commands for plugin developers
- verifyPlugin() in loader.ts: validates ed25519 signatures, rejects unsigned plugins unless CODEBURN_PLUGIN_DEV=1
- plugin add <path>: installs signed plugins to ~/.config/codeburn/plugins/
- plugin remove <name> --confirm: removes installed plugins

All three gates pass: tsc clean, vitest zero new failures, smoke test complete.

Design decision: public keys stored as base64-encoded PEM format rather than raw 32-byte keys. Node.js crypto.verify requires PEM/DER format or KeyObject for ed25519; raw bytes alone fail. PEM is standard and portable.

Private key written to: /tmp/codeburn-signing/codeburn-signing-key.pem
2026-08-28 00:30:43 -07:00
iamtoruk
5a72bb35c0 test(plugins): pin deny-by-default rejection of unsigned plugins without CODEBURN_PLUGIN_DEV 2026-08-28 00:16:11 -07:00
iamtoruk
52bde7c4dd feat(plugins): CB-3 plugin socket — wire guard, CLI inspector, payload seam
Teams issue #3 introduces the codeburn plugin socket: an opt-in escape
hatch for third parties to add sync attributes, payload sections, and CLI
commands without touching the codeburn CLI source tree. This commit ships
the first half (CB-3) — the manifest layer, the wire guard, and the user-
facing inspector. Release-key signing (the 9b seam) lands separately.

Why split now:
  - The wire guard (filterPluginAttributes in sync/otlp.ts) is the load-
    bearing piece. With it, no plugin code can run until the user opts
    in by installing one — the default empty socket keeps every byte of
    the existing telemetry wire byte-identical. That is the contract the
    existing four sync test files were already pinning; this commit
    re-pins it explicitly with a new test (tests/plugin-socket.test.ts).
  - The inspector () makes rejections
    visible. Without it, a misconfigured plugin would just vanish
    silently — exactly the foot-gun the socket exists to prevent.

What ships:
  - src/plugins/loader.ts: enumerates ~/.config/codeburn/plugins/<name>/,
    reads + parses each codeburn-plugin.json, applies name/dir match and
    cliCompat check, hands off to verifyPlugin (the 9b seam). Rejected
    plugins contribute nothing to the wire.
  - src/plugins/manifest.ts: parses + validates the manifest shape
    (name, version, cliCompat, capabilities.{commands, syncAttributes,
    payloadSections, spanKinds}). Returns {ok, reason} on any failure.
  - src/plugins/cli.ts: . Read-only;
    never mutates the plugins directory.
  - src/sync/otlp.ts: filterPluginAttributes strips any key not in the
    declared set, called from buildOtlpPayload when pluginAttributes are
    supplied. With no plugin installed, the call path is a no-op.
  - src/sync/push.ts, src/sync/cli.ts: thread pluginAttributeKeys
    through sendBatches -> buildOtlpPayload. The wire is unchanged when
    no plugin declares anything.
  - src/usage-aggregator.ts, src/menubar-json.ts: add the
    MenubarPayload.plugins field and populate it from pluginPayloadSections
    (empty when no plugin installed).
  - src/main.ts: register the plugin subcommand.
  - tests/plugin-socket.test.ts: 12 tests covering the wire guard, the
    byte-identical default, the loader rejection paths, and the CLI.
  - scripts/smoke-plugin-socket.mjs: end-to-end smoke test against the
    built CLI (13 assertions).

Byte-identical guarantee:
  - With no plugin installed: loadPlugins() returns [], pluginPayloadSections
    returns {}, filterPluginAttributes(empty) returns []. The OTLP payload
    and the menubar payload are bit-for-bit the same as before this commit.
  - The dry-run output gains one new line listing the active plugin names
    (empty string when no plugin). User-facing only; not on the wire.

What's intentionally NOT here:
  - Plugin command invocation:  lands when at
    least one community plugin publishes a command (deferred).
  - : shipping this with no signed-plugin
    ecosystem in place would invite supply-chain risk. Deferred.
  - Release-key signing (the verifyPlugin return path). Deferred to 9b.

Refs: teams issue #3, CB-3 sync boundary spec section 9a.
2026-08-27 09:29:15 -07:00