@portll/cobolwork 0.5.0 → 0.7.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.
Files changed (163) hide show
  1. package/README.md +89 -26
  2. package/STABILITY.md +76 -0
  3. package/bin/cobolwork.mjs +56 -14
  4. package/lib/baseline.mjs +9 -0
  5. package/lib/bms.mjs +21 -7
  6. package/lib/build.mjs +133 -53
  7. package/lib/capabilities.mjs +13 -11
  8. package/lib/cics-commands.mjs +501 -9
  9. package/lib/compliance.mjs +11 -0
  10. package/lib/consequence.mjs +17 -0
  11. package/lib/control-reuse.mjs +113 -0
  12. package/lib/control-workers.mjs +161 -0
  13. package/lib/control.mjs +77 -4
  14. package/lib/dataflow.mjs +402 -152
  15. package/lib/db2/cursor.mjs +15 -0
  16. package/lib/db2/read.mjs +170 -0
  17. package/lib/db2/rules.mjs +77 -0
  18. package/lib/db2/stmt/alter.mjs +473 -0
  19. package/lib/db2/stmt/grant.mjs +125 -0
  20. package/lib/db2/stmt/index.mjs +141 -0
  21. package/lib/db2/stmt/misc.mjs +325 -0
  22. package/lib/db2/stmt/routine.mjs +564 -0
  23. package/lib/db2/stmt/storage.mjs +146 -0
  24. package/lib/db2/stmt/table.mjs +540 -0
  25. package/lib/db2/stmt/view.mjs +146 -0
  26. package/lib/diff.mjs +17 -5
  27. package/lib/equivalence.mjs +44 -5
  28. package/lib/evidence/cli.mjs +26 -7
  29. package/lib/evidence/record.mjs +16 -6
  30. package/lib/evidence/seal.mjs +17 -0
  31. package/lib/evidence/store.mjs +44 -15
  32. package/lib/evidence/timestamp.mjs +83 -0
  33. package/lib/evidence/verify.mjs +120 -42
  34. package/lib/exec-reading.mjs +51 -0
  35. package/lib/execution.mjs +3 -2
  36. package/lib/explain.mjs +2 -0
  37. package/lib/exploitability.mjs +11 -2
  38. package/lib/hlasm/asm/data.mjs +128 -0
  39. package/lib/hlasm/asm/listing.mjs +43 -0
  40. package/lib/hlasm/asm/output.mjs +45 -0
  41. package/lib/hlasm/asm/sections.mjs +161 -0
  42. package/lib/hlasm/asm/symbols.mjs +78 -0
  43. package/lib/hlasm/exec.mjs +27 -0
  44. package/lib/hlasm/expr.mjs +167 -0
  45. package/lib/hlasm/instr.mjs +73 -0
  46. package/lib/hlasm/locate.mjs +333 -0
  47. package/lib/hlasm/macro/authorization.mjs +146 -0
  48. package/lib/hlasm/macro/datasets.mjs +107 -0
  49. package/lib/hlasm/macro/io.mjs +162 -0
  50. package/lib/hlasm/macro/le.mjs +53 -0
  51. package/lib/hlasm/macro/linkage.mjs +143 -0
  52. package/lib/hlasm/macro/operator.mjs +77 -0
  53. package/lib/hlasm/macro/program.mjs +184 -0
  54. package/lib/hlasm/macro/recovery.mjs +85 -0
  55. package/lib/hlasm/macro/storage.mjs +167 -0
  56. package/lib/hlasm/macro/structured.mjs +131 -0
  57. package/lib/hlasm/model.mjs +97 -0
  58. package/lib/hlasm/mvs38.mjs +47 -0
  59. package/lib/hlasm/operands.mjs +59 -0
  60. package/lib/hlasm/optable.mjs +61 -0
  61. package/lib/hlasm/read.mjs +130 -0
  62. package/lib/hlasm.mjs +44 -7
  63. package/lib/ims/dli.mjs +37 -0
  64. package/lib/ims/macro/dbd.mjs +299 -0
  65. package/lib/ims/macro/psb.mjs +286 -0
  66. package/lib/ims/model.mjs +149 -0
  67. package/lib/ims/operands.mjs +23 -0
  68. package/lib/ims/read.mjs +37 -0
  69. package/lib/ims/rules.mjs +135 -0
  70. package/lib/inventory.mjs +10 -7
  71. package/lib/ironwork-ids.mjs +24 -0
  72. package/lib/ironwork.mjs +20 -13
  73. package/lib/kernel/pds-archive.mjs +256 -0
  74. package/lib/kernel/registry.mjs +32 -23
  75. package/lib/kernel/shared-pass.mjs +163 -0
  76. package/lib/kernel/source-tree.mjs +104 -36
  77. package/lib/kernel/version-key.mjs +17 -0
  78. package/lib/layout.mjs +26 -31
  79. package/lib/options.mjs +26 -4
  80. package/lib/parser.mjs +136 -17
  81. package/lib/pli/cursor.mjs +15 -0
  82. package/lib/pli/expr.mjs +101 -0
  83. package/lib/pli/include.mjs +82 -0
  84. package/lib/pli/layout.mjs +125 -0
  85. package/lib/pli/lex.mjs +198 -0
  86. package/lib/pli/program.mjs +280 -0
  87. package/lib/pli/rules/based.mjs +95 -0
  88. package/lib/pli/rules/conditions.mjs +68 -0
  89. package/lib/pli/rules/entry.mjs +130 -0
  90. package/lib/pli/rules/index.mjs +24 -0
  91. package/lib/pli/rules/preprocessor.mjs +55 -0
  92. package/lib/pli/statements.mjs +130 -0
  93. package/lib/pli/stmt/alloc.mjs +45 -0
  94. package/lib/pli/stmt/assignment.mjs +56 -0
  95. package/lib/pli/stmt/call.mjs +104 -0
  96. package/lib/pli/stmt/conditions.mjs +94 -0
  97. package/lib/pli/stmt/control.mjs +219 -0
  98. package/lib/pli/stmt/declare.mjs +149 -0
  99. package/lib/pli/stmt/exec.mjs +55 -0
  100. package/lib/pli/stmt/io.mjs +239 -0
  101. package/lib/pli/stmt/misc.mjs +4 -0
  102. package/lib/pli/stmt/preprocessor.mjs +242 -0
  103. package/lib/pli/stmt/procedure.mjs +258 -0
  104. package/lib/pli/stmt/stream.mjs +283 -0
  105. package/lib/pli/storage.mjs +129 -0
  106. package/lib/policy.mjs +6 -0
  107. package/lib/precompile-check.mjs +124 -0
  108. package/lib/precompile-cics.mjs +8 -4
  109. package/lib/reach.mjs +11 -2
  110. package/lib/revision.json +1 -1
  111. package/lib/sarif.mjs +44 -3
  112. package/lib/scan.mjs +28 -12
  113. package/lib/sets/abend.mjs +82 -8
  114. package/lib/sets/build.mjs +43 -37
  115. package/lib/sets/cics.mjs +20 -38
  116. package/lib/sets/compile.mjs +46 -18
  117. package/lib/sets/copybook.mjs +5 -3
  118. package/lib/sets/crypto.mjs +5 -3
  119. package/lib/sets/ddl.mjs +36 -0
  120. package/lib/sets/flow.mjs +32 -8
  121. package/lib/sets/hidden.mjs +5 -3
  122. package/lib/sets/hlasm.mjs +124 -8
  123. package/lib/sets/ims.mjs +139 -0
  124. package/lib/sets/log.mjs +11 -9
  125. package/lib/sets/opaque.mjs +27 -7
  126. package/lib/sets/pli.mjs +40 -0
  127. package/lib/sets/priv.mjs +5 -3
  128. package/lib/sets/recon.mjs +5 -3
  129. package/lib/sets/secrets.mjs +112 -0
  130. package/lib/sets/semantics.mjs +8 -3
  131. package/lib/sets/web.mjs +39 -23
  132. package/lib/site.mjs +10 -0
  133. package/lib/sources.mjs +80 -20
  134. package/lib/statement-cursor.mjs +67 -0
  135. package/lib/verify.mjs +3 -2
  136. package/lib/version.mjs +6 -0
  137. package/package.json +3 -2
  138. package/rules/compliance-cobit2019.json +520 -5
  139. package/rules/compliance-dora.json +509 -5
  140. package/rules/compliance-ffiec.json +505 -1
  141. package/rules/compliance-nist80053.json +557 -1
  142. package/rules/gitleaks-mainframe.toml +46 -15
  143. package/rules/hlasm-instructions.json +2396 -0
  144. package/rules/hlasm-optables.json +8024 -0
  145. package/schema/cobolwork-baseline.schema.json +36 -0
  146. package/schema/cobolwork-build-provenance.schema.json +187 -0
  147. package/schema/cobolwork-build.schema.json +382 -0
  148. package/schema/cobolwork-capabilities.schema.json +239 -0
  149. package/schema/cobolwork-diff.schema.json +217 -0
  150. package/schema/cobolwork-evidence.schema.json +161 -0
  151. package/schema/cobolwork-execution.schema.json +53 -0
  152. package/schema/cobolwork-explain.schema.json +360 -0
  153. package/schema/cobolwork-finding.schema.json +465 -0
  154. package/schema/cobolwork-flow.schema.json +465 -0
  155. package/schema/cobolwork-gate.schema.json +211 -0
  156. package/schema/cobolwork-inventory.schema.json +206 -0
  157. package/schema/cobolwork-parse.schema.json +105 -0
  158. package/schema/cobolwork-reach.schema.json +74 -0
  159. package/schema/cobolwork-report.schema.json +559 -0
  160. package/schema/cobolwork-witness.schema.json +107 -0
  161. package/schema/cobolwork.baseline.schema.json +101 -0
  162. package/schema/cobolwork.policy.schema.json +7 -0
  163. package/schema/cobolwork.site.schema.json +116 -0
@@ -0,0 +1,15 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // The Db2 statement cursor: the shared token cursor, failing with Db2Syntax.
3
+ import { tokenCursor, split } from '../statement-cursor.mjs';
4
+
5
+ export class Db2Syntax extends Error {
6
+ constructor(message, tok) {
7
+ super(message);
8
+ this.name = 'Db2Syntax';
9
+ this.line = tok ? tok.line : null;
10
+ this.col = tok ? tok.col : null;
11
+ }
12
+ }
13
+
14
+ export const cursor = (toks) => tokenCursor(toks, Db2Syntax);
15
+ export { split };
@@ -0,0 +1,170 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Db2 SQL scripts as tokens and statements, and each statement's kind: CREATE TABLE, GRANT, ALTER
3
+ // TABLE and the rest. A script ends statements with ';' unless a `--#SET TERMINATOR x` line names
4
+ // another character, as Db2's command line processor allows for procedure bodies.
5
+ import { cursor, Db2Syntax } from './cursor.mjs';
6
+ import { parsers as table } from './stmt/table.mjs';
7
+ import { parsers as index } from './stmt/index.mjs';
8
+ import { parsers as view } from './stmt/view.mjs';
9
+ import { parsers as grant } from './stmt/grant.mjs';
10
+ import { parsers as alter } from './stmt/alter.mjs';
11
+ import { parsers as storage } from './stmt/storage.mjs';
12
+ import { parsers as routine } from './stmt/routine.mjs';
13
+ import { parsers as misc } from './stmt/misc.mjs';
14
+
15
+ export const PARSERS = Object.freeze({ ...table, ...index, ...view, ...grant, ...alter, ...storage, ...routine, ...misc });
16
+
17
+ const WORD = /[A-Za-z_$#@À-ɏ][A-Za-z0-9_$#@À-ɏ]*/y;
18
+ const NUMBER = /(?:\d+\.?\d*|\.\d+)(?:[Ee][+-]?\d+)?/y;
19
+ const OPS = ['<>', '¬=', '!=', '<=', '>=', '||', '=>', '(', ')', ',', '.', ';', ':', '+', '-', '*', '/', '=', '<', '>', '?', '@', '#', '%', '&', '|', '[', ']'];
20
+
21
+ // { t: 'word', v, u } | { t: 'lit', v, prefix } | { t: 'ident', v } (a "delimited identifier") |
22
+ // { t: 'num', v } | { t: 'op', v } | { t: 'end' } at a statement terminator; each with line and col.
23
+ export function tokenize(text) {
24
+ const tokens = [];
25
+ const diags = [];
26
+ let terminator = ';';
27
+ let line = 1;
28
+ let col = 1;
29
+ let i = 0;
30
+ const advance = (n) => { for (let k = 0; k < n; k++, i++) { if (text[i] === '\n') { line++; col = 1; } else col++; } };
31
+ while (i < text.length) {
32
+ const c = text[i];
33
+ if (c === '\n' || c === ' ' || c === '\t' || c === '\r' || c === '\f') { advance(1); continue; }
34
+ if (c === '-' && text[i + 1] === '-') {
35
+ let e = text.indexOf('\n', i);
36
+ if (e < 0) e = text.length;
37
+ const comment = text.slice(i, e);
38
+ const set = /^--#SET\s+TERMINATOR\s+(\S)/i.exec(comment);
39
+ if (set) terminator = set[1];
40
+ advance(e - i);
41
+ continue;
42
+ }
43
+ if (c === '/' && text[i + 1] === '*') {
44
+ const e = text.indexOf('*/', i + 2);
45
+ if (e < 0) { diags.push({ sev: 'error', kind: 'unterminated-comment', line, col }); advance(text.length - i); continue; }
46
+ advance(e + 2 - i);
47
+ continue;
48
+ }
49
+ const at = { line, col };
50
+ if (c === terminator) { tokens.push({ t: 'end', v: c, ...at }); advance(1); continue; }
51
+ const prefixed = /^(?:[XxGgNn]|[Uu]&|[Bb][Xx]|[Gg][Xx])'/.exec(text.slice(i, i + 3));
52
+ if (c === "'" || prefixed) {
53
+ const start = prefixed ? i + prefixed[0].length - 1 : i;
54
+ let j = start + 1, v = '';
55
+ for (; j < text.length; j++) {
56
+ if (text[j] === "'") { if (text[j + 1] === "'") { v += "'"; j++; continue; } break; }
57
+ v += text[j];
58
+ }
59
+ if (j >= text.length) diags.push({ sev: 'error', kind: 'unterminated-literal', ...at });
60
+ tokens.push({ t: 'lit', v, prefix: prefixed ? prefixed[0].slice(0, -1).toUpperCase() : '', ...at });
61
+ advance(j + 1 - i);
62
+ continue;
63
+ }
64
+ if (c === '"') {
65
+ let j = i + 1, v = '';
66
+ for (; j < text.length; j++) { if (text[j] === '"') { if (text[j + 1] === '"') { v += '"'; j++; continue; } break; } v += text[j]; }
67
+ tokens.push({ t: 'ident', v, ...at });
68
+ advance(j + 1 - i);
69
+ continue;
70
+ }
71
+ NUMBER.lastIndex = i;
72
+ const num = /[0-9.]/.test(c) ? NUMBER.exec(text) : null;
73
+ if (num && num[0] !== '.') { tokens.push({ t: 'num', v: num[0], ...at }); advance(num[0].length); continue; }
74
+ WORD.lastIndex = i;
75
+ const w = WORD.exec(text);
76
+ // A terminator such as @ is otherwise a character a name may hold.
77
+ const word = w && (w[0].includes(terminator) ? w[0].slice(0, w[0].indexOf(terminator)) : w[0]);
78
+ if (word) { tokens.push({ t: 'word', v: word, u: word.toUpperCase(), ...at }); advance(word.length); continue; }
79
+ const op = OPS.find((o) => text.startsWith(o, i));
80
+ if (op) { tokens.push({ t: 'op', v: op, ...at }); advance(op.length); continue; }
81
+ diags.push({ sev: 'warn', kind: 'unexpected-char', char: c, ...at });
82
+ advance(1);
83
+ }
84
+ return { tokens, diags };
85
+ }
86
+
87
+ // END IF, END LOOP and the like close statements that opened nothing the splitter counts; a CASE,
88
+ // statement or expression, opens a level its END closes.
89
+ const NON_OPENING_ENDS = new Set(['IF', 'LOOP', 'WHILE', 'REPEAT', 'FOR']);
90
+
91
+ // Whether the tokens so far open a routine whose body is an SQL compound statement: its own
92
+ // statements end in the same terminator, so the routine ends at the terminator after its last END.
93
+ const routineHead = (cur) => {
94
+ const w = cur.slice(0, 6).filter((t) => t.t === 'word').map((t) => t.u);
95
+ return w[0] === 'CREATE' && w.slice(1, 4).some((x) => x === 'PROCEDURE' || x === 'FUNCTION' || x === 'TRIGGER');
96
+ };
97
+
98
+ // Statements split at terminators; a trailing run with none is kept and flagged.
99
+ export function statements(tokens) {
100
+ const out = [];
101
+ let cur = [];
102
+ let depth = 0;
103
+ const close = (end) => {
104
+ if (!cur.length) return;
105
+ out.push({ toks: cur, line: cur[0].line, endLine: (end || cur[cur.length - 1]).line, ...(end ? {} : { unterminated: true }) });
106
+ cur = [];
107
+ depth = 0;
108
+ };
109
+ for (let k = 0; k < tokens.length; k++) {
110
+ const t = tokens[k];
111
+ if (t.t === 'end') { if (depth > 0) cur.push({ ...t, t: 'op', v: t.v }); else close(t); continue; }
112
+ cur.push(t);
113
+ if (t.t !== 'word' || !routineHead(cur)) continue;
114
+ const afterEnd = cur[cur.length - 2]?.t === 'word' && cur[cur.length - 2].u === 'END';
115
+ if (t.u === 'BEGIN' || (t.u === 'CASE' && !afterEnd)) depth++;
116
+ else if (t.u === 'END' && depth > 0 && !(tokens[k + 1]?.t === 'word' && NON_OPENING_ENDS.has(tokens[k + 1].u))) depth--;
117
+ }
118
+ close(null);
119
+ return out;
120
+ }
121
+
122
+ export function readDb2(text) {
123
+ const { tokens, diags } = tokenize(text);
124
+ return { statements: statements(tokens), diags };
125
+ }
126
+
127
+ const OBJECTS = ['TABLE', 'INDEX', 'VIEW', 'TABLESPACE', 'DATABASE', 'STOGROUP', 'SEQUENCE', 'PROCEDURE', 'FUNCTION', 'TRIGGER', 'ALIAS', 'SYNONYM', 'TYPE', 'ROLE', 'MASK', 'PERMISSION', 'VARIABLE', 'SCHEMA', 'BUFFERPOOL', 'TRUSTED', 'AUXILIARY', 'AUX', 'GLOBAL'];
128
+
129
+ // The kind a statement's leading words give it: CREATE TABLE (also CREATE OR REPLACE, CREATE UNIQUE
130
+ // INDEX, CREATE GLOBAL TEMPORARY TABLE), ALTER TABLE, DROP TABLE, GRANT, REVOKE, COMMENT ON, ...
131
+ export function classify(st) {
132
+ const w = st.toks.filter((t, k) => k < 8 && t.t === 'word').map((t) => t.u);
133
+ if (!w.length || st.toks[0].t !== 'word') return 'UNKNOWN';
134
+ const verb = w[0];
135
+ if (verb === 'CREATE' || verb === 'ALTER' || verb === 'DROP') {
136
+ const rest = w.slice(1).filter((x) => !['OR', 'REPLACE', 'UNIQUE', 'LARGE', 'LOB', 'TEMPORARY', 'PUBLIC', 'WHERE', 'NOT', 'NULL', 'CLUSTERED', 'REGULAR', 'USER', 'SYSTEM'].includes(x));
137
+ const obj = rest.find((x) => OBJECTS.includes(x));
138
+ if (!obj) return `${verb} UNKNOWN`;
139
+ if (obj === 'AUXILIARY' || obj === 'AUX' || obj === 'GLOBAL') return `${verb} TABLE`;
140
+ if (obj === 'TRUSTED') return `${verb} TRUSTED CONTEXT`;
141
+ return `${verb} ${obj}`;
142
+ }
143
+ if (verb === 'COMMENT' || verb === 'LABEL') return `${verb} ON`;
144
+ if (['GRANT', 'REVOKE', 'INSERT', 'UPDATE', 'DELETE', 'SELECT', 'MERGE', 'SET', 'COMMIT', 'ROLLBACK', 'CONNECT', 'DECLARE', 'CALL', 'RENAME', 'TRUNCATE', 'LOCK', 'EXPLAIN', 'VALUES', 'WITH', 'REFRESH', 'RUNSTATS', 'REORG', 'TERMINATE', 'DISCONNECT', 'EXPORT', 'IMPORT', 'LOAD', 'BEGIN', 'END'].includes(verb)) return verb;
145
+ return 'UNKNOWN';
146
+ }
147
+
148
+ // Statements Db2 runs against data rather than defining it; this reader recognises them and
149
+ // does not parse them.
150
+ export const DATA_KINDS = new Set(['INSERT', 'UPDATE', 'DELETE', 'SELECT', 'MERGE', 'VALUES', 'WITH', 'CALL', 'COMMIT', 'ROLLBACK', 'CONNECT', 'DISCONNECT', 'TERMINATE', 'EXPLAIN', 'REFRESH', 'RUNSTATS', 'REORG', 'EXPORT', 'IMPORT', 'LOAD', 'LOCK', 'BEGIN', 'END', 'DECLARE']);
151
+
152
+ // { kind, status, node?, reason? }: parsed, unbuilt, unparsed or unknown, as the other readers give
153
+ // them; a data statement is parsed by recognition alone and says so.
154
+ export function parseDb2Statement(st) {
155
+ const kind = classify(st);
156
+ if (kind === 'UNKNOWN' || kind.endsWith(' UNKNOWN')) return { kind, status: 'unknown', reason: 'no Db2 statement kind fits' };
157
+ if (DATA_KINDS.has(kind)) return { kind, status: 'parsed', node: { kind, data: true } };
158
+ const parser = PARSERS[kind];
159
+ if (!parser) return { kind, status: 'unbuilt' };
160
+ const c = cursor(st.toks);
161
+ let node;
162
+ try {
163
+ node = parser(c, st);
164
+ } catch (e) {
165
+ if (e instanceof Db2Syntax) return { kind, status: 'unparsed', reason: e.message };
166
+ return { kind, status: 'unparsed', reason: `parser error: ${e && e.message}`, crash: true };
167
+ }
168
+ if (!c.done()) return { kind, status: 'unparsed', reason: `tokens left at '${c.peek().v}'`, node };
169
+ return { kind, status: 'parsed', node };
170
+ }
@@ -0,0 +1,77 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Security rules over Db2 DDL: what a GRANT opens to PUBLIC or lets a grantee pass on, system and
3
+ // database authorities granted, and exit routines a table runs inside Db2.
4
+ import { readDb2, parseDb2Statement } from './read.mjs';
5
+
6
+ const WRITES = new Set(['INSERT', 'UPDATE', 'DELETE', 'ALTER', 'ALL']);
7
+ const AUTHORITIES = new Set(['SYSADM', 'SYSCTRL', 'SYSOPR', 'DBADM', 'DBCTRL', 'DBMAINT', 'SECADM', 'ACCESSCTRL', 'DATAACCESS']);
8
+ const FLAGGED_AUTHORITIES = new Set(['SYSADM', 'SYSCTRL', 'DBADM', 'DBCTRL', 'SECADM', 'ACCESSCTRL', 'DATAACCESS']);
9
+
10
+ export const DB2_RULES = Object.freeze({
11
+ 'db2-grant-to-public': {
12
+ sev: 'high', evidence: 'construct', cwe: 'CWE-732',
13
+ text: 'A GRANT gives privileges to PUBLIC, every authorization ID',
14
+ impact: 'Every user and every program that can connect to the subsystem holds the privilege, now and whoever is added later; a write privilege or an authority to PUBLIC lets anyone change the data',
15
+ remedy: 'Grant to the roles or authorization IDs that need the privilege, and REVOKE it FROM PUBLIC',
16
+ },
17
+ 'db2-grant-with-grant-option': {
18
+ sev: 'med', evidence: 'construct', cwe: 'CWE-269',
19
+ text: 'WITH GRANT OPTION lets the grantee grant the privilege to others',
20
+ impact: 'Who holds the privilege is no longer decided by whoever administers it: the grantee can pass it on, and the grants it makes outlive a review of its own',
21
+ remedy: 'Grant without WITH GRANT OPTION, and keep the authority to grant with the administrators who own the object',
22
+ },
23
+ 'db2-system-authority-granted': {
24
+ sev: 'med', evidence: 'construct', cwe: 'CWE-250',
25
+ text: 'A system or database authority is granted',
26
+ impact: 'The grantee can create, change, drop or read objects across the subsystem or database, beyond any one table a job needs',
27
+ remedy: 'Grant the table and routine privileges the work needs; keep SYSADM, DBADM and the other authorities with named administrators, and record who holds them',
28
+ },
29
+ 'db2-exit-routine-declared': { sev: 'info', evidence: 'context', cwe: 'CWE-829', text: 'A table names an exit routine that runs inside Db2' },
30
+ 'db2-external-routine': { sev: 'info', evidence: 'context', cwe: 'CWE-829', text: 'A procedure or function runs an external load module' },
31
+ });
32
+
33
+ const scope = (node) => (node.objects ? ` on ${node.objectType} ${node.objects.map((o) => o.join('.')).join(', ')}` : node.objectType ? ` on ${node.objectType}` : '');
34
+
35
+ function grantFindings(node, at) {
36
+ const privileges = node.privileges.map((p) => p.name);
37
+ const what = `${privileges.join(', ')}${scope(node)}`;
38
+ const grantees = node.grantees.map((g) => g.name).join(', ');
39
+ const out = [];
40
+ if (node.grantees.some((g) => g.type === 'PUBLIC')) {
41
+ const crit = privileges.some((p) => WRITES.has(p) || AUTHORITIES.has(p));
42
+ out.push(at('db2-grant-to-public', `${what} granted to PUBLIC`, crit ? 'crit' : undefined));
43
+ }
44
+ if (node.withGrantOption) out.push(at('db2-grant-with-grant-option', `${grantees} may grant ${what} to others`));
45
+ for (const p of privileges.filter((x) => FLAGGED_AUTHORITIES.has(x))) out.push(at('db2-system-authority-granted', `${p}${scope(node)} granted to ${grantees}`));
46
+ return out;
47
+ }
48
+
49
+ function exitRoutineFindings(node, at) {
50
+ const out = [];
51
+ if (node.options?.editproc) out.push(at('db2-exit-routine-declared', `${node.name} runs EDITPROC ${node.options.editproc.program}`));
52
+ if (node.options?.validproc) out.push(at('db2-exit-routine-declared', `${node.name} runs VALIDPROC ${node.options.validproc}`));
53
+ for (const col of node.columns || []) {
54
+ if (col.fieldproc) out.push(at('db2-exit-routine-declared', `${node.name}.${col.name} runs FIELDPROC ${col.fieldproc.program}`));
55
+ }
56
+ return out;
57
+ }
58
+
59
+ // The load module an external routine runs, with the authority and address space it runs under.
60
+ function externalRoutineFindings(node, at) {
61
+ if (!node.external) return [];
62
+ const module = node.external.implicit ? `${node.external.name} (named by the routine)` : node.external.name;
63
+ const where = node.wlmEnvironment ? ` in WLM environment ${node.wlmEnvironment.name}` : '';
64
+ return [at('db2-external-routine', `${node.name} runs ${node.language} load module ${module} with SECURITY ${node.security}${where}`)];
65
+ }
66
+
67
+ // The findings one statement, already parsed, gives.
68
+ export function statementFindings(path, st, r) {
69
+ if (r.status !== 'parsed') return [];
70
+ const at = (rule, detail, sev = DB2_RULES[rule].sev) => ({ rule, path, line: st.line, sev, detail });
71
+ if (r.kind === 'GRANT') return grantFindings(r.node, at);
72
+ if (r.kind === 'CREATE TABLE') return exitRoutineFindings(r.node, at);
73
+ if (r.kind === 'CREATE PROCEDURE' || r.kind === 'CREATE FUNCTION') return externalRoutineFindings(r.node, at);
74
+ return [];
75
+ }
76
+
77
+ export const checkDb2 = (path, text) => readDb2(text).statements.flatMap((st) => statementFindings(path, st, parseDb2Statement(st)));