@@ -40,6 +40,67 @@ function findFiles(dir, extensions) {
4040 return results ;
4141}
4242
43+ /**
44+ * Built-in regex patterns for common credential types
45+ * Each pattern is designed to match legitimate credential formats
46+ */
47+ const BUILT_IN_PATTERNS = [
48+ // GitHub tokens
49+ { name : "GitHub Personal Access Token (classic)" , pattern : / g h p _ [ 0 - 9 a - z A - Z ] { 36 } / g } ,
50+ { name : "GitHub Server-to-Server Token" , pattern : / g h s _ [ 0 - 9 a - z A - Z ] { 36 } / g } ,
51+ { name : "GitHub OAuth Access Token" , pattern : / g h o _ [ 0 - 9 a - z A - Z ] { 36 } / g } ,
52+ { name : "GitHub User Access Token" , pattern : / g h u _ [ 0 - 9 a - z A - Z ] { 36 } / g } ,
53+ { name : "GitHub Fine-grained PAT" , pattern : / g i t h u b _ p a t _ [ 0 - 9 a - z A - Z _ ] { 82 } / g } ,
54+ { name : "GitHub Refresh Token" , pattern : / g h r _ [ 0 - 9 a - z A - Z ] { 36 } / g } ,
55+
56+ // Azure tokens
57+ { name : "Azure Storage Account Key" , pattern : / [ a - z A - Z 0 - 9 + / ] { 88 } = = / g } ,
58+ { name : "Azure SAS Token" , pattern : / \? s v = [ 0 - 9 - ] + & s [ r t s ] = [ \w \- ] + & s i g = [ A - Z a - z 0 - 9 % + / = ] + / g } ,
59+
60+ // Google/GCP tokens
61+ { name : "Google API Key" , pattern : / A I z a S y [ 0 - 9 A - Z a - z _ - ] { 33 } / g } ,
62+ { name : "Google OAuth Access Token" , pattern : / y a 2 9 \. [ 0 - 9 A - Z a - z _ - ] + / g } ,
63+
64+ // AWS tokens
65+ { name : "AWS Access Key ID" , pattern : / A K I A [ 0 - 9 A - Z ] { 16 } / g } ,
66+
67+ // OpenAI tokens
68+ { name : "OpenAI API Key" , pattern : / s k - [ a - z A - Z 0 - 9 ] { 48 } / g } ,
69+ { name : "OpenAI Project API Key" , pattern : / s k - p r o j - [ a - z A - Z 0 - 9 ] { 48 , 64 } / g } ,
70+
71+ // Anthropic tokens
72+ { name : "Anthropic API Key" , pattern : / s k - a n t - a p i 0 3 - [ a - z A - Z 0 - 9 _ - ] { 95 } / g } ,
73+ ] ;
74+
75+ /**
76+ * Detects and redacts secrets matching built-in patterns
77+ * @param {string } content - File content to process
78+ * @returns {{content: string, redactionCount: number, detectedPatterns: string[]} } Redacted content, count, and detected pattern types
79+ */
80+ function redactBuiltInPatterns ( content ) {
81+ let redactionCount = 0 ;
82+ let redacted = content ;
83+ const detectedPatterns = [ ] ;
84+
85+ for ( const { name, pattern } of BUILT_IN_PATTERNS ) {
86+ const matches = redacted . match ( pattern ) ;
87+ if ( matches && matches . length > 0 ) {
88+ // Redact each match
89+ for ( const match of matches ) {
90+ const prefix = match . substring ( 0 , 3 ) ;
91+ const asterisks = "*" . repeat ( Math . max ( 0 , match . length - 3 ) ) ;
92+ const replacement = prefix + asterisks ;
93+ redacted = redacted . split ( match ) . join ( replacement ) ;
94+ }
95+ redactionCount += matches . length ;
96+ detectedPatterns . push ( name ) ;
97+ core . info ( `Redacted ${ matches . length } occurrence(s) of ${ name } ` ) ;
98+ }
99+ }
100+
101+ return { content : redacted , redactionCount, detectedPatterns } ;
102+ }
103+
43104/**
44105 * Redacts secrets from file content using exact string matching
45106 * @param {string } content - File content to process
@@ -83,12 +144,22 @@ function redactSecrets(content, secretValues) {
83144function processFile ( filePath , secretValues ) {
84145 try {
85146 const content = fs . readFileSync ( filePath , "utf8" ) ;
86- const { content : redactedContent , redactionCount } = redactSecrets ( content , secretValues ) ;
87- if ( redactionCount > 0 ) {
88- fs . writeFileSync ( filePath , redactedContent , "utf8" ) ;
89- core . info ( `Processed ${ filePath } : ${ redactionCount } redaction(s)` ) ;
147+
148+ // First, redact built-in patterns
149+ const builtInResult = redactBuiltInPatterns ( content ) ;
150+ let redacted = builtInResult . content ;
151+ let totalRedactions = builtInResult . redactionCount ;
152+
153+ // Then, redact custom secrets
154+ const customResult = redactSecrets ( redacted , secretValues ) ;
155+ redacted = customResult . content ;
156+ totalRedactions += customResult . redactionCount ;
157+
158+ if ( totalRedactions > 0 ) {
159+ fs . writeFileSync ( filePath , redacted , "utf8" ) ;
160+ core . info ( `Processed ${ filePath } : ${ totalRedactions } redaction(s)` ) ;
90161 }
91- return redactionCount ;
162+ return totalRedactions ;
92163 } catch ( error ) {
93164 core . warning ( `Failed to process file ${ filePath } : ${ getErrorMessage ( error ) } ` ) ;
94165 return 0 ;
@@ -101,30 +172,32 @@ function processFile(filePath, secretValues) {
101172async function main ( ) {
102173 // Get the list of secret names from environment variable
103174 const secretNames = process . env . GH_AW_SECRET_NAMES ;
104- if ( ! secretNames ) {
105- core . info ( "GH_AW_SECRET_NAMES not set, no redaction performed" ) ;
106- return ;
107- }
175+
108176 core . info ( "Starting secret redaction in /tmp/gh-aw directory" ) ;
109177 try {
110- // Parse the comma-separated list of secret names
111- const secretNameList = secretNames . split ( "," ) . filter ( name => name . trim ( ) ) ;
112- // Collect the actual secret values from environment variables
178+ // Collect custom secret values from environment variables
113179 const secretValues = [ ] ;
114- for ( const secretName of secretNameList ) {
115- const envVarName = `SECRET_${ secretName } ` ;
116- const secretValue = process . env [ envVarName ] ;
117- // Skip empty or undefined secrets
118- if ( ! secretValue || secretValue . trim ( ) === "" ) {
119- continue ;
180+ if ( secretNames ) {
181+ // Parse the comma-separated list of secret names
182+ const secretNameList = secretNames . split ( "," ) . filter ( name => name . trim ( ) ) ;
183+ for ( const secretName of secretNameList ) {
184+ const envVarName = `SECRET_${ secretName } ` ;
185+ const secretValue = process . env [ envVarName ] ;
186+ // Skip empty or undefined secrets
187+ if ( ! secretValue || secretValue . trim ( ) === "" ) {
188+ continue ;
189+ }
190+ secretValues . push ( secretValue . trim ( ) ) ;
120191 }
121- secretValues . push ( secretValue . trim ( ) ) ;
122192 }
123- if ( secretValues . length === 0 ) {
124- core . info ( "No secret values found to redact" ) ;
125- return ;
193+
194+ if ( secretValues . length > 0 ) {
195+ core . info ( `Found ${ secretValues . length } custom secret(s) to redact` ) ;
126196 }
127- core . info ( `Found ${ secretValues . length } secret(s) to redact` ) ;
197+
198+ // Always scan for built-in patterns, even if there are no custom secrets
199+ core . info ( "Scanning for built-in credential patterns and custom secrets" ) ;
200+
128201 // Find all target files in /tmp/gh-aw directory
129202 const targetExtensions = [ ".txt" , ".json" , ".log" , ".md" , ".mdx" , ".yml" , ".jsonl" ] ;
130203 const files = findFiles ( "/tmp/gh-aw" , targetExtensions ) ;
@@ -151,4 +224,4 @@ async function main() {
151224
152225const { getErrorMessage } = require ( "./error_helpers.cjs" ) ;
153226
154- module . exports = { main } ;
227+ module . exports = { main, redactSecrets , redactBuiltInPatterns , BUILT_IN_PATTERNS } ;
0 commit comments