Skip to content

Commit 963f626

Browse files
committed
Gate process plugins via api.GenerateOptions.InsecureProcessPluginNames
Add an explicit allowlist of process-based plugin names to api.GenerateOptions. Generate fails before any parse or codegen runs if the configuration declares a process plugin whose name is not in the list. The "Insecure" prefix mirrors crypto/tls.Config.InsecureSkipVerify to flag the trust decision callers are making — process plugins execute arbitrary local commands. The CLI populates the allowlist by scanning the user's own config for declared process plugins, so `sqlc generate`, `sqlc compile`, and `sqlc diff` keep working. SQLCDEBUG=processplugins=0 still disables process plugins by leaving the allowlist nil. https://claude.ai/code/session_01RCzB2JR5Y5ScFDUmwcxGVZ
1 parent 9848cd6 commit 963f626

3 files changed

Lines changed: 73 additions & 11 deletions

File tree

internal/api/generate.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"io"
77
"path/filepath"
8+
"slices"
89
"strings"
910
"sync"
1011

@@ -33,6 +34,15 @@ type GenerateOptions struct {
3334
// on disk and writes a unified diff for differences to Stderr. If any
3435
// differences are found, an error is appended to GenerateResult.Errors.
3536
Diff bool
37+
38+
// InsecureProcessPluginNames is the allowlist of process-based plugin
39+
// names that Generate is permitted to invoke. Any process plugin declared
40+
// in the configuration whose name is not in this list causes Generate to
41+
// fail before parsing or codegen runs. Process plugins execute arbitrary
42+
// local commands; the "Insecure" prefix mirrors
43+
// crypto/tls.Config.InsecureSkipVerify as a reminder that callers must
44+
// consciously trust each plugin name they pass here.
45+
InsecureProcessPluginNames []string
3646
}
3747

3848
// GenerateResult is the outcome of a Generate call. Files maps absolute output
@@ -72,6 +82,18 @@ func Generate(ctx context.Context, opts GenerateOptions) GenerateResult {
7282
return res
7383
}
7484

85+
for _, plug := range conf.Plugins {
86+
if plug.Process == nil {
87+
continue
88+
}
89+
if !slices.Contains(opts.InsecureProcessPluginNames, plug.Name) {
90+
err := fmt.Errorf("process plugin %q is not in InsecureProcessPluginNames; refusing to run", plug.Name)
91+
fmt.Fprintf(stderr, "error validating %s: %s\n", base, err)
92+
res.Errors = append(res.Errors, err)
93+
return res
94+
}
95+
}
96+
7597
g := &generator{
7698
dir: opts.Dir,
7799
output: map[string]string{},

internal/cmd/cmd.go

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -182,18 +182,35 @@ func getConfigPath(stderr io.Writer, f *pflag.Flag) (string, string) {
182182
}
183183
}
184184

185+
// allowedProcessPluginNames returns the set of process plugin names the CLI
186+
// trusts to run. SQLCDEBUG=processplugins=0 disables every process plugin by
187+
// returning nil; otherwise we trust whatever the user declared in their own
188+
// config.
189+
func allowedProcessPluginNames(env Env, stderr io.Writer, dir, name string) []string {
190+
if !env.Debug.ProcessPlugins {
191+
return nil
192+
}
193+
names, err := processPluginNames(stderr, dir, name)
194+
if err != nil {
195+
os.Exit(1)
196+
}
197+
return names
198+
}
199+
185200
var genCmd = &cobra.Command{
186201
Use: "generate",
187202
Short: "Generate source code from SQL",
188203
RunE: func(cmd *cobra.Command, args []string) error {
189204
defer trace.StartRegion(cmd.Context(), "generate").End()
190205
stderr := cmd.ErrOrStderr()
191206
dir, name := getConfigPath(stderr, cmd.Flag("file"))
207+
env := ParseEnv(cmd)
192208
res := api.Generate(cmd.Context(), api.GenerateOptions{
193-
Dir: dir,
194-
File: name,
195-
Stderr: stderr,
196-
Write: true,
209+
Dir: dir,
210+
File: name,
211+
Stderr: stderr,
212+
Write: true,
213+
InsecureProcessPluginNames: allowedProcessPluginNames(env, stderr, dir, name),
197214
})
198215
if len(res.Errors) > 0 {
199216
os.Exit(1)
@@ -209,10 +226,12 @@ var checkCmd = &cobra.Command{
209226
defer trace.StartRegion(cmd.Context(), "compile").End()
210227
stderr := cmd.ErrOrStderr()
211228
dir, name := getConfigPath(stderr, cmd.Flag("file"))
229+
env := ParseEnv(cmd)
212230
res := api.Generate(cmd.Context(), api.GenerateOptions{
213-
Dir: dir,
214-
File: name,
215-
Stderr: stderr,
231+
Dir: dir,
232+
File: name,
233+
Stderr: stderr,
234+
InsecureProcessPluginNames: allowedProcessPluginNames(env, stderr, dir, name),
216235
})
217236
if len(res.Errors) > 0 {
218237
os.Exit(1)
@@ -228,11 +247,13 @@ var diffCmd = &cobra.Command{
228247
defer trace.StartRegion(cmd.Context(), "diff").End()
229248
stderr := cmd.ErrOrStderr()
230249
dir, name := getConfigPath(stderr, cmd.Flag("file"))
250+
env := ParseEnv(cmd)
231251
res := api.Generate(cmd.Context(), api.GenerateOptions{
232-
Dir: dir,
233-
File: name,
234-
Stderr: stderr,
235-
Diff: true,
252+
Dir: dir,
253+
File: name,
254+
Stderr: stderr,
255+
Diff: true,
256+
InsecureProcessPluginNames: allowedProcessPluginNames(env, stderr, dir, name),
236257
})
237258
if len(res.Errors) > 0 {
238259
os.Exit(1)

internal/cmd/generate.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,25 @@ func readConfig(stderr io.Writer, dir, filename string) (string, *config.Config,
106106
return configPath, &conf, nil
107107
}
108108

109+
// processPluginNames returns the names of every process-based plugin declared
110+
// in the sqlc configuration at dir/filename. The CLI passes the result to
111+
// api.GenerateOptions.InsecureProcessPluginNames so commands run by the user
112+
// (who wrote the config) can invoke any plugin they declared, while library
113+
// callers are still required to opt in explicitly.
114+
func processPluginNames(stderr io.Writer, dir, filename string) ([]string, error) {
115+
_, conf, err := readConfig(stderr, dir, filename)
116+
if err != nil {
117+
return nil, err
118+
}
119+
var names []string
120+
for _, p := range conf.Plugins {
121+
if p.Process != nil {
122+
names = append(names, p.Name)
123+
}
124+
}
125+
return names, nil
126+
}
127+
109128
func parse(ctx context.Context, name, dir string, sql config.SQL, combo config.CombinedSettings, parserOpts opts.Parser, stderr io.Writer) (*compiler.Result, bool) {
110129
defer trace.StartRegion(ctx, "parse").End()
111130
c, err := compiler.NewCompiler(sql, combo, parserOpts)

0 commit comments

Comments
 (0)