jde_query rejects statements that do not start with SELECT/WITH and flags
stacked statements and obvious DML/DDL keywords. Treat that as a convenience
and an early-fail, not as a security boundary. It is string matching, not a SQL
parser, and it can be bypassed. For example, on DB2 for i a user-defined function
or table function declared MODIFIES SQL DATA can be invoked from inside a plain
SELECT (SELECT * FROM TABLE(some_writing_function())) and would pass the check
while still writing.
The real read-only guarantee must live in IBM i object authority. Run the
LIVE connection under a profile that has read (*USE / SELECT) authority on the
data library and no add/update/delete authority (*ADD, *UPD, *DLT) on
its objects. With that in place, even a query that slips past the string check
cannot mutate anything, because the database itself refuses the write.
- IBM i object authority (the wall). A dedicated service profile with
*USE(read) on the data library and no*ADD/*UPD/*DLT/*CHANGE/*ALLon its*FILEobjects. This is an OS guarantee, independent of this app, and the control a security review should verify. - No execute on mutating routines. A
SELECTcan invoke a UDF or stored procedure that writes (DB2 for iMODIFIES SQL DATA). Deny the profile execute authority on procedures/functions that mutate, so this class cannot fire. - Scoped library list. Run with a minimal
CURLIB/library list (only the libraries the agent needs), so unqualified names cannot resolve to writable objects elsewhere. - Transaction read-only. Independently of the parser, run the session as a
read-only transaction (
SET TRANSACTION READ ONLYwhere the connection model allows). A second barrier that does not depend on this app's code. - Least privilege + secret hygiene. Service profile, not a person's; rotate the password; credentials from the environment, never the repo.
- App-level check (courtesy).
assertReadOnlyin the open core fails fast on the common mistakes with a clean message. Not a boundary (string matching, not a parser). If you ever promote it to a real control, replace the regex with a true SQL parser that walks the tree and rejects any non-SELECT/WITHnode, including CTE bodies and routine calls. - Deployment mode. The open core ships read-only; the controlled-write tools are a separate module and absent here entirely.
- The connecting profile cannot
INSERT/UPDATE/DELETE/CALLagainst the data library. Confirm with the authority catalog, e.g.SELECT * FROM QSYS2.OBJECT_PRIVILEGES WHERE SYSTEM_OBJECT_SCHEMA = 'JDFDATA'. - Credentials come from the environment, never from the repo.
- Network access to the IBM i / Mapepire daemon is restricted to the host running the server.
Found a way around any of this? Open a private security advisory on the repository rather than a public issue.