Skip to content

Commit c5972fe

Browse files
committed
Add 1Password option for configuring CAPI_DEV_KEY
1 parent f6bbe71 commit c5972fe

File tree

2 files changed

+78
-1
lines changed

2 files changed

+78
-1
lines changed

extensions/ql-vscode/src/config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,3 +961,9 @@ export const AUTOFIX_MODEL = new Setting("model", AUTOFIX_SETTING);
961961
export function getAutofixModel(): string | undefined {
962962
return AUTOFIX_MODEL.getValue<string>() || undefined;
963963
}
964+
965+
export const AUTOFIX_CAPI_DEV_KEY = new Setting("capiDevKey", AUTOFIX_SETTING);
966+
967+
export function getAutofixCapiDevKey(): string | undefined {
968+
return AUTOFIX_CAPI_DEV_KEY.getValue<string>() || undefined;
969+
}

extensions/ql-vscode/src/variant-analysis/view-autofixes.ts

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ import type { VariantAnalysisResultsManager } from "./variant-analysis-results-m
3838
import {
3939
getAutofixPath,
4040
getAutofixModel,
41+
getAutofixCapiDevKey,
4142
downloadTimeout,
4243
AUTOFIX_PATH,
4344
AUTOFIX_MODEL,
45+
AUTOFIX_CAPI_DEV_KEY,
4446
} from "../config";
4547
import { asError, getErrorMessage } from "../common/helpers-pure";
4648
import { createTimeoutSignal } from "../common/fetch-stream";
@@ -155,6 +157,39 @@ async function findLocalAutofix(): Promise<string> {
155157
return localAutofixPath;
156158
}
157159

160+
/**
161+
* Finds and resolves the Copilot API dev key from the `codeQL.autofix.capiDevKey` setting.
162+
* The key can be specified as an environment variable reference (e.g., `env:MY_ENV_VAR`)
163+
* or a 1Password secret reference (e.g., `op://vault/item/field`). By default, it uses
164+
* the environment variable `CAPI_DEV_KEY`.
165+
*
166+
* @returns The resolved Copilot API dev key.
167+
* @throws Error if the Copilot API dev key is not found or invalid.
168+
*/
169+
async function findCapiDevKey(): Promise<string> {
170+
let capiDevKey = getAutofixCapiDevKey() || "env:CAPI_DEV_KEY";
171+
172+
if (!capiDevKey.startsWith("env:") && !capiDevKey.startsWith("op://")) {
173+
// Don't allow literal keys in config.json for security reasons
174+
throw new Error(
175+
`Invalid CAPI dev key format. Use 'env:<ENV_VAR_NAME>' or 'op://<1PASSWORD_SECRET_REFERENCE>'.`,
176+
);
177+
}
178+
if (capiDevKey.startsWith("env:")) {
179+
const envVarName = capiDevKey.substring("env:".length);
180+
capiDevKey = process.env[envVarName] || "";
181+
}
182+
if (capiDevKey.startsWith("op://")) {
183+
capiDevKey = await opRead(capiDevKey);
184+
}
185+
if (!capiDevKey) {
186+
throw new Error(
187+
`Copilot API dev key not found. Make sure ${AUTOFIX_CAPI_DEV_KEY.qualifiedName} is set correctly.`,
188+
);
189+
}
190+
return capiDevKey;
191+
}
192+
158193
/**
159194
* Overrides the query help from a given variant analysis
160195
* at a location within the `localAutofixPath` directory .
@@ -758,7 +793,7 @@ async function runAutofixOnResults(
758793
{
759794
cwd: workDir,
760795
env: {
761-
CAPI_DEV_KEY: process.env.CAPI_DEV_KEY,
796+
CAPI_DEV_KEY: await findCapiDevKey(),
762797
PATH: process.env.PATH,
763798
},
764799
},
@@ -828,6 +863,42 @@ function execAutofix(
828863
});
829864
}
830865

866+
/** Execute the 1Password CLI command `op read <secretReference>`, if the `op` command exists on the PATH. */
867+
async function opRead(secretReference: string): Promise<string> {
868+
return new Promise((resolve, reject) => {
869+
const opProcess = spawn("op", ["read", secretReference], {
870+
stdio: ["ignore", "pipe", "pipe"],
871+
});
872+
873+
let stdoutBuffer = "";
874+
let stderrBuffer = "";
875+
876+
opProcess.stdout?.on("data", (data) => {
877+
stdoutBuffer += data.toString();
878+
});
879+
880+
opProcess.stderr?.on("data", (data) => {
881+
stderrBuffer += data.toString();
882+
});
883+
884+
opProcess.on("error", (error) => {
885+
reject(error);
886+
});
887+
888+
opProcess.on("exit", (code) => {
889+
if (code === 0) {
890+
resolve(stdoutBuffer.trim());
891+
} else {
892+
reject(
893+
new Error(
894+
`1Password CLI exited with code ${code}. Stderr: ${stderrBuffer.trim()}`,
895+
),
896+
);
897+
}
898+
});
899+
});
900+
}
901+
831902
/**
832903
* Creates a new file path by appending the given suffix.
833904
* @param filePath The original file path.

0 commit comments

Comments
 (0)