COBOL applications on the mainframe often handle sensitive data and critical business operations. Security involves both the platform (e.g. RACF access control) and how you write programs: validating input, avoiding SQL injection, protecting sensitive data, and following least privilege. This page introduces secure coding practices for COBOL, how they relate to mainframe security, and practical steps you can take.
Security in a program is like locking the doors and checking who comes in. You only let in data that is allowed (validation). You do not let strangers change the meaning of what you do (e.g. injection). You keep secrets (like passwords or account numbers) hidden or scrambled (encryption, masking). And you make sure each program and user can only do what they are supposed to do (least privilege). COBOL security is doing these things in your code and with the mainframe's security manager (RACF).
On z/OS, RACF (Resource Access Control Facility) is IBM's security manager. It controls who can log on, which programs and datasets they can use, and which CICS transactions they can run. Your COBOL program runs under a user id (or started task) that RACF has already authorized for certain resources. The program itself does not bypass RACF: if the user is not allowed to open a dataset, the OPEN will fail. You can also call RACF from your application to check whether the user is allowed to perform a specific action before doing it (e.g. before updating a sensitive file). That way you enforce both "can run this program" and "can do this operation inside the program."
RACF provides authentication (who the user is), authorization (what they are allowed to access), and logging (e.g. to SMF) for accountability. As a COBOL developer, you design your program to request only the access it needs (least privilege) and, where appropriate, perform additional authorization checks before sensitive operations.
Every piece of data that comes from outside your program—keyed input, files, parameters from other programs, or CICS commareas—should be validated before use. Invalid input can cause wrong results, injection attacks, or unstable behavior. Validation includes checking length (so you do not overrun buffers or truncate in unexpected ways), type (numeric fields contain valid digits, dates are valid), range (numbers and dates within allowed bounds), and allowed characters (reject or escape characters that are dangerous in your context, such as quotes in SQL).
123456789101112131415161718192021*> Example: validate length and numeric content IF WS-INPUT-LEN > 10 MOVE 'Input too long' TO WS-MESSAGE PERFORM SEND-ERROR GOBACK END-IF INSPECT WS-INPUT TALLYING WS-NON-NUM FOR CHARACTERS BEFORE INITIAL SPACE IF WS-NON-NUM > 0 MOVE 'Non-numeric in numeric field' TO WS-MESSAGE PERFORM SEND-ERROR GOBACK END-IF *> Validate range IF WS-AMOUNT < 0 OR WS-AMOUNT > 999999.99 MOVE 'Amount out of range' TO WS-MESSAGE PERFORM SEND-ERROR GOBACK END-IF
For alphanumeric fields that might later be used in SQL or commands, also check for dangerous characters (single quote, double quote, semicolon, comment markers) and reject or escape them according to your standards. Enforce a maximum length and consider a whitelist of allowed characters where possible.
SQL injection occurs when user-controlled input is concatenated into SQL text. An attacker can supply values that change the meaning of the statement (e.g. adding OR 1=1 to return all rows or running a different statement). In COBOL-DB2, building SQL with STRING and then EXECUTE IMMEDIATE or PREPARE is risky. The safe approach is to use parameterized (prepared) statements: write the SQL with placeholders (e.g. host variables or parameter markers) and pass user input as bound data, not as part of the SQL string. The DB2 precompiler and runtime treat the bound values as data, so they cannot change the structure of the SQL.
12345678910*> Unsafe: user input concatenated into SQL *> If WS-NAME contains ' OR ''1''=''1' the query can return all rows MOVE 'SELECT * FROM CUSTOMERS WHERE NAME = ''' TO SQL-STRING STRING WS-NAME DELIMITED BY SIZE INTO SQL-STRING EXEC SQL EXECUTE IMMEDIATE :SQL-STRING END-EXEC *> Safer: use a prepared statement with host variable EXEC SQL PREPARE STMT FROM 'SELECT * FROM CUSTOMERS WHERE NAME = ?' END-EXEC EXEC SQL EXECUTE STMT USING :WS-NAME END-EXEC
Even with parameterized SQL, validate input: limit length and reject or escape characters that are not allowed in your business rules. Avoid dynamic SQL built from user input when a static or parameterized statement can do the job.
Sensitive data (e.g. personally identifiable information, account numbers, passwords) should be protected at rest and, where applicable, in transit. On the mainframe, options include dataset-level encryption (so the whole file is encrypted by the system), field-level encryption (so only certain fields are encrypted, often via a product or exit), and masking when data is displayed or written to logs. Your COBOL program typically does not implement the encryption algorithm itself; it calls system or product APIs to encrypt or decrypt, or relies on encryption that is applied transparently (e.g. by the access method or a security product). Never store encryption keys in source code or in plain text in datasets that are broadly accessible; use a key management service or secure key store as per your site's standards.
In code, avoid moving sensitive data into areas that might be logged (e.g. CICS trace, dumps) or displayed unless masked. Use working storage only for what is needed for the current step and clear sensitive fields when no longer needed if your standards require it.
Least privilege means the program and the user id it runs under have only the minimum access required. The program should be authorized only for the datasets, CICS resources, and DB2 objects it actually uses, and with the minimum level (e.g. read-only where update is not needed). That way, if the program or user is compromised or has a bug, the damage is limited. Work with your security or RACF administrators to define discrete profiles for your program and to avoid giving it broad authority (e.g. generic dataset access) when specific profiles will do.
| Risk | Typical cause | Mitigation |
|---|---|---|
| SQL injection | User input concatenated into SQL text | Parameterized queries, input validation, avoid dynamic SQL with user input |
| Unauthorized access | Missing or weak RACF checks | Check authorization before sensitive operations, least privilege |
| Sensitive data exposure | Plain text storage, logs, or screens | Encrypt at rest, mask in logs and displays |
| Buffer overflow / bad data | Unvalidated length or type | Validate length and content of all inputs |
Security events (e.g. failed authorization, invalid input that might indicate probing, or sensitive operations) should be logged in a way that supports auditing. On the mainframe, SMF and product-specific logging (e.g. CICS) are commonly used. Do not log sensitive data in clear text; mask or omit it. Log enough context (e.g. user id, transaction, timestamp, result) so that security and operations can investigate incidents.
1. To reduce SQL injection risk in COBOL-DB2 you should:
2. RACF is used on the mainframe to:
3. Input validation in COBOL should include: