@@ -5,14 +5,22 @@ import { fileURLToPath } from "node:url"
55import { spawn } from "node:child_process"
66
77import {
8+ claudeOauthTokenFileMode ,
89 claudeOauthTokenPath ,
910 classifyClaudeSetupTokenResult ,
1011 extractClaudeOauthToken ,
11- formatClaudeOauthTokenFile
12+ flushClaudeOauthTokenRedactionState ,
13+ formatClaudeOauthTokenFile ,
14+ initialClaudeOauthTokenRedactionState ,
15+ redactClaudeOauthTokenChunk ,
16+ type ClaudeOauthTokenRedactionState
1217} from "./claude-oauth-token.js"
1318
1419export const defaultClaudeDockerOauthImage = "docker-git-auth-claude:latest"
1520export const defaultClaudeDockerOauthContainerHome = "/claude-home"
21+ export const claudeDockerOauthBaseImage =
22+ "node:24-bookworm-slim@sha256:b31e7a42fdf8b8aa5f5ed477c72d694301273f1069c5a2f71d53c6482e99a2fc"
23+ export const claudeDockerOauthClaudeCodeVersion = "2.1.195"
1624
1725export type ClaudeDockerOauthOptions = {
1826 readonly cwd ?: string
@@ -81,23 +89,19 @@ export type ClaudeDockerProbeStatus =
8189
8290const outputWindowSize = 262_144
8391
84- const claudeDockerfile = String . raw `FROM ubuntu:24.04
92+ export const renderClaudeDockerOauthDockerfile = ( ) : string =>
93+ String . raw `FROM ${ claudeDockerOauthBaseImage }
8594ENV DEBIAN_FRONTEND=noninteractive
8695RUN apt-get update \
87- && apt-get install -y --no-install-recommends ca-certificates curl bsdutils \
96+ && apt-get install -y --no-install-recommends ca-certificates bsdutils \
8897 && rm -rf /var/lib/apt/lists/*
89- RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \
90- && apt-get install -y --no-install-recommends nodejs \
91- && node -v \
98+ RUN node -v \
9299 && npm -v \
93- && rm -rf /var/lib/apt/lists/*
94- RUN npm install -g @anthropic-ai/claude-code@latest
100+ && npm install -g --no-audit --no-fund @anthropic-ai/claude-code@ ${ claudeDockerOauthClaudeCodeVersion } \
101+ && claude --version
95102ENTRYPOINT ["claude"]
96103`
97104
98- const redactedOauthTokenText = ( text : string ) : string =>
99- text . replaceAll ( / s k - a n t - [ A - Z a - z 0 - 9 . _ - ] + / gu, "<redacted-oauth-token>" )
100-
101105const appendOutputWindow = ( outputWindow : string , chunk : string ) : string => {
102106 const next = `${ outputWindow } ${ chunk } `
103107 return next . length > outputWindowSize ? next . slice ( - outputWindowSize ) : next
@@ -138,7 +142,7 @@ const ensureClaudeDockerImage = async (
138142 }
139143 const contextPath = await mkdtemp ( join ( tmpdir ( ) , "docker-git-auth-oauth-image-" ) )
140144 try {
141- await writeFile ( join ( contextPath , "Dockerfile" ) , claudeDockerfile , "utf8" )
145+ await writeFile ( join ( contextPath , "Dockerfile" ) , renderClaudeDockerOauthDockerfile ( ) , "utf8" )
142146 const exitCode = await runBuild ( {
143147 dockerCommand,
144148 args : [ "build" , "-t" , image , contextPath ] ,
@@ -219,17 +223,36 @@ const runDockerSetupToken = (spec: ClaudeDockerSetupTokenSpec): Promise<ClaudeDo
219223 const decoder = new TextDecoder ( "utf-8" )
220224 let outputWindow = ""
221225 let token : string | null = null
226+ let stdoutRedactionState : ClaudeOauthTokenRedactionState = initialClaudeOauthTokenRedactionState
227+ let stderrRedactionState : ClaudeOauthTokenRedactionState = initialClaudeOauthTokenRedactionState
228+
229+ const writeOutput = ( fd : 1 | 2 , output : string ) : void => {
230+ if ( output . length === 0 ) {
231+ return
232+ }
233+ if ( fd === 2 ) {
234+ process . stderr . write ( output )
235+ return
236+ }
237+ process . stdout . write ( output )
238+ }
222239
223240 const capture = ( chunk : Uint8Array , fd : 1 | 2 ) : void => {
224241 const text = decoder . decode ( chunk )
225242 outputWindow = appendOutputWindow ( outputWindow , text )
226243 token = token ?? extractClaudeOauthToken ( outputWindow )
227- const output = spec . redactLiveOutput ? redactedOauthTokenText ( text ) : text
228- if ( fd === 2 ) {
229- process . stderr . write ( output )
244+ if ( ! spec . redactLiveOutput ) {
245+ writeOutput ( fd , text )
230246 return
231247 }
232- process . stdout . write ( output )
248+ const state = fd === 2 ? stderrRedactionState : stdoutRedactionState
249+ const redacted = redactClaudeOauthTokenChunk ( state , text )
250+ if ( fd === 2 ) {
251+ stderrRedactionState = redacted . state
252+ } else {
253+ stdoutRedactionState = redacted . state
254+ }
255+ writeOutput ( fd , redacted . output )
233256 }
234257
235258 child . stdout ?. on ( "data" , ( chunk : Uint8Array ) => {
@@ -240,6 +263,10 @@ const runDockerSetupToken = (spec: ClaudeDockerSetupTokenSpec): Promise<ClaudeDo
240263 } )
241264 child . on ( "error" , reject )
242265 child . on ( "close" , ( code ) => {
266+ if ( spec . redactLiveOutput ) {
267+ writeOutput ( 1 , flushClaudeOauthTokenRedactionState ( stdoutRedactionState ) )
268+ writeOutput ( 2 , flushClaudeOauthTokenRedactionState ( stderrRedactionState ) )
269+ }
243270 resolveResult ( { exitCode : code ?? 1 , token } )
244271 } )
245272 } )
@@ -259,7 +286,7 @@ const runDockerProbe = (spec: ClaudeDockerProbeSpec): Promise<number> =>
259286const writeCapturedToken = async ( accountPath : string , token : string ) : Promise < void > => {
260287 const tokenPath = claudeOauthTokenPath ( accountPath )
261288 await writeFile ( tokenPath , formatClaudeOauthTokenFile ( token ) , "utf8" )
262- await chmod ( tokenPath , 0o600 ) . catch ( ( ) => undefined )
289+ await chmod ( tokenPath , claudeOauthTokenFileMode )
263290}
264291
265292const dockerProbeStatusFromExitCode = ( exitCode : number ) : ClaudeDockerProbeStatus =>
@@ -361,7 +388,7 @@ const isDirectExecution = (): boolean => {
361388}
362389
363390if ( isDirectExecution ( ) ) {
364- const printToken = ! process . argv . includes ( "--no -print-token" )
391+ const printToken = process . argv . includes ( "--print-token" )
365392 const accountPath = readFlagValue ( process . argv , "--account-path" )
366393 const dockerHostPath = readFlagValue ( process . argv , "--docker-host-path" )
367394 const image = readFlagValue ( process . argv , "--image" )
0 commit comments