Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions spices/SPICE-0022-http-headers.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
= Custom HTTP Headers

* Proposal: link:./SPICE-0022-http-headers.adoc[SPICE-0022]
* Author: link:https://github.com/kyokuping[kyokuping]
* Status: Proposed
* Category: Language, Standard Library, Tooling

== Introduction

This SPICE proposes a new evaluator option to allow adding custom HTTP headers to outbound requests, with the ability to target specific URL prefixes.

== Motivation

When Pkl interacts with HTTP resources, it may need to provide authentication tokens or other custom headers to access them. For example, a Pkl module might be hosted in a private repository that requires an `Authorization` header for access.

Currently, there is no way to add custom headers to Pkl's HTTP requests. This limits Pkl's ability to interact with a wide range of HTTP-based resources.

== Proposed Solution

A new HTTP setting will be added, called `headers`. This setting allows users to specify a list of headers to be added to outbound HTTP requests. To avoid leaking credentials, headers can be configured on a per-URL-prefix basis.

For example, to provide an authentication token for a specific host, the configuration would look like this:

.~/.pkl/settings.pkl
[source,pkl]
----
amends "pkl:settings"

http {
headers {
["https://my.private.repo/"] {
Pair("Authorization", "Bearer my-secret-token")
}
}
}
Comment thread
bioball marked this conversation as resolved.
----

This will add the `Authorization` header to all requests sent to `https://my.private.repo/` and its sub-paths.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many auth methods require some sort of extra logic to form a token (for example, some process to fetch a short-lived cookie).

It'd be good to think about how this should work in Pkl.

Possibly, users can use a custom resource reader:

headers {
  ["https://my.private.repo/"] {
    ["Cookie"] = read("auth:get-my-cookie")
  }
}

One issue here is that custom resource readers are not available when evaluating ~/.pkl/settings.pkl and PklProject. This is a chicken-and-egg problem; custom readers can possibly be configured here in the first place.

This isn't necessarily blocking this feature, but something to think about as we design t his.


== Detailed design

The `pkl.EvaluatorSettings.Http` class will be extended with a new `headers` property.

.pkl:EvaluatorSettings
[source,pkl]
----
class Http {
// ... existing properties

/// HTTP headers to add to outbound requests targeting specified URLs.
headers: Mapping<HttpRewrite, Listing<Pair<String, String>>>?
Comment thread
kyokuping marked this conversation as resolved.
Outdated
}
----

The `HttpRewrite` typealias from `SPICE-0016-http-rewrites` will be reused to define the URL prefixes.

When multiple prefixes match a request URL, the longest prefix will be chosen, ensuring that the most specific rule is applied.

A new CLI flag, `--http-header`, will be introduced to allow specifying headers from the command line. The flag will accept key-value pairs in the format `<url-prefix>=<header-name>:<header-value>[,<header-name>:<header-value>...]`.

Example:
[source,shell]
----
pkl eval \
--http-header "https://my.private.repo/:Authorization=Bearer my-secret-token" \
myModule.pkl
----

The HTTP client will be modified to check for matching header configurations for each outgoing request and add the corresponding headers.

== Compatibility

This change is strictly backward-compatible. Existing Pkl programs will continue to work as-is.

== Future directions

Future enhancements could include support for more complex header manipulation, such as removing default headers or adding headers based on regular expression matching on the URL.

== Alternatives considered

=== Global HTTP headers

The initial proposal involved a simpler approach of having a single set of custom headers that would be applied to all outbound HTTP requests. This was rejected due to security concerns. Sending the same set of headers, which might include authentication tokens, to all hosts is a significant security risk. The per-URL-prefix approach provides a more secure way to manage custom headers.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's valid to want headers to apply to all requests; for example, you might want all of Pkl's requests to use your own user-agent header.

We can perhaps have * be a special case that represents all hosts?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have any strong opinion on this. Curious what the rest of the team thinks about this.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of special-casing *, what about applying a matching system similar to URLPattern? This would allow users to create flexible rules.
For example, in MSA, users could apply an internal auth token exclusively to the *://*.service.internal pattern. It will allow users to safely target a much broader scope than a single host.

@bioball bioball Oct 4, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's possible, although, it differs from how HTTP rewrites work. Theoretically, we can change HTTP rewrites to follow this logic too, but it would be a breaking change:

  • We determine precedence by longest prefix. This makes sense right now because these prefixes are verbatim, therefore, the longest is the most specific. However, this doesn't hold if we use wildcards (e.g. [f][o][o]* is less specific than foob* but is longer).
  • Wildcards are also URL characters, so existing valid prefixes would then be re-interpreted as a wildcard.

If we accept patterns, glob patterns would probably be better than regexes (glob patterns are designed to work well with paths, / and . are not special characters). Also, we have our own specification for glob patterns: https://pkl-lang.org/main/current/language-reference/index.html#glob-patterns

Perhaps we can have HTTP headers and HTTP rewrites just have different rules too (one is a verbatim match, one is a wildcard match), because these address different problems in the first place that require different solutions (mirroring vs. authentication).


=== Auth-specific support

Someone could argue it'd make more sense to specifically support auth handling rather than exposing the full ability to add custom HTTP headers. While this can be considered more future-proof in terms of exposing minimal API surface to maintain compatibility, this would limit the potential of supporting many different use cases outside of auth. That being said, adding separate auth support can also be considered later, mostly for supporting complex auth flows like OAuth.

== Acknowledgements

Thanks to the Pkl team for their feedback on the initial proposal, which helped shape the more secure and flexible design presented in this SPICE.