@portll/cobolwork 0.0.1 → 0.2.75
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/LICENSE +661 -0
- package/LICENSING.md +93 -0
- package/NOTICE +9 -0
- package/README.md +323 -3
- package/THIRD-PARTY-NOTICES.md +118 -0
- package/bin/cobolwork.mjs +354 -0
- package/lib/advisories.mjs +133 -0
- package/lib/baseline.mjs +154 -0
- package/lib/bms.mjs +453 -0
- package/lib/build.mjs +402 -0
- package/lib/capabilities.mjs +79 -0
- package/lib/cics-commands.mjs +281 -0
- package/lib/compliance.mjs +81 -0
- package/lib/consequence.mjs +139 -0
- package/lib/control.mjs +1515 -0
- package/lib/csd.mjs +77 -0
- package/lib/dataflow.mjs +1506 -0
- package/lib/diff.mjs +344 -0
- package/lib/explain.mjs +145 -0
- package/lib/gate.mjs +383 -0
- package/lib/index.mjs +6 -0
- package/lib/inventory.mjs +79 -0
- package/lib/jcl.mjs +478 -0
- package/lib/kernel/findings.mjs +94 -0
- package/lib/kernel/identity.mjs +216 -0
- package/lib/kernel/memory.mjs +217 -0
- package/lib/kernel/printable.mjs +6 -0
- package/lib/kernel/registry.mjs +79 -0
- package/lib/kernel/ruleset.mjs +72 -0
- package/lib/kernel/source-tree.mjs +159 -0
- package/lib/kev.mjs +27 -0
- package/lib/options.mjs +512 -0
- package/lib/packs.mjs +148 -0
- package/lib/parser.mjs +2055 -0
- package/lib/policy.mjs +163 -0
- package/lib/precompile-cics.mjs +169 -0
- package/lib/precompile.mjs +544 -0
- package/lib/reach.mjs +122 -0
- package/lib/revision.json +1 -0
- package/lib/revision.mjs +89 -0
- package/lib/sarif.mjs +222 -0
- package/lib/scan.mjs +272 -0
- package/lib/sets/build.mjs +234 -0
- package/lib/sets/cics.mjs +306 -0
- package/lib/sets/compile.mjs +187 -0
- package/lib/sets/copybook.mjs +174 -0
- package/lib/sets/flow.mjs +487 -0
- package/lib/sets/hidden.mjs +216 -0
- package/lib/sets/jcl.mjs +440 -0
- package/lib/sets/log.mjs +406 -0
- package/lib/sets/opaque.mjs +102 -0
- package/lib/sets/priv.mjs +322 -0
- package/lib/sets/recon.mjs +267 -0
- package/lib/sets/vendor.mjs +117 -0
- package/lib/sets/web.mjs +327 -0
- package/lib/site.mjs +164 -0
- package/lib/sources.mjs +156 -0
- package/lib/tui/app.mjs +325 -0
- package/lib/tui/keys.mjs +39 -0
- package/lib/tui/model.mjs +96 -0
- package/lib/tui/run.mjs +38 -0
- package/lib/tui/screen.mjs +59 -0
- package/lib/tui/terminal.mjs +46 -0
- package/lib/utilities.mjs +296 -0
- package/lib/version.mjs +15 -0
- package/lib/words.mjs +318 -0
- package/package.json +45 -6
- package/rules/advisories.json +264 -0
- package/rules/compliance-dora.json +2151 -0
- package/rules/compliance-ffiec.json +2134 -0
- package/rules/compliance-nist80053.json +2134 -0
- package/rules/gitleaks-mainframe.toml +57 -0
- package/rules/kev-ids.json +1729 -0
- package/rules/packs/broadcom.json +124 -0
- package/rules/packs/connectdirect.json +116 -0
- package/rules/packs/controlm.json +114 -0
- package/rules/system-layouts.json +28 -0
- package/schema/cobolwork-coverage.schema.json +65 -0
- package/schema/cobolwork.policy.schema.json +54 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"name": "broadcom",
|
|
4
|
+
"vendor": "Broadcom",
|
|
5
|
+
"product": "CA ACF2 and CA Top Secret",
|
|
6
|
+
"validation": {
|
|
7
|
+
"corpus": {
|
|
8
|
+
"date": "2026-09-21",
|
|
9
|
+
"repositories": 127,
|
|
10
|
+
"repositoriesWithJcl": 45,
|
|
11
|
+
"filesScanned": 558281,
|
|
12
|
+
"firedOn": 0,
|
|
13
|
+
"perRule": {},
|
|
14
|
+
"rulesThatFired": 0,
|
|
15
|
+
"rulesTotal": 8,
|
|
16
|
+
"note": "Measured 2026-09-21 over 127 public GitHub COBOL repositories, 558,281 files, with diag/measure-rules.mjs --packs. Read the denominator before the number: every rule in this pack is JCL-scoped, and only 45 of the 127 repositories contain any JCL at all. WHAT THIS DOES AND DOES NOT SHOW. It shows these rules are not broadly noisy - they do not fire on ordinary COBOL and JCL. It does not show they are quiet where they would actually run, because a public corpus of teaching material, vendor demos and tooling contains essentially no estate running this product. A rule that cannot fire for want of the product is not the same as a rule that sees the product and stays quiet, and this measurement cannot tell those apart. The practitioner review is still the half that would."
|
|
17
|
+
},
|
|
18
|
+
"practitioner": null,
|
|
19
|
+
"_note": "Two different questions. A corpus measurement over public COBOL repositories tells you how often these rules fire, which is their noise. It cannot tell you whether the risk statements are true: if the claim about GSO records is wrong, a thousand repositories will report it quietly and wrongly. Fill corpus with {repositories, firedOn, perRule} from diag/measure-rules.mjs; fill practitioner with {by, date, note} when someone who has administered the product has read it."
|
|
20
|
+
},
|
|
21
|
+
"retrieved": "2026-09-20",
|
|
22
|
+
"source": "CA ACF2 and CA Top Secret command references. Some are behind a Broadcom support login; a customer who runs the product can supply theirs.",
|
|
23
|
+
"coverage": "Logonid and ACID administration, resource rule maintenance, and global option changes. Not covered: reporting utilities, database maintenance, or anything whose risk depends on the installation's own rule set.",
|
|
24
|
+
"rules": [
|
|
25
|
+
{
|
|
26
|
+
"id": "acf2-logonid-insert",
|
|
27
|
+
"verb": "INSERT",
|
|
28
|
+
"pattern": "^\\s*INSERT\\s+[A-Z0-9$#@]{1,8}\\b",
|
|
29
|
+
"appliesTo": [
|
|
30
|
+
"jcl-instream"
|
|
31
|
+
],
|
|
32
|
+
"severity": "high",
|
|
33
|
+
"risk": "creates a logonid",
|
|
34
|
+
"rationale": "An INSERT while the session is in logonid maintenance mode creates a logonid. The mode matters: without it this matches SQL INSERT INTO, which a corpus measurement over 125 repositories found 322 times. A logonid created from a job in a repository is a credential whose existence is decided by whoever can commit.",
|
|
35
|
+
"requiresContext": "acf2-lid-mode"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "acf2-logonid-change",
|
|
39
|
+
"verb": "CHANGE",
|
|
40
|
+
"pattern": "^\\s*CHANGE\\s+[A-Z0-9$#@]{1,8}\\b",
|
|
41
|
+
"appliesTo": [
|
|
42
|
+
"jcl-instream"
|
|
43
|
+
],
|
|
44
|
+
"severity": "high",
|
|
45
|
+
"risk": "alters a logonid, including its privileges",
|
|
46
|
+
"rationale": "CHANGE against a logonid alters its attributes. The privileged attributes here are the ones that matter, and the same statement carries both ordinary and privileged changes.",
|
|
47
|
+
"requiresContext": "acf2-lid-mode"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "acf2-rule-maintenance",
|
|
51
|
+
"verb": "ACFNRULE",
|
|
52
|
+
"pattern": "\\bACFNRULE\\b|^\\s*SET\\s+RESOURCE\\b|^\\s*RECKEY\\b",
|
|
53
|
+
"appliesTo": [
|
|
54
|
+
"jcl-instream"
|
|
55
|
+
],
|
|
56
|
+
"severity": "high",
|
|
57
|
+
"risk": "changes an access rule set",
|
|
58
|
+
"rationale": "Access rule maintenance decides who may reach which dataset or resource. Changing it from a job means the access model is edited by whoever can edit the repository.",
|
|
59
|
+
"setsContext": "acf2-lid-mode",
|
|
60
|
+
"setsContextPattern": "^\\s*SET\\s+(?:LID|RULE|PROFILE|RESOURCE)\\b"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "acf2-global-options",
|
|
64
|
+
"verb": "SET CONTROL",
|
|
65
|
+
"pattern": "^\\s*SET\\s+CONTROL\\s*\\(?\\s*GSO",
|
|
66
|
+
"appliesTo": [
|
|
67
|
+
"jcl-instream"
|
|
68
|
+
],
|
|
69
|
+
"severity": "crit",
|
|
70
|
+
"risk": "changes a global system option",
|
|
71
|
+
"rationale": "GSO records are installation-wide security options. A change here affects every user and every resource at once, which makes it the highest-value single statement in the product."
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"id": "topsecret-acid-create",
|
|
75
|
+
"verb": "TSS CREATE",
|
|
76
|
+
"pattern": "^\\s*TSS\\s+CREATE\\b",
|
|
77
|
+
"appliesTo": [
|
|
78
|
+
"jcl-instream"
|
|
79
|
+
],
|
|
80
|
+
"severity": "high",
|
|
81
|
+
"risk": "creates an ACID",
|
|
82
|
+
"rationale": "TSS CREATE defines a new accessor identity. As with an ACF2 logonid, an identity created from a committed job is decided by whoever can commit."
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"id": "topsecret-permit",
|
|
86
|
+
"verb": "TSS PERMIT",
|
|
87
|
+
"pattern": "^\\s*TSS\\s+PERMIT\\b",
|
|
88
|
+
"appliesTo": [
|
|
89
|
+
"jcl-instream"
|
|
90
|
+
],
|
|
91
|
+
"severity": "high",
|
|
92
|
+
"risk": "grants access to a resource",
|
|
93
|
+
"rationale": "TSS PERMIT grants an ACID access to a resource. It is the statement that turns an identity into an authorisation, and it is the one an attacker most wants to be able to issue."
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"id": "topsecret-addto-privilege",
|
|
97
|
+
"verb": "TSS ADDTO",
|
|
98
|
+
"pattern": "^\\s*TSS\\s+ADD(?:TO)?\\b[\\s\\S]{0,120}\\b(MASTFAC|NOSUSPEND|BYPASS|ACID\\s*\\(\\s*MASTER)",
|
|
99
|
+
"appliesTo": [
|
|
100
|
+
"jcl-instream"
|
|
101
|
+
],
|
|
102
|
+
"severity": "crit",
|
|
103
|
+
"risk": "grants a bypassing or master-level attribute",
|
|
104
|
+
"rationale": "The attributes matched here remove or bypass normal validation. An ACID carrying one of them is outside the controls every other identity is subject to, which is a different class of finding from an ordinary grant."
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"id": "topsecret-revoke",
|
|
108
|
+
"verb": "TSS REVOKE",
|
|
109
|
+
"pattern": "^\\s*TSS\\s+(?:REVOKE|REMOVE|DELETE)\\b",
|
|
110
|
+
"appliesTo": [
|
|
111
|
+
"jcl-instream"
|
|
112
|
+
],
|
|
113
|
+
"severity": "med",
|
|
114
|
+
"risk": "removes an identity or an authorisation",
|
|
115
|
+
"rationale": "Removing access from a job is less immediately dangerous than granting it, and it is still the security model being edited from a repository. It is listed so the route is visible, at a severity that says which way round it is."
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
"programs": [
|
|
119
|
+
"ACFBATCH",
|
|
120
|
+
"ACFRPTXR",
|
|
121
|
+
"TSSUTIL",
|
|
122
|
+
"TSSCFILE"
|
|
123
|
+
]
|
|
124
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"name": "connectdirect",
|
|
4
|
+
"vendor": "IBM",
|
|
5
|
+
"product": "Connect:Direct for z/OS",
|
|
6
|
+
"validation": {
|
|
7
|
+
"corpus": {
|
|
8
|
+
"date": "2026-09-21",
|
|
9
|
+
"repositories": 127,
|
|
10
|
+
"repositoriesWithJcl": 45,
|
|
11
|
+
"filesScanned": 558281,
|
|
12
|
+
"firedOn": 1,
|
|
13
|
+
"perRule": {
|
|
14
|
+
"cd-batch-interface": {
|
|
15
|
+
"findings": 2,
|
|
16
|
+
"repositories": 1
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"rulesThatFired": 1,
|
|
20
|
+
"rulesTotal": 7,
|
|
21
|
+
"note": "Measured 2026-09-21 over 127 public GitHub COBOL repositories, 558,281 files, with diag/measure-rules.mjs --packs. Read the denominator before the number: every rule in this pack is JCL-scoped, and only 45 of the 127 repositories contain any JCL at all. WHAT THIS DOES AND DOES NOT SHOW. It shows these rules are not broadly noisy - they do not fire on ordinary COBOL and JCL. It does not show they are quiet where they would actually run, because a public corpus of teaching material, vendor demos and tooling contains essentially no estate running this product. A rule that cannot fire for want of the product is not the same as a rule that sees the product and stays quiet, and this measurement cannot tell those apart. The practitioner review is still the half that would."
|
|
22
|
+
},
|
|
23
|
+
"practitioner": null,
|
|
24
|
+
"_note": "Two different questions. A corpus measurement over public COBOL repositories tells you how often these rules fire, which is their noise. It cannot tell you whether the risk statements are true. Fill corpus with {repositories, firedOn, perRule} from diag/measure-rules.mjs --packs connectdirect; fill practitioner with {by, date, note} when someone who has run Connect:Direct has read it."
|
|
25
|
+
},
|
|
26
|
+
"retrieved": "2026-09-20",
|
|
27
|
+
"source": "IBM Connect:Direct for z/OS User Guide and Process Language Reference. Process statements are submitted through DMBATCH, so they appear in JCL in-stream data rather than in any COBOL source.",
|
|
28
|
+
"coverage": "Process language statements submitted through the batch interface: SIGNON, RUN TASK, RUN JOB, SUBMIT and COPY. Not covered: the Secure+ configuration, the network map, initialisation parameters, or anything whose risk depends on the node's own authorisation settings. Those live outside the repository.",
|
|
29
|
+
"why": "Connect:Direct exists to move data off the platform, which makes every one of its processes an egress path by design. Two of its statements do more than move data: RUN TASK and RUN JOB execute code on the partner node. A repository that carries them carries remote execution.",
|
|
30
|
+
"rules": [
|
|
31
|
+
{
|
|
32
|
+
"id": "cd-run-task",
|
|
33
|
+
"verb": "RUN TASK",
|
|
34
|
+
"pattern": "^\\s*(?:[A-Z0-9$#@]+\\s+)?RUN\\s+TASK\\b",
|
|
35
|
+
"appliesTo": [
|
|
36
|
+
"jcl-instream"
|
|
37
|
+
],
|
|
38
|
+
"severity": "crit",
|
|
39
|
+
"risk": "executes a program on the partner node",
|
|
40
|
+
"rationale": "RUN TASK runs a program on the node at the other end of the transfer. It is remote execution written into a repository, and the machine it runs on is by definition one this repository does not control."
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "cd-run-job",
|
|
44
|
+
"verb": "RUN JOB",
|
|
45
|
+
"pattern": "^\\s*(?:[A-Z0-9$#@]+\\s+)?RUN\\s+JOB\\b",
|
|
46
|
+
"appliesTo": [
|
|
47
|
+
"jcl-instream"
|
|
48
|
+
],
|
|
49
|
+
"severity": "crit",
|
|
50
|
+
"risk": "submits a job on the partner node",
|
|
51
|
+
"rationale": "RUN JOB submits JCL for execution on the partner node. Everything true of RUN TASK is true here, with the submitted job's own authority added to it."
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "cd-signon-password",
|
|
55
|
+
"verb": "SIGNON",
|
|
56
|
+
"pattern": "^\\s*(?:[A-Z0-9$#@]+\\s+)?SIGNON\\b[\\s\\S]{0,200}?\\bUSERID\\s*=\\s*\\([^,)]{0,256},[^)]{1,256}\\)",
|
|
57
|
+
"appliesTo": [
|
|
58
|
+
"jcl-instream"
|
|
59
|
+
],
|
|
60
|
+
"severity": "crit",
|
|
61
|
+
"risk": "carries a userid and password in the process",
|
|
62
|
+
"rationale": "A SIGNON whose USERID operand carries a second positional value is supplying a password in the process text. The credential is then in the repository, readable by everyone who can read it."
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "cd-snode-credentials",
|
|
66
|
+
"verb": "SNODEID",
|
|
67
|
+
"pattern": "\\bSNODEID\\s*=\\s*\\([^,)]{0,256},[^)]{1,256}\\)",
|
|
68
|
+
"appliesTo": [
|
|
69
|
+
"jcl-instream"
|
|
70
|
+
],
|
|
71
|
+
"severity": "crit",
|
|
72
|
+
"risk": "carries the partner node's credentials",
|
|
73
|
+
"rationale": "SNODEID supplies the security credentials used on the partner node. Like SIGNON with a password, a second positional value is a secret, and it is a secret for a system this repository does not own."
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "cd-copy-outbound",
|
|
77
|
+
"verb": "COPY",
|
|
78
|
+
"pattern": "^\\s*(?:[A-Z0-9$#@]+\\s+)?COPY\\b[\\s\\S]{0,300}?\\bSNODE\\b",
|
|
79
|
+
"appliesTo": [
|
|
80
|
+
"jcl-instream"
|
|
81
|
+
],
|
|
82
|
+
"severity": "med",
|
|
83
|
+
"risk": "transfers a dataset to another node",
|
|
84
|
+
"rationale": "A COPY naming a secondary node moves data off this system. It is the ordinary purpose of the product rather than a defect, and it is listed at a severity that says so, because an egress path is worth seeing even when it is intended."
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"id": "cd-submit",
|
|
88
|
+
"verb": "SUBMIT",
|
|
89
|
+
"pattern": "^\\s*(?:[A-Z0-9$#@]+\\s+)?SUBMIT\\b[\\s\\S]{0,200}?\\b(PROC|DSN)\\s*=",
|
|
90
|
+
"appliesTo": [
|
|
91
|
+
"jcl-instream"
|
|
92
|
+
],
|
|
93
|
+
"severity": "high",
|
|
94
|
+
"risk": "submits a further process from a dataset",
|
|
95
|
+
"rationale": "SUBMIT runs another Connect:Direct process named by a dataset. What that process does is decided elsewhere, so the reachable behaviour of this job is not visible in this job."
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"id": "cd-batch-interface",
|
|
99
|
+
"verb": "DMBATCH",
|
|
100
|
+
"pattern": "^DMBATCH$",
|
|
101
|
+
"appliesTo": [
|
|
102
|
+
"jcl-step"
|
|
103
|
+
],
|
|
104
|
+
"severity": "info",
|
|
105
|
+
"risk": "invokes the batch interface",
|
|
106
|
+
"rationale": "DMBATCH is how a job talks to Connect:Direct at all, and it is named on the EXEC statement rather than in the process text below it. It is not itself a finding; it is listed so a reader can see which jobs reach the product, which is the inventory question that precedes every other rule in this pack."
|
|
107
|
+
}
|
|
108
|
+
],
|
|
109
|
+
"programs": [
|
|
110
|
+
"DMBATCH",
|
|
111
|
+
"DMCHLAPI",
|
|
112
|
+
"DMNOTFY",
|
|
113
|
+
"DMRTSUB",
|
|
114
|
+
"NDMCMD"
|
|
115
|
+
]
|
|
116
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"name": "controlm",
|
|
4
|
+
"vendor": "BMC",
|
|
5
|
+
"product": "Control-M for z/OS",
|
|
6
|
+
"validation": {
|
|
7
|
+
"corpus": {
|
|
8
|
+
"date": "2026-09-21",
|
|
9
|
+
"repositories": 127,
|
|
10
|
+
"repositoriesWithJcl": 45,
|
|
11
|
+
"filesScanned": 558281,
|
|
12
|
+
"firedOn": 0,
|
|
13
|
+
"perRule": {},
|
|
14
|
+
"rulesThatFired": 0,
|
|
15
|
+
"rulesTotal": 7,
|
|
16
|
+
"note": "Measured 2026-09-21 over 127 public GitHub COBOL repositories, 558,281 files, with diag/measure-rules.mjs --packs. Read the denominator before the number: every rule in this pack is JCL-scoped, and only 45 of the 127 repositories contain any JCL at all. WHAT THIS DOES AND DOES NOT SHOW. It shows these rules are not broadly noisy - they do not fire on ordinary COBOL and JCL. It does not show they are quiet where they would actually run, because a public corpus of teaching material, vendor demos and tooling contains essentially no estate running this product. A rule that cannot fire for want of the product is not the same as a rule that sees the product and stays quiet, and this measurement cannot tell those apart. The practitioner review is still the half that would."
|
|
17
|
+
},
|
|
18
|
+
"practitioner": null,
|
|
19
|
+
"_note": "Two different questions. A corpus measurement over public COBOL repositories tells you how often these rules fire, which is their noise. It cannot tell you whether the risk statements are true. Fill corpus with {repositories, firedOn, perRule} from diag/measure-rules.mjs --packs controlm; fill practitioner with {by, date, note} when someone who has administered Control-M has read it."
|
|
20
|
+
},
|
|
21
|
+
"retrieved": "2026-09-20",
|
|
22
|
+
"source": "BMC Control-M for z/OS utility and AutoEdit references. Control-M utilities are invoked as job steps, and their control statements arrive in in-stream data.",
|
|
23
|
+
"coverage": "Utilities invoked from JCL and the AutoEdit substitution that reaches them. NOT covered: the scheduling definitions themselves, which live in the Control-M database rather than in a repository, and which is where most of a scheduler's real authority sits. A repository sees the jobs, not the schedule.",
|
|
24
|
+
"why": "A scheduler decides what runs, when, and under whose authority. Most of that is held outside any repository, which is itself the finding worth stating: this pack reads the part that is committed and is explicit that the larger part is not.",
|
|
25
|
+
"rules": [
|
|
26
|
+
{
|
|
27
|
+
"id": "ctm-order-force",
|
|
28
|
+
"verb": "CTMAPI ORDER/FORCE",
|
|
29
|
+
"pattern": "^\\s*(?:ORDER|FORCE)\\s+(?:JOB|TABLE)\\b",
|
|
30
|
+
"appliesTo": [
|
|
31
|
+
"jcl-instream"
|
|
32
|
+
],
|
|
33
|
+
"severity": "high",
|
|
34
|
+
"risk": "orders or forces a job into the active schedule",
|
|
35
|
+
"rationale": "ORDER places a job into the active environment and FORCE does so regardless of its scheduling criteria. A job that can force another job has escaped the schedule that was reviewed, and what it forces is named here rather than in the scheduler."
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "ctm-autoedit-variable-in-command",
|
|
39
|
+
"verb": "%%AUTOEDIT",
|
|
40
|
+
"pattern": "%%[A-Z0-9_$#@.]{2,}",
|
|
41
|
+
"appliesTo": [
|
|
42
|
+
"jcl-instream"
|
|
43
|
+
],
|
|
44
|
+
"severity": "med",
|
|
45
|
+
"risk": "substitutes a scheduler variable into the statement at run time",
|
|
46
|
+
"rationale": "An AutoEdit variable is replaced by the scheduler before the statement runs, so what the committed text says is not what executes. The value comes from the scheduler's own tables, which are outside this repository and outside this review."
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "ctm-runas-owner",
|
|
50
|
+
"verb": "OWNER / RUNAS",
|
|
51
|
+
"pattern": "^\\s*(?:OWNER|RUN_AS|RUNAS)\\s*=\\s*[A-Z0-9$#@]{1,8}",
|
|
52
|
+
"appliesTo": [
|
|
53
|
+
"jcl-instream"
|
|
54
|
+
],
|
|
55
|
+
"severity": "high",
|
|
56
|
+
"risk": "runs work under a named identity other than the submitter's",
|
|
57
|
+
"rationale": "Naming an owner runs the work with that identity's authority rather than the submitter's. Whether that is a privilege escalation depends on the identity, which is why this is reported rather than judged."
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "ctm-security-utility",
|
|
61
|
+
"verb": "CTMSEC",
|
|
62
|
+
"pattern": "\\bCTMSEC\\b|^\\s*(?:DEFINE|UPDATE)\\s+USER\\b",
|
|
63
|
+
"appliesTo": [
|
|
64
|
+
"jcl-instream"
|
|
65
|
+
],
|
|
66
|
+
"severity": "crit",
|
|
67
|
+
"risk": "changes Control-M security definitions",
|
|
68
|
+
"rationale": "The scheduler's own security definitions decide who may order, force or delete work across the estate. Editing them from a job puts the scheduler's access model in the hands of whoever can commit."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"id": "ctm-delete-condition",
|
|
72
|
+
"verb": "DELCOND",
|
|
73
|
+
"pattern": "^\\s*(?:DELCOND|DELETE\\s+COND(?:ITION)?)\\b",
|
|
74
|
+
"appliesTo": [
|
|
75
|
+
"jcl-instream"
|
|
76
|
+
],
|
|
77
|
+
"severity": "med",
|
|
78
|
+
"risk": "deletes a scheduling condition other work waits on",
|
|
79
|
+
"rationale": "Conditions are how one job tells another it may start. Deleting one releases whatever was waiting, so the effect of this statement is on jobs it does not name and whose definitions are not in this repository."
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"id": "ctm-file-watcher-command",
|
|
83
|
+
"verb": "CTMFW",
|
|
84
|
+
"pattern": "\\bCTMFW\\b",
|
|
85
|
+
"appliesTo": [
|
|
86
|
+
"jcl-instream"
|
|
87
|
+
],
|
|
88
|
+
"severity": "low",
|
|
89
|
+
"risk": "waits on a file and triggers work when it appears",
|
|
90
|
+
"rationale": "A file watcher turns the arrival of a dataset into the start of work. It is an entry point driven by something outside the job, and it is listed at low severity so the route is visible rather than because arrival is itself suspicious."
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"id": "ctm-utility-step",
|
|
94
|
+
"verb": "CTMAPI / CTMJSA",
|
|
95
|
+
"pattern": "^CTM(API|JSA|CSU|LDNRS|BLT)$",
|
|
96
|
+
"appliesTo": [
|
|
97
|
+
"jcl-step"
|
|
98
|
+
],
|
|
99
|
+
"severity": "info",
|
|
100
|
+
"risk": "reaches the scheduler from a job",
|
|
101
|
+
"rationale": "A Control-M utility invoked as a step is where a job stops being scheduled work and starts being work that changes the schedule. It is listed so a reader can see which jobs reach the scheduler, which is the question that precedes every other rule in this pack."
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
"programs": [
|
|
105
|
+
"CTMAPI",
|
|
106
|
+
"CTMJSA",
|
|
107
|
+
"CTMCSU",
|
|
108
|
+
"CTMLDNRS",
|
|
109
|
+
"CTMBLT",
|
|
110
|
+
"CTMFW",
|
|
111
|
+
"CTMSEC",
|
|
112
|
+
"CONTROLM"
|
|
113
|
+
]
|
|
114
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "The byte layout of system copybooks a repository copy can shadow, as elementary fields [name, offset, size, occurs]. A repository copy whose elementary fields match is the system's own layout; one that differs compiles its programs against something else. Offsets follow from the documented order and lengths, and each total matches the length the documentation states. Group names are not compared: IBM's own copies group SQLWARN8-A under SQLEXT, which vendored copies often fold into SQLWARN.",
|
|
3
|
+
"SQLCA": {
|
|
4
|
+
"source": { "title": "Db2 13 for z/OS, Description of SQLCA fields", "url": "https://www.ibm.com/docs/en/db2-for-zos/13?topic=sqlca-description-fields", "retrieved": "2026-09-27" },
|
|
5
|
+
"length": 136,
|
|
6
|
+
"fields": [
|
|
7
|
+
["SQLCAID", 0, 8, 1],
|
|
8
|
+
["SQLCABC", 8, 4, 1],
|
|
9
|
+
["SQLCODE", 12, 4, 1],
|
|
10
|
+
["SQLERRML", 16, 2, 1],
|
|
11
|
+
["SQLERRMC", 18, 70, 1],
|
|
12
|
+
["SQLERRP", 88, 8, 1],
|
|
13
|
+
["SQLERRD", 96, 4, 6],
|
|
14
|
+
["SQLWARN0", 120, 1, 1],
|
|
15
|
+
["SQLWARN1", 121, 1, 1],
|
|
16
|
+
["SQLWARN2", 122, 1, 1],
|
|
17
|
+
["SQLWARN3", 123, 1, 1],
|
|
18
|
+
["SQLWARN4", 124, 1, 1],
|
|
19
|
+
["SQLWARN5", 125, 1, 1],
|
|
20
|
+
["SQLWARN6", 126, 1, 1],
|
|
21
|
+
["SQLWARN7", 127, 1, 1],
|
|
22
|
+
["SQLWARN8", 128, 1, 1],
|
|
23
|
+
["SQLWARN9", 129, 1, 1],
|
|
24
|
+
["SQLWARNA", 130, 1, 1],
|
|
25
|
+
["SQLSTATE", 131, 5, 1]
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/Portll/cobolwork/schema/cobolwork-coverage.schema.json",
|
|
4
|
+
"title": "cobolwork/coverage",
|
|
5
|
+
"description": "A SARIF property-bag convention for analyser coverage: what a run read, what it did not, and why it stopped. SARIF 2.1.0 can say that files could not be read, through invocations[].toolExecutionNotifications, but it has no standard way to state coverage as a ratio with a denominator. This is that denominator. It lives under invocations[].properties['cobolwork/coverage'], namespaced so it cannot be mistaken for a standard field, and it is an addition to the standard notifications rather than a replacement for them: a conforming consumer that ignores this key still learns which files were not read. It is published with one implementation, which is not enough to make it a standard. If a second analyser adopts it, the right venue is a proposed invocation.coverage object at the OASIS SARIF Technical Committee, where the bar is prior use rather than a good argument.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["coverageIncomplete"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"coverageIncomplete": {
|
|
10
|
+
"type": "boolean",
|
|
11
|
+
"description": "True when the run did not read everything it selected. The verdict, and the reason this convention exists: a finding count over source nobody read is not a clean result. It is duplicated in invocations[].properties.coverageIncomplete and reflected in executionSuccessful, so a consumer cannot miss it by not knowing about this key."
|
|
12
|
+
},
|
|
13
|
+
"filesScanned": {
|
|
14
|
+
"type": ["integer", "null"],
|
|
15
|
+
"minimum": 0,
|
|
16
|
+
"description": "Artifacts whose bytes were read by at least one analysis."
|
|
17
|
+
},
|
|
18
|
+
"bySet": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"description": "Per analysis component, keyed by the component's own name. A run whose components read different slices of the tree cannot state one number, and a single total hides which analysis fell short.",
|
|
21
|
+
"additionalProperties": { "$ref": "#/$defs/componentCoverage" }
|
|
22
|
+
},
|
|
23
|
+
"setsIncomplete": {
|
|
24
|
+
"type": "array",
|
|
25
|
+
"description": "Components that did not fully run, each with the reason. Two distinct causes share this list and `kind` distinguishes them: a component that could not run for want of configuration, and one that ran and did not reach everything. Only `coverage` bears on whether the scan read what it was given.",
|
|
26
|
+
"items": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"required": ["set", "kind", "why"],
|
|
29
|
+
"properties": {
|
|
30
|
+
"set": { "type": "string" },
|
|
31
|
+
"kind": { "enum": ["coverage", "configuration"], "description": "coverage: the component ran and did not read everything. configuration: it could not run in full because it was not told enough, such as a missing cobolwork.site.json." },
|
|
32
|
+
"why": { "type": "string", "description": "Written for a person deciding what to do about it, from the reasons for that kind." }
|
|
33
|
+
},
|
|
34
|
+
"additionalProperties": false
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"additionalProperties": false,
|
|
39
|
+
"$defs": {
|
|
40
|
+
"componentCoverage": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"description": "filesScanned + filesNotRead + filesUnreadable + filesUnparsed accounts for every artifact the component selected.",
|
|
43
|
+
"properties": {
|
|
44
|
+
"filesScanned": { "type": "integer", "minimum": 0, "description": "Read, and contributed." },
|
|
45
|
+
"filesUnreadable": { "type": "integer", "minimum": 0, "description": "Selected, and the bytes never arrived." },
|
|
46
|
+
"filesUnparsed": { "type": "integer", "minimum": 0, "description": "Read, and could not be parsed. Distinct from unreadable: counting these as unreadable understates what the run managed to look at." },
|
|
47
|
+
"filesNotRead": { "type": "integer", "minimum": 0, "description": "Selected and never opened, because the run stopped before reaching them." },
|
|
48
|
+
"filesBinary": { "type": "integer", "minimum": 0 },
|
|
49
|
+
"stoppedBy": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"description": "What ended the reading early, in the words of the limit that was hit.",
|
|
52
|
+
"examples": ["memory reserve", "memory exhausted", "byte budget"]
|
|
53
|
+
},
|
|
54
|
+
"unreadable": {
|
|
55
|
+
"type": "array",
|
|
56
|
+
"items": { "type": "string" },
|
|
57
|
+
"description": "'path: reason', one per artifact. A count names nothing anyone can go and look at, and it cannot tell a permission denied from the analyser being wrong."
|
|
58
|
+
},
|
|
59
|
+
"unparsed": { "type": "array", "items": { "type": "string" }, "description": "'path: reason', one per artifact." },
|
|
60
|
+
"notRead": { "type": "string", "description": "One sentence a report can print verbatim, saying how much was missed and that the result is not a claim about the whole input." }
|
|
61
|
+
},
|
|
62
|
+
"additionalProperties": false
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/Portll/cobolwork/schema/cobolwork.policy.schema.json",
|
|
4
|
+
"title": "cobolwork build policy",
|
|
5
|
+
"description": "Which findings stop a build, which run-time checks the compiler must generate, and what an incomplete scan means. docs/spec/build-gate.md section 4. lib/policy.mjs validates the same shape and also refuses a rule id cobolwork does not declare.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["policyVersion"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"_comment": { "type": "string" },
|
|
11
|
+
"policyVersion": { "const": 1 },
|
|
12
|
+
"block": { "$ref": "#/$defs/tier", "description": "A finding at or above this tier blocks. Default high." },
|
|
13
|
+
"always": { "$ref": "#/$defs/tier", "description": "In ratchet mode, a finding at or above this tier blocks whether or not the change introduced it. Default crit." },
|
|
14
|
+
"classes": {
|
|
15
|
+
"type": "array",
|
|
16
|
+
"items": { "enum": ["privilege-escalation", "data-mutation"] },
|
|
17
|
+
"uniqueItems": true,
|
|
18
|
+
"description": "A finding in one of these consequence classes blocks at any tier. Default both."
|
|
19
|
+
},
|
|
20
|
+
"rules": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"additionalProperties": { "enum": ["block", "warn"] },
|
|
23
|
+
"description": "Per rule id: block whatever its tier, or warn whatever its tier or class."
|
|
24
|
+
},
|
|
25
|
+
"coverage": { "enum": ["block", "warn"], "description": "Incomplete coverage makes the build undecided (block) or is reported (warn). Default block." },
|
|
26
|
+
"options": { "enum": ["block", "warn"], "description": "A build without a required run-time check fails, and options nothing declares make it undecided (block); or both are reported and the build passes relaxed (warn). An option forbid names, and a check the change removes, fail either way. Default warn." },
|
|
27
|
+
"waivers": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"additionalProperties": false,
|
|
30
|
+
"required": ["maxDays"],
|
|
31
|
+
"properties": { "maxDays": { "type": "integer", "minimum": 1 } },
|
|
32
|
+
"description": "A waiver written with an expiry further off than maxDays covers nothing. Default 180."
|
|
33
|
+
},
|
|
34
|
+
"checks": {
|
|
35
|
+
"type": "array",
|
|
36
|
+
"items": { "enum": ["subscript", "reference-modification", "numeric-data", "argument-length"] },
|
|
37
|
+
"uniqueItems": true,
|
|
38
|
+
"description": "The run-time checks every program must be compiled with. Default subscript and reference-modification."
|
|
39
|
+
},
|
|
40
|
+
"forbid": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"additionalProperties": false,
|
|
43
|
+
"properties": {
|
|
44
|
+
"enterprise": { "$ref": "#/$defs/options" },
|
|
45
|
+
"gnucobol": { "$ref": "#/$defs/options" }
|
|
46
|
+
},
|
|
47
|
+
"description": "Options a build must not pass, beyond those that turn a required check off."
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"$defs": {
|
|
51
|
+
"tier": { "enum": ["info", "low", "med", "high", "crit", "known-exploitable"] },
|
|
52
|
+
"options": { "type": "array", "items": { "type": "string", "minLength": 1 } }
|
|
53
|
+
}
|
|
54
|
+
}
|