Skip to content
Open
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/fix-dockerfile-env-quoted-values.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'e2b': patch
---

Fix `Template.fromDockerfile` parsing of `ENV`/`ARG` values that contain whitespace. A quoted value like `ENV NAME="John Doe"` (and an unquoted `ENV KEY=hello world`) was split into a malformed key; it is now parsed as a single value with surrounding quotes stripped, matching the Python SDK. Multiple `key=value` pairs on one line stay separated.
85 changes: 27 additions & 58 deletions packages/js-sdk/src/template/dockerfileParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,67 +231,36 @@ function handleEnvInstruction(
const argumentsData = instruction.getArguments()
const keyword = instruction.getKeyword()

if (argumentsData && argumentsData.length >= 1) {
const envVars: Record<string, string> = {}

if (argumentsData.length === 2) {
// ENV key value format OR multiple key=value pairs (from line continuation)
const firstArg = argumentsData[0].getValue()
const secondArg = argumentsData[1].getValue()

// Check if both arguments contain '=' (multiple key=value pairs)
if (firstArg.includes('=') && secondArg.includes('=')) {
// Both are key=value pairs (line continuation)
for (const arg of argumentsData) {
const envString = arg.getValue()
const equalIndex = envString.indexOf('=')
if (equalIndex > 0) {
const key = envString.substring(0, equalIndex)
const value = envString.substring(equalIndex + 1)
envVars[key] = value
}
}
} else {
// Traditional ENV key value format
envVars[firstArg] = secondArg
}
} else if (argumentsData.length === 1) {
// ENV/ARG key=value format (single argument) or ARG key (without default)
const envString = argumentsData[0].getValue()

// Check if it's a simple key=value or just a key (for ARG without default)
const equalIndex = envString.indexOf('=')
if (equalIndex > 0) {
const key = envString.substring(0, equalIndex)
const value = envString.substring(equalIndex + 1)
envVars[key] = value
} else if (keyword === 'ARG' && envString.trim()) {
// ARG without default value - set as empty ENV
const key = envString.trim()
envVars[key] = ''
}
} else {
// Multiple arguments (from line continuation with backslashes)
for (const arg of argumentsData) {
const envString = arg.getValue()
const equalIndex = envString.indexOf('=')
if (equalIndex > 0) {
const key = envString.substring(0, equalIndex)
const value = envString.substring(equalIndex + 1)
envVars[key] = value
} else if (keyword === 'ARG') {
// ARG without default value
const key = envString
envVars[key] = ''
}
}
}
if (!argumentsData || argumentsData.length === 0) {
return
}

// Call setEnvs once with all environment variables from this instruction
if (Object.keys(envVars).length > 0) {
templateBuilder.setEnvs(envVars)
// dockerfile-ast splits arguments on whitespace, including inside quotes, so
// rejoin them to recover the raw value and parse it like the Python SDK: in
// the `key=value` form a value runs across whitespace until the next `key=`
// token, and surrounding quotes are stripped. Parsing each token in isolation
// mangled `ENV NAME="John Doe"` and `ENV KEY=a b` into a broken key.
const value = argumentsData.map((arg) => arg.getValue()).join(' ')
const envVars: Record<string, string> = {}

if (value.includes('=')) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve legacy ENV values containing equals signs

Docker's legacy ENV <key> <value> form permits = inside the value, but selecting the assignment form whenever the whole line contains = misparses ENV KEY value=foo as { value: "foo" } and drops KEY; the parent implementation's two-token branch correctly produced KEY=value=foo. Determine the syntax from the leading token rather than from any equals sign in the joined value, and make the equivalent correction in the shared Python parser, which currently uses the same faulty test.

AGENTS.md reference: AGENTS.md:L3-L3

Useful? React with 👍 / 👎.

const pairRegex = /(\w+)=([^\s]*(?:\s+(?!\w+=)[^\s]*)*)/g

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve punctuation in ENV variable names

Docker ENV keys are not restricted to JavaScript's ASCII \w class, so a valid instruction such as ENV com.example.setting=on is now parsed as { setting: "on" }; similarly, ENV MY-VAR=value becomes VAR=value. The previous substring(0, equalIndex) logic preserved these names, so the matcher should capture the complete non-whitespace key before = and the equivalent Python regex should be corrected as well.

AGENTS.md reference: AGENTS.md:L3-L3

Useful? React with 👍 / 👎.

let match: RegExpExecArray | null
while ((match = pairRegex.exec(value)) !== null) {
envVars[match[1]] = match[2].replace(/^["']+|["']+$/g, '')
}
} else {
const spaceForm = value.match(/^(\S+)\s+([\s\S]+)$/)
if (spaceForm) {
envVars[spaceForm[1]] = spaceForm[2].replace(/^["']+|["']+$/g, '')
} else if (keyword === 'ARG' && value.trim()) {
envVars[value.trim()] = ''
}
}

if (Object.keys(envVars).length > 0) {
templateBuilder.setEnvs(envVars)
}
}

function handleCmdEntrypointInstruction(
Expand Down
21 changes: 21 additions & 0 deletions packages/js-sdk/tests/template/methods/fromDockerfile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,24 @@ COPY --chown=anotheruser config.json /config/`
assert.equal(copyInstruction2.args[1], '/config/')
assert.equal(copyInstruction2.args[2], 'anotheruser') // user from --chown (without group)
})

buildTemplateTest('fromDockerfile parses quoted and spaced ENV values', async () => {
const dockerfile = `FROM node:24
ENV NAME="John Doe"
ENV GREETING=hello world
ENV A=1 B=2`

const template = Template().fromDockerfile(dockerfile)

const envInstructions = (
// @ts-expect-error - instructions is not a property of TemplateBuilder
template.instructions as { type: InstructionType; args: string[] }[]
).filter((i) => i.type === InstructionType.ENV)

// A quoted value with a space stays a single value (was split into a broken key).
assert.deepEqual(envInstructions[0].args, ['NAME', 'John Doe'])
// An unquoted value runs to the next `key=` token.
assert.deepEqual(envInstructions[1].args, ['GREETING', 'hello world'])
// Multiple key=value pairs on one line stay separated.
assert.deepEqual(envInstructions[2].args, ['A', '1', 'B', '2'])
})