Skip to content

cobolwork 0.7.0

Choose a tag to compare

@github-actions github-actions released this 07 Oct 04:56
· 79 commits to main since this release

Summary

A scan reads three more kinds of mainframe source beside COBOL, JCL and assembler: PL/I programs, IMS definitions with the DL/I calls that use them, and Db2 DDL.

  • PL/I programs join the data-flow graph, so input reaching a sink through PL/I is reported as it is in COBOL; 98.8% of hand-written PL/I statements parse on the development corpus and 99.8% on each held-out one.
  • A PSB that grants an option its programs' DL/I calls never use is high, and a GRANT to PUBLIC, WITH GRANT OPTION or of a system authority is reported from Db2 DDL.
  • A CICS queue item, the data a START passes and a web request's form fields are input: 16 cics-queue-to-* rules, which added 161 findings over the 72 corpus repositories holding EXEC CICS.
  • build --precompile checks that each program with EXEC SQL or EXEC CICS compiles under cobc or gcobol once translated, before the compiler command runs.
  • scan, diff and build read a PDS export that holds TSO TRANSMIT files and IEBCOPY unloads, naming members as DATA.SET/MEMBER.
  • Assembler reads MVS 3.8 macro operands and every program of a batch file, and reports a MODESET switch to any system key.
  • Every document cobolwork writes or reads has a JSON Schema and a version, and STABILITY.md lists what a release keeps.

Limit: PL/I has no compiler that may grade the reader, and a pointer, an entry variable or the preprocessor takes PL/I's flow out of sight; those are listed, not followed. DL/I ISRT and REPL are not sinks yet.

What 0.7.0 adds over 0.6.0

Install: npm install -g @portll/cobolwork or pip install cobolwork; either gives the command cobolwork. The PyPI package runs the same files with the Node.js on your PATH, which must be Node 22 or later. The npm package is attached here as cobolwork-0.7.0.tgz to pin, and as cobolwork.tgz.

PL/I

  • A scan reads .pli, .pl1 and .plx programs. The reader parses each statement by kind: DECLARE and structure fragments, PROCEDURE, ENTRY, PACKAGE, BEGIN and END, assignments (compound and BY NAME), DO, IF, SELECT and jumps, CALL, FETCH and RELEASE, OPEN, CLOSE and record I/O, PUT, GET, DISPLAY, FORMAT and FLUSH, ON, SIGNAL, REVERT and RESIGNAL, ALLOCATE and FREE, EXEC SQL, EXEC CICS and EXEC DLI, and preprocessor statements. Expressions are parsed by precedence with their references read. %INCLUDE members are spliced in before statements are split. Structures are mapped to offsets and sizes by IBM's pairing rules.
  • PL/I programs join the flow graph, and the existing data-flow rules report input reaching a sink in them. Subscripts, TITLEs, a MAIN procedure's parameters and the records a job's DD statements supply reach the flow rules, and a program that includes IBM's PL/I SQLCA has it declared. On the 20 repositories of the 500 set that hold PL/I, reading PL/I adds 73 findings, all in PL/I programs: 70 in programs nothing in the tree starts, and 3 where a job's PARM sets a substring's position or length. No COBOL finding is added or lost. The 20 scans take 223 s against 190 s.
  • The pli set reports what the program text shows: pli-fetch-title-variable (medium) for a FETCH whose TITLE is not a literal; pli-error-condition-ignored (low) for an ON unit for ERROR, CONVERSION, ZERODIVIDE, SUBSCRIPTRANGE or a like condition whose body does nothing or only jumps away; and pli-based-storage-addressing, pli-entry-variable-call and pli-preprocessor-in-use (info), where the flow leaves what a static reading follows. Each statement the reader does not parse is counted by kind and sets coverageIncomplete. COBOLWORK_PLI=0 leaves PL/I out of a scan.
  • Measured by how much of a corpus parses (diag/pli-measure.mjs), on 2026-10-07: 98.8% of 9,547 hand-written statements in 9 repositories of the 500 set, which was used while building the reader; 99.9% of 263,531 statements in its one generated repository, counted apart; 99.8% of 1,768 statements in 3 repositories of the held-out 200 set, and of 3,686 in 6 of the held-out 300 set.

IMS and Db2 definitions

  • Two rule sets run in every scan. ims reads .dbd and .psb files, assembler files that hold a DBD or PSB, and members with no extension whose DBD or PCB statements say what they are. ddl reads .sql, .ddl and .db2 files and members that open with a CREATE or GRANT statement. A statement either reader refuses is named by kind and sets coverageIncomplete.
  • ims-procopt-broader-than-used (high, CWE-250) compares a PSB with the DL/I calls of the programs that run with it: one a DFSRRC00 step's PARM names with it, one that schedules it, or the program its PSBGEN name or file name names. The calls read are CALL 'CBLTDLI' and 'AIBTDLI' and EXEC DLI. Only an option no call of those programs uses at all is reported, and a PSB is skipped when one of its programs has an undecided function code, passes on its PCB or a LINKAGE item, or schedules the PSB and LINKs or XCTLs. Over the 14 repositories of the 500 set that issue DL/I it fires once.
  • A DL/I get call (GU, GN, GNP and their hold forms) fills its I/O area as a database value, as EXEC SQL INTO does, and the database-to-* rules read it from there.
  • DBDs and PSBs are read into segments, fields, PCBs and sensitive segments with every operand checked. ims-definition-inconsistent (low) carries what DBDGEN or PSBGEN refuses under IBM's message number (FLD100, FLD120, FLD170, FLD190, FLD230, SEGM140, SEGM170, SEG110, SEG140, SEG150, SEG160), and DFS0919I where KEYLEN is shorter than the concatenated key along the DBD's physical path. ims-senseg-unknown-segment (low) names a SENSEG its DBD does not define, which ends the program with U0914, when that DBD is in the tree, read whole and defined once. ims-pcb-procopt-all (info) lists a database PCB that can get, insert, replace and delete. Over the 500 set these give 41 PROCOPT inventories in 10 repositories and 42 KEYLEN errors in one.
  • Db2 for z/OS DDL is read after the Db2 13 reference: CREATE TABLE, INDEX, VIEW, DATABASE, STOGROUP, TABLESPACE, PROCEDURE and FUNCTION, ALTER TABLE, GRANT and REVOKE, DROP, COMMENT ON, LABEL ON, SET, RENAME and sequences. The ddl set reports db2-grant-to-public (high, critical for a write or an authority, CWE-732), db2-grant-with-grant-option (medium, CWE-269) and db2-system-authority-granted (medium, CWE-250, for SYSADM, SYSCTRL, DBADM, DBCTRL, SECADM, ACCESSCTRL and DATAACCESS). db2-exit-routine-declared and db2-external-routine (info) name the EDITPROC, VALIDPROC, FIELDPROC or EXTERNAL NAME load module that runs inside Db2. Over the 500 set's SQL in 54 repositories the set reported nothing.
  • On the corpora's DDL, the reader parses 6,404 of 7,039 statements in the 500 set's 29 repositories that hold DDL and 3,061 of 3,482 in the held-out sets' 19. Each statement it refuses was read against the Db2 13 for z/OS syntax diagrams and is another dialect or a form the diagrams do not draw.

CICS sources and sinks

  • An item READQ TS or READQ TD returns, and the data RETRIEVE returns from the START that began the task, are the cics-queue source, followed as a file record is, with 16 rules from cics-queue-to-os-command to cics-queue-to-extrapartition-queue. They warn in the build gate until their precision is measured. A web request's form fields, query parameters and headers, which WEB READ, WEB READNEXT and WEB EXTRACT return, are web input. Over the 72 repositories of the 500 set that hold EXEC CICS this adds 161 findings and changes none: 107 cics-queue-to-cics-dynamic-transfer, 30 cics-queue-to-socket-send, 16 cics-queue-to-queue-name, 5 cics-queue-to-dynamic-file-path and 3 cics-web-to-reference-modification. The 16 queue-name findings, in BankDemo, are false: two MOVEs overwrite the queue name RETRIEVE fills before the DELETEQ, and the flow engine does not end a route at a later write, for any source.
  • A data name in the FILE or DATASET option of EXEC CICS READ, WRITE, DELETE or STARTBR is a dynamic file path sink, so cics-terminal-to-dynamic-file-path and cics-web-to-dynamic-file-path can fire on a CICS program. Its verification plan expects FILENOTFOUND. None of the 11 corpus repositories that name a CICS file through a data name gains a finding.
  • Each EXEC block is read once, and the command is named by the precompile table: SEND MAP is SEND MAP whatever the order of its options, and DELETEQ without TS is DELETEQ TS. The control analysis takes an option as written only where the table says CICS returns a value in it, so a bound proved on an index holds across WRITEQ TS LENGTH(WS-I). Over the 72 repositories the 27,703 findings are unchanged.

Precompiling in the build gate

  • build --precompile -- cobc|gcobol … translates, on a pass, each program the compiler command names that holds EXEC SQL or EXEC CICS, and runs the same compiler on the translation with -fsyntax-only. A refused translation exits 4 with its first errors named by the program's own lines and no literal quoted, and the compiler command does not run. A program holding EXEC DLI is named under precompiled.skipped and left to the compiler command. Without -- and a cobc or gcobol, --precompile exits 2. The translation is a check, never the artifact.
  • The precompile table names 61 more commands: WEB and DOCUMENT, channel browsing, SOAP faults and transforms, QUERY SECURITY, APPC conversations, INQUIRE and SET for eight resources, and the asynchronous API. DATASET passes as FILE. Over the 500 set, of 205,951 EXEC CICS blocks the one left unknown is a misspelt ASKTTIME.

PDS exports

  • scan --pds-export reads a TSO TRANSMIT file or an IEBCOPY unload into its data set, named by the transmission's INMR02 or by the file, and places a partitioned data set's members by their directory entries. A PDSE unload, an IEBCOPY marked in error, or a transmission of several files or cut short is listed under summary.pdsExport.archives and not read. No corpus holds one; the tests are built from IBM's published layouts.
  • diff --pds-export compares two revisions member by member. A copybook widened in one data set is a layout change in the programs of another that copy it.
  • build --pds-export names findings, the ratchet's pairing, waivers and provenance hashes as DATA.SET/MEMBER, takes options from the members' cards and the export's JCL, and runs --ironwork over the members. A compiler command given with it is refused, and equivalence is not run over an export.

Assembler

  • MVS 3.8's macro operands that z/OS 3.1's documentation does not list are read with the meaning the MVS 3.8j macro gives them, among them EXTKEY=SUPR, SRM, RSM and VSM, which switch to key zero, BTAM's READ and WRITE types and VTAM basic mode. scan --no-mvs38-forms refuses them.
  • hlasm-supervisor-state-change reports at high a MODESET switch to another system key: EXTKEY=KEY2, KEY3, KEY4 or KEY7, and MVS 3.8's names for keys 1 to 7. TCB, RBT1 and RBT234 take a key decided at run time and are not reported.
  • A file is read past its first END, each program in turn, as HLASM's default BATCH option assembles them. JCL, SMP/E and IEBUPDTE cards and an end-of-file mark are set aside wherever they stand. On the CBT Tape corpus this reads 142,993 more statements in 416 files.
  • scan --hlasm-optable <table> picks the operation code table; without it a *PROCESS OVERRIDE, the estate's assembly PARM when its steps agree, or the file's *PROCESS decides, and UNI otherwise. A mnemonic outside the table is a macro call. rules/hlasm-optables.json holds each mnemonic's tables from IBM's HLASM 1.6 table of supported instructions.
  • A repository's own macro named IF, DO, LINK, SAVE or like a library macro is read as the repository's macro, and a macro the file defines with an instruction's name overrides it from its definition on.
  • The reader takes list, execute and branch-entry forms of MODESET, LINK, XCTL, GETMAIN and FREEMAIN; every form of READ, WRITE, OPEN, CLOSE, LOAD, ATTACH, ESTAE, WTO and WTOR, with BDAM, BISAM, BSAM and VSAM types; STAI and ESTAI exit lists; EXCP DCB keywords; CCW, CCW0 and CCW1; model statements whose variables carry strings or numbers, marked inexact so the locator places nothing from them; and unnamed DSECTs. Over the CBT Tape and MVS 3.8j corpora the list and execute forms parse 2,283 more statements, the data management and supervisor macros 3,622 more in open code, CCWs, model names and redefined opcodes 7,878 more, and valued model statements 1,569 more.
  • A disassembler's listing and a manual saved as .asm are counted as a listing and a document, not read for findings and not counted against coverage.
  • Outside conditional assembly, 99.8% of statements parse on the held-out corpus (5,784 of 5,796), and 99.5% on the development corpus. The reader refuses what the assembler refuses in cards and lengths, and against z390 agrees on 6,460 of 6,466 statements on the development split and 437,125 of 437,142 on the CBT Tape held-out split.

Credentials

  • cobolwork's mainframe credential rules are offered to the secret scanner Betterleaks as a social PR: an offer to the wider secret-scanning community, betterleaks/betterleaks#379 with issue #378. It adds four rules, jcl-racf-password, jcl-racf-new-password, cobol-value-credential and embedded-sql-connect-password, which on the 500 held-out repositories give the same 23 findings in 9 repositories that cobolwork's own rules do. The PR is open and awaits the maintainers' review. cobolwork does not depend on it: its rules stay in rules/gitleaks-mainframe.toml, and the secrets set runs without gitleaks or Betterleaks.

Contracts and output formats

  • STABILITY.md lists what tools built on cobolwork depend on: rule ids, the command line, exit statuses, document versions and the cobolwork/v1 fingerprint, whose hashes a test pins. From 0.9.0 these change only in a major release. The package ships the file.
  • schema/ holds a JSON Schema for every document cobolwork writes or reads, and each finding is described once in schema/cobolwork-finding.schema.json. Tests check real reports against them and fail on a key a schema does not describe.
  • explain, parse, baseline and evidence output carry schemaVersion 1, and capabilities reports it.
  • cobolwork.site.json, the witness feed and the reachability extract take a version, read as 0 when absent; a newer one is refused, naming both numbers. A key cobolwork does not read is listed under summary.siteWarnings or summary.feedWarnings and as a SARIF configuration notification. A feed that is not a JSON object is refused instead of failing the scan.
  • SARIF carries a CWE taxonomy that each rule naming a CWE points at, lists each file once in runs[0].artifacts with every location pointing at it by index, and gives each rule descriptor its framework clauses as properties.compliance.
  • The compliance files map the evidence cobolwork and ironwork write: NIST SP 800-53 takes a run journal's records under AU-3, its chain under AU-9(3), the provenance statement under SR-4 and ironwork's options in force under CM-6, and COBIT 2019 follows. evidence verify reports under compliance the clauses for the journal it found and, once it verifies, its chain. Every new rule has a row in each compliance file.
  • A run journal's close record may name its executor, vm or interpreter.
  • build --ironwork reads the message id ironwork puts before a diagnostic, keeps it as the diagnostic's id, and reads an IWR id, a construct ironwork does not run yet, as undecided.

Scan time

  • The control analyses of programs of 32 KiB or more are built in worker threads, by default one fewer than the machine's threads and at most four; COBOLWORK_CONTROL_WORKERS sets the count and 0 turns them off. With four workers cnafbadboy's Batch scans in 109 s against 314 s, and one Unieuro program in ten in about 310 s against 440 s, at up to 2.5 GB more peak memory. Reports are identical to those built without workers on 11 repositories, and COBOLWORK_VERIFY_REUSE=1 rebuilds each worker's analysis on the main thread and compares it. Workers claim files in order up to twice their number ahead of the main thread.
  • The crypto, recon, secrets, hidden and web sets read through the shared pass instead of a whole-tree read of their own, each of which cost 15 to 23 s on one Unieuro program in ten (31,520 files). Copybooks and their declarations are read once, only COMPUTE statements are judged for SIZE ERROR unless TRUNC(OPT) is in force, pointer declarations are found only when a CALL needs them, the items inside a checked field are found by offset, and the flow walk keys its visits by number.

Labelling

  • The labeller labels a finding on a file a SELECT assigns from a data item by running it under ironwork's --compliance extended. Those rows record labelledOn: extended, or rewritten+extended for command-line input, and bench/precision.mjs counts each as its own stratum. The EXEC CICS FILE or DATASET form stays unknown, since ironwork does not trace it.

Changed

  • A scan reads PL/I, IMS and Db2 DDL by default, through the new pli, ims and ddl sets and the flow set. A tree holding them gains findings and may gain coverageIncomplete for statements those readers refuse.
  • MVS 3.8 macro operands are read rather than refused, and the key-zero names among them count under hlasm-supervisor-state-change.
  • input-selects-program takes U4038 with CEE3501S, and in a CICS task transaction abend 4038, as it took S806, which it keeps for manifests from earlier ironwork releases.
  • A file that is valid UTF-8 and holds characters beyond ASCII is read a character to a column. Before, such a character took a column per byte, which moved column 72 and the sequence area.

Fixed

  • The parser reads every data item, size and call of the 500-repository grade as GnuCOBOL does: an FD's RECORD clause past LABEL RECORD, REPLACE ==:TAG:== inside a picture or name, debugging lines under WITH DEBUGGING MODE, PIC U, screen VALUEs, report groups and a CALL literal holding a path. The grade now matches 845,704 of 845,704 items, 719,184 sizes and 27,703 calls; one label of 146,004 differs, an EJECT in Area A.
  • Two writers breaking one stale ledger lock no longer fork the ledger: the breaker holds ledger.lock.break and renames it over the lock. Twelve closers racing on one stale lock forked 5 ledgers in 100 rounds before; the same races now verify 100 of 100.
  • A member copied off the mainframe in binary keeps its 80-byte records when a constant holds X'15' or X'25'.
  • An extensionless assembler member whose first 4 KB decide nothing is read up to 64 KB before it is classified: over 33,217 CBT Tape and MVS 3.8j members, recall rises from 0.650 to 0.970 at precision 0.986, against 0.989.
  • A model name such as KFBR&SYSNDX no longer sends the reader into a loop that ran out of stack; 3,149 such statements in the CBT Tape corpus and 740 in the MVS 3.8j held-out split were reported as parser errors.
  • L'* and L'= no longer run into the remarks, also when a continuation splits them, and a trailing comma on a keyword-only macro is read as an omitted operand.
  • A --repos summary carries flowModel, toolVersion, executionFeeds, byExecution and executionFeedProblems, as a single repository's report does.
  • SARIF keeps the advisories a finding rests on out of its related locations and artifacts, where an advisory became the URI undefined.
  • Copies of a program with no procedure division count as one under programsUnordered.
  • build --pds-export with a compiler command is refused for that reason, before the compiler is looked for.
  • web's CSD definitions are read under the memory guard, and a URIMAP defined in a file after the program that opens through it is judged once the pass ends.
  • capabilities lists --mvs38-forms, --no-mvs38-forms and --hlasm-optable.

Upgrading

  • A tool reading cobolwork's documents should ignore keys it does not know; STABILITY.md says additions come in a minor release. New keys here: summary.siteWarnings, summary.feedWarnings, summary.pdsExport.archives, checks.precompile and precompiled in the build document, schemaVersion on explain, parse, baseline and evidence output, and compliance in evidence verify. The report versions are unchanged.
  • A finding whose statement holds a character beyond ASCII in a UTF-8 file may have a new fingerprint, so a baseline or waiver entry for it no longer matches; run baseline again for those.
  • New rules can change a build gate's outcome where they fire: the pli, ims and ddl sets' rules and the MODESET system-key findings are gated by tier like any other finding, and a policy entry of warn for a rule keeps it from blocking. The 16 cics-queue-to-* rules warn unless the policy names them block.
  • A build whose tree holds statements the new readers refuse, such as SQL in another dialect in a .sql file or a PL/I statement the reader does not parse, has incomplete coverage, which the default policy (coverage: block) makes undecided, exit 3. The reason names the set. A policy with coverage: warn reports it and lets the build pass relaxed.
  • A site file, witness feed or reachability extract may state version: 1. One with a key cobolwork does not read now produces a warning rather than silence; a key opening with _ stays a note for people.
  • An estate whose assembler relies on MVS 3.8 operands being refused can pass --no-mvs38-forms. A PL/I tree can be left out with COBOLWORK_PLI=0.
  • With control workers, on a machine short of memory the flow set can stop reading sooner than without them, since the memory guard reads the machine's free memory; COBOLWORK_CONTROL_WORKERS=0 turns them off.

The package's lib/revision.json names the commit it was built from.