@microsoft/sarif 5.4.3 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/regions.js +71 -2
- package/dist/vcp.d.ts +36 -3
- package/dist/vcp.js +47 -10
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export * from './sarif.js';
|
|
|
10
10
|
export { FileRegionsCache, NewLineIndex } from './regions.js';
|
|
11
11
|
export { computeRollingHashes } from './rollingHash.js';
|
|
12
12
|
export { AIRuleIdConvention, AIRuleIdConventionError } from './aiRuleId.js';
|
|
13
|
-
export { tryValidateRepositoryUri, tryDerivePortableRoot, isGitHubHostedRun, type PortableRoot, } from './vcp.js';
|
|
13
|
+
export { tryValidateRepositoryUri, tryDerivePortableRoot, isGitHubHostedRun, parseAuthorizedHosts, type PortableRoot, type VcpOptions, type AuthorizedHost, type AuthorizedHostType, } from './vcp.js';
|
|
14
14
|
export { insertOptionalData, tryReconstructAbsoluteUri, PRIMARY_LOCATION_LINE_HASH, type InsertOptionalDataFlags, } from './insertOptionalData.js';
|
|
15
15
|
export { atomicWrite, stripNulls, serializeSarifLog } from './io.js';
|
|
16
16
|
export { rewriteRelativeLinks, extractFrontmatterDescription } from './skill.js';
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ export * from './sarif.js';
|
|
|
12
12
|
export { FileRegionsCache, NewLineIndex } from './regions.js';
|
|
13
13
|
export { computeRollingHashes } from './rollingHash.js';
|
|
14
14
|
export { AIRuleIdConvention, AIRuleIdConventionError } from './aiRuleId.js';
|
|
15
|
-
export { tryValidateRepositoryUri, tryDerivePortableRoot, isGitHubHostedRun, } from './vcp.js';
|
|
15
|
+
export { tryValidateRepositoryUri, tryDerivePortableRoot, isGitHubHostedRun, parseAuthorizedHosts, } from './vcp.js';
|
|
16
16
|
export { insertOptionalData, tryReconstructAbsoluteUri, PRIMARY_LOCATION_LINE_HASH, } from './insertOptionalData.js';
|
|
17
17
|
export { atomicWrite, stripNulls, serializeSarifLog } from './io.js';
|
|
18
18
|
export { rewriteRelativeLinks, extractFrontmatterDescription } from './skill.js';
|
package/dist/regions.js
CHANGED
|
@@ -201,7 +201,11 @@ export class FileRegionsCache {
|
|
|
201
201
|
const fileText = idx.text;
|
|
202
202
|
const original = this.populateTextRegionProperties(inputRegion, absoluteUri, true);
|
|
203
203
|
if ((original.charLength ?? 0) >= BIG_SNIPPET_LENGTH) {
|
|
204
|
-
|
|
204
|
+
// A context region must be a proper superset of the region (SARIF §3.29.5,
|
|
205
|
+
// enforced by SARIF1008). The region already meets the snippet cap, so no
|
|
206
|
+
// larger in-cap snippet exists; omit the context region rather than return
|
|
207
|
+
// one identical to the region.
|
|
208
|
+
return undefined;
|
|
205
209
|
}
|
|
206
210
|
const maxLine = idx.maximumLineNumber;
|
|
207
211
|
const startLine = (inputRegion.startLine ?? 1) === 1 ? 1 : (inputRegion.startLine ?? 1) - 1;
|
|
@@ -215,7 +219,11 @@ export class FileRegionsCache {
|
|
|
215
219
|
const charOffset = Math.max(0, (original.charOffset ?? 0) - SMALL_SNIPPET_LENGTH);
|
|
216
220
|
const charLength = Math.min(BIG_SNIPPET_LENGTH, fileText.length - charOffset);
|
|
217
221
|
context = this.populateTextRegionProperties({ charOffset, charLength }, absoluteUri, true);
|
|
218
|
-
|
|
222
|
+
// The capped char-offset window does not always contain the region (a region
|
|
223
|
+
// longer than the window's reach past its start runs off the end). Emit the
|
|
224
|
+
// context region only when it is a genuine proper superset of the region;
|
|
225
|
+
// otherwise omit it (SARIF §3.29.5 / SARIF1008).
|
|
226
|
+
return isProperSupersetOf(context, original) ? context : undefined;
|
|
219
227
|
}
|
|
220
228
|
}
|
|
221
229
|
// ---------------------------------------------------------------------------
|
|
@@ -224,6 +232,67 @@ export class FileRegionsCache {
|
|
|
224
232
|
function isBinaryRegion(r) {
|
|
225
233
|
return r.byteOffset !== undefined && r.byteOffset >= 0;
|
|
226
234
|
}
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
// Proper-superset test (mirrors Region.IsProperSupersetOf)
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
/**
|
|
239
|
+
* True when `sup` strictly contains `sub`: every position covered by `sub` lies
|
|
240
|
+
* within `sup` and the two spans are not identical. Each populated coordinate
|
|
241
|
+
* dimension (line/column, char offset) must independently hold. Mirrors
|
|
242
|
+
* Region.IsProperSupersetOf.
|
|
243
|
+
*/
|
|
244
|
+
function isProperSupersetOf(sup, sub) {
|
|
245
|
+
if (isLineColumnBasedRegion(sup) &&
|
|
246
|
+
isLineColumnBasedRegion(sub) &&
|
|
247
|
+
!isLineColumnProperSupersetOf(sup, sub)) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
if (isOffsetBasedRegion(sup) &&
|
|
251
|
+
isOffsetBasedRegion(sub) &&
|
|
252
|
+
!isOffsetProperSupersetOf(sup, sub)) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
function isLineColumnBasedRegion(r) {
|
|
258
|
+
return (r.startLine ?? 0) >= 1;
|
|
259
|
+
}
|
|
260
|
+
function isOffsetBasedRegion(r) {
|
|
261
|
+
return (r.charOffset ?? -1) >= 0;
|
|
262
|
+
}
|
|
263
|
+
function isLineColumnProperSupersetOf(sup, sub) {
|
|
264
|
+
const supStartLine = sup.startLine ?? 0;
|
|
265
|
+
const supEndLine = sup.endLine ?? supStartLine;
|
|
266
|
+
const supStartColumn = sup.startColumn ?? 1;
|
|
267
|
+
const supEndColumn = sup.endColumn ?? Number.MAX_SAFE_INTEGER;
|
|
268
|
+
const subStartLine = sub.startLine ?? 0;
|
|
269
|
+
const subEndLine = sub.endLine ?? subStartLine;
|
|
270
|
+
const subStartColumn = sub.startColumn ?? 1;
|
|
271
|
+
const subEndColumn = sub.endColumn ?? Number.MAX_SAFE_INTEGER;
|
|
272
|
+
if (supStartLine > subStartLine || supEndLine < subEndLine)
|
|
273
|
+
return false;
|
|
274
|
+
if (supStartLine === subStartLine && supStartColumn > subStartColumn)
|
|
275
|
+
return false;
|
|
276
|
+
if (supEndLine === subEndLine && supEndColumn < subEndColumn)
|
|
277
|
+
return false;
|
|
278
|
+
// Identical span is not a proper superset.
|
|
279
|
+
return !(supStartLine === subStartLine &&
|
|
280
|
+
supEndLine === subEndLine &&
|
|
281
|
+
supStartColumn === subStartColumn &&
|
|
282
|
+
supEndColumn === subEndColumn);
|
|
283
|
+
}
|
|
284
|
+
function isOffsetProperSupersetOf(sup, sub) {
|
|
285
|
+
const supOffset = sup.charOffset ?? 0;
|
|
286
|
+
const supLength = sup.charLength ?? 0;
|
|
287
|
+
const subOffset = sub.charOffset ?? 0;
|
|
288
|
+
const subLength = sub.charLength ?? 0;
|
|
289
|
+
if (supOffset > subOffset)
|
|
290
|
+
return false;
|
|
291
|
+
if (supOffset + supLength < subOffset + subLength)
|
|
292
|
+
return false;
|
|
293
|
+
// Same start and no greater length is not a proper superset.
|
|
294
|
+
return !(supOffset === subOffset && supLength <= subLength);
|
|
295
|
+
}
|
|
227
296
|
function reconcile(overwrite, propertyName, computed, authored) {
|
|
228
297
|
if (authored === computed)
|
|
229
298
|
return authored;
|
package/dist/vcp.d.ts
CHANGED
|
@@ -12,6 +12,31 @@ export interface PortableRoot {
|
|
|
12
12
|
leaf: string;
|
|
13
13
|
revisionWebUrl: string;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* A VCS product recognized by portable-root derivation. Extensible: a new
|
|
17
|
+
* product (e.g. `gitlab`, `bitbucket`) is a new type with its own permalink
|
|
18
|
+
* shape in `classify`/`tryDerivePortableRoot`. Currently only `ghe` (GitHub
|
|
19
|
+
* Enterprise Server) is recognized.
|
|
20
|
+
*/
|
|
21
|
+
export type AuthorizedHostType = 'ghe';
|
|
22
|
+
export interface AuthorizedHost {
|
|
23
|
+
/** The VCS product running on `host`. */
|
|
24
|
+
type: AuthorizedHostType;
|
|
25
|
+
/** The host, lowercased, e.g. `githost.contoso.com`. */
|
|
26
|
+
host: string;
|
|
27
|
+
}
|
|
28
|
+
export interface VcpOptions {
|
|
29
|
+
/**
|
|
30
|
+
* Caller-attested hosts, each tagged with the VCS product running there (see
|
|
31
|
+
* `parseAuthorizedHosts` for the `<hostType>:<host>` grammar). A self-hosted
|
|
32
|
+
* instance runs on a custom domain that is structurally indistinguishable
|
|
33
|
+
* from any other host, so it cannot be auto-recognized; an attested host is
|
|
34
|
+
* classified per its type (a `ghe` host mints a github.com-style
|
|
35
|
+
* `{host}/{owner}/{repo}/blob/{sha}/` portable root). Matched
|
|
36
|
+
* case-insensitively.
|
|
37
|
+
*/
|
|
38
|
+
authorizedHosts?: readonly AuthorizedHost[];
|
|
39
|
+
}
|
|
15
40
|
type Result<T> = {
|
|
16
41
|
ok: true;
|
|
17
42
|
value: T;
|
|
@@ -19,16 +44,24 @@ type Result<T> = {
|
|
|
19
44
|
ok: false;
|
|
20
45
|
error: string;
|
|
21
46
|
};
|
|
47
|
+
/**
|
|
48
|
+
* Parses a comma-separated `--authorized-hosts` value of `<hostType>:<host>`
|
|
49
|
+
* tokens (e.g. `ghe:githost.contoso.com,ghe:other.example.com`) into attested
|
|
50
|
+
* hosts. A new VCS product is added by extending `AuthorizedHostType` and the
|
|
51
|
+
* derivation switch, not by changing this grammar. Currently only `ghe`
|
|
52
|
+
* (GitHub Enterprise Server) is recognized.
|
|
53
|
+
*/
|
|
54
|
+
export declare function parseAuthorizedHosts(raw: string | undefined): Result<AuthorizedHost[]>;
|
|
22
55
|
/** Validates that a portable root can be derived (no revisionId required). */
|
|
23
|
-
export declare function tryValidateRepositoryUri(rawRepositoryUri: string): Result<{
|
|
56
|
+
export declare function tryValidateRepositoryUri(rawRepositoryUri: string, options?: VcpOptions): Result<{
|
|
24
57
|
leaf: string;
|
|
25
58
|
}>;
|
|
26
59
|
/** Mints the portable root for a repository URI + revision id. */
|
|
27
|
-
export declare function tryDerivePortableRoot(rawRepositoryUri: string, revisionId: string): Result<PortableRoot>;
|
|
60
|
+
export declare function tryDerivePortableRoot(rawRepositoryUri: string, revisionId: string, options?: VcpOptions): Result<PortableRoot>;
|
|
28
61
|
/**
|
|
29
62
|
* Default-deny: true only when the run carries ≥1 VCP entry and every entry's
|
|
30
63
|
* repositoryUri classifies as a supported GitHub host. Mirrors
|
|
31
64
|
* VcpPortableRoot.IsGitHubHostedRun.
|
|
32
65
|
*/
|
|
33
|
-
export declare function isGitHubHostedRun(run: Run): boolean;
|
|
66
|
+
export declare function isGitHubHostedRun(run: Run, options?: VcpOptions): boolean;
|
|
34
67
|
export {};
|
package/dist/vcp.js
CHANGED
|
@@ -3,15 +3,52 @@
|
|
|
3
3
|
const AZURE_DEVOPS_HOST = 'dev.azure.com';
|
|
4
4
|
const AZURE_DEVOPS_SSH_HOST = 'ssh.dev.azure.com';
|
|
5
5
|
const INVALID_SCP_HOST_CHARS = /[@/\\:?#\s]/;
|
|
6
|
+
const AUTHORIZED_HOST_TYPES = ['ghe'];
|
|
6
7
|
function ok(value) {
|
|
7
8
|
return { ok: true, value };
|
|
8
9
|
}
|
|
9
10
|
function err(error) {
|
|
10
11
|
return { ok: false, error };
|
|
11
12
|
}
|
|
12
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Parses a comma-separated `--authorized-hosts` value of `<hostType>:<host>`
|
|
15
|
+
* tokens (e.g. `ghe:githost.contoso.com,ghe:other.example.com`) into attested
|
|
16
|
+
* hosts. A new VCS product is added by extending `AuthorizedHostType` and the
|
|
17
|
+
* derivation switch, not by changing this grammar. Currently only `ghe`
|
|
18
|
+
* (GitHub Enterprise Server) is recognized.
|
|
19
|
+
*/
|
|
20
|
+
export function parseAuthorizedHosts(raw) {
|
|
21
|
+
const hosts = [];
|
|
22
|
+
if (!raw?.trim())
|
|
23
|
+
return ok(hosts);
|
|
24
|
+
for (const token of raw.split(',')) {
|
|
25
|
+
const spec = token.trim();
|
|
26
|
+
if (!spec)
|
|
27
|
+
continue;
|
|
28
|
+
const colon = spec.indexOf(':');
|
|
29
|
+
if (colon <= 0 || colon === spec.length - 1) {
|
|
30
|
+
return err(`authorized host '${spec}' must take the form <hostType>:<host> (e.g. ghe:githost.contoso.com).`);
|
|
31
|
+
}
|
|
32
|
+
const type = spec.slice(0, colon).toLowerCase();
|
|
33
|
+
const host = spec.slice(colon + 1).trim().toLowerCase();
|
|
34
|
+
if (!AUTHORIZED_HOST_TYPES.includes(type)) {
|
|
35
|
+
return err(`authorized host '${spec}' uses unknown host type '${type}'; supported types: ${AUTHORIZED_HOST_TYPES.join(', ')}.`);
|
|
36
|
+
}
|
|
37
|
+
if (!host || INVALID_SCP_HOST_CHARS.test(host)) {
|
|
38
|
+
return err(`authorized host '${spec}' has an invalid host.`);
|
|
39
|
+
}
|
|
40
|
+
hosts.push({ type: type, host });
|
|
41
|
+
}
|
|
42
|
+
return ok(hosts);
|
|
43
|
+
}
|
|
44
|
+
function isGitHubHost(host, authorizedHosts) {
|
|
13
45
|
const h = host.toLowerCase();
|
|
14
|
-
|
|
46
|
+
if (h === 'github.com' || h.endsWith('.ghe.com'))
|
|
47
|
+
return true;
|
|
48
|
+
// A caller-attested GitHub Enterprise Server runs on a custom domain that is
|
|
49
|
+
// structurally indistinguishable from any other host, so it cannot be
|
|
50
|
+
// auto-recognized; an attested `ghe` host is classified as GitHub.
|
|
51
|
+
return authorizedHosts?.some((a) => a.type === 'ghe' && a.host.toLowerCase() === h) ?? false;
|
|
15
52
|
}
|
|
16
53
|
function isLegacyAzureDevOpsHost(host) {
|
|
17
54
|
const h = host.toLowerCase();
|
|
@@ -123,7 +160,7 @@ function tryNormalizeToHttps(rawRepositoryUri) {
|
|
|
123
160
|
return err('repositoryUri must be an absolute https URL, or a github-compatible ssh/scp clone URL (git@host:owner/repo).');
|
|
124
161
|
}
|
|
125
162
|
}
|
|
126
|
-
function classify(rawRepositoryUri) {
|
|
163
|
+
function classify(rawRepositoryUri, options) {
|
|
127
164
|
if (!rawRepositoryUri) {
|
|
128
165
|
return err('repositoryUri is required so a portable root can be derived.');
|
|
129
166
|
}
|
|
@@ -144,7 +181,7 @@ function classify(rawRepositoryUri) {
|
|
|
144
181
|
const segments = u.pathname.split('/').filter((s) => s.length > 0);
|
|
145
182
|
const schemeAndServer = `${u.protocol}//${u.host}`;
|
|
146
183
|
const host = u.hostname;
|
|
147
|
-
if (isGitHubHost(host)) {
|
|
184
|
+
if (isGitHubHost(host, options?.authorizedHosts)) {
|
|
148
185
|
if (segments.length !== 2) {
|
|
149
186
|
return err(`github repositoryUri must take the form https://<host>/<owner>/<repo>; '${display}' did not.`);
|
|
150
187
|
}
|
|
@@ -187,13 +224,13 @@ function classify(rawRepositoryUri) {
|
|
|
187
224
|
return err(`portable root derivation supports GitHub (github.com or <slug>.ghe.com) and Azure DevOps (dev.azure.com); '${display}' is not a supported host.`);
|
|
188
225
|
}
|
|
189
226
|
/** Validates that a portable root can be derived (no revisionId required). */
|
|
190
|
-
export function tryValidateRepositoryUri(rawRepositoryUri) {
|
|
191
|
-
const c = classify(rawRepositoryUri);
|
|
227
|
+
export function tryValidateRepositoryUri(rawRepositoryUri, options) {
|
|
228
|
+
const c = classify(rawRepositoryUri, options);
|
|
192
229
|
return c.ok ? ok({ leaf: c.value.leaf }) : c;
|
|
193
230
|
}
|
|
194
231
|
/** Mints the portable root for a repository URI + revision id. */
|
|
195
|
-
export function tryDerivePortableRoot(rawRepositoryUri, revisionId) {
|
|
196
|
-
const cr = classify(rawRepositoryUri);
|
|
232
|
+
export function tryDerivePortableRoot(rawRepositoryUri, revisionId, options) {
|
|
233
|
+
const cr = classify(rawRepositoryUri, options);
|
|
197
234
|
if (!cr.ok)
|
|
198
235
|
return cr;
|
|
199
236
|
const c = cr.value;
|
|
@@ -212,14 +249,14 @@ export function tryDerivePortableRoot(rawRepositoryUri, revisionId) {
|
|
|
212
249
|
* repositoryUri classifies as a supported GitHub host. Mirrors
|
|
213
250
|
* VcpPortableRoot.IsGitHubHostedRun.
|
|
214
251
|
*/
|
|
215
|
-
export function isGitHubHostedRun(run) {
|
|
252
|
+
export function isGitHubHostedRun(run, options) {
|
|
216
253
|
const vcp = run.versionControlProvenance;
|
|
217
254
|
if (!vcp || vcp.length === 0)
|
|
218
255
|
return false;
|
|
219
256
|
for (const d of vcp) {
|
|
220
257
|
if (!d)
|
|
221
258
|
return false;
|
|
222
|
-
const c = classify(d.repositoryUri);
|
|
259
|
+
const c = classify(d.repositoryUri, options);
|
|
223
260
|
if (!c.ok || !c.value.isGitHub)
|
|
224
261
|
return false;
|
|
225
262
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@microsoft/sarif",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.5.0",
|
|
4
4
|
"description": "SARIF SDK for Node.js: open-typed object model, region/snippet resolution, AI ruleId convention, and serialization helpers. Native TypeScript; no CLR dependency.",
|
|
5
5
|
"author": "Microsoft",
|
|
6
6
|
"license": "MIT",
|