@microsoft/sarif 5.4.3 → 5.6.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 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
- return { ...original };
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
- return context;
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
- function isGitHubHost(host) {
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
- return h === 'github.com' || h.endsWith('.ghe.com');
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.4.3",
3
+ "version": "5.6.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",