@@ -71,6 +71,9 @@ type Config struct {
7171 Sign bool // Enable signing with cosign
7272 CosignKeyPath string // Path to cosign private key
7373 CosignPasswordEnv string // Environment variable for cosign password
74+
75+ // Direct Buildah options
76+ BuildahOpts []string
7477}
7578
7679// AttestationConfig represents a single --attest flag
@@ -125,6 +128,11 @@ func executeBuildah(config Config, ctx *Context) error {
125128 logger .Debug ("Running as non-root (UID %d) - using chroot isolation with user namespaces" , os .Getuid ())
126129 }
127130
131+ // Warn if --buildkit-opt was passed — these are ignored by Buildah
132+ if len (config .BuildKitOpts ) > 0 {
133+ logger .Warning ("--buildkit-opt flags are ignored when using Buildah backend: %v" , config .BuildKitOpts )
134+ }
135+
128136 logger .Info ("Starting buildah build..." )
129137
130138 // ========================================
@@ -262,13 +270,35 @@ func executeBuildah(config Config, ctx *Context) error {
262270 args = append (args , "-t" , dest )
263271 }
264272
273+ // ========================================
274+ // Pass-through args — must be added before ctx.Path
275+ // ========================================
276+ // Supports both "--flag=value" and "--flag value" (e.g. Buildah's --sbom flags).
277+ // Each --buildah-opt is split into at most two tokens; validateBuildahInputs
278+ // enforces that the flag name portion contains no dangerous characters.
279+ for _ , opt := range config .BuildahOpts {
280+ parts := strings .SplitN (opt , " " , 2 )
281+ if len (parts ) == 2 {
282+ args = append (args , parts [0 ], parts [1 ])
283+ } else {
284+ args = append (args , parts [0 ])
285+ }
286+ }
287+
265288 // Add context path
266289 args = append (args , ctx .Path )
267290
268291 // Log the command
269- logger .Debug ("Buildah command: buildah %s" , strings .Join (args , " " ))
292+ logger .Debug ("Buildah command: buildah %s" , strings .Join (sanitizeCommandArgs ( args ) , " " ))
270293
271294 // Execute buildah
295+ // #nosec G204 -- all args validated by validateBuildahInputs:
296+ // - BuildahOpts: flag name validated by ValidateBuildctlArg (null bytes,
297+ // shell metacharacters); value portion checked for null bytes only since
298+ // scanner commands and paths may contain characters the general validator
299+ // would reject; conflict-checked against Kimia-managed flags
300+ // - All other args (dockerfile, build-arg, label, dest) are Kimia-constructed
301+ // from validated inputs
272302 cmd := exec .Command ("buildah" , args ... )
273303 var stdoutBuf , stderrBuf bytes.Buffer
274304 cmd .Stdout = io .MultiWriter (os .Stdout , & stdoutBuf )
@@ -482,18 +512,97 @@ func validateBuildahInputs(config Config, ctx *Context) error {
482512 homeDir = "/home/kimia"
483513 }
484514 homeDir = filepath .Clean (homeDir )
485-
515+
486516 if err := validation .ValidatePathWithinBase (config .TarPath , homeDir ); err != nil {
487517 return fmt .Errorf ("invalid tar path: %v" , err )
488518 }
489519 }
490520
521+ // Flags already managed explicitly by Kimia.
522+ // IMPORTANT: If new flags are added to executeBuildah, add them here too.
523+ conflictingFlags := map [string ]string {
524+ "-f" : "use -f/--dockerfile instead" ,
525+ "--file" : "use -f/--dockerfile instead" ,
526+ "--build-arg" : "use --build-arg instead" ,
527+ "--label" : "use --label instead" ,
528+ "--target" : "use -t/--target instead" ,
529+ "--platform" : "use --custom-platform instead" ,
530+ "--timestamp" : "use --timestamp or --reproducible instead" ,
531+ "--source-date-epoch" : "use --timestamp or --reproducible instead" ,
532+ // Don't prevent users from overriding --tls-verify
533+ //"--tls-verify": "use --insecure or --insecure-registry instead",
534+ "--retry" : "use --image-download-retry instead" ,
535+ "-t" : "use -d/--destination instead" ,
536+ "--tag" : "use -d/--destination instead" ,
537+ "--no-cache" : "use --cache=false instead" ,
538+ "--layers" : "use --cache instead" ,
539+ // Security-sensitive flags managed implicitly by Kimia via BUILDAH_ISOLATION=chroot
540+ "--isolation" : "isolation is managed by Kimia (chroot)" ,
541+ "--userns" : "user namespace configuration is managed by Kimia" ,
542+ "--userns-uid-map" : "user namespace configuration is managed by Kimia" ,
543+ "--userns-gid-map" : "user namespace configuration is managed by Kimia" ,
544+ "--cap-add" : "capability management is outside Kimia's scope" ,
545+ "--cap-drop" : "capability management is outside Kimia's scope" ,
546+ "--security-opt" : "security options are managed by Kimia" ,
547+ "--privileged" : "privileged mode is not supported by Kimia" ,
548+ }
549+
550+ for i , opt := range config .BuildahOpts {
551+ opt = strings .TrimSpace (opt )
552+
553+ if opt == "" {
554+ return fmt .Errorf ("--buildah-opt value %d is empty" , i )
555+ }
556+
557+ // Split into at most two tokens: "--flag" and optional "value".
558+ // This matches the split logic in executeBuildah and correctly handles
559+ // both "--flag=value" and "--flag value" (e.g. Buildah's --sbom flags).
560+ parts := strings .SplitN (opt , " " , 2 )
561+ flagName := parts [0 ]
562+ if idx := strings .Index (flagName , "=" ); idx != - 1 {
563+ flagName = flagName [:idx ]
564+ }
565+
566+ // Validate the flag name only — not the full opt string.
567+ // The old ValidateBuildctlArg(opt) call blocked spaces, which breaks
568+ // legitimate "--flag value" opts like "--sbom syft-cyclonedx".
569+ if err := validation .ValidateBuildctlArg (flagName ); err != nil {
570+ return fmt .Errorf ("invalid --buildah-opt flag name %d (%q): %v" , i , flagName , err )
571+ }
572+
573+ // Validate the value portion for null bytes regardless of form.
574+ // Space-separated values (parts[1]) are passed through as-is since
575+ // they may be paths or scanner commands that ValidateBuildctlArg
576+ // would incorrectly reject.
577+ var optValue string
578+ if len (parts ) == 2 {
579+ optValue = parts [1 ] // "--flag value" form
580+ } else if idx := strings .Index (parts [0 ], "=" ); idx != - 1 {
581+ optValue = parts [0 ][idx + 1 :] // "--flag=value" form
582+ }
583+ if strings .Contains (optValue , "\x00 " ) {
584+ return fmt .Errorf ("--buildah-opt value %d contains null byte" , i )
585+ }
586+
587+ if suggestion , conflicts := conflictingFlags [flagName ]; conflicts {
588+ return fmt .Errorf (
589+ "--buildah-opt %q is managed by Kimia: %s" ,
590+ flagName , suggestion ,
591+ )
592+ }
593+ }
594+
491595 return nil
492596}
493597
494598func executeBuildKit (config Config , ctx * Context ) error {
495599 logger .Info ("Starting BuildKit build..." )
496600
601+ // Warn if --buildah-opt was passed — these are ignored by BuildKit
602+ if len (config .BuildahOpts ) > 0 {
603+ logger .Warning ("--buildah-opt flags are ignored when using BuildKit backend: %v" , config .BuildahOpts )
604+ }
605+
497606 // ========================================
498607 // SETUP: Environment and paths
499608 // ========================================
@@ -1720,7 +1829,7 @@ func sanitizeCommandArgs(args []string) []string {
17201829 "SECRET" ,
17211830 "CREDENTIALS" ,
17221831 }
1723-
1832+
17241833 sanitized := make ([]string , len (args ))
17251834 for i , arg := range args {
17261835 if strings .HasPrefix (arg , "context=" ) || strings .HasPrefix (arg , "dockerfile=" ) {
@@ -1753,8 +1862,17 @@ func sanitizeCommandArgs(args []string) []string {
17531862 sanitized [i ] = arg
17541863 }
17551864 } else {
1756- // For any other arg that might be a URL
1757- sanitized [i ] = logger .SanitizeGitURL (arg )
1865+ // Only sanitize args that look like URLs -- calling SanitizeGitURL
1866+ // on non-URL values (e.g. --buildah-opt scanner commands) causes
1867+ // spaces and braces to be URL-encoded in log output.
1868+ if strings .HasPrefix (arg , "http://" ) ||
1869+ strings .HasPrefix (arg , "https://" ) ||
1870+ strings .HasPrefix (arg , "git://" ) ||
1871+ strings .HasPrefix (arg , "git@" ) {
1872+ sanitized [i ] = logger .SanitizeGitURL (arg )
1873+ } else {
1874+ sanitized [i ] = arg
1875+ }
17581876 }
17591877 }
17601878 return sanitized
0 commit comments