@adversarylabs/sdk 0.1.4 → 0.1.7
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/README.md +85 -8
- package/dist/index.d.ts +25 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +125 -19
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +49 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +89 -0
- package/dist/manifest.js.map +1 -0
- package/package.json +7 -3
- package/schemas/adversary.manifest.v1.schema.json +159 -0
- package/schemas/adversary.review.v1.schema.json +136 -0
- package/templates/basic/README.md +5 -0
- package/templates/basic/adversary.yaml +8 -2
- package/templates/basic/npm-shrinkwrap.json +59 -4
- package/templates/basic/package.json +1 -1
- package/templates/basic/src/index.ts +1 -7
- package/schemas/adversary.run.v1.schema.json +0 -275
package/README.md
CHANGED
|
@@ -9,11 +9,28 @@ and write runtime output.
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npm install @adversarylabs/sdk
|
|
12
|
+
npm install @adversarylabs/sdk@^0.1.5
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Requires Node 22 or newer and ESM.
|
|
16
16
|
|
|
17
|
+
## Migrating from 0.1.4
|
|
18
|
+
|
|
19
|
+
SDK 0.1.4 output is incompatible with the current strict Adversary CLI schema. Version 0.1.5 is
|
|
20
|
+
the canonical protocol release. Existing adversaries should:
|
|
21
|
+
|
|
22
|
+
1. Upgrade the package to `@adversarylabs/sdk@^0.1.5`.
|
|
23
|
+
2. Set `findings.format` to `adversary.review.v1` in `adversary.yaml`.
|
|
24
|
+
3. Stop reading `result.schemaVersion`; protocol selection is expressed by `protocolVersion: 1`
|
|
25
|
+
and the manifest format.
|
|
26
|
+
4. If consuming serialized evidence, read `file`, `line`, `endLine`, `message`, `snippet`, and
|
|
27
|
+
`metadata` instead of `location`, `label`, and `data`.
|
|
28
|
+
5. If consuming review scores, read the canonical review observation with key `score.<score-key>`;
|
|
29
|
+
the complete authored score is preserved in that note's metadata.
|
|
30
|
+
|
|
31
|
+
There is no compatibility adapter or dual wire format. Output is validated against the strict
|
|
32
|
+
`adversary.review.v1` schema before it is written.
|
|
33
|
+
|
|
17
34
|
## Author an adversary
|
|
18
35
|
|
|
19
36
|
```ts
|
|
@@ -287,7 +304,10 @@ ctx.review.score({
|
|
|
287
304
|
});
|
|
288
305
|
```
|
|
289
306
|
|
|
290
|
-
Scores are optional
|
|
307
|
+
Scores are optional and remain available as an authoring API and terminal section. Because the CLI
|
|
308
|
+
schema has no top-level score collection, the SDK serializes each score as a review
|
|
309
|
+
observation. The note key is `score.<score-key>`, its summary is the rendered score, and its metadata
|
|
310
|
+
preserves the authored `key`, `label`, `score`, `max`, and `summary`. No score data is discarded.
|
|
291
311
|
|
|
292
312
|
### Observation-First Authoring
|
|
293
313
|
|
|
@@ -325,10 +345,9 @@ type ReviewResult = {
|
|
|
325
345
|
assessment?: { risk: "none" | "low" | "medium" | "high" | "critical"; summary?: string };
|
|
326
346
|
positives: ReviewNote[];
|
|
327
347
|
observations: ReviewNote[];
|
|
328
|
-
scores?: ReviewScore[];
|
|
329
348
|
findings: ReviewFinding[];
|
|
330
349
|
opinion?: { ship?: boolean; summary: string };
|
|
331
|
-
suppressed: { findings: number };
|
|
350
|
+
suppressed: { observations: number; findings: number };
|
|
332
351
|
timing?: { totalMs?: number };
|
|
333
352
|
};
|
|
334
353
|
```
|
|
@@ -336,13 +355,29 @@ type ReviewResult = {
|
|
|
336
355
|
Renderers consume this result. The SDK includes `TerminalRenderer` and `JsonRenderer`:
|
|
337
356
|
|
|
338
357
|
```ts
|
|
339
|
-
const result = await app.run({ });
|
|
358
|
+
const result = await app.run({ input: { source: { path: "/repo" } } });
|
|
340
359
|
new TerminalRenderer().render(result);
|
|
341
360
|
new JsonRenderer().render(result);
|
|
342
361
|
```
|
|
343
362
|
|
|
344
363
|
Adversary implementations should not manually format review output.
|
|
345
364
|
|
|
365
|
+
### Canonical wire evidence
|
|
366
|
+
|
|
367
|
+
The authoring API continues to accept nested `location`, structured `data`, and `label`. Envelope
|
|
368
|
+
serialization translates those fields without losing their meaning:
|
|
369
|
+
|
|
370
|
+
| Authoring field | Canonical wire field |
|
|
371
|
+
| --- | --- |
|
|
372
|
+
| `location.file` | `file` |
|
|
373
|
+
| `location.line` | `line` |
|
|
374
|
+
| `location.endLine` | `endLine` |
|
|
375
|
+
| `data` | `metadata` |
|
|
376
|
+
| `label` when `message` is absent | `message` |
|
|
377
|
+
|
|
378
|
+
Wire evidence never contains `location`, `data`, or `label`. Findings likewise never contain SDK
|
|
379
|
+
diagnostic fields such as `synthesisSource`.
|
|
380
|
+
|
|
346
381
|
## Comment Sentence Example
|
|
347
382
|
|
|
348
383
|
`adversary.yaml`:
|
|
@@ -358,6 +393,11 @@ triggers:
|
|
|
358
393
|
- "*.ts"
|
|
359
394
|
- "**/*.ts"
|
|
360
395
|
|
|
396
|
+
detection:
|
|
397
|
+
files:
|
|
398
|
+
- "*.ts"
|
|
399
|
+
- "**/*.ts"
|
|
400
|
+
|
|
361
401
|
runtime:
|
|
362
402
|
name: node
|
|
363
403
|
version: "22"
|
|
@@ -371,12 +411,45 @@ permissions:
|
|
|
371
411
|
write:
|
|
372
412
|
- .adversary/results
|
|
373
413
|
network: false
|
|
374
|
-
|
|
414
|
+
environment:
|
|
415
|
+
allow: []
|
|
375
416
|
|
|
376
417
|
findings:
|
|
377
|
-
format: adversary.
|
|
418
|
+
format: adversary.review.v1
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Automatic detection
|
|
422
|
+
|
|
423
|
+
The SDK owns the canonical `adversary.yaml` model, parser, validation, and published JSON schema.
|
|
424
|
+
The CLI uses the parsed optional `detection` section when deciding which installed adversaries are
|
|
425
|
+
relevant to `adversary auto`.
|
|
426
|
+
|
|
427
|
+
Use declarative file detection when changed repository-relative paths are sufficient:
|
|
428
|
+
|
|
429
|
+
```yaml
|
|
430
|
+
detection:
|
|
431
|
+
files:
|
|
432
|
+
- Dockerfile
|
|
433
|
+
- "**/Dockerfile"
|
|
434
|
+
- .dockerignore
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Use a detector entrypoint when applicability requires lightweight repository or change inspection:
|
|
438
|
+
|
|
439
|
+
```yaml
|
|
440
|
+
detection:
|
|
441
|
+
entrypoint: dist/detect.js
|
|
378
442
|
```
|
|
379
443
|
|
|
444
|
+
Both forms may be declared together. The entrypoint is validated as a portable project-relative
|
|
445
|
+
path, but its build output does not need to exist while the manifest is parsed. Detector execution
|
|
446
|
+
and matching behavior belong to the CLI/runtime and are not implemented by this SDK feature.
|
|
447
|
+
|
|
448
|
+
Use `parseAdversaryManifest(...)` to parse YAML or `validateAdversaryManifest(...)` to validate an
|
|
449
|
+
already-decoded value. The resulting `AdversaryManifest` exposes `detection` directly, so consumers
|
|
450
|
+
never need to re-read raw YAML. The schema is published as
|
|
451
|
+
`@adversarylabs/sdk/schemas/adversary.manifest.v1`.
|
|
452
|
+
|
|
380
453
|
`src/index.ts`:
|
|
381
454
|
|
|
382
455
|
```ts
|
|
@@ -512,7 +585,6 @@ Output shape:
|
|
|
512
585
|
{
|
|
513
586
|
"protocolVersion": 1,
|
|
514
587
|
"result": {
|
|
515
|
-
"schemaVersion": "adversary.review.v1",
|
|
516
588
|
"adversary": {
|
|
517
589
|
"name": "adversarylabs/example"
|
|
518
590
|
},
|
|
@@ -524,12 +596,17 @@ Output shape:
|
|
|
524
596
|
"observations": [],
|
|
525
597
|
"findings": [],
|
|
526
598
|
"suppressed": {
|
|
599
|
+
"observations": 0,
|
|
527
600
|
"findings": 0
|
|
528
601
|
}
|
|
529
602
|
}
|
|
530
603
|
}
|
|
531
604
|
```
|
|
532
605
|
|
|
606
|
+
The package exports the exact CLI schema at
|
|
607
|
+
`@adversarylabs/sdk/schemas/adversary.review.v1`. `writeOutput(...)` and
|
|
608
|
+
`runFromEnvironment(...)` validate the complete envelope against that schema before writing.
|
|
609
|
+
|
|
533
610
|
## Development
|
|
534
611
|
|
|
535
612
|
```bash
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
export { ADVERSARY_MANIFEST_FILE_NAME, ADVERSARY_MANIFEST_MAX_BYTES, ManifestValidationError, parseAdversaryManifest, validateAdversaryManifest, type AdversaryManifest, type DetectionManifest, type EnvironmentPermissionsManifest, type FilesystemPermissionsManifest, type FindingsManifest, type PermissionsManifest, type RuntimeManifest, type TriggerManifest, } from "./manifest.js";
|
|
1
2
|
export declare const DEFAULT_INPUT_PATH = "/adversary/input.json";
|
|
2
3
|
export declare const DEFAULT_OUTPUT_PATH = "/adversary/output.json";
|
|
3
4
|
export declare const ADVERSARY_RUN_PROTOCOL_VERSION = 1;
|
|
4
|
-
export declare const REVIEW_RESULT_SCHEMA_VERSION = "adversary.review.v1";
|
|
5
5
|
export declare const Severity: {
|
|
6
6
|
readonly Info: "info";
|
|
7
7
|
readonly Low: "low";
|
|
@@ -120,7 +120,6 @@ export interface ReviewFinding {
|
|
|
120
120
|
evidence: Evidence[];
|
|
121
121
|
recommendation?: string;
|
|
122
122
|
remediation?: Remediation;
|
|
123
|
-
synthesisSource?: "rule" | "generic";
|
|
124
123
|
tags?: string[];
|
|
125
124
|
metadata?: Record<string, unknown>;
|
|
126
125
|
}
|
|
@@ -156,7 +155,6 @@ export interface ReviewPolicy {
|
|
|
156
155
|
severityOverrides?: Record<string, Severity>;
|
|
157
156
|
}
|
|
158
157
|
export interface ReviewResult {
|
|
159
|
-
schemaVersion: typeof REVIEW_RESULT_SCHEMA_VERSION;
|
|
160
158
|
adversary: {
|
|
161
159
|
name: string;
|
|
162
160
|
version?: string;
|
|
@@ -168,10 +166,10 @@ export interface ReviewResult {
|
|
|
168
166
|
assessment?: ReviewAssessment;
|
|
169
167
|
positives: ReviewNote[];
|
|
170
168
|
observations: ReviewNote[];
|
|
171
|
-
scores?: ReviewScore[];
|
|
172
169
|
findings: ReviewFinding[];
|
|
173
170
|
opinion?: ReviewOpinion;
|
|
174
171
|
suppressed: {
|
|
172
|
+
observations: number;
|
|
175
173
|
findings: number;
|
|
176
174
|
};
|
|
177
175
|
timing?: {
|
|
@@ -185,7 +183,27 @@ export interface ReviewResult {
|
|
|
185
183
|
}
|
|
186
184
|
export interface AdversaryRunEnvelope {
|
|
187
185
|
protocolVersion: typeof ADVERSARY_RUN_PROTOCOL_VERSION;
|
|
188
|
-
result:
|
|
186
|
+
result: WireReviewResult;
|
|
187
|
+
}
|
|
188
|
+
export interface WireEvidence {
|
|
189
|
+
file?: string;
|
|
190
|
+
line?: number;
|
|
191
|
+
endLine?: number;
|
|
192
|
+
message?: string;
|
|
193
|
+
snippet?: string;
|
|
194
|
+
metadata?: Record<string, unknown>;
|
|
195
|
+
}
|
|
196
|
+
export interface WireReviewFinding extends Omit<ReviewFinding, "evidence"> {
|
|
197
|
+
evidence: WireEvidence[];
|
|
198
|
+
}
|
|
199
|
+
export interface WireReviewNote extends Omit<ReviewNote, "evidence"> {
|
|
200
|
+
evidence?: WireEvidence[];
|
|
201
|
+
}
|
|
202
|
+
export interface WireReviewResult extends Omit<ReviewResult, "positives" | "observations" | "findings" | "suppressedFindings"> {
|
|
203
|
+
positives: WireReviewNote[];
|
|
204
|
+
observations: WireReviewNote[];
|
|
205
|
+
findings: WireReviewFinding[];
|
|
206
|
+
suppressedFindings?: WireReviewFinding[];
|
|
189
207
|
}
|
|
190
208
|
export interface RuntimeInput {
|
|
191
209
|
source: {
|
|
@@ -286,7 +304,8 @@ export declare class Adversary {
|
|
|
286
304
|
}
|
|
287
305
|
export declare function createAdversaryRunEnvelope(result: ReviewResult): AdversaryRunEnvelope;
|
|
288
306
|
export declare function parseInput(path?: string): Promise<RuntimeInput>;
|
|
289
|
-
export declare function writeOutput(output:
|
|
307
|
+
export declare function writeOutput(output: AdversaryRunEnvelope, path?: string): Promise<void>;
|
|
308
|
+
export declare function validateRunEnvelope(output: unknown): Promise<void>;
|
|
290
309
|
export declare function normalizeConfidence(confidence: ConfidenceInput, thresholds?: ConfidenceThresholds): Confidence;
|
|
291
310
|
export declare function rankFindings(findings: ReviewFinding[]): ReviewFinding[];
|
|
292
311
|
export declare class JsonRenderer implements ReviewRenderer {
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
3
|
+
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
4
|
+
export { ADVERSARY_MANIFEST_FILE_NAME, ADVERSARY_MANIFEST_MAX_BYTES, ManifestValidationError, parseAdversaryManifest, validateAdversaryManifest, } from "./manifest.js";
|
|
3
5
|
export const DEFAULT_INPUT_PATH = "/adversary/input.json";
|
|
4
6
|
export const DEFAULT_OUTPUT_PATH = "/adversary/output.json";
|
|
5
7
|
export const ADVERSARY_RUN_PROTOCOL_VERSION = 1;
|
|
6
|
-
export const REVIEW_RESULT_SCHEMA_VERSION = "adversary.review.v1";
|
|
7
8
|
const verboseValues = new Set(["1", "true", "TRUE", "yes", "YES"]);
|
|
9
|
+
let envelopeValidator;
|
|
8
10
|
export const Severity = {
|
|
9
11
|
Info: "info",
|
|
10
12
|
Low: "low",
|
|
@@ -208,9 +210,61 @@ export class Adversary {
|
|
|
208
210
|
export function createAdversaryRunEnvelope(result) {
|
|
209
211
|
return {
|
|
210
212
|
protocolVersion: ADVERSARY_RUN_PROTOCOL_VERSION,
|
|
211
|
-
result,
|
|
213
|
+
result: toWireReviewResult(result),
|
|
212
214
|
};
|
|
213
215
|
}
|
|
216
|
+
function toWireReviewResult(result) {
|
|
217
|
+
return omitUndefined({
|
|
218
|
+
adversary: omitUndefined(result.adversary),
|
|
219
|
+
target: omitUndefined(result.target),
|
|
220
|
+
assessment: result.assessment === undefined ? undefined : omitUndefined({ ...result.assessment }),
|
|
221
|
+
positives: result.positives.map(toWireReviewNote),
|
|
222
|
+
observations: result.observations.map(toWireReviewNote),
|
|
223
|
+
findings: result.findings.map(toWireFinding),
|
|
224
|
+
opinion: result.opinion === undefined ? undefined : omitUndefined({ ...result.opinion }),
|
|
225
|
+
suppressed: result.suppressed,
|
|
226
|
+
timing: result.timing === undefined ? undefined : omitUndefined(result.timing),
|
|
227
|
+
suppressedFindings: result.suppressedFindings?.map(toWireFinding),
|
|
228
|
+
rawObservations: result.rawObservations,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
function toWireReviewNote(note) {
|
|
232
|
+
return omitUndefined({
|
|
233
|
+
key: note.key,
|
|
234
|
+
summary: note.summary,
|
|
235
|
+
evidence: note.evidence?.map(toWireEvidence),
|
|
236
|
+
metadata: note.metadata,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
function toWireFinding(finding) {
|
|
240
|
+
return omitUndefined({
|
|
241
|
+
id: finding.id,
|
|
242
|
+
ruleId: finding.ruleId,
|
|
243
|
+
groupKey: finding.groupKey,
|
|
244
|
+
title: finding.title,
|
|
245
|
+
category: finding.category,
|
|
246
|
+
severity: finding.severity,
|
|
247
|
+
confidence: finding.confidence,
|
|
248
|
+
summary: finding.summary,
|
|
249
|
+
whyItMatters: finding.whyItMatters,
|
|
250
|
+
impact: finding.impact,
|
|
251
|
+
evidence: finding.evidence.map(toWireEvidence),
|
|
252
|
+
recommendation: finding.recommendation,
|
|
253
|
+
remediation: finding.remediation === undefined ? undefined : omitUndefined({ ...finding.remediation }),
|
|
254
|
+
tags: finding.tags,
|
|
255
|
+
metadata: finding.metadata,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
function toWireEvidence(evidence) {
|
|
259
|
+
return omitUndefined({
|
|
260
|
+
file: evidence.location?.file,
|
|
261
|
+
line: evidence.location?.line,
|
|
262
|
+
endLine: evidence.location?.endLine,
|
|
263
|
+
message: evidence.message ?? evidence.label,
|
|
264
|
+
snippet: evidence.snippet,
|
|
265
|
+
metadata: evidence.data,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
214
268
|
export async function parseInput(path = DEFAULT_INPUT_PATH) {
|
|
215
269
|
const raw = await readFile(path, "utf8");
|
|
216
270
|
const parsed = JSON.parse(raw);
|
|
@@ -226,9 +280,21 @@ export async function parseInput(path = DEFAULT_INPUT_PATH) {
|
|
|
226
280
|
return parsed;
|
|
227
281
|
}
|
|
228
282
|
export async function writeOutput(output, path = DEFAULT_OUTPUT_PATH) {
|
|
283
|
+
await validateRunEnvelope(output);
|
|
229
284
|
await mkdir(dirname(path), { recursive: true });
|
|
230
285
|
await writeFile(path, `${JSON.stringify(output, null, 2)}\n`, "utf8");
|
|
231
286
|
}
|
|
287
|
+
export async function validateRunEnvelope(output) {
|
|
288
|
+
let validator = envelopeValidator;
|
|
289
|
+
if (validator === undefined) {
|
|
290
|
+
const schema = JSON.parse(await readFile(new URL("../schemas/adversary.review.v1.schema.json", import.meta.url), "utf8"));
|
|
291
|
+
validator = new Ajv2020({ allErrors: true, strict: true }).compile(schema);
|
|
292
|
+
envelopeValidator = validator;
|
|
293
|
+
}
|
|
294
|
+
if (!validator(output)) {
|
|
295
|
+
throw new Error(`Invalid adversary.review.v1 envelope: ${JSON.stringify(validator.errors)}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
232
298
|
export function normalizeConfidence(confidence, thresholds = DEFAULT_CONFIDENCE_THRESHOLDS) {
|
|
233
299
|
if (isConfidence(confidence)) {
|
|
234
300
|
return confidence;
|
|
@@ -270,7 +336,7 @@ export class JsonRenderer {
|
|
|
270
336
|
this.write = write;
|
|
271
337
|
}
|
|
272
338
|
render(result) {
|
|
273
|
-
this.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
339
|
+
this.write(`${JSON.stringify(toWireReviewResult(result), null, 2)}\n`);
|
|
274
340
|
}
|
|
275
341
|
}
|
|
276
342
|
export class TerminalRenderer {
|
|
@@ -292,10 +358,12 @@ export class TerminalRenderer {
|
|
|
292
358
|
lines.push(normalizeParagraph(result.assessment.summary), "");
|
|
293
359
|
}
|
|
294
360
|
}
|
|
295
|
-
|
|
361
|
+
const scoreNotes = result.observations.filter(isScoreReviewNote);
|
|
362
|
+
const additionalObservations = result.observations.filter((note) => !isScoreReviewNote(note));
|
|
363
|
+
if (scoreNotes.length > 0) {
|
|
296
364
|
lines.push("Scores", "");
|
|
297
|
-
for (const
|
|
298
|
-
lines.push(
|
|
365
|
+
for (const note of scoreNotes) {
|
|
366
|
+
lines.push(note.summary);
|
|
299
367
|
}
|
|
300
368
|
lines.push("");
|
|
301
369
|
}
|
|
@@ -306,9 +374,9 @@ export class TerminalRenderer {
|
|
|
306
374
|
}
|
|
307
375
|
lines.push("");
|
|
308
376
|
}
|
|
309
|
-
if (
|
|
377
|
+
if (additionalObservations.length > 0) {
|
|
310
378
|
lines.push("Additional observations", "");
|
|
311
|
-
for (const observation of
|
|
379
|
+
for (const observation of additionalObservations) {
|
|
312
380
|
lines.push(`- ${normalizeParagraph(observation.summary)}`);
|
|
313
381
|
}
|
|
314
382
|
lines.push("");
|
|
@@ -456,9 +524,9 @@ function createReviewCollector() {
|
|
|
456
524
|
}
|
|
457
525
|
function buildReviewResult(input) {
|
|
458
526
|
const thresholds = input.policy.confidenceThresholds ?? DEFAULT_CONFIDENCE_THRESHOLDS;
|
|
459
|
-
const
|
|
527
|
+
const synthesis = synthesizeObservationFindings(input.collector.observations, thresholds, input.registry);
|
|
460
528
|
const allFindings = deduplicateFindings([
|
|
461
|
-
...
|
|
529
|
+
...synthesis.findings.map((finding) => ({ finding, deduplicate: true })),
|
|
462
530
|
...input.collector.findings,
|
|
463
531
|
]).map((finding) => calibrateFindingSeverity(finding, input.policy));
|
|
464
532
|
const ranked = rankFindings(allFindings);
|
|
@@ -479,9 +547,11 @@ function buildReviewResult(input) {
|
|
|
479
547
|
}
|
|
480
548
|
}
|
|
481
549
|
const positives = selectPositiveSignals(input.collector.positives);
|
|
482
|
-
const reviewObservations = deduplicateReviewObservations(
|
|
550
|
+
const reviewObservations = deduplicateReviewObservations([
|
|
551
|
+
...input.collector.reviewObservations,
|
|
552
|
+
...deduplicateScores(input.collector.scores).map(scoreToReviewNote),
|
|
553
|
+
], positives);
|
|
483
554
|
return omitUndefined({
|
|
484
|
-
schemaVersion: REVIEW_RESULT_SCHEMA_VERSION,
|
|
485
555
|
adversary: input.adversary,
|
|
486
556
|
target: omitUndefined({
|
|
487
557
|
repository: input.repository,
|
|
@@ -490,10 +560,10 @@ function buildReviewResult(input) {
|
|
|
490
560
|
assessment: input.collector.assessment ?? synthesizeAssessment(eligible, positives),
|
|
491
561
|
positives,
|
|
492
562
|
observations: reviewObservations,
|
|
493
|
-
scores: input.collector.scores.length > 0 ? deduplicateScores(input.collector.scores) : undefined,
|
|
494
563
|
findings: eligible,
|
|
495
564
|
opinion: input.collector.opinion ?? synthesizeOpinion(eligible),
|
|
496
565
|
suppressed: {
|
|
566
|
+
observations: synthesis.suppressedObservations,
|
|
497
567
|
findings: suppressedFindings.length,
|
|
498
568
|
},
|
|
499
569
|
timing: input.timing,
|
|
@@ -504,17 +574,19 @@ function buildReviewResult(input) {
|
|
|
504
574
|
function synthesizeObservationFindings(observations, thresholds, registry) {
|
|
505
575
|
const grouped = new Map();
|
|
506
576
|
const seen = new Set();
|
|
577
|
+
let suppressedObservations = 0;
|
|
507
578
|
for (const observation of observations) {
|
|
508
579
|
const rule = registry.lookup(observation.ruleId);
|
|
509
580
|
const groupKey = observation.groupKey ?? defaultObservationGroupKey(observation, rule);
|
|
510
581
|
const dedupeKey = stableStringify({ groupKey, observation });
|
|
511
582
|
if (observation.deduplicate !== false && seen.has(dedupeKey)) {
|
|
583
|
+
suppressedObservations += 1;
|
|
512
584
|
continue;
|
|
513
585
|
}
|
|
514
586
|
seen.add(dedupeKey);
|
|
515
587
|
grouped.set(groupKey, [...(grouped.get(groupKey) ?? []), observation]);
|
|
516
588
|
}
|
|
517
|
-
|
|
589
|
+
const findings = [...grouped.entries()].map(([groupKey, group]) => {
|
|
518
590
|
const first = group[0];
|
|
519
591
|
if (first === undefined) {
|
|
520
592
|
throw new Error("Cannot synthesize finding from an empty observation group.");
|
|
@@ -547,13 +619,13 @@ function synthesizeObservationFindings(observations, thresholds, registry) {
|
|
|
547
619
|
evidence,
|
|
548
620
|
recommendation: recommendation.length > 0 ? recommendation : undefined,
|
|
549
621
|
remediation: synthesis.remediation ?? first.remediation,
|
|
550
|
-
synthesisSource,
|
|
551
622
|
tags: synthesis.tags ?? uniqueStrings(group.flatMap((observation) => observation.tags ?? [])),
|
|
552
623
|
metadata: synthesis.metadata ?? first.metadata,
|
|
553
624
|
};
|
|
554
625
|
assertFindingInput(finding, `adversary aggregate rule "${first.ruleId}"`);
|
|
555
626
|
return finding;
|
|
556
627
|
});
|
|
628
|
+
return { findings, suppressedObservations };
|
|
557
629
|
}
|
|
558
630
|
function normalizeFindingInput(input, occurrence = 0) {
|
|
559
631
|
return omitUndefined({
|
|
@@ -623,10 +695,17 @@ function deduplicateEvidence(evidence) {
|
|
|
623
695
|
}
|
|
624
696
|
function normalizeEvidence(input) {
|
|
625
697
|
const legacy = input;
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
698
|
+
const hasLocation = legacy.location !== undefined ||
|
|
699
|
+
legacy.file !== undefined ||
|
|
700
|
+
legacy.line !== undefined ||
|
|
701
|
+
legacy.endLine !== undefined;
|
|
702
|
+
const location = hasLocation
|
|
703
|
+
? omitUndefined({
|
|
704
|
+
file: legacy.location?.file ?? legacy.file,
|
|
705
|
+
line: legacy.location?.line ?? legacy.line,
|
|
706
|
+
endLine: legacy.location?.endLine ?? legacy.endLine,
|
|
707
|
+
})
|
|
708
|
+
: undefined;
|
|
630
709
|
return omitUndefined({
|
|
631
710
|
location,
|
|
632
711
|
label: input.label,
|
|
@@ -769,6 +848,25 @@ function deduplicateScores(scores) {
|
|
|
769
848
|
}
|
|
770
849
|
return result.sort((left, right) => compareStrings(left.key, right.key));
|
|
771
850
|
}
|
|
851
|
+
function scoreToReviewNote(score) {
|
|
852
|
+
return {
|
|
853
|
+
key: `score.${score.key}`,
|
|
854
|
+
summary: formatScore(score),
|
|
855
|
+
metadata: {
|
|
856
|
+
kind: "score",
|
|
857
|
+
score: omitUndefined({
|
|
858
|
+
key: score.key,
|
|
859
|
+
label: score.label,
|
|
860
|
+
score: score.score,
|
|
861
|
+
max: score.max,
|
|
862
|
+
summary: score.summary,
|
|
863
|
+
}),
|
|
864
|
+
},
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
function isScoreReviewNote(note) {
|
|
868
|
+
return note.metadata?.kind === "score" && isRecord(note.metadata.score);
|
|
869
|
+
}
|
|
772
870
|
function observationToEvidence(observation) {
|
|
773
871
|
const data = isRecord(observation.evidence)
|
|
774
872
|
? observation.evidence
|
|
@@ -1254,6 +1352,14 @@ function optionalEvidence(value, field) {
|
|
|
1254
1352
|
optionalPositiveInteger(value.location.line, `${field}.location.line`);
|
|
1255
1353
|
optionalPositiveInteger(value.location.endLine, `${field}.location.endLine`);
|
|
1256
1354
|
}
|
|
1355
|
+
const line = value.location?.line ?? input.line;
|
|
1356
|
+
const endLine = value.location?.endLine ?? input.endLine;
|
|
1357
|
+
if (endLine !== undefined && line === undefined) {
|
|
1358
|
+
throw new Error(`${field}.endLine requires line.`);
|
|
1359
|
+
}
|
|
1360
|
+
if (endLine !== undefined && line !== undefined && endLine < line) {
|
|
1361
|
+
throw new Error(`${field}.endLine must not precede line.`);
|
|
1362
|
+
}
|
|
1257
1363
|
if (value.data !== undefined && !isRecord(value.data)) {
|
|
1258
1364
|
throw new Error(`${field}.data must be an object.`);
|
|
1259
1365
|
}
|