This NQE extracts the config for each ACL, accounting for all syntaxes used in Cisco devices. The order is determined by the line number, which is used to compare the line of the deny statement with the last line of the ACL config and returns the failures by device and ACL name.
standard(config) =
foreach match in patternMatches(config, `access-list {aclName:string} {!"remark"}`)
group match.line as config by match.data.aclName as name
select { name, config };
nested(config) =
foreach match
in patternMatches(config, `ip access-list {string} {name:string}`)
let config = (foreach line in match.line.children where !hasMatch(line.text, re`.*remark.*`) select line)
select { name: match.data.name, config };
foreach device in network.devices
foreach acl in standard(device.files.config) + nested(device.files.config)
let maxLine = max(foreach line in acl.config
select line.lineNumber)
foreach line in acl.config
where hasMatch(line.text, re`.*deny\s*ip any any.*`) &&
line.lineNumber < maxLine
select { device: device.name, acl: acl.name }
This expands on the NQE in Evaluating ACL rules from configuration to find last entry, where I referenced the method for grouping standard ACL config lines.