From 4dec063ea5a6853e096d34f12b652a28df42a8ff Mon Sep 17 00:00:00 2001 From: Jamie Nelson Date: Sun, 11 Oct 2026 07:57:36 +1100 Subject: [PATCH 1/3] fix: guard malformed runs metadata and preserve load error causes --- src/Action.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Action.ts b/src/Action.ts index e8fee51d..d55dad20 100644 --- a/src/Action.ts +++ b/src/Action.ts @@ -123,14 +123,14 @@ export type ActionYaml = { * Parses and represents metadata from action.yml. */ export default class Action implements ActionYaml { - static validate(obj: unknown): obj is ActionType { + static validate(obj: unknown): obj is ActionYaml { if (typeof obj !== 'object' || obj === null) { return false; } const record = obj as Record; if ('name' in record && 'description' in record && 'runs' in record) { - const runs = record.runs as Record | undefined; - if (runs && 'using' in runs) { + const runs = record.runs; + if (typeof runs === 'object' && runs !== null && !Array.isArray(runs) && 'using' in runs) { return ( typeof record.name === 'string' && typeof record.description === 'string' && @@ -183,7 +183,7 @@ export default class Action implements ActionYaml { try { actionYaml = this.loadActionFrom(actionPath); } catch (error) { - throw new Error(`Failed to load ${actionPath}. ${String(error)}`); + throw new Error(`Failed to load ${actionPath}. ${String(error)}`, { cause: error }); } this.log.debug(`Action YAML: ${JSON.stringify(actionYaml)}`); @@ -215,7 +215,7 @@ export default class Action implements ActionYaml { this.rawYamlString = fs.readFileSync(actionPath, 'utf8'); this.log.debug(`Parse ${actionPath} from ${actionDir}`); - const actionObj = YAML.parse(this.rawYamlString) as ActionYaml; + const actionObj: unknown = YAML.parse(this.rawYamlString); if (Action.validate(actionObj)) { return actionObj; } From 2dd9b340e4f8f2b57482ffdc609f55162ab5317f Mon Sep 17 00:00:00 2001 From: Jamie Nelson Date: Sun, 11 Oct 2026 08:04:52 +1100 Subject: [PATCH 2/3] fix: validate nested metadata consumed by README generation --- src/Action.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Action.ts b/src/Action.ts index d55dad20..aa125cb6 100644 --- a/src/Action.ts +++ b/src/Action.ts @@ -128,6 +128,26 @@ export default class Action implements ActionYaml { return false; } const record = obj as Record; + const isRecord = (value: unknown): value is Record => + typeof value === 'object' && value !== null && !Array.isArray(value); + const validEntries = (value: unknown, fields: readonly string[]): boolean => + value === undefined || + (isRecord(value) && + Object.values(value).every( + (entry) => + isRecord(entry) && + fields.every((field) => entry[field] === undefined || typeof entry[field] === 'string'), + )); + if ( + !validEntries(record.inputs, ['description', 'default', 'deprecationMessage']) || + !validEntries(record.outputs, ['description', 'value']) || + (record.branding !== undefined && + (!isRecord(record.branding) || + (record.branding.icon !== undefined && typeof record.branding.icon !== 'string') || + (record.branding.color !== undefined && typeof record.branding.color !== 'string'))) + ) { + return false; + } if ('name' in record && 'description' in record && 'runs' in record) { const runs = record.runs; if (typeof runs === 'object' && runs !== null && !Array.isArray(runs) && 'using' in runs) { From 2f55352144e1d3273bbffabdc985ddeddd133bd4 Mon Sep 17 00:00:00 2001 From: Jamie Nelson Date: Sun, 11 Oct 2026 09:43:23 +1100 Subject: [PATCH 3/3] fix: validate consumed descriptions without restricting scalar defaults --- src/Action.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/Action.ts b/src/Action.ts index aa125cb6..d84930bf 100644 --- a/src/Action.ts +++ b/src/Action.ts @@ -130,22 +130,14 @@ export default class Action implements ActionYaml { const record = obj as Record; const isRecord = (value: unknown): value is Record => typeof value === 'object' && value !== null && !Array.isArray(value); - const validEntries = (value: unknown, fields: readonly string[]): boolean => + const validDescriptions = (value: unknown): boolean => value === undefined || (isRecord(value) && Object.values(value).every( - (entry) => - isRecord(entry) && - fields.every((field) => entry[field] === undefined || typeof entry[field] === 'string'), + (entry) => isRecord(entry) && + (entry.description === undefined || typeof entry.description === 'string'), )); - if ( - !validEntries(record.inputs, ['description', 'default', 'deprecationMessage']) || - !validEntries(record.outputs, ['description', 'value']) || - (record.branding !== undefined && - (!isRecord(record.branding) || - (record.branding.icon !== undefined && typeof record.branding.icon !== 'string') || - (record.branding.color !== undefined && typeof record.branding.color !== 'string'))) - ) { + if (!validDescriptions(record.inputs) || !validDescriptions(record.outputs)) { return false; } if ('name' in record && 'description' in record && 'runs' in record) {