Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 5 additions & 0 deletions .changeset/beige-dogs-melt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@vercel/sandbox": minor
---

Add L7 request matchers and `forwardURL` support for network policy rules.
26 changes: 26 additions & 0 deletions packages/vercel-sandbox/src/api-client/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,37 @@ import { z } from "zod";

export type SandboxMetaData = z.infer<typeof Sandbox>;

const InjectionRuleMatcherValidator = z.object({
exact: z.string().optional(),
startsWith: z.string().optional(),
regex: z.string().optional(),
});

const InjectionRuleKeyValueMatcherValidator = z.object({
key: InjectionRuleMatcherValidator.optional(),
value: InjectionRuleMatcherValidator.optional(),
});

const InjectionRuleMatchValidator = z.object({
path: InjectionRuleMatcherValidator.optional(),
method: z.array(z.string()).optional(),
queryString: z.array(InjectionRuleKeyValueMatcherValidator).optional(),
headers: z.array(InjectionRuleKeyValueMatcherValidator).optional(),
});

export const InjectionRuleValidator = z.object({
domain: z.string(),
// headers are only sent in requests
headers: z.record(z.string()).optional(),
// headerNames are returned in responses
headerNames: z.array(z.string()).optional(),
match: InjectionRuleMatchValidator.optional(),
});

export const ForwardRuleValidator = z.object({
domain: z.string(),
forwardURL: z.string(),
match: InjectionRuleMatchValidator.optional(),
});

export const NetworkPolicyValidator = z.union([
Expand All @@ -20,6 +45,7 @@ export const NetworkPolicyValidator = z.union([
allowedCIDRs: z.array(z.string()).optional(),
deniedCIDRs: z.array(z.string()).optional(),
injectionRules: z.array(InjectionRuleValidator).optional(),
forwardRules: z.array(ForwardRuleValidator).optional(),
})
.passthrough(),
]);
Expand Down
3 changes: 3 additions & 0 deletions packages/vercel-sandbox/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
export {
Sandbox,
type NetworkPolicy,
type NetworkPolicyKeyValueMatcher,
type NetworkPolicyMatch,
type NetworkPolicyMatcher,
type NetworkPolicyRule,
type NetworkTransformer,
} from "./sandbox.js";
Expand Down
58 changes: 58 additions & 0 deletions packages/vercel-sandbox/src/network-policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,63 @@ export type NetworkTransformer = {
headers?: Record<string, string>;
};

/**
* Defines how a request value is matched.
*/
export type NetworkPolicyMatcher = {
/** Match the value exactly. */
exact?: string;
} | {
/** Match values that start with the provided prefix. */
startsWith?: string;
} | {
/** Match values against an RE2 regular expression. */
regex?: string;
};
Comment thread
QuiiBz marked this conversation as resolved.

/**
* Matcher for key/value request entries such as headers and query parameters.
*/
export type NetworkPolicyKeyValueMatcher = {
/** Matcher for the entry key. */
key?: NetworkPolicyMatcher;
/** Matcher for the entry value. */
value?: NetworkPolicyMatcher;
};

/**
* Request matcher for a network policy rule.
*
* All specified dimensions must match. Multiple methods are ORed; multiple
* header and query-string matchers are ANDed.
*/
export type NetworkPolicyMatch = {
/** Match on the request path. */
path?: NetworkPolicyMatcher;
/** Match on the HTTP method. */
method?: string[];
/** Match on query-string entries. */
queryString?: NetworkPolicyKeyValueMatcher[];
/** Match on request headers. */
headers?: NetworkPolicyKeyValueMatcher[];
};

/**
* A rule applied to requests matching a domain in the network policy.
*/
export type NetworkPolicyRule = {
/**
* Optional request matcher. When provided, transforms only apply to requests
* that match every specified dimension.
*/
match?: NetworkPolicyMatch;
/** Transforms to apply to matching requests. */
transform?: NetworkTransformer[];
/**
* Optional HTTPS proxy URL to forward matching requests to.
* Must not include query string or fragment.
*/
forwardURL?: string;
};

/**
Expand Down Expand Up @@ -60,6 +111,13 @@ export type NetworkPolicyRule = {
* allow: {
* "ai-gateway.vercel.sh": [
* {
* match: {
* method: ["POST"],
* path: { startsWith: "/v1/" },
* headers: [
* { key: { exact: "x-api-key" }, value: { exact: "placeholder" } }
* ]
* },
* transform: [{
* headers: { authorization: "Bearer ..." }
* }]
Expand Down
12 changes: 11 additions & 1 deletion packages/vercel-sandbox/src/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import { Command, CommandFinished } from "./command.js";
import type { RUNTIMES } from "./constants.js";
import type {
NetworkPolicy,
NetworkPolicyKeyValueMatcher,
NetworkPolicyMatch,
NetworkPolicyMatcher,
NetworkPolicyRule,
NetworkTransformer,
} from "./network-policy.js";
Expand All @@ -24,7 +27,14 @@ import {
import { getPrivateParams, type WithPrivate } from "./utils/types.js";
import { FileSystem } from "./filesystem.js";

export type { NetworkPolicy, NetworkPolicyRule, NetworkTransformer };
export type {
NetworkPolicy,
NetworkPolicyKeyValueMatcher,
NetworkPolicyMatch,
NetworkPolicyMatcher,
NetworkPolicyRule,
NetworkTransformer,
};

/** @inline */
export interface BaseCreateSandboxParams {
Expand Down
Loading
Loading