@portll/cobolwork 0.4.0 → 0.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/README.md +53 -5
- package/bin/cobolwork.mjs +34 -9
- package/lib/baseline.mjs +9 -0
- package/lib/bms.mjs +8 -0
- package/lib/build.mjs +33 -8
- package/lib/capabilities.mjs +6 -3
- package/lib/consequence.mjs +17 -0
- package/lib/constants.mjs +60 -0
- package/lib/control-reuse.mjs +113 -0
- package/lib/control.mjs +86 -18
- package/lib/csd.mjs +6 -1
- package/lib/dataflow.mjs +343 -95
- package/lib/equivalence.mjs +44 -5
- package/lib/evidence/cli.mjs +13 -5
- package/lib/evidence/record.mjs +16 -6
- package/lib/evidence/seal.mjs +17 -0
- package/lib/evidence/store.mjs +30 -14
- package/lib/evidence/timestamp.mjs +83 -0
- package/lib/evidence/verify.mjs +120 -42
- package/lib/hlasm/asm/data.mjs +116 -0
- package/lib/hlasm/asm/listing.mjs +43 -0
- package/lib/hlasm/asm/output.mjs +45 -0
- package/lib/hlasm/asm/sections.mjs +159 -0
- package/lib/hlasm/asm/symbols.mjs +78 -0
- package/lib/hlasm/exec.mjs +27 -0
- package/lib/hlasm/expr.mjs +167 -0
- package/lib/hlasm/instr.mjs +48 -0
- package/lib/hlasm/locate.mjs +333 -0
- package/lib/hlasm/macro/authorization.mjs +126 -0
- package/lib/hlasm/macro/datasets.mjs +94 -0
- package/lib/hlasm/macro/io.mjs +139 -0
- package/lib/hlasm/macro/le.mjs +53 -0
- package/lib/hlasm/macro/linkage.mjs +143 -0
- package/lib/hlasm/macro/operator.mjs +66 -0
- package/lib/hlasm/macro/program.mjs +142 -0
- package/lib/hlasm/macro/recovery.mjs +80 -0
- package/lib/hlasm/macro/storage.mjs +117 -0
- package/lib/hlasm/macro/structured.mjs +131 -0
- package/lib/hlasm/model.mjs +72 -0
- package/lib/hlasm/operands.mjs +49 -0
- package/lib/hlasm/read.mjs +116 -0
- package/lib/hlasm.mjs +79 -0
- package/lib/inventory.mjs +10 -7
- package/lib/ironwork-ids.mjs +24 -0
- package/lib/ironwork.mjs +4 -1
- package/lib/kernel/registry.mjs +24 -17
- package/lib/kernel/shared-pass.mjs +138 -0
- package/lib/options.mjs +26 -4
- package/lib/policy.mjs +6 -0
- package/lib/precompile-cics.mjs +1 -1
- package/lib/revision.json +1 -1
- package/lib/sarif.mjs +3 -0
- package/lib/scan.mjs +21 -11
- package/lib/sets/abend.mjs +115 -13
- package/lib/sets/build.mjs +43 -37
- package/lib/sets/cics.mjs +5 -3
- package/lib/sets/compile.mjs +5 -3
- package/lib/sets/copybook.mjs +5 -3
- package/lib/sets/crypto.mjs +110 -0
- package/lib/sets/flow.mjs +32 -7
- package/lib/sets/hlasm.mjs +177 -0
- package/lib/sets/jcl.mjs +15 -1
- package/lib/sets/log.mjs +5 -3
- package/lib/sets/priv.mjs +5 -3
- package/lib/sets/secrets.mjs +110 -0
- package/lib/sets/semantics.mjs +5 -3
- package/lib/sets/web.mjs +104 -7
- package/lib/sources.mjs +12 -2
- package/package.json +1 -1
- package/rules/compliance-cobit2019.json +216 -0
- package/rules/compliance-dora.json +234 -0
- package/rules/compliance-ffiec.json +234 -0
- package/rules/compliance-nist80053.json +234 -0
- package/rules/gitleaks-mainframe.toml +61 -16
- package/rules/hlasm-instructions.json +2396 -0
- package/rules/hlasm-operations.json +451 -0
- package/rules/icsf-services.json +1178 -0
- package/schema/cobolwork.policy.schema.json +7 -0
package/lib/control.mjs
CHANGED
|
@@ -622,10 +622,14 @@ export function buildControl(prog, resolve, { extra = null } = {}) {
|
|
|
622
622
|
|
|
623
623
|
// ---- checks ----
|
|
624
624
|
// The storage an item lies in: its record, or the record an 01 REDEFINES lays itself over.
|
|
625
|
+
const recordOf = new Map();
|
|
625
626
|
const itemsInRecord = (item) => {
|
|
626
|
-
let top = item;
|
|
627
|
+
let top = recordOf.get(item);
|
|
628
|
+
if (top) return top;
|
|
629
|
+
top = item;
|
|
627
630
|
while (top.parent) top = top.parent;
|
|
628
631
|
for (let hops = 0; top.redefinesItem && hops < 64; hops++) top = top.redefinesItem;
|
|
632
|
+
if (item.parent) recordOf.set(item, top);
|
|
629
633
|
return top;
|
|
630
634
|
};
|
|
631
635
|
const fieldKey = (f) => (f.index ? `IX:${f.index}` : f);
|
|
@@ -639,6 +643,33 @@ export function buildControl(prog, resolve, { extra = null } = {}) {
|
|
|
639
643
|
const fEnd = f.offset + (f.contributes || f.size);
|
|
640
644
|
return w.offset < fEnd && f.offset < wEnd;
|
|
641
645
|
};
|
|
646
|
+
// Entries grouped by the storage their item lies in, so a lookup tries only what `overlaps` can
|
|
647
|
+
// accept: the same record, the same index, or a 66-level item, which overlaps any field.
|
|
648
|
+
const byStorage = (entries, itemOf) => {
|
|
649
|
+
const byRecord = new Map();
|
|
650
|
+
const byIndex = new Map();
|
|
651
|
+
const renames = [];
|
|
652
|
+
const plain = [];
|
|
653
|
+
const add = (map, k, e) => { const list = map.get(k); if (list) list.push(e); else map.set(k, [e]); };
|
|
654
|
+
for (const e of entries) {
|
|
655
|
+
const it = itemOf(e);
|
|
656
|
+
if (it.index) { add(byIndex, it.index, e); continue; }
|
|
657
|
+
plain.push(e);
|
|
658
|
+
if (it.level === 66) renames.push(e);
|
|
659
|
+
else add(byRecord, itemsInRecord(it), e);
|
|
660
|
+
}
|
|
661
|
+
return {
|
|
662
|
+
mayOverlap: (it) => {
|
|
663
|
+
if (it.index) return byIndex.get(it.index) || [];
|
|
664
|
+
if (it.level === 66) return plain;
|
|
665
|
+
const same = byRecord.get(itemsInRecord(it));
|
|
666
|
+
return same ? (renames.length ? same.concat(renames) : same) : renames;
|
|
667
|
+
},
|
|
668
|
+
};
|
|
669
|
+
};
|
|
670
|
+
// Arguments the CICS API documents as data-values, which the command reads and never fills: the
|
|
671
|
+
// program or transaction it transfers to or starts, and the system it ships the request to.
|
|
672
|
+
const CICS_READS = { LINK: ['PROGRAM', 'TRANSID', 'SYSID'], XCTL: ['PROGRAM'], START: ['TRANSID', 'SYSID'], RETURN: ['TRANSID'], LOAD: ['PROGRAM'], RELEASE: ['PROGRAM'] };
|
|
642
673
|
const writtenBy = (n) => {
|
|
643
674
|
const out = [];
|
|
644
675
|
const add = (tok) => { const f = tok ? resolve(tok) : null; if (f) out.push(f.level === 88 && f.parent ? f.parent : f); };
|
|
@@ -667,28 +698,31 @@ export function buildControl(prog, resolve, { extra = null } = {}) {
|
|
|
667
698
|
}
|
|
668
699
|
}
|
|
669
700
|
} else if (n.e) {
|
|
670
|
-
// A command may fill what any option is given directly, except what it sends FROM
|
|
671
|
-
// inside that argument's own parentheses - a subscript, a
|
|
701
|
+
// A command may fill what any option is given directly, except what it sends FROM and the
|
|
702
|
+
// data-values it reads. A name inside that argument's own parentheses - a subscript, a
|
|
703
|
+
// reference modification - is read.
|
|
704
|
+
const reads = (n.e.kind === 'CICS' && CICS_READS[n.e.toks[0]?.u]) || [];
|
|
672
705
|
let depth = 0;
|
|
673
706
|
let option = null;
|
|
674
707
|
for (let i = 0; i < n.e.toks.length; i++) {
|
|
675
708
|
const t = n.e.toks[i];
|
|
676
709
|
if (t.t === 'sep') { depth += t.v === '(' ? 1 : -1; continue; }
|
|
677
710
|
if (depth === 0) { option = t.t === 'word' ? t.u : option; continue; }
|
|
678
|
-
if (depth === 1 && t.t === 'word' && option !== 'FROM') add(t);
|
|
711
|
+
if (depth === 1 && t.t === 'word' && option !== 'FROM' && !reads.includes(option)) add(t);
|
|
679
712
|
}
|
|
680
713
|
}
|
|
681
714
|
return out;
|
|
682
715
|
};
|
|
683
716
|
// A VALUE holds for the whole run only if no statement writes storage that overlaps the item.
|
|
684
|
-
let
|
|
717
|
+
let written = null;
|
|
685
718
|
const neverWritten = (it) => {
|
|
686
719
|
if (it.external) return false;
|
|
687
|
-
if (!
|
|
688
|
-
|
|
689
|
-
for (const n of nodes) if (n.kind === 'stmt' || n.kind === 'exec' || n.kind === 'step' || n.kind === 'call')
|
|
720
|
+
if (!written) {
|
|
721
|
+
const all = [];
|
|
722
|
+
for (const n of nodes) if (n.kind === 'stmt' || n.kind === 'exec' || n.kind === 'step' || n.kind === 'call') all.push(...writtenBy(n));
|
|
723
|
+
written = byStorage(all, (w) => w);
|
|
690
724
|
}
|
|
691
|
-
return !
|
|
725
|
+
return !written.mayOverlap(it).some((w) => overlaps(w, it));
|
|
692
726
|
};
|
|
693
727
|
const constValue = (toks) => {
|
|
694
728
|
if (toks.length === 3 && WORD(toks[0], 'LENGTH') && WORD(toks[1], 'OF')) {
|
|
@@ -2373,17 +2407,17 @@ export function buildControl(prog, resolve, { extra = null } = {}) {
|
|
|
2373
2407
|
for (const f of [c.x, c.t, c.f, c.rt, c.rf]) if (f != null) list.push(f);
|
|
2374
2408
|
factsOfKey.set(c.key, list);
|
|
2375
2409
|
}
|
|
2410
|
+
const checkedFields = byStorage([...factsOfKey.keys()].filter((key) => fields.get(key)), (key) => fields.get(key));
|
|
2376
2411
|
const killCache = new Map();
|
|
2377
2412
|
const killOf = (w) => {
|
|
2378
2413
|
if (!w) return null;
|
|
2379
2414
|
let bits = killCache.get(w);
|
|
2380
2415
|
if (bits !== undefined) return bits;
|
|
2381
2416
|
bits = null;
|
|
2382
|
-
for (const
|
|
2383
|
-
|
|
2384
|
-
if (!f || !overlaps(w, f)) continue;
|
|
2417
|
+
for (const key of checkedFields.mayOverlap(w)) {
|
|
2418
|
+
if (!overlaps(w, fields.get(key))) continue;
|
|
2385
2419
|
bits ||= new Uint32Array(words);
|
|
2386
|
-
for (const x of
|
|
2420
|
+
for (const x of factsOfKey.get(key)) bits[x >>> 5] |= 1 << (x & 31);
|
|
2387
2421
|
}
|
|
2388
2422
|
killCache.set(w, bits);
|
|
2389
2423
|
return bits;
|
|
@@ -2629,10 +2663,15 @@ export function buildControl(prog, resolve, { extra = null } = {}) {
|
|
|
2629
2663
|
// Every route from `entry` that reaches `stopAt` or the program's end, meeting at joins. A call
|
|
2630
2664
|
// whose range never returns ends the route, as a STOP RUN does. `calls` collects the ranges the
|
|
2631
2665
|
// routes perform, for whoever needs to know what depends on what.
|
|
2666
|
+
// A run holds a bitset per node it reaches. Those of a run nobody reads any more are handed back
|
|
2667
|
+
// and reused, since allocating them afresh for every run was most of the analysis's collection time.
|
|
2668
|
+
const spare = [];
|
|
2669
|
+
const copyOf = (bits) => { const a = spare.pop(); if (!a) return bits.slice(); a.set(bits); return a; };
|
|
2670
|
+
const release = (IN) => { for (const a of IN.values()) spare.push(a); };
|
|
2632
2671
|
function run(entry, input, stopAt, calls = null) {
|
|
2633
2672
|
const IN = new Map();
|
|
2634
2673
|
let exitOut = null;
|
|
2635
|
-
IN.set(entry, input
|
|
2674
|
+
IN.set(entry, copyOf(input));
|
|
2636
2675
|
push(entry);
|
|
2637
2676
|
while (heap.length) {
|
|
2638
2677
|
const id = pop();
|
|
@@ -2643,7 +2682,7 @@ export function buildControl(prog, resolve, { extra = null } = {}) {
|
|
|
2643
2682
|
if (id === stopAt) { if (exitOut) meetInto(exitOut, out); else exitOut = out.slice(); continue; }
|
|
2644
2683
|
for (const s of n.succ) {
|
|
2645
2684
|
const cur = IN.get(s);
|
|
2646
|
-
if (!cur) { IN.set(s, out
|
|
2685
|
+
if (!cur) { IN.set(s, copyOf(out)); push(s); } else if (meetInto(cur, out)) push(s);
|
|
2647
2686
|
}
|
|
2648
2687
|
}
|
|
2649
2688
|
return { IN, exitOut };
|
|
@@ -2675,7 +2714,10 @@ export function buildControl(prog, resolve, { extra = null } = {}) {
|
|
|
2675
2714
|
for (const d of n.derive || []) if (d.needs.every((x) => has(bits, x))) approved.add(d);
|
|
2676
2715
|
for (const entry of n.implies || []) if (entry[2] && has(bits, entry[0])) approved.add(entry);
|
|
2677
2716
|
}
|
|
2678
|
-
|
|
2717
|
+
release(fromNone.IN);
|
|
2718
|
+
const fromAll = run(r.entry, ONES, r.end, calls);
|
|
2719
|
+
const top = fromAll.exitOut;
|
|
2720
|
+
release(fromAll.IN);
|
|
2679
2721
|
approved = null;
|
|
2680
2722
|
for (const k of calls) { if (!callers.has(k)) callers.set(k, new Set()); callers.get(k).add(r); }
|
|
2681
2723
|
const old = summaries.get(r.key);
|
|
@@ -2704,7 +2746,9 @@ export function buildControl(prog, resolve, { extra = null } = {}) {
|
|
|
2704
2746
|
waiting.delete(c.key);
|
|
2705
2747
|
const input = c.stopAt == null ? ZERO : rangeIn.get(c.key);
|
|
2706
2748
|
const res = run(c.entry, input, c.stopAt);
|
|
2749
|
+
const superseded = results.get(c.key);
|
|
2707
2750
|
results.set(c.key, res.IN);
|
|
2751
|
+
if (superseded) release(superseded);
|
|
2708
2752
|
for (const [id, bits] of res.IN) {
|
|
2709
2753
|
const n = nodes[id];
|
|
2710
2754
|
if (n.kind !== 'call' || !byKey.has(n.range.key)) continue;
|
|
@@ -2723,7 +2767,7 @@ export function buildControl(prog, resolve, { extra = null } = {}) {
|
|
|
2723
2767
|
for (const IN of results.values()) {
|
|
2724
2768
|
for (const [id, bits] of IN) {
|
|
2725
2769
|
const had = facts.get(id);
|
|
2726
|
-
if (had) meetInto(had, bits); else facts.set(id, bits
|
|
2770
|
+
if (had) meetInto(had, bits); else facts.set(id, bits);
|
|
2727
2771
|
}
|
|
2728
2772
|
}
|
|
2729
2773
|
};
|
|
@@ -2906,7 +2950,31 @@ function readersOf(tree) {
|
|
|
2906
2950
|
return out;
|
|
2907
2951
|
}
|
|
2908
2952
|
|
|
2909
|
-
|
|
2953
|
+
// A fact set is a Uint32Array with one bit per fact, or, where a program keeps it after its analysis,
|
|
2954
|
+
// a Uint16Array holding the sorted numbers of the facts that hold (sparseFacts).
|
|
2955
|
+
export const hasFact = (bits, x) => x != null && bits != null
|
|
2956
|
+
&& (bits instanceof Uint16Array ? holdsSorted(bits, x) : (bits[x >>> 5] & (1 << (x & 31))) !== 0);
|
|
2957
|
+
function holdsSorted(list, x) {
|
|
2958
|
+
let lo = 0;
|
|
2959
|
+
let hi = list.length - 1;
|
|
2960
|
+
while (lo <= hi) {
|
|
2961
|
+
const mid = (lo + hi) >>> 1;
|
|
2962
|
+
if (list[mid] === x) return true;
|
|
2963
|
+
if (list[mid] < x) lo = mid + 1; else hi = mid - 1;
|
|
2964
|
+
}
|
|
2965
|
+
return false;
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
// The facts a set holds as their sorted numbers, where every number fits 16 bits; the set itself otherwise.
|
|
2969
|
+
export function sparseFacts(bits) {
|
|
2970
|
+
if (bits.length * 32 > 65536) return bits;
|
|
2971
|
+
let n = 0;
|
|
2972
|
+
for (let i = 0; i < bits.length; i++) for (let w = bits[i]; w; w &= w - 1) n++;
|
|
2973
|
+
const out = new Uint16Array(n);
|
|
2974
|
+
let k = 0;
|
|
2975
|
+
for (let i = 0; i < bits.length; i++) for (let w = bits[i]; w; w &= w - 1) out[k++] = i * 32 + (31 - Math.clz32(w & -w));
|
|
2976
|
+
return out;
|
|
2977
|
+
}
|
|
2910
2978
|
|
|
2911
2979
|
// What a node's checks say at a point, given the facts that hold there: 2 when the value it holds is
|
|
2912
2980
|
// safe for the sink, 1 when a check has run on every route to the point, 0 when neither. The check
|
package/lib/csd.mjs
CHANGED
|
@@ -14,7 +14,7 @@ const COMMAND = /^\s*(DEFINE|ADD|ALTER|APPEND|COPY|DELETE|INITIALIZE|LIST|REMOVE
|
|
|
14
14
|
// { tdqueues: Map(name -> { type, ddname, indirect, line }),
|
|
15
15
|
// transactions: Map(name -> { program, cmdsec, ressec, line }),
|
|
16
16
|
// tcpipservices: Map(name -> { port, protocol, ssl, authenticate, transaction, line }),
|
|
17
|
-
// urimaps: Map(name -> { usage, path, program, transaction, tcpipservice, line }) }
|
|
17
|
+
// urimaps: Map(name -> { usage, path, program, transaction, tcpipservice, scheme, host, attls, line }) }
|
|
18
18
|
export function parseCsd(text) {
|
|
19
19
|
const tdqueues = new Map();
|
|
20
20
|
const transactions = new Map();
|
|
@@ -64,6 +64,11 @@ export function parseCsd(text) {
|
|
|
64
64
|
program: attrs.get('PROGRAM') || null,
|
|
65
65
|
transaction: attrs.get('TRANSACTION') || null,
|
|
66
66
|
tcpipservice: attrs.get('TCPIPSERVICE') || null,
|
|
67
|
+
// A client URIMAP's scheme decides whether WEB OPEN URIMAP(name) asks for TLS; ATTLS(AWARE)
|
|
68
|
+
// has AT-TLS secure an HTTP scheme outside CICS.
|
|
69
|
+
scheme: attrs.get('SCHEME') || null,
|
|
70
|
+
host: attrs.get('HOST') || null,
|
|
71
|
+
attls: attrs.get('ATTLS') || null,
|
|
67
72
|
line: b.line,
|
|
68
73
|
});
|
|
69
74
|
} else if (kind === 'TDQUEUE') {
|