Skip to content

Commit a11ebc0

Browse files
committed
Add 1Password option for configuring CAPI_DEV_KEY
1 parent 91c0443 commit a11ebc0

File tree

2 files changed

+68
-1
lines changed

2 files changed

+68
-1
lines changed

extensions/ql-vscode/src/config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -968,3 +968,9 @@ export const AUTOFIX_MODEL = new Setting("model", AUTOFIX_SETTING);
968968
export function getAutofixModel(): string | undefined {
969969
return AUTOFIX_MODEL.getValue<string>() || undefined;
970970
}
971+
972+
export const AUTOFIX_CAPI_DEV_KEY = new Setting("capiDevKey", AUTOFIX_SETTING);
973+
974+
export function getAutofixCapiDevKey(): string | undefined {
975+
return AUTOFIX_CAPI_DEV_KEY.getValue<string>() || undefined;
976+
}

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

Lines changed: 62 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,29 @@ async function findLocalAutofix(): Promise<string> {
155157
return localAutofixPath;
156158
}
157159

160+
async function findCapiDevKey(): Promise<string> {
161+
let capiDevKey = getAutofixCapiDevKey() || "env:CAPI_DEV_KEY";
162+
163+
if (capiDevKey.startsWith("env:")) {
164+
const envVarName = capiDevKey.substring("env:".length);
165+
capiDevKey = process.env[envVarName] || "";
166+
} else if (capiDevKey.startsWith("op://")) {
167+
capiDevKey = await opRead(capiDevKey);
168+
} else {
169+
// Don't allow literal keys for security reasons
170+
throw new Error(
171+
`Invalid CAPI dev key format. Use 'env:<ENV_VAR_NAME>' or 'op://<1PASSWORD_SECRET_REFERENCE>'.`,
172+
);
173+
}
174+
175+
if (!capiDevKey) {
176+
throw new Error(
177+
`Copilot API dev key not found. Make sure ${AUTOFIX_CAPI_DEV_KEY.qualifiedName} is set correctly.`,
178+
);
179+
}
180+
return capiDevKey;
181+
}
182+
158183
/**
159184
* Overrides the query help from a given variant analysis
160185
* at a location within the `localAutofixPath` directory .
@@ -758,7 +783,7 @@ async function runAutofixOnResults(
758783
{
759784
cwd: workDir,
760785
env: {
761-
CAPI_DEV_KEY: process.env.CAPI_DEV_KEY,
786+
CAPI_DEV_KEY: await findCapiDevKey(),
762787
PATH: process.env.PATH,
763788
},
764789
},
@@ -828,6 +853,42 @@ function execAutofix(
828853
});
829854
}
830855

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

0 commit comments

Comments
 (0)