Skip to content

X-Registry-Config is encoded as standard base64 in buildImage, but Docker daemon expects URL-safe base64 #194

@cristianrgreco

Description

@cristianrgreco

Summary

docker-modem currently sets X-Registry-Config using standard base64 in buildImage requests.

Code path:

https://github.com/apocas/docker-modem/blob/v5.0.6/lib/modem.js#L191-L193

optionsf.headers['X-Registry-Config'] = options.registryconfig.base64 ||
    Buffer.from(JSON.stringify(options.registryconfig)).toString('base64');

However, Docker daemon build route decodes X-Registry-Config with URL-safe base64 (base64.URLEncoding) and ignores decode errors:

https://github.com/moby/moby/blob/59995f1b91d135ce740a7e4a9b801a748d1f7848/daemon/server/router/build/build_routes.go#L336-L348

func getAuthConfigs(header http.Header) map[string]registry.AuthConfig {
	authConfigs := map[string]registry.AuthConfig{}
	authConfigsEncoded := header.Get("X-Registry-Config")

	if authConfigsEncoded == "" {
		return authConfigs
	}

	authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded))
	// Pulling an image does not error when no auth is provided so to remain
	// consistent with the existing api decode errors are ignored
	_ = json.NewDecoder(authConfigsJSON).Decode(&authConfigs)
	return authConfigs
}

So if the encoded header contains + or /, decode can fail and auth configs become empty.

Expected behavior

X-Registry-Config should be URL-safe base64 encoded (compatible with daemon decode path), like the Moby client does:

https://github.com/moby/moby/blob/59995f1b91d135ce740a7e4a9b801a748d1f7848/client/image_build.go#L31-L33

headers := http.Header{}
headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf))
headers.Set("Content-Type", "application/x-tar")

Actual behavior

docker-modem sends standard base64.

For payloads where standard base64 output contains + or /, daemon-side decode fails and auth map is empty.

Repro

Using a payload that produces + in standard base64:

package main

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"strings"
)

type authConfig struct {
	Username string `json:"username,omitempty"`
	Password string `json:"password,omitempty"`
}

func decodeLikeMoby(header string) (map[string]authConfig, error) {
	m := map[string]authConfig{}
	err := json.NewDecoder(base64.NewDecoder(base64.URLEncoding, strings.NewReader(header))).Decode(&m)
	return m, err
}

func main() {
	// This is the JSON docker-modem would base64-encode into X-Registry-Config.
	payload := `{"https://index.docker.io/v1/":{"username":"u","password":">"}}`

	// docker-modem behavior today (standard base64):
	std := base64.StdEncoding.EncodeToString([]byte(payload))
	// Docker/Moby client behavior (URL-safe base64):
	url := base64.URLEncoding.EncodeToString([]byte(payload))

	fmt.Printf("payload=%s\n", payload)
	fmt.Printf("std_has_plus=%v\n", strings.Contains(std, "+"))
	fmt.Printf("std_has_slash=%v\n", strings.Contains(std, "/"))

	stdM, stdErr := decodeLikeMoby(std)
	urlM, urlErr := decodeLikeMoby(url)

	fmt.Printf("decode_std_err=%v\n", stdErr)
	fmt.Printf("decode_std_entries=%d\n", len(stdM))
	fmt.Printf("decode_url_err=%v\n", urlErr)
	fmt.Printf("decode_url_entries=%d\n", len(urlM))
}

Real-world impact

Downstream users can see authenticated pull working for normal pulls but Dockerfile FROM pull during build behaving as unauthenticated, causing Docker Hub rate-limit failures (toomanyrequests) despite valid credentials.

Proposed fix

In docker-modem, for X-Registry-Config default encoding, use URL-safe base64 (preserve padding), e.g. standard base64 + replace + -> -, / -> _.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions