@owasp-aghast/aghast 0.8.2 → 0.8.4

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.
Files changed (56) hide show
  1. package/README.md +3 -2
  2. package/config/pricing.json +39 -9
  3. package/dist/build-config.js +1 -1
  4. package/dist/check-library.d.ts.map +1 -1
  5. package/dist/check-library.js +7 -1
  6. package/dist/check-library.js.map +1 -1
  7. package/dist/claude-code-provider.d.ts +7 -0
  8. package/dist/claude-code-provider.d.ts.map +1 -1
  9. package/dist/claude-code-provider.js +65 -1
  10. package/dist/claude-code-provider.js.map +1 -1
  11. package/dist/discoveries/opengrep-discovery.d.ts +10 -0
  12. package/dist/discoveries/opengrep-discovery.d.ts.map +1 -0
  13. package/dist/discoveries/opengrep-discovery.js +41 -0
  14. package/dist/discoveries/opengrep-discovery.js.map +1 -0
  15. package/dist/discoveries/sarif-target-enrichment.d.ts +7 -0
  16. package/dist/discoveries/sarif-target-enrichment.d.ts.map +1 -0
  17. package/dist/discoveries/sarif-target-enrichment.js +28 -0
  18. package/dist/discoveries/sarif-target-enrichment.js.map +1 -0
  19. package/dist/discoveries/semgrep-discovery.d.ts.map +1 -1
  20. package/dist/discoveries/semgrep-discovery.js +2 -23
  21. package/dist/discoveries/semgrep-discovery.js.map +1 -1
  22. package/dist/error-codes.d.ts +3 -1
  23. package/dist/error-codes.d.ts.map +1 -1
  24. package/dist/error-codes.js +4 -2
  25. package/dist/error-codes.js.map +1 -1
  26. package/dist/formatters/csv-formatter.d.ts +34 -0
  27. package/dist/formatters/csv-formatter.d.ts.map +1 -0
  28. package/dist/formatters/csv-formatter.js +114 -0
  29. package/dist/formatters/csv-formatter.js.map +1 -0
  30. package/dist/formatters/html-formatter.d.ts +39 -0
  31. package/dist/formatters/html-formatter.d.ts.map +1 -0
  32. package/dist/formatters/html-formatter.js +392 -0
  33. package/dist/formatters/html-formatter.js.map +1 -0
  34. package/dist/formatters/index.d.ts.map +1 -1
  35. package/dist/formatters/index.js +4 -0
  36. package/dist/formatters/index.js.map +1 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +41 -5
  39. package/dist/index.js.map +1 -1
  40. package/dist/new-check.d.ts.map +1 -1
  41. package/dist/new-check.js +37 -21
  42. package/dist/new-check.js.map +1 -1
  43. package/dist/opengrep-runner.d.ts +21 -0
  44. package/dist/opengrep-runner.d.ts.map +1 -0
  45. package/dist/opengrep-runner.js +31 -0
  46. package/dist/opengrep-runner.js.map +1 -0
  47. package/dist/scan-runner.d.ts.map +1 -1
  48. package/dist/scan-runner.js +2 -0
  49. package/dist/scan-runner.js.map +1 -1
  50. package/dist/semgrep-runner.d.ts +50 -4
  51. package/dist/semgrep-runner.d.ts.map +1 -1
  52. package/dist/semgrep-runner.js +60 -26
  53. package/dist/semgrep-runner.js.map +1 -1
  54. package/dist/types.d.ts +1 -1
  55. package/dist/types.d.ts.map +1 -1
  56. package/package.json +3 -2
@@ -1 +1 @@
1
- {"version":3,"file":"semgrep-discovery.js","sourceRoot":"","sources":["../../src/discoveries/semgrep-discovery.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAIxD,MAAM,GAAG,GAAG,mBAAmB,CAAC;AAEhC,SAAS,2BAA2B,CAAC,IAAY,EAAE,SAAiB,EAAE,OAAe;IACnF,OAAO;;;UAGC,IAAI;WACH,SAAS,IAAI,OAAO;;;;;;;;;;;;;;;CAe9B,CAAC;AACF,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAoB;IAC/C,IAAI,EAAE,SAAS;IACf,oBAAoB,EAAE,sBAAsB;IAC5C,iBAAiB,EAAE,IAAI;IACvB,kBAAkB,EAAE,IAAI;IAExB,KAAK,CAAC,QAAQ,CACZ,KAAoB,EACpB,QAAgB,EAChB,QAA2B;QAE3B,MAAM,WAAW,GAAG,KAAK,CAAC,WAAY,CAAC;QAEvC,QAAQ,CAAC,GAAG,EAAE,8BAA8B,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;QAExD,MAAM,YAAY,GAAG,MAAM,UAAU,CAAC;YACpC,cAAc,EAAE,QAAQ;YACxB,KAAK,EAAE,WAAW,CAAC,KAAK;YACxB,MAAM,EAAE,WAAW,CAAC,MAAM;SAC3B,CAAC,CAAC;QAEH,IAAI,OAAO,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC;QACvC,OAAO,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;QAEtC,QAAQ,CAAC,GAAG,EAAE,cAAc,OAAO,CAAC,MAAM,UAAU,CAAC,CAAC;QAEtD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;YACnC,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,KAAK,EAAE,WAAW,GAAG,GAAG,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG;YAC9C,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,gBAAgB,EAAE,2BAA2B,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC;SAC7F,CAAC,CAAC,CAAC;IACN,CAAC;CACF,CAAC"}
1
+ {"version":3,"file":"semgrep-discovery.js","sourceRoot":"","sources":["../../src/discoveries/semgrep-discovery.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,gCAAgC,EAAE,MAAM,8BAA8B,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAIxD,MAAM,GAAG,GAAG,mBAAmB,CAAC;AAEhC,MAAM,CAAC,MAAM,gBAAgB,GAAoB;IAC/C,IAAI,EAAE,SAAS;IACf,oBAAoB,EAAE,sBAAsB;IAC5C,iBAAiB,EAAE,IAAI;IACvB,kBAAkB,EAAE,IAAI;IAExB,KAAK,CAAC,QAAQ,CACZ,KAAoB,EACpB,QAAgB,EAChB,QAA2B;QAE3B,MAAM,WAAW,GAAG,KAAK,CAAC,WAAY,CAAC;QAEvC,QAAQ,CAAC,GAAG,EAAE,8BAA8B,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;QAExD,MAAM,YAAY,GAAG,MAAM,UAAU,CAAC;YACpC,cAAc,EAAE,QAAQ;YACxB,KAAK,EAAE,WAAW,CAAC,KAAK;YACxB,MAAM,EAAE,WAAW,CAAC,MAAM;SAC3B,CAAC,CAAC;QAEH,IAAI,OAAO,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC;QACvC,OAAO,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;QAEtC,QAAQ,CAAC,GAAG,EAAE,cAAc,OAAO,CAAC,MAAM,UAAU,CAAC,CAAC;QAEtD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;YACnC,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,KAAK,EAAE,WAAW,GAAG,GAAG,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG;YAC9C,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,gBAAgB,EAAE,gCAAgC,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC;SAClG,CAAC,CAAC,CAAC;IACN,CAAC;CACF,CAAC"}
@@ -6,8 +6,9 @@
6
6
  * E2xxx — Configuration (config dir, checks, runtime config)
7
7
  * E3xxx — Agent provider
8
8
  * E4xxx — Repository/target validation
9
- * E5xxx — Semgrep
9
+ * E5xxx — Semgrep / Opengrep
10
10
  * E6xxx — OpenAnt
11
+ * E7xxx — Budget / cost controls
11
12
  * E9xxx — Internal/fatal
12
13
  */
13
14
  export interface ErrorCode {
@@ -29,6 +30,7 @@ export declare const ERROR_CODES: {
29
30
  readonly E3004: ErrorCode;
30
31
  readonly E4001: ErrorCode;
31
32
  readonly E5001: ErrorCode;
33
+ readonly E5101: ErrorCode;
32
34
  readonly E6001: ErrorCode;
33
35
  readonly E6002: ErrorCode;
34
36
  readonly E7001: ErrorCode;
@@ -1 +1 @@
1
- {"version":3,"file":"error-codes.d.ts","sourceRoot":"","sources":["../src/error-codes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAMD,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;CAsCd,CAAC;AAEX;;;GAGG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEzE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAWzE"}
1
+ {"version":3,"file":"error-codes.d.ts","sourceRoot":"","sources":["../src/error-codes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAMD,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;CAuCd,CAAC;AAEX;;;GAGG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEzE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAWzE"}
@@ -6,8 +6,9 @@
6
6
  * E2xxx — Configuration (config dir, checks, runtime config)
7
7
  * E3xxx — Agent provider
8
8
  * E4xxx — Repository/target validation
9
- * E5xxx — Semgrep
9
+ * E5xxx — Semgrep / Opengrep
10
10
  * E6xxx — OpenAnt
11
+ * E7xxx — Budget / cost controls
11
12
  * E9xxx — Internal/fatal
12
13
  */
13
14
  function ec(code, label) {
@@ -35,8 +36,9 @@ export const ERROR_CODES = {
35
36
  E3004: ec('E3004', 'OpenCode not installed'),
36
37
  // E4xxx — Repository/target validation
37
38
  E4001: ec('E4001', 'Repository path not found'),
38
- // E5xxx — Semgrep
39
+ // E5xxx — Semgrep / Opengrep
39
40
  E5001: ec('E5001', 'Semgrep not installed'),
41
+ E5101: ec('E5101', 'Opengrep not installed'),
40
42
  // E6xxx — OpenAnt
41
43
  E6001: ec('E6001', 'OpenAnt not installed'),
42
44
  E6002: ec('E6002', 'OpenAnt execution failed'),
@@ -1 +1 @@
1
- {"version":3,"file":"error-codes.js","sourceRoot":"","sources":["../src/error-codes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH,SAAS,EAAE,CAAC,IAAY,EAAE,KAAa;IACrC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACzB,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,sBAAsB;IACtB,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,gCAAgC,CAAC;IACpD,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACrC,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,wBAAwB,CAAC;IAE5C,wBAAwB;IACxB,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,gCAAgC,CAAC;IACpD,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,oCAAoC,CAAC;IACxD,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACrC,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,0BAA0B,CAAC;IAC9C,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,qBAAqB,CAAC;IAEzC,yBAAyB;IACzB,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACrC,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,wBAAwB,CAAC;IAC5C,yEAAyE;IACzE,wEAAwE;IACxE,qEAAqE;IACrE,oEAAoE;IACpE,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,iCAAiC,CAAC;IACrD,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,wBAAwB,CAAC;IAE5C,uCAAuC;IACvC,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,2BAA2B,CAAC;IAE/C,kBAAkB;IAClB,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,uBAAuB,CAAC;IAE3C,kBAAkB;IAClB,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,uBAAuB,CAAC;IAC3C,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,0BAA0B,CAAC;IAE9C,iCAAiC;IACjC,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,uBAAuB,CAAC;IAE3C,mBAAmB;IACnB,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,sBAAsB,CAAC;CAClC,CAAC;AAEX;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,SAAoB,EAAE,OAAe;IAC/D,OAAO,UAAU,SAAS,CAAC,IAAI,MAAM,OAAO,EAAE,CAAC;AACjD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe,EAAE,OAAe;IAC/D,MAAM,KAAK,GAAG,kBAAkB,CAAC,SAAS,OAAO,EAAE,CAAC,CAAC;IACrD,MAAM,IAAI,GAAG,kBAAkB,CAAC,gBAAgB,OAAO,mBAAmB,OAAO,EAAE,CAAC,CAAC;IACrF,MAAM,GAAG,GAAG,2DAA2D,KAAK,SAAS,IAAI,aAAa,CAAC;IACvG,OAAO;QACL,uBAAuB,WAAW,CAAC,KAAK,CAAC,IAAI,MAAM,OAAO,EAAE;QAC5D,YAAY,OAAO,EAAE;QACrB,EAAE;QACF,0CAA0C;QAC1C,KAAK,GAAG,EAAE;KACX,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"error-codes.js","sourceRoot":"","sources":["../src/error-codes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAOH,SAAS,EAAE,CAAC,IAAY,EAAE,KAAa;IACrC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACzB,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,sBAAsB;IACtB,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,gCAAgC,CAAC;IACpD,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACrC,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,wBAAwB,CAAC;IAE5C,wBAAwB;IACxB,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,gCAAgC,CAAC;IACpD,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,oCAAoC,CAAC;IACxD,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACrC,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,0BAA0B,CAAC;IAC9C,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,qBAAqB,CAAC;IAEzC,yBAAyB;IACzB,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACrC,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,wBAAwB,CAAC;IAC5C,yEAAyE;IACzE,wEAAwE;IACxE,qEAAqE;IACrE,oEAAoE;IACpE,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,iCAAiC,CAAC;IACrD,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,wBAAwB,CAAC;IAE5C,uCAAuC;IACvC,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,2BAA2B,CAAC;IAE/C,6BAA6B;IAC7B,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,uBAAuB,CAAC;IAC3C,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,wBAAwB,CAAC;IAE5C,kBAAkB;IAClB,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,uBAAuB,CAAC;IAC3C,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,0BAA0B,CAAC;IAE9C,iCAAiC;IACjC,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,uBAAuB,CAAC;IAE3C,mBAAmB;IACnB,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,sBAAsB,CAAC;CAClC,CAAC;AAEX;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,SAAoB,EAAE,OAAe;IAC/D,OAAO,UAAU,SAAS,CAAC,IAAI,MAAM,OAAO,EAAE,CAAC;AACjD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe,EAAE,OAAe;IAC/D,MAAM,KAAK,GAAG,kBAAkB,CAAC,SAAS,OAAO,EAAE,CAAC,CAAC;IACrD,MAAM,IAAI,GAAG,kBAAkB,CAAC,gBAAgB,OAAO,mBAAmB,OAAO,EAAE,CAAC,CAAC;IACrF,MAAM,GAAG,GAAG,2DAA2D,KAAK,SAAS,IAAI,aAAa,CAAC;IACvG,OAAO;QACL,uBAAuB,WAAW,CAAC,KAAK,CAAC,IAAI,MAAM,OAAO,EAAE;QAC5D,YAAY,OAAO,EAAE;QACrB,EAAE;QACF,0CAA0C;QAC1C,KAAK,GAAG,EAAE;KACX,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * CSV output formatter — one row per SecurityIssue (plus a row per ERROR check)
3
+ * for spreadsheet analysis (Excel, Google Sheets, etc.).
4
+ *
5
+ * Output contract:
6
+ * - Encoding: UTF-8, no BOM. Excel-on-Windows may render non-ASCII bytes as
7
+ * mojibake when a CSV is opened directly without an encoding hint; consumers
8
+ * that need Excel auto-detection should use the JSON or SARIF output instead,
9
+ * or import the CSV via Excel's "From Text/CSV" wizard which lets you pick
10
+ * UTF-8 explicitly.
11
+ * - Line terminator: CRLF (RFC 4180). Final row is also CRLF-terminated.
12
+ * - Quoting: RFC 4180 — fields containing `,`, `"`, CR, or LF are wrapped in
13
+ * double quotes; embedded `"` is doubled.
14
+ * - Description: flattened to a single line (CR/LF/CRLF → single space) and
15
+ * truncated to {@link DESCRIPTION_MAX_LENGTH} chars with a U+2026 ellipsis.
16
+ * - The structured `dataFlow` taint trace on `SecurityIssue` is intentionally
17
+ * omitted — flat per-issue rows can't represent a list well; SARIF/JSON
18
+ * output keeps the full trace.
19
+ */
20
+ import type { ScanResults } from '../types.js';
21
+ import type { OutputFormatter } from './types.js';
22
+ /** Escapes a single CSV field per RFC 4180. */
23
+ export declare function escapeCsvField(value: string | number | undefined): string;
24
+ /**
25
+ * Truncates a description to fit within the CSV column and replaces newlines
26
+ * with spaces so each issue stays on a single row.
27
+ */
28
+ export declare function normalizeDescription(description: string): string;
29
+ export declare class CsvFormatter implements OutputFormatter {
30
+ readonly id = "csv";
31
+ readonly fileExtension = ".csv";
32
+ format(results: ScanResults): string;
33
+ }
34
+ //# sourceMappingURL=csv-formatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csv-formatter.d.ts","sourceRoot":"","sources":["../../src/formatters/csv-formatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAwC,MAAM,aAAa,CAAC;AACrF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAkBlD,+CAA+C;AAC/C,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAQzE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAMhE;AAyCD,qBAAa,YAAa,YAAW,eAAe;IAClD,QAAQ,CAAC,EAAE,SAAS;IACpB,QAAQ,CAAC,aAAa,UAAU;IAEhC,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM;CAqBrC"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * CSV output formatter — one row per SecurityIssue (plus a row per ERROR check)
3
+ * for spreadsheet analysis (Excel, Google Sheets, etc.).
4
+ *
5
+ * Output contract:
6
+ * - Encoding: UTF-8, no BOM. Excel-on-Windows may render non-ASCII bytes as
7
+ * mojibake when a CSV is opened directly without an encoding hint; consumers
8
+ * that need Excel auto-detection should use the JSON or SARIF output instead,
9
+ * or import the CSV via Excel's "From Text/CSV" wizard which lets you pick
10
+ * UTF-8 explicitly.
11
+ * - Line terminator: CRLF (RFC 4180). Final row is also CRLF-terminated.
12
+ * - Quoting: RFC 4180 — fields containing `,`, `"`, CR, or LF are wrapped in
13
+ * double quotes; embedded `"` is doubled.
14
+ * - Description: flattened to a single line (CR/LF/CRLF → single space) and
15
+ * truncated to {@link DESCRIPTION_MAX_LENGTH} chars with a U+2026 ellipsis.
16
+ * - The structured `dataFlow` taint trace on `SecurityIssue` is intentionally
17
+ * omitted — flat per-issue rows can't represent a list well; SARIF/JSON
18
+ * output keeps the full trace.
19
+ */
20
+ /** Maximum length of the description field in CSV output (longer values are truncated with an ellipsis). */
21
+ const DESCRIPTION_MAX_LENGTH = 500;
22
+ const CSV_HEADERS = [
23
+ 'checkId',
24
+ 'checkName',
25
+ 'status',
26
+ 'file',
27
+ 'startLine',
28
+ 'endLine',
29
+ 'severity',
30
+ 'confidence',
31
+ 'description',
32
+ 'recommendation',
33
+ ];
34
+ /** Escapes a single CSV field per RFC 4180. */
35
+ export function escapeCsvField(value) {
36
+ if (value === undefined || value === null)
37
+ return '';
38
+ const str = String(value);
39
+ // Wrap in quotes if it contains a comma, quote, CR, or LF.
40
+ if (/[",\r\n]/.test(str)) {
41
+ return `"${str.replace(/"/g, '""')}"`;
42
+ }
43
+ return str;
44
+ }
45
+ /**
46
+ * Truncates a description to fit within the CSV column and replaces newlines
47
+ * with spaces so each issue stays on a single row.
48
+ */
49
+ export function normalizeDescription(description) {
50
+ // Replace any sequence of CR/LF with a single space so the cell stays on one line
51
+ // (CSV viewers do support multi-line cells via quoting, but flattening keeps spreadsheets readable).
52
+ const flattened = description.replace(/\r\n|\r|\n/g, ' ').replace(/\s+/g, ' ').trim();
53
+ if (flattened.length <= DESCRIPTION_MAX_LENGTH)
54
+ return flattened;
55
+ return flattened.slice(0, DESCRIPTION_MAX_LENGTH - 1) + '…'; // ellipsis
56
+ }
57
+ function statusForIssue(issue, checkStatusByCheckId) {
58
+ // Fall back to 'UNKNOWN' (not 'FAIL') for orphaned issues so a row in the
59
+ // spreadsheet doesn't claim a check status that wasn't actually reported.
60
+ return checkStatusByCheckId.get(issue.checkId) ?? 'UNKNOWN';
61
+ }
62
+ function issueRow(issue, checkStatus) {
63
+ return [
64
+ escapeCsvField(issue.checkId),
65
+ escapeCsvField(issue.checkName),
66
+ escapeCsvField(checkStatus),
67
+ escapeCsvField(issue.file),
68
+ escapeCsvField(issue.startLine),
69
+ escapeCsvField(issue.endLine),
70
+ escapeCsvField(issue.severity),
71
+ escapeCsvField(issue.confidence),
72
+ escapeCsvField(normalizeDescription(issue.description)),
73
+ escapeCsvField(issue.recommendation),
74
+ ].join(',');
75
+ }
76
+ function errorCheckRow(check) {
77
+ // Surface execution errors so they show up alongside issues in spreadsheets.
78
+ // No file/line/severity/confidence/recommendation — just the error in the description column.
79
+ const description = normalizeDescription(check.error ? `Check execution error: ${check.error}` : 'Check execution error');
80
+ return [
81
+ escapeCsvField(check.checkId),
82
+ escapeCsvField(check.checkName),
83
+ escapeCsvField(check.status),
84
+ '', // file
85
+ '', // startLine
86
+ '', // endLine
87
+ '', // severity
88
+ '', // confidence
89
+ escapeCsvField(description),
90
+ '', // recommendation
91
+ ].join(',');
92
+ }
93
+ export class CsvFormatter {
94
+ id = 'csv';
95
+ fileExtension = '.csv';
96
+ format(results) {
97
+ const checkStatusByCheckId = new Map();
98
+ for (const check of results.checks) {
99
+ checkStatusByCheckId.set(check.checkId, check.status);
100
+ }
101
+ const lines = [CSV_HEADERS.join(',')];
102
+ for (const issue of results.issues) {
103
+ lines.push(issueRow(issue, statusForIssue(issue, checkStatusByCheckId)));
104
+ }
105
+ for (const check of results.checks) {
106
+ if (check.status === 'ERROR') {
107
+ lines.push(errorCheckRow(check));
108
+ }
109
+ }
110
+ // RFC 4180 specifies CRLF line endings.
111
+ return lines.join('\r\n') + '\r\n';
112
+ }
113
+ }
114
+ //# sourceMappingURL=csv-formatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csv-formatter.js","sourceRoot":"","sources":["../../src/formatters/csv-formatter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * HTML output formatter — self-contained interactive report.
3
+ *
4
+ * Emits a single .html file with inline CSS + JS. Includes:
5
+ * - Header with title, scan timestamp, repository info
6
+ * - Summary statistics (totals + per-status counters)
7
+ * - Filterable, severity-badged issues table
8
+ * - Expandable per-check sections with optional code snippets
9
+ * - The full `ScanResults` embedded as a JSON island for client-side filtering
10
+ *
11
+ * All user-controlled strings are HTML-escaped via `escapeHtml`. The embedded
12
+ * JSON has `</` sequences neutralised so an attacker-controlled string cannot
13
+ * break out of the `<script type="application/json">` tag.
14
+ */
15
+ import type { ScanResults } from '../types.js';
16
+ import type { OutputFormatter } from './types.js';
17
+ /** Escapes the five HTML special characters. */
18
+ export declare function escapeHtml(value: unknown): string;
19
+ /**
20
+ * Embeds JSON inside a `<script type="application/json">` block safely.
21
+ *
22
+ * Two patterns matter for HTML's script-data tokenizer:
23
+ * 1. `</` — replaced with `<\/` so a stray `</script>` cannot terminate the
24
+ * script element early. `\/` is valid JSON for `/`, so JSON.parse
25
+ * round-trips faithfully.
26
+ * 2. `<!--` — the script-data tokenizer enters a "script data escaped"
27
+ * state on `<!--`, in which a nested `<script>...</script>` pair can
28
+ * terminate the outer element ahead of the literal closing tag we control.
29
+ * Encoding `<` as the Unicode escape `<` is unambiguous JSON
30
+ * (parses back to `<`) and prevents the tokenizer from ever entering the
31
+ * escaped state.
32
+ */
33
+ export declare function escapeJsonForScriptTag(json: string): string;
34
+ export declare class HtmlFormatter implements OutputFormatter {
35
+ readonly id = "html";
36
+ readonly fileExtension = ".html";
37
+ format(results: ScanResults): string;
38
+ }
39
+ //# sourceMappingURL=html-formatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-formatter.d.ts","sourceRoot":"","sources":["../../src/formatters/html-formatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EACV,WAAW,EAKZ,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,gDAAgD;AAChD,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CASjD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI3D;AA0TD,qBAAa,aAAc,YAAW,eAAe;IACnD,QAAQ,CAAC,EAAE,UAAU;IACrB,QAAQ,CAAC,aAAa,WAAW;IAEjC,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM;CAsCrC"}
@@ -0,0 +1,392 @@
1
+ /**
2
+ * HTML output formatter — self-contained interactive report.
3
+ *
4
+ * Emits a single .html file with inline CSS + JS. Includes:
5
+ * - Header with title, scan timestamp, repository info
6
+ * - Summary statistics (totals + per-status counters)
7
+ * - Filterable, severity-badged issues table
8
+ * - Expandable per-check sections with optional code snippets
9
+ * - The full `ScanResults` embedded as a JSON island for client-side filtering
10
+ *
11
+ * All user-controlled strings are HTML-escaped via `escapeHtml`. The embedded
12
+ * JSON has `</` sequences neutralised so an attacker-controlled string cannot
13
+ * break out of the `<script type="application/json">` tag.
14
+ */
15
+ /** Escapes the five HTML special characters. */
16
+ export function escapeHtml(value) {
17
+ if (value === undefined || value === null)
18
+ return '';
19
+ const str = String(value);
20
+ return str
21
+ .replace(/&/g, '&amp;')
22
+ .replace(/</g, '&lt;')
23
+ .replace(/>/g, '&gt;')
24
+ .replace(/"/g, '&quot;')
25
+ .replace(/'/g, '&#39;');
26
+ }
27
+ /**
28
+ * Embeds JSON inside a `<script type="application/json">` block safely.
29
+ *
30
+ * Two patterns matter for HTML's script-data tokenizer:
31
+ * 1. `</` — replaced with `<\/` so a stray `</script>` cannot terminate the
32
+ * script element early. `\/` is valid JSON for `/`, so JSON.parse
33
+ * round-trips faithfully.
34
+ * 2. `<!--` — the script-data tokenizer enters a "script data escaped"
35
+ * state on `<!--`, in which a nested `<script>...</script>` pair can
36
+ * terminate the outer element ahead of the literal closing tag we control.
37
+ * Encoding `<` as the Unicode escape `<` is unambiguous JSON
38
+ * (parses back to `<`) and prevents the tokenizer from ever entering the
39
+ * escaped state.
40
+ */
41
+ export function escapeJsonForScriptTag(json) {
42
+ return json
43
+ .replace(/<\//g, '<\\/')
44
+ .replace(/<!--/g, '\\u003c!--');
45
+ }
46
+ function severityClass(severity) {
47
+ switch (severity) {
48
+ case 'critical':
49
+ case 'high':
50
+ return 'sev-high';
51
+ case 'medium':
52
+ return 'sev-medium';
53
+ case 'low':
54
+ case 'informational':
55
+ return 'sev-low';
56
+ default:
57
+ return 'sev-unknown';
58
+ }
59
+ }
60
+ function statusClass(status) {
61
+ switch (status) {
62
+ case 'PASS':
63
+ return 'status-pass';
64
+ case 'FAIL':
65
+ return 'status-fail';
66
+ case 'FLAG':
67
+ return 'status-flag';
68
+ case 'ERROR':
69
+ return 'status-error';
70
+ default:
71
+ return 'status-unknown';
72
+ }
73
+ }
74
+ const CSS = `
75
+ :root {
76
+ color-scheme: light dark;
77
+ --bg: #f6f7f9;
78
+ --fg: #1f2329;
79
+ --muted: #6a7280;
80
+ --card: #ffffff;
81
+ --border: #e2e5ea;
82
+ --pass: #15803d;
83
+ --fail: #b91c1c;
84
+ --flag: #b45309;
85
+ --error: #6b21a8;
86
+ --sev-high: #b91c1c;
87
+ --sev-medium: #b45309;
88
+ --sev-low: #2563eb;
89
+ --sev-unknown: #6a7280;
90
+ }
91
+ @media (prefers-color-scheme: dark) {
92
+ :root {
93
+ --bg: #14171c;
94
+ --fg: #e6e8eb;
95
+ --muted: #9ba1aa;
96
+ --card: #1c2026;
97
+ --border: #2a2f37;
98
+ }
99
+ }
100
+ * { box-sizing: border-box; }
101
+ body {
102
+ margin: 0;
103
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
104
+ background: var(--bg);
105
+ color: var(--fg);
106
+ line-height: 1.5;
107
+ }
108
+ .container { max-width: 1200px; margin: 0 auto; padding: 24px; }
109
+ header { margin-bottom: 24px; }
110
+ header h1 { margin: 0 0 4px; font-size: 1.6rem; }
111
+ header .meta { color: var(--muted); font-size: 0.9rem; }
112
+ .muted { color: var(--muted); }
113
+ .card {
114
+ background: var(--card);
115
+ border: 1px solid var(--border);
116
+ border-radius: 8px;
117
+ padding: 16px;
118
+ margin-bottom: 16px;
119
+ }
120
+ .summary-grid {
121
+ display: grid;
122
+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
123
+ gap: 12px;
124
+ }
125
+ .stat {
126
+ background: var(--card);
127
+ border: 1px solid var(--border);
128
+ border-radius: 8px;
129
+ padding: 12px;
130
+ text-align: center;
131
+ }
132
+ .stat .num { font-size: 1.6rem; font-weight: 600; }
133
+ .stat .label { color: var(--muted); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em; }
134
+ .badge {
135
+ display: inline-block;
136
+ padding: 2px 8px;
137
+ border-radius: 999px;
138
+ font-size: 0.75rem;
139
+ font-weight: 600;
140
+ text-transform: uppercase;
141
+ letter-spacing: 0.04em;
142
+ color: #fff;
143
+ }
144
+ .badge.sev-high { background: var(--sev-high); }
145
+ .badge.sev-medium { background: var(--sev-medium); }
146
+ .badge.sev-low { background: var(--sev-low); }
147
+ .badge.sev-unknown { background: var(--sev-unknown); }
148
+ .badge.status-pass { background: var(--pass); }
149
+ .badge.status-fail { background: var(--fail); }
150
+ .badge.status-flag { background: var(--flag); }
151
+ .badge.status-error { background: var(--error); }
152
+ .badge.status-unknown { background: var(--sev-unknown); }
153
+ .controls { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
154
+ .controls input, .controls select {
155
+ background: var(--card);
156
+ color: var(--fg);
157
+ border: 1px solid var(--border);
158
+ border-radius: 6px;
159
+ padding: 6px 10px;
160
+ font: inherit;
161
+ }
162
+ table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
163
+ th, td {
164
+ text-align: left;
165
+ padding: 8px 10px;
166
+ border-bottom: 1px solid var(--border);
167
+ vertical-align: top;
168
+ }
169
+ th { background: var(--card); font-weight: 600; }
170
+ tr.hidden { display: none; }
171
+ details { margin-top: 8px; }
172
+ details summary { cursor: pointer; font-weight: 600; }
173
+ pre.snippet {
174
+ background: var(--bg);
175
+ border: 1px solid var(--border);
176
+ border-radius: 6px;
177
+ padding: 8px;
178
+ overflow-x: auto;
179
+ font-size: 0.8rem;
180
+ margin: 8px 0 0;
181
+ }
182
+ .empty { color: var(--muted); font-style: italic; }
183
+ footer { color: var(--muted); font-size: 0.8rem; text-align: center; margin: 32px 0 16px; }
184
+ `;
185
+ const CLIENT_JS = `
186
+ (function () {
187
+ var data;
188
+ try {
189
+ data = JSON.parse(document.getElementById('aghast-results').textContent);
190
+ } catch (e) {
191
+ return;
192
+ }
193
+ var search = document.getElementById('filter-search');
194
+ var severity = document.getElementById('filter-severity');
195
+ var status = document.getElementById('filter-status');
196
+ var rows = Array.prototype.slice.call(document.querySelectorAll('#issues-table tbody tr'));
197
+
198
+ function applyFilters() {
199
+ var q = (search.value || '').toLowerCase();
200
+ var sev = severity.value;
201
+ var st = status.value;
202
+ rows.forEach(function (row) {
203
+ var text = row.getAttribute('data-search') || '';
204
+ var rowSev = row.getAttribute('data-severity') || '';
205
+ var rowStatus = row.getAttribute('data-status') || '';
206
+ var matches = (!q || text.indexOf(q) !== -1)
207
+ && (!sev || rowSev === sev)
208
+ && (!st || rowStatus === st);
209
+ row.classList.toggle('hidden', !matches);
210
+ });
211
+ }
212
+
213
+ if (search) search.addEventListener('input', applyFilters);
214
+ if (severity) severity.addEventListener('change', applyFilters);
215
+ if (status) status.addEventListener('change', applyFilters);
216
+
217
+ // Expose data for debugging / extensibility.
218
+ window.aghastResults = data;
219
+ })();
220
+ `;
221
+ function renderHeader(results) {
222
+ const repo = results.repository;
223
+ const repoLabel = repo.remoteUrl
224
+ ? `${escapeHtml(repo.remoteUrl)}`
225
+ : escapeHtml(repo.path);
226
+ const branch = repo.branch ? ` <span class="muted">on ${escapeHtml(repo.branch)}</span>` : '';
227
+ const commit = repo.commit ? ` <span class="muted">@ ${escapeHtml(repo.commit.slice(0, 12))}</span>` : '';
228
+ return `
229
+ <header>
230
+ <h1>aghast Security Scan Report</h1>
231
+ <div class="meta">
232
+ Scan ID: <code>${escapeHtml(results.scanId)}</code> &middot;
233
+ Generated: ${escapeHtml(results.timestamp)} &middot;
234
+ Provider: ${escapeHtml(results.agentProvider.name)}
235
+ </div>
236
+ <div class="meta">
237
+ Repository: ${repoLabel}${branch}${commit}
238
+ </div>
239
+ </header>
240
+ `;
241
+ }
242
+ function renderSummary(summary, _repo) {
243
+ return `
244
+ <section class="summary-grid">
245
+ <div class="stat"><div class="num">${summary.totalChecks}</div><div class="label">Checks</div></div>
246
+ <div class="stat"><div class="num">${summary.passedChecks}</div><div class="label">Passed</div></div>
247
+ <div class="stat"><div class="num">${summary.failedChecks}</div><div class="label">Failed</div></div>
248
+ <div class="stat"><div class="num">${summary.flaggedChecks}</div><div class="label">Flagged</div></div>
249
+ <div class="stat"><div class="num">${summary.errorChecks}</div><div class="label">Errors</div></div>
250
+ <div class="stat"><div class="num">${summary.totalIssues}</div><div class="label">Issues</div></div>
251
+ </section>
252
+ `;
253
+ }
254
+ function renderIssuesTable(issues, statusByCheck) {
255
+ if (issues.length === 0) {
256
+ return `<section class="card"><h2>Issues</h2><p class="empty">No issues detected.</p></section>`;
257
+ }
258
+ const rows = issues.map((issue) => {
259
+ const sev = issue.severity ?? '';
260
+ const sevCls = severityClass(issue.severity);
261
+ const sevBadge = `<span class="badge ${sevCls}">${escapeHtml(sev || 'unknown')}</span>`;
262
+ // Fall back to 'UNKNOWN' (not 'FAIL') for orphaned issues so the row's
263
+ // status badge and the data-status filter don't lie when an issue
264
+ // references a checkId that isn't in the checks list.
265
+ const status = statusByCheck.get(issue.checkId) ?? 'UNKNOWN';
266
+ const statusCls = statusClass(status);
267
+ const statusBadge = `<span class="badge ${statusCls}">${escapeHtml(status)}</span>`;
268
+ const searchBlob = `${issue.checkId} ${issue.checkName} ${issue.file} ${issue.description}`.toLowerCase();
269
+ return `
270
+ <tr data-severity="${escapeHtml(sev)}" data-status="${escapeHtml(status)}" data-search="${escapeHtml(searchBlob)}">
271
+ <td>${escapeHtml(issue.checkId)}</td>
272
+ <td>${escapeHtml(issue.checkName)}</td>
273
+ <td>${statusBadge}</td>
274
+ <td>${sevBadge}</td>
275
+ <td><code>${escapeHtml(issue.file)}</code>:${escapeHtml(issue.startLine)}-${escapeHtml(issue.endLine)}</td>
276
+ <td>${escapeHtml(issue.description)}</td>
277
+ </tr>
278
+ `;
279
+ }).join('');
280
+ return `
281
+ <section class="card">
282
+ <h2>Issues</h2>
283
+ <div class="controls">
284
+ <input id="filter-search" type="search" placeholder="Filter by text..." />
285
+ <select id="filter-severity">
286
+ <option value="">All severities</option>
287
+ <option value="critical">Critical</option>
288
+ <option value="high">High</option>
289
+ <option value="medium">Medium</option>
290
+ <option value="low">Low</option>
291
+ <option value="informational">Informational</option>
292
+ </select>
293
+ <select id="filter-status">
294
+ <option value="">All statuses</option>
295
+ <option value="PASS">Pass</option>
296
+ <option value="FAIL">Fail</option>
297
+ <option value="FLAG">Flag</option>
298
+ <option value="ERROR">Error</option>
299
+ </select>
300
+ </div>
301
+ <table id="issues-table" aria-label="Security issues">
302
+ <thead>
303
+ <tr><th>Check ID</th><th>Check</th><th>Status</th><th>Severity</th><th>Location</th><th>Description</th></tr>
304
+ </thead>
305
+ <tbody>${rows}</tbody>
306
+ </table>
307
+ </section>
308
+ `;
309
+ }
310
+ function renderCheckDetails(checks, issuesByCheck) {
311
+ if (checks.length === 0) {
312
+ return '';
313
+ }
314
+ const sections = checks.map((check) => {
315
+ const checkIssues = issuesByCheck.get(check.checkId) ?? [];
316
+ const statusBadge = `<span class="badge ${statusClass(check.status)}">${escapeHtml(check.status)}</span>`;
317
+ const errorBlock = check.error
318
+ ? `<p><strong>Error:</strong> ${escapeHtml(check.error)}</p>`
319
+ : '';
320
+ const issuesBlock = checkIssues.length === 0
321
+ ? '<p class="empty">No issues for this check.</p>'
322
+ : checkIssues.map((issue) => {
323
+ const snippet = issue.codeSnippet
324
+ ? `<pre class="snippet">${escapeHtml(issue.codeSnippet)}</pre>`
325
+ : '';
326
+ const recommendation = issue.recommendation
327
+ ? `<p><strong>Recommendation:</strong> ${escapeHtml(issue.recommendation)}</p>`
328
+ : '';
329
+ return `
330
+ <div class="card">
331
+ <div><strong><code>${escapeHtml(issue.file)}</code>:${escapeHtml(issue.startLine)}-${escapeHtml(issue.endLine)}</strong>
332
+ ${issue.severity ? `<span class="badge ${severityClass(issue.severity)}">${escapeHtml(issue.severity)}</span>` : ''}
333
+ </div>
334
+ <p>${escapeHtml(issue.description)}</p>
335
+ ${recommendation}
336
+ ${snippet}
337
+ </div>
338
+ `;
339
+ }).join('');
340
+ return `
341
+ <details>
342
+ <summary>${escapeHtml(check.checkName)} (${escapeHtml(check.checkId)}) ${statusBadge} &middot; ${check.issuesFound} issue(s)</summary>
343
+ ${errorBlock}
344
+ ${issuesBlock}
345
+ </details>
346
+ `;
347
+ }).join('');
348
+ return `<section class="card"><h2>Checks</h2>${sections}</section>`;
349
+ }
350
+ export class HtmlFormatter {
351
+ id = 'html';
352
+ fileExtension = '.html';
353
+ format(results) {
354
+ const statusByCheck = new Map();
355
+ const issuesByCheck = new Map();
356
+ for (const check of results.checks) {
357
+ statusByCheck.set(check.checkId, check.status);
358
+ issuesByCheck.set(check.checkId, []);
359
+ }
360
+ for (const issue of results.issues) {
361
+ const arr = issuesByCheck.get(issue.checkId);
362
+ if (arr)
363
+ arr.push(issue);
364
+ else
365
+ issuesByCheck.set(issue.checkId, [issue]);
366
+ }
367
+ const embeddedJson = escapeJsonForScriptTag(JSON.stringify(results));
368
+ const title = `aghast scan ${escapeHtml(results.scanId)}`;
369
+ return `<!DOCTYPE html>
370
+ <html lang="en">
371
+ <head>
372
+ <meta charset="utf-8" />
373
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
374
+ <title>${title}</title>
375
+ <style>${CSS}</style>
376
+ </head>
377
+ <body>
378
+ <div class="container">
379
+ ${renderHeader(results)}
380
+ ${renderSummary(results.summary, results.repository)}
381
+ ${renderIssuesTable(results.issues, statusByCheck)}
382
+ ${renderCheckDetails(results.checks, issuesByCheck)}
383
+ <footer>Generated by aghast v${escapeHtml(results.version)}</footer>
384
+ </div>
385
+ <script id="aghast-results" type="application/json">${embeddedJson}</script>
386
+ <script>${CLIENT_JS}</script>
387
+ </body>
388
+ </html>
389
+ `;
390
+ }
391
+ }
392
+ //# sourceMappingURL=html-formatter.js.map