Skip to content

Commit 8566273

Browse files
author
Craig Furman
committedJul 29, 2021
fix: IaC path parsing
Fix a bug in which IaC resource paths (more context below) was naively split by dot, without distinguishing between separator dots and dots inside string literals. For example: "foo.bar.baz" was split into 3 words correctly, but "metadata.annotations['container.apparmor.security.beta.kubernetes.io/web']" was not. The "path" / "cloud config path" is a sort of address used to identify locations in files where vulnerabilities reside. It is very similar to a jsonpath. The user receives it as an array of components, which in the future can be useful for building tooling around, e.g. the upcoming CLI ignores. The code that evaluates policies against source files is also responsible for returning a representation of this path, which it does as a dot-separated string.
1 parent d8d41a4 commit 8566273

File tree

4 files changed

+57
-1
lines changed

4 files changed

+57
-1
lines changed
 

‎package.json

+1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
114114
"open": "^7.0.3",
115115
"ora": "5.4.0",
116116
"os-name": "^3.0.0",
117+
"pegjs": "^0.10.0",
117118
"promise-queue": "^2.2.5",
118119
"proxy-from-env": "^1.0.0",
119120
"rimraf": "^2.6.3",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import * as peg from 'pegjs';
2+
3+
const grammar = `
4+
start
5+
= element+
6+
7+
element
8+
= component:component "."? { return component; }
9+
10+
component
11+
= $(identifier index?)
12+
13+
identifier
14+
= $([^'"\\[\\]\\.]+)
15+
16+
index
17+
= $("[" ['"]? [^'"\\]]+ ['"]? "]")
18+
`;
19+
20+
export const parsePath = createPathParser();
21+
22+
function createPathParser(): (expr: string) => string[] {
23+
const parser = peg.generate(grammar);
24+
return (expr: string) => {
25+
try {
26+
return parser.parse(expr);
27+
} catch (e) {
28+
// I haven't actually been able to write a testcase that triggers this
29+
// code path, but I've included it anyway as a fallback to allow users to
30+
// keep using the CLI even if this does occur. Their paths might look
31+
// strange, but that's better than nothing.
32+
return expr.split('.');
33+
}
34+
};
35+
}

‎src/cli/commands/test/iac-local-execution/results-formatter.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
PolicyMetadata,
88
TestMeta,
99
} from './types';
10+
import { parsePath } from './parsers/path';
1011
import * as path from 'path';
1112
import { SEVERITY } from '../../../../lib/snyk-test/common';
1213
import { IacProjectType } from '../../../../lib/iac/constants';
@@ -55,7 +56,7 @@ function formatScanResult(
5556
const formattedIssues = scanResult.violatedPolicies.map((policy) => {
5657
const cloudConfigPath =
5758
scanResult.docId !== undefined
58-
? [`[DocId: ${scanResult.docId}]`].concat(policy.msg.split('.'))
59+
? [`[DocId: ${scanResult.docId}]`].concat(parsePath(policy.msg))
5960
: policy.msg.split('.');
6061

6162
const flagsRequiringLineNumber = [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { parsePath } from '../../../../src/cli/commands/test/iac-local-execution/parsers/path';
2+
3+
describe('parsing cloudConfigPath', () => {
4+
it.each([
5+
['foo', ['foo']],
6+
['foo.bar.baz', ['foo', 'bar', 'baz']],
7+
['foo_1._bar2.baz3_', ['foo_1', '_bar2', 'baz3_']],
8+
['foo.bar[abc].baz', ['foo', 'bar[abc]', 'baz']],
9+
['foo.bar[abc.def].baz', ['foo', 'bar[abc.def]', 'baz']],
10+
["foo.bar['abc.def'].baz", ['foo', "bar['abc.def']", 'baz']],
11+
['foo.bar["abc.def"].baz', ['foo', 'bar["abc.def"]', 'baz']],
12+
["foo.bar['abc/def'].baz", ['foo', "bar['abc/def']", 'baz']],
13+
["foo.bar['abcdef'].baz", ['foo', "bar['abcdef']", 'baz']],
14+
["bar['abc.def']", ["bar['abc.def']"]],
15+
["fo%o.bar['ab$c/def'].baz", ['fo%o', "bar['ab$c/def']", 'baz']],
16+
])('%s', (input, expected) => {
17+
expect(parsePath(input)).toEqual(expected);
18+
});
19+
});

0 commit comments

Comments
 (0)
Please sign in to comment.