Skip to content

Feature/v0.4.0#73

Merged
SiebeBaree merged 8 commits into
mainfrom
feature/v0.4.0
May 21, 2026
Merged

Feature/v0.4.0#73
SiebeBaree merged 8 commits into
mainfrom
feature/v0.4.0

Conversation

@SiebeBaree
Copy link
Copy Markdown
Member

@SiebeBaree SiebeBaree commented May 20, 2026

Summary by CodeRabbit

  • New Features

    • Added a scan command that detects hardcoded secrets (Betterleaks) and shows a terminal scan report
    • Configure command now supports Git-scoped setup and a CLI --git option
    • Introduced a secure, versioned local store for auth and secret-cache with migration
  • Improvements

    • CLI auth and telemetry now use the secure store and can skip auth lookup to avoid password prompts
    • New spinner UI and improved scan/install UX with remediation guidance
  • Version

    • Bumped to 0.4.0

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

Walkthrough

This PR centralizes persisted CLI state in a new versioned secureStore (auth + secret-cache) with legacy migration, adds git-aware configuration scoping (path vs git) and surfaces scope to the configure command and client, and implements an ek scan command using Betterleaks with binary install, JSON parsing, Ink-based report UI, and analytics integration. Tests were added/updated to cover secureStore, config scoping, scan behavior, and migration paths. The package version is bumped to 0.4.0.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Enkryptify/cli#72: Implements the scan command and Betterleaks integration similar to the additions in this PR.
  • Enkryptify/cli#38: Changes to secret-cache persistence that overlap with the secret-cache → secureStore migration in this PR.
  • Enkryptify/cli#39: Modifies authentication storage/flow that may conflict with the auth migration to secureStore here.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feature/v0.4.0' is vague and generic, using a branch-naming convention rather than describing the actual changes made in the pull request. Replace the title with a descriptive summary of the main changes, such as 'Add secret scanning with Betterleaks and migrate auth/cache to secure storage' or similar.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/v0.4.0

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (3)
src/lib/secureStore.ts (1)

49-50: ⚡ Quick win

Unsafe type cast after partial validation.

After checking parsed.version === SECURE_STORE_VERSION, the code casts to SecureStoreData without validating auth or secretCache shapes. If stored data has a correct version but malformed nested fields, downstream code could fail unexpectedly.

Consider validating nested fields or treating invalid shapes as corrupted data (returning empty store).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/secureStore.ts` around lines 49 - 50, The current branch returns
parsed as SecureStoreData after only checking isRecord(parsed) and
parsed.version === SECURE_STORE_VERSION, which is unsafe because nested fields
like auth and secretCache aren't validated; update the logic in
src/lib/secureStore.ts so that after the version check you explicitly validate
the shapes of parsed.auth and parsed.secretCache (e.g., add helper predicates or
inline checks similar to isRecord for those nested objects) and only return {
store: parsed as SecureStoreData, legacyAuth: false } when those validations
pass; if nested fields are missing or malformed, treat the data as corrupted and
return an empty store (or the existing empty-store fallback) instead of casting
blindly.
src/lib/secretCache.ts (1)

52-54: 💤 Low value

Outdated comment references keyring.

The comment says "Keyring unavailable" but storage now uses secureStore. Update for clarity.

📝 Proposed fix
     } catch {
-        // Keyring unavailable; silently degrade
+        // Secure store unavailable; silently degrade
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/secretCache.ts` around lines 52 - 54, Update the misleading catch
comment in src/lib/secretCache.ts to reflect the current storage mechanism:
replace "Keyring unavailable; silently degrade" with a message mentioning
secureStore (e.g., "secureStore unavailable; silently degrade") in the catch
block where secret retrieval/fallback occurs (the catch inside the secret
cache/secret retrieval logic that references secureStore). Ensure the comment
accurately references the symbol secureStore so future readers understand which
storage is degrading.
src/lib/sharedHttpClient.ts (1)

6-11: 💤 Low value

Remove unused keyringKey field from HttpClientConfig.

The keyringKey field in HttpClientConfig is no longer referenced in the implementation since token retrieval now uses secureStore.getAuth(). Remove it from the type definition and all call sites to eliminate dead code and reduce confusion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/sharedHttpClient.ts` around lines 6 - 11, Remove the now-unused
keyringKey property from the HttpClientConfig type and from any places that
construct or reference that config; update the type declaration for
HttpClientConfig (remove keyringKey) and modify callers that pass or read
keyringKey to instead rely on secureStore.getAuth() / existing token retrieval
logic (e.g., where HttpClientConfig is constructed or consumed in
functions/classes using sharedHttpClient). Ensure no leftover references to
keyringKey remain in types, constructors, or call sites.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/cli.ts`:
- Around line 46-47: The current check uses process.argv[2] === "scan" which
fails if flags precede the subcommand; update the logic used when calling
analytics.init({ skipAuthLookup: ... }) to locate the first non-flag CLI
argument (e.g., find the first element in process.argv.slice(2) that does not
start with '-') and compare that value to "scan". Change the condition used in
analytics.init to use this firstNonFlagArg check so skipAuthLookup is true when
the detected subcommand is "scan".

In `@src/cmd/configure.ts`:
- Around line 27-33: The current logic defaults to "git" when selectName(...)
returns no value; update the function that calls selectName (referencing
selectName, GIT_SCOPE_LABEL and PATH_SCOPE_LABEL) to explicitly handle a
canceled/undefined selection by aborting: if selectedScope is
null/undefined/empty, do not return "git"—instead throw or return a clear
cancellation signal (e.g., throw a user-cancelled Error or return
undefined/null) so callers know the user aborted and no config is saved under an
unintended scope.

In `@src/cmd/sdk.ts`:
- Around line 33-37: The current empty catch around
config.findProjectConfig(process.cwd()) swallows all errors; change it to catch
the error as a variable (e) and only suppress the expected "no project
configured" condition (check error type/message or a specific error class
returned by config.findProjectConfig), but rethrow or surface any other errors
so real failures aren't masked; update the try/catch around setup = await
config.findProjectConfig(process.cwd()) to implement this selective handling.

In `@src/lib/betterleaks.ts`:
- Around line 76-83: The code downloads a remote archive (downloadUrl) to
archivePath using axios.get and then extracts it via execFileSync based on
assetName without verifying integrity; update the flow in the function that
performs the download so you compute a cryptographic hash (e.g., SHA-256) of
response.data after axios.get and before fs.writeFileSync, compare it against a
trusted expected checksum (fetched from release metadata, a .sha256 file, or
provided configuration), and only write and extract when the hashes match;
alternatively, if a signature (.asc/.sig) is available, verify the detached GPG
signature before extraction; on mismatch or failed signature verification,
throw/exit and avoid calling execFileSync (keep variables downloadUrl,
response.data, archivePath, assetName, tmpDir and the extraction logic intact
but gated by the verification step).
- Around line 148-164: The current code awaits proc.exited (the Bun.spawn child)
with no timeout; implement a timeout guard around awaiting proc.exited (use
Promise.race or equivalent) so that if the Betterleaks process hangs past a
configured timeout you call proc.kill() to terminate it and return/map the
failure to SCAN_RUN_FAILED; update the logic around the Bun.spawn call
(referencing proc, Bun.spawn, proc.exited and reportPath) to clear any timers,
handle the killed/timeout branch deterministically, and ensure the function
returns the SCAN_RUN_FAILED result on timeout.

In `@src/lib/secureStore.ts`:
- Around line 75-79: updateStore currently does a non-atomic read-modify-write
(readStore -> updater -> writeStore) and can lose concurrent updates (e.g.,
concurrent calls from setAuth or setSecretCacheEntry); wrap the
read/update/write with a local async mutex to serialize access: acquire the
mutex at the start of updateStore, call readStore, run the updater, call
writeStore, and always release the mutex in a finally block to avoid deadlocks;
keep the mutex file-local (e.g., a Promise-based lock or a small Lock class) so
callers (setAuth, setSecretCacheEntry) need no changes and all updates become
atomic.

---

Nitpick comments:
In `@src/lib/secretCache.ts`:
- Around line 52-54: Update the misleading catch comment in
src/lib/secretCache.ts to reflect the current storage mechanism: replace
"Keyring unavailable; silently degrade" with a message mentioning secureStore
(e.g., "secureStore unavailable; silently degrade") in the catch block where
secret retrieval/fallback occurs (the catch inside the secret cache/secret
retrieval logic that references secureStore). Ensure the comment accurately
references the symbol secureStore so future readers understand which storage is
degrading.

In `@src/lib/secureStore.ts`:
- Around line 49-50: The current branch returns parsed as SecureStoreData after
only checking isRecord(parsed) and parsed.version === SECURE_STORE_VERSION,
which is unsafe because nested fields like auth and secretCache aren't
validated; update the logic in src/lib/secureStore.ts so that after the version
check you explicitly validate the shapes of parsed.auth and parsed.secretCache
(e.g., add helper predicates or inline checks similar to isRecord for those
nested objects) and only return { store: parsed as SecureStoreData, legacyAuth:
false } when those validations pass; if nested fields are missing or malformed,
treat the data as corrupted and return an empty store (or the existing
empty-store fallback) instead of casting blindly.

In `@src/lib/sharedHttpClient.ts`:
- Around line 6-11: Remove the now-unused keyringKey property from the
HttpClientConfig type and from any places that construct or reference that
config; update the type declaration for HttpClientConfig (remove keyringKey) and
modify callers that pass or read keyringKey to instead rely on
secureStore.getAuth() / existing token retrieval logic (e.g., where
HttpClientConfig is constructed or consumed in functions/classes using
sharedHttpClient). Ensure no leftover references to keyringKey remain in types,
constructors, or call sites.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6ceec6b2-6258-43a5-9128-13d6f098737c

📥 Commits

Reviewing files that changed from the base of the PR and between bdef7de and e8a18ea.

📒 Files selected for processing (29)
  • package.json
  • src/api/auth.ts
  • src/api/client.ts
  • src/cli.ts
  • src/cmd/configure.ts
  • src/cmd/index.ts
  • src/cmd/login.ts
  • src/cmd/logout.ts
  • src/cmd/scan.ts
  • src/cmd/sdk.ts
  • src/cmd/whoami.ts
  • src/lib/analytics.ts
  • src/lib/betterleaks.ts
  • src/lib/config.ts
  • src/lib/errors.ts
  • src/lib/git.ts
  • src/lib/secretCache.ts
  • src/lib/secureStore.ts
  • src/lib/sharedHttpClient.ts
  • src/ui/ScanReport.tsx
  • src/ui/Spinner.tsx
  • tests/integration/config.test.ts
  • tests/integration/configure-command.test.ts
  • tests/integration/configure-flow.test.ts
  • tests/integration/http-client.test.ts
  • tests/integration/login-flow.test.ts
  • tests/integration/logout-command.test.ts
  • tests/integration/secret-cache.test.ts
  • tests/integration/secure-store.test.ts

Comment thread src/cli.ts
Comment thread src/cmd/configure.ts
Comment thread src/cmd/sdk.ts
Comment thread src/lib/betterleaks.ts
Comment thread src/lib/betterleaks.ts
Comment thread src/lib/secureStore.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/analytics.ts`:
- Around line 136-148: The "version_usage" analytics event is emitted every time
init() runs, causing duplicate events in the same process; add a module-level
boolean flag (e.g., sentVersionUsage or versionUsageEmitted) and check it inside
init() before calling analytics.track("version_usage", ...), only calling
analytics.track when the flag is false, then set the flag to true immediately
after sending to ensure the event is emitted once per process; reference init(),
analytics.track("version_usage", ...) and the new
sentVersionUsage/versionUsageEmitted symbol in your change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3e9d71e9-a6f4-4632-8089-5d92db5901ce

📥 Commits

Reviewing files that changed from the base of the PR and between e8a18ea and c4a6bae.

📒 Files selected for processing (1)
  • src/lib/analytics.ts

Comment thread src/lib/analytics.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/lib/secureStore.ts (1)

102-109: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Route every SECURE_STORE_KEY mutation through this mutex.

updateStore() is serialized now, but readStore({ upgradeLegacy: true }) still calls writeStore() outside the lock and clearAll() deletes the same key without it. A concurrent legacy upgrade or clear can still land after setAuth() / setSecretCacheEntry() and drop the newer state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/secureStore.ts` around lines 102 - 109, The SECURE_STORE_KEY
mutations must be serialized: ensure any code path that can write or delete that
key acquires storeMutex before reading/upgrading/writing. Specifically, change
readStore({upgradeLegacy: true}) so the legacy-upgrade path performs its
writeStore() while holding storeMutex (or call updateStore under the lock), and
make clearAll() acquire/release storeMutex around the deletion of
SECURE_STORE_KEY; also ensure setAuth() and setSecretCacheEntry() call
writeStore() only while holding storeMutex (or route them through
updateStore()). In short, wrap readStore(upgradeLegacy:true) upgrade writes,
clearAll key deletion, and all set*/writeStore calls with
storeMutex.acquire()/release() or centralize mutation logic behind updateStore()
so every mutation goes through the same mutex.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/betterleaks.ts`:
- Around line 185-193: The race currently awaits proc.exited but ignores its
exit code; modify the Promise.race to capture the resolved exit code (e.g.,
const exitCode = await Promise.race([proc.exited, new Promise<never>((_, reject)
=> { timeoutId = setTimeout(() => reject(new Error("timeout")), SCAN_TIMEOUT);
})])); then check exitCode !== 0 and if so throw the existing SCAN_RUN_FAILED
error (matching the pattern used in sdk.ts and run.ts); also ensure you
clearTimeout(timeoutId) on success/failure and propagate the timeout error as
before.

---

Duplicate comments:
In `@src/lib/secureStore.ts`:
- Around line 102-109: The SECURE_STORE_KEY mutations must be serialized: ensure
any code path that can write or delete that key acquires storeMutex before
reading/upgrading/writing. Specifically, change readStore({upgradeLegacy: true})
so the legacy-upgrade path performs its writeStore() while holding storeMutex
(or call updateStore under the lock), and make clearAll() acquire/release
storeMutex around the deletion of SECURE_STORE_KEY; also ensure setAuth() and
setSecretCacheEntry() call writeStore() only while holding storeMutex (or route
them through updateStore()). In short, wrap readStore(upgradeLegacy:true)
upgrade writes, clearAll key deletion, and all set*/writeStore calls with
storeMutex.acquire()/release() or centralize mutation logic behind updateStore()
so every mutation goes through the same mutex.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4c992e08-8d81-4107-b349-db5b9cf998db

📥 Commits

Reviewing files that changed from the base of the PR and between c4a6bae and 1cd9bb0.

📒 Files selected for processing (2)
  • src/lib/betterleaks.ts
  • src/lib/secureStore.ts

Comment thread src/lib/betterleaks.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/lib/betterleaks.ts (1)

100-104: 💤 Low value

Use Node.js zip library or PowerShell's Expand-Archive instead of tar for Windows .zip extraction.

Windows downloads .zip files (via getAssetName() line 54), but the extraction at lines 101-102 uses tar -xf, which is unreliable on Windows. Older Windows versions lack tar, and even Windows 10+ may not handle .zip consistently. Consider using adm-zip or unzipper (or PowerShell's Expand-Archive as a fallback) for cross-platform compatibility.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/betterleaks.ts` around lines 100 - 104, The .zip extraction currently
calls execFileSync("tar", ...) which fails on many Windows systems; update the
extraction branch in src/lib/betterleaks.ts (the block that checks
assetName.endsWith(".zip") and uses execFileSync with archivePath and tmpDir) to
use a cross-platform ZIP extractor instead: either integrate a Node ZIP library
(e.g., adm-zip or unzipper) to programmatically extract archivePath into tmpDir,
or detect Windows (process.platform === "win32") and call PowerShell's
Expand-Archive via spawnSync as a fallback, preserving error handling and the
same tmpDir target and stdio behavior as the non-zip branch; keep the non-zip
tar extraction path unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/lib/betterleaks.ts`:
- Around line 100-104: The .zip extraction currently calls execFileSync("tar",
...) which fails on many Windows systems; update the extraction branch in
src/lib/betterleaks.ts (the block that checks assetName.endsWith(".zip") and
uses execFileSync with archivePath and tmpDir) to use a cross-platform ZIP
extractor instead: either integrate a Node ZIP library (e.g., adm-zip or
unzipper) to programmatically extract archivePath into tmpDir, or detect Windows
(process.platform === "win32") and call PowerShell's Expand-Archive via
spawnSync as a fallback, preserving error handling and the same tmpDir target
and stdio behavior as the non-zip branch; keep the non-zip tar extraction path
unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cf8ddb59-9816-4f33-b6d3-116d542bb6f5

📥 Commits

Reviewing files that changed from the base of the PR and between 1cd9bb0 and ff808f5.

📒 Files selected for processing (1)
  • src/lib/betterleaks.ts

@SiebeBaree SiebeBaree merged commit 1fd467f into main May 21, 2026
10 of 11 checks passed
@SiebeBaree SiebeBaree deleted the feature/v0.4.0 branch May 21, 2026 09:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant