@@ -38,9 +38,11 @@ import type { VariantAnalysisResultsManager } from "./variant-analysis-results-m
3838import {
3939 getAutofixPath ,
4040 getAutofixModel ,
41+ getAutofixCapiDevKey ,
4142 downloadTimeout ,
4243 AUTOFIX_PATH ,
4344 AUTOFIX_MODEL ,
45+ AUTOFIX_CAPI_DEV_KEY ,
4446} from "../config" ;
4547import { asError , getErrorMessage } from "../common/helpers-pure" ;
4648import { 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