@@ -111,6 +111,12 @@ export interface TemplateAuditCliOptions {
111111
112112export type TemplateLiveAuditInputs = Record < string , Record < string , unknown > > ;
113113
114+ export interface TemplateAuditSecretResolution {
115+ secretMappings : Record < string , string > ;
116+ providedSecretNames : string [ ] ;
117+ missingSecretNames : string [ ] ;
118+ }
119+
114120export type TemplateValidationFreshnessStatus =
115121 | 'current'
116122 | 'missing'
@@ -174,12 +180,14 @@ export function createTemplateLiveAuditInputs(): TemplateLiveAuditInputs {
174180 } ,
175181 'Container Image CVE Triage' : {
176182 imageRef : 'alpine:3.18' ,
177- deploymentContext : 'Live audit fixture: small public Linux base image for bounded CVE triage.' ,
183+ deploymentContext :
184+ 'Live audit fixture: small public Linux base image for bounded CVE triage.' ,
178185 authorizationNotes : 'Live audit fixture using a public container image.' ,
179186 } ,
180187 'Exposed Service CVE Mapper' : {
181188 targets : [ 'scanme.nmap.org' ] ,
182- authorizationNotes : 'Live audit fixture: Nmap-provided scan target for bounded service checks.' ,
189+ authorizationNotes :
190+ 'Live audit fixture: Nmap-provided scan target for bounded service checks.' ,
183191 } ,
184192 'GitHub Repo Dependency CVE Triage' : {
185193 repositoryUrl : 'https://github.com/OWASP/NodeGoat' ,
@@ -189,7 +197,8 @@ export function createTemplateLiveAuditInputs(): TemplateLiveAuditInputs {
189197 } ,
190198 'NPM Dependency CVE Hunt' : {
191199 packageSpecs : [ 'lodash@4.17.20' , 'minimist@0.0.8' , 'axios@0.21.1' ] ,
192- researchNotes : 'Live audit fixture using public npm packages with known historical advisories.' ,
200+ researchNotes :
201+ 'Live audit fixture using public npm packages with known historical advisories.' ,
193202 } ,
194203 'Passive OSINT Subdomain Expansion' : {
195204 domain : 'example.com' ,
@@ -235,6 +244,61 @@ export function createTemplateLiveAuditInputs(): TemplateLiveAuditInputs {
235244 outOfScopePaths : [ '/logout' , '/admin/delete' ] ,
236245 scanIntensity : 'safe' ,
237246 } ,
247+ 'Security Scan Discord Report' : {
248+ imageRef : 'alpine:3.18' ,
249+ } ,
250+ } ;
251+ }
252+
253+ function normalizeAuditSecretEnvName ( secretName : string ) : string {
254+ return `TEMPLATE_AUDIT_SECRET_${ secretName . replace ( / [ ^ a - z A - Z 0 - 9 ] + / g, '_' ) . toUpperCase ( ) } ` ;
255+ }
256+
257+ function parseSecretMappingsJson ( value : string | undefined ) : Record < string , string > {
258+ if ( ! value ?. trim ( ) ) return { } ;
259+
260+ try {
261+ const parsed = JSON . parse ( value ) as unknown ;
262+ if ( ! parsed || typeof parsed !== 'object' || Array . isArray ( parsed ) ) {
263+ throw new Error ( 'not-object' ) ;
264+ }
265+
266+ const mappings : Record < string , string > = { } ;
267+ for ( const [ key , rawValue ] of Object . entries ( parsed as Record < string , unknown > ) ) {
268+ if ( typeof rawValue === 'string' && rawValue . trim ( ) . length > 0 ) {
269+ mappings [ key ] = rawValue ;
270+ }
271+ }
272+ return mappings ;
273+ } catch {
274+ throw new Error ( 'TEMPLATE_AUDIT_SECRET_MAPPINGS must be a JSON object' ) ;
275+ }
276+ }
277+
278+ export function resolveTemplateAuditSecretMappings (
279+ requiredSecretNames : string [ ] ,
280+ env : Record < string , string | undefined > = process . env ,
281+ ) : TemplateAuditSecretResolution {
282+ const jsonMappings = parseSecretMappingsJson ( env . TEMPLATE_AUDIT_SECRET_MAPPINGS ) ;
283+ const uniqueRequiredNames = Array . from (
284+ new Set ( requiredSecretNames . map ( ( name ) => name . trim ( ) ) . filter ( Boolean ) ) ,
285+ ) ;
286+ const secretMappings : Record < string , string > = { } ;
287+
288+ for ( const secretName of uniqueRequiredNames ) {
289+ const jsonValue = jsonMappings [ secretName ] ;
290+ const envValue = env [ normalizeAuditSecretEnvName ( secretName ) ] ;
291+ const value =
292+ typeof jsonValue === 'string' && jsonValue . trim ( ) . length > 0 ? jsonValue : envValue ;
293+ if ( typeof value === 'string' && value . trim ( ) . length > 0 ) {
294+ secretMappings [ secretName ] = value ;
295+ }
296+ }
297+
298+ return {
299+ secretMappings,
300+ providedSecretNames : uniqueRequiredNames . filter ( ( name ) => Boolean ( secretMappings [ name ] ) ) ,
301+ missingSecretNames : uniqueRequiredNames . filter ( ( name ) => ! secretMappings [ name ] ) ,
238302 } ;
239303}
240304
0 commit comments