Skip to content
Open
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: 2 additions & 0 deletions src/cmd/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { registerListCommand } from "@/cmd/listCommand";
import { registerLoginCommand } from "@/cmd/login";
import { registerRunCommand } from "@/cmd/run";
import { registerRunFileCommand } from "@/cmd/run-file";
import { registerSdkCommand } from "@/cmd/sdk";
import { registerSetApiUrlCommand } from "@/cmd/setApiUrl";
import { registerUpdateCommand } from "@/cmd/update";
import type { Command } from "commander";
Expand All @@ -14,6 +15,7 @@ export function registerCommands(program: Command) {
registerConfigureCommand(program);
registerRunCommand(program);
registerRunFileCommand(program);
registerSdkCommand(program);
registerListCommand(program);
registerCreateCommand(program);
registerDeleteCommand(program);
Expand Down
62 changes: 62 additions & 0 deletions src/cmd/sdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { config } from "@/lib/config";
import { logError } from "@/lib/error";
import http from "@/providers/enkryptify/httpClient";
import type { Command } from "commander";

export function registerSdkCommand(program: Command): void {
program
.command("sdk")
.description("Run a command with a read-only Enkryptify SDK token")
.allowUnknownOption()
.allowExcessArguments()
.action(async (_options, cmd: Command) => {
const args = cmd.args as string[];
if (args.length === 0) {
logError("No command provided. Usage: ek sdk -- <command>");
process.exit(1);
}

// 1. Load project config (walks up from cwd)
let setup;
try {
setup = await config.getConfigure(process.cwd());
} catch {
// getConfigure returns null if not found, doesn't throw
}

if (!setup || setup.provider !== "enkryptify") {
logError("No Enkryptify project configured in this directory. Run `ek configure` first.");
process.exit(1);
}

// 2. Create scoped SDK token (read-only, single environment, 8h)
let token: string;
try {
const { data } = await http.post<{ token: string }>(
`/v1/workspace/${setup.workspace_slug}/tokens/cli`,
{ environmentId: setup.environment_id },
);
token = data.token;
Comment on lines +27 to +39
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate the loaded config fields and returned token before continuing.

setup.provider === "enkryptify" does not guarantee workspace_slug and environment_id exist, and data.token is used without a runtime check. A stale/broken project config or a malformed 200 response can turn into /v1/workspace/undefined/... requests or a child process launched without credentials.

🛡️ Proposed validation guard
-            if (!setup || setup.provider !== "enkryptify") {
+            if (
+                !setup ||
+                setup.provider !== "enkryptify" ||
+                !setup.workspace_slug ||
+                !setup.environment_id
+            ) {
                 logError("No Enkryptify project configured in this directory. Run `ek configure` first.");
                 process.exit(1);
             }
@@
                 const { data } = await http.post<{ token: string }>(
                     `/v1/workspace/${setup.workspace_slug}/tokens/cli`,
                     { environmentId: setup.environment_id },
                 );
+                if (!data?.token) {
+                    throw new Error("Failed to create SDK token: response did not include a token.");
+                }
                 token = data.token;

As per coding guidelines: Validate all user inputs, and always throw descriptive errors with context.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cmd/sdk.ts` around lines 27 - 39, The code assumes required config fields
and the returned token exist; add explicit validation after loading `setup` and
after the HTTP call: check that `setup.workspace_slug` and
`setup.environment_id` are non-empty before calling `http.post`, and after the
post verify `data` and `data.token` are present and non-empty before assigning
to the local `token`; if any check fails, call `logError` with a descriptive
message (including which field is missing) and exit (as the current flow does)
so you never build a request with `undefined` or continue without credentials
(references: `setup`, `setup.workspace_slug`, `setup.environment_id`,
`http.post(...).then(({data}) => ...)`, and the local `token` variable).

} catch (error) {
logError(error instanceof Error ? error.message : String(error));
process.exit(1);
}

// 3. Spawn child process with token injected
const [bin, ...rest] = args;
if (!bin) {
logError("No command provided. Usage: ek sdk -- <command>");
process.exit(1);
}

const proc = Bun.spawn([bin, ...rest], {
env: { ...process.env, ENKRYPTIFY_TOKEN: token },
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",
});

const exitCode = await proc.exited;
process.exit(exitCode);
Comment on lines +52 to +60
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/cmd/sdk.ts

Repository: Enkryptify/cli

Length of output: 2784


Wrap Bun.spawn() call in try/catch to handle process failures properly.

The code at lines 52-60 lacks error handling for Bun.spawn() and await proc.exited. If the executable cannot be resolved or the wait fails, the error surfaces as an unhandled failure instead of a user-facing CLI error. This violates the pattern used elsewhere in this file (see lines 21-43) where all operations are wrapped with try/catch and errors are passed to logError().

Proposed fix
-            const proc = Bun.spawn([bin, ...rest], {
-                env: { ...process.env, ENKRYPTIFY_TOKEN: token },
-                stdin: "inherit",
-                stdout: "inherit",
-                stderr: "inherit",
-            });
-
-            const exitCode = await proc.exited;
-            process.exit(exitCode);
+            try {
+                const proc = Bun.spawn([bin, ...rest], {
+                    env: { ...process.env, ENKRYPTIFY_TOKEN: token },
+                    stdin: "inherit",
+                    stdout: "inherit",
+                    stderr: "inherit",
+                });
+
+                const exitCode = await proc.exited;
+                process.exit(exitCode);
+            } catch (error: unknown) {
+                logError(error instanceof Error ? error.message : String(error));
+                process.exit(1);
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const proc = Bun.spawn([bin, ...rest], {
env: { ...process.env, ENKRYPTIFY_TOKEN: token },
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",
});
const exitCode = await proc.exited;
process.exit(exitCode);
try {
const proc = Bun.spawn([bin, ...rest], {
env: { ...process.env, ENKRYPTIFY_TOKEN: token },
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",
});
const exitCode = await proc.exited;
process.exit(exitCode);
} catch (error: unknown) {
logError(error instanceof Error ? error.message : String(error));
process.exit(1);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cmd/sdk.ts` around lines 52 - 60, Wrap the Bun.spawn([bin, ...rest], ...)
call and the await proc.exited in a try/catch block so failures to start or wait
on the child process are caught; on error call logError(err) (same helper used
earlier in this file) and exit with a non-zero code, and on success keep the
existing process.exit(exitCode) behavior. Specifically, enclose the Bun.spawn
invocation and the await proc.exited usage (refer to Bun.spawn and proc.exited)
in try/catch, forward the caught error to logError(), and ensure the process
exits appropriately when an exception occurs.

});
}
2 changes: 1 addition & 1 deletion src/providers/enkryptify/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ export class EnkryptifyAuth implements AuthProvider {
async getUserInfo(token: string): Promise<UserInfo | null> {
const res = await http.get<UserInfo | string>("/v1/me", {
headers: {
"X-API-Key": token,
"Authorization": `Bearer ${token}`,
},
validateStatus: () => true,
});
Expand Down
3 changes: 2 additions & 1 deletion src/providers/enkryptify/httpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { createAuthenticatedHttpClient } from "@/lib/sharedHttpClient";
const http = createAuthenticatedHttpClient({
baseURL: env.API_BASE_URL,
keyringKey: "enkryptify",
authHeaderName: "X-API-Key",
authHeaderName: "Authorization",
authHeaderPrefix: "Bearer ",
});

http.interceptors.request.use((config) => {
Expand Down