Skip to content

Commit 8f242dd

Browse files
committed
fix(loop): reject multiline flow mappings
1 parent aa2090a commit 8f242dd

2 files changed

Lines changed: 48 additions & 1 deletion

File tree

loops/issue-dev-loop/scripts/lib/validation.mjs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,49 @@ function yamlMapping(line) {
5959
return {
6060
indent: match[1].length,
6161
key: match[3] ?? match[4],
62+
lineIndent: match[1].length,
6263
quoted: Boolean(match[2]),
6364
value: match[5].replace(/\s+#.*$/, '').trim(),
6465
}
6566
}
6667

68+
function yamlSequenceMapping(line) {
69+
const match = line.match(
70+
/^(\s*)-\s+(?:(["'])([^"']+)\2|([A-Za-z_][A-Za-z0-9_-]*))\s*:(.*)$/,
71+
)
72+
if (!match) return null
73+
return {
74+
indent: match[1].length + 2,
75+
key: match[3] ?? match[4],
76+
lineIndent: match[1].length,
77+
quoted: Boolean(match[2]),
78+
value: match[5].replace(/\s+#.*$/, '').trim(),
79+
}
80+
}
81+
82+
function conservativeYamlBlock(lines) {
83+
let scalarParentIndent = null
84+
for (const line of lines) {
85+
const lineIndent = line.match(/^\s*/)?.[0].length ?? 0
86+
if (scalarParentIndent !== null && lineIndent > scalarParentIndent) continue
87+
scalarParentIndent = null
88+
89+
const mapping = yamlMapping(line) ?? yamlSequenceMapping(line)
90+
if (
91+
!mapping ||
92+
mapping.quoted ||
93+
/^[{[]/.test(mapping.value) ||
94+
/^[!&*]/.test(mapping.value)
95+
) {
96+
return false
97+
}
98+
if (/^[>|][+-]?(?:[1-9])?$/.test(mapping.value)) {
99+
scalarParentIndent = mapping.lineIndent
100+
}
101+
}
102+
return true
103+
}
104+
67105
function historicalWorkflowIsLowPrivilege(source) {
68106
const lines = activeYamlLines(source)
69107
if (
@@ -142,7 +180,9 @@ function historicalWorkflowIsLowPrivilege(source) {
142180

143181
const jobsEntries = topLevelMappings.filter(({ mapping }) => mapping.key === 'jobs')
144182
if (jobsEntries.length !== 1 || jobsEntries[0].mapping.value) return false
145-
const jobDeclarations = blockLines(jobsEntries[0]).filter(
183+
const jobsBlock = blockLines(jobsEntries[0])
184+
if (!conservativeYamlBlock(jobsBlock)) return false
185+
const jobDeclarations = jobsBlock.filter(
146186
(line) => (line.match(/^\s*/)?.[0].length ?? 0) <= 2,
147187
)
148188
return (

loops/issue-dev-loop/tests/runtime.test.mjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5092,6 +5092,13 @@ test('historical active-run targets can validate without newer trusted runtime f
50925092
'jobs:\n',
50935093
'jobs:\n unsafe: {permissions: write-all, runs-on: ubuntu-latest, steps: []}\n',
50945094
),
5095+
historicalWorkflow.replace(
5096+
'jobs:\n',
5097+
`jobs:
5098+
unsafe:
5099+
{permissions: write-all, runs-on: ubuntu-latest, steps: []}
5100+
`,
5101+
),
50955102
]
50965103
for (const unsafeWorkflow of unsafeWorkflows) {
50975104
await writeFile(workflowPath, unsafeWorkflow, 'utf8')

0 commit comments

Comments
 (0)