Repository navigation
cobolwork 0.7.0
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 --precompilechecks that each program with EXEC SQL or EXEC CICS compiles under cobc or gcobol once translated, before the compiler command runs.scan,diffandbuildread a PDS export that holds TSO TRANSMIT files and IEBCOPY unloads, naming members asDATA.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,.pl1and.plxprograms. 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.%INCLUDEmembers 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
pliset 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; andpli-based-storage-addressing,pli-entry-variable-callandpli-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 setscoverageIncomplete.COBOLWORK_PLI=0leaves 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.
imsreads.dbdand.psbfiles, assembler files that hold a DBD or PSB, and members with no extension whose DBD or PCB statements say what they are.ddlreads.sql,.ddland.db2files and members that open with a CREATE or GRANT statement. A statement either reader refuses is named by kind and setscoverageIncomplete. 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 areCALL '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
ddlset reportsdb2-grant-to-public(high, critical for a write or an authority, CWE-732),db2-grant-with-grant-option(medium, CWE-269) anddb2-system-authority-granted(medium, CWE-250, for SYSADM, SYSCTRL, DBADM, DBCTRL, SECADM, ACCESSCTRL and DATAACCESS).db2-exit-routine-declaredanddb2-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-queuesource, followed as a file record is, with 16 rules fromcics-queue-to-os-commandtocics-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: 107cics-queue-to-cics-dynamic-transfer, 30cics-queue-to-socket-send, 16cics-queue-to-queue-name, 5cics-queue-to-dynamic-file-pathand 3cics-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-pathandcics-web-to-dynamic-file-pathcan 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 underprecompiled.skippedand left to the compiler command. Without--and a cobc or gcobol,--precompileexits 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-exportreads 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 undersummary.pdsExport.archivesand not read. No corpus holds one; the tests are built from IBM's published layouts.diff --pds-exportcompares 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-exportnames findings, the ratchet's pairing, waivers and provenance hashes asDATA.SET/MEMBER, takes options from the members' cards and the export's JCL, and runs--ironworkover 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,RSMandVSM, which switch to key zero, BTAM's READ and WRITE types and VTAM basic mode.scan --no-mvs38-formsrefuses them. hlasm-supervisor-state-changereports at high a MODESET switch to another system key:EXTKEY=KEY2,KEY3,KEY4orKEY7, 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*PROCESSdecides, and UNI otherwise. A mnemonic outside the table is a macro call.rules/hlasm-optables.jsonholds 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
.asmare 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-credentialandembedded-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 inrules/gitleaks-mainframe.toml, and thesecretsset 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/v1fingerprint, 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 inschema/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
schemaVersion1, andcapabilitiesreports it. cobolwork.site.json, the witness feed and the reachability extract take aversion, read as 0 when absent; a newer one is refused, naming both numbers. A key cobolwork does not read is listed undersummary.siteWarningsorsummary.feedWarningsand 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].artifactswith every location pointing at it by index, and gives each rule descriptor its framework clauses asproperties.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 verifyreports undercompliancethe 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,vmorinterpreter. build --ironworkreads the message id ironwork puts before a diagnostic, keeps it as the diagnostic'sid, 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_WORKERSsets 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, andCOBOLWORK_VERIFY_REUSE=1rebuilds 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 recordlabelledOn: extended, orrewritten+extendedfor command-line input, andbench/precision.mjscounts 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,imsandddlsets and the flow set. A tree holding them gains findings and may gaincoverageIncompletefor 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-programtakes 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.breakand 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&SYSNDXno 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'*andL'=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
--repossummary carriesflowModel,toolVersion,executionFeeds,byExecutionandexecutionFeedProblems, 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-exportwith 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.
capabilitieslists--mvs38-forms,--no-mvs38-formsand--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.precompileandprecompiledin the build document,schemaVersionon explain, parse, baseline and evidence output, andcomplianceinevidence 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
baselineagain for those. - New rules can change a build gate's outcome where they fire: the
pli,imsandddlsets' rules and the MODESET system-key findings are gated by tier like any other finding, and a policy entry ofwarnfor a rule keeps it from blocking. The 16cics-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
.sqlfile 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 withcoverage: warnreports 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 withCOBOLWORK_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=0turns them off.
The package's lib/revision.json names the commit it was built from.