#9445·OWASP ZAP

Session persistence fails with HSQLDB RowInputBinary / EOFException due to corrupt HISTORY index

Author: daniel-ayersCreated Aug 31, 2026Updated Sep 4, 2026
Labelsenhancement

Describe the bug:

After completing a large scan in an initially unpersisted session, attempting to persist the session fails because HSQLDB cannot reopen the copied session database.

The failure is caused by an invalid/corrupt persisted index root for PUBLIC.HISTORY.

The relevant script line was:

sql
SET TABLE PUBLIC.HISTORY INDEX '18394196 853749 1774086 54181390 0 0 0 0 12012'

HSQLDB failed reading the fourth index root:

IO error: RowInputBinary 54181390
Caused by: java.io.EOFException

The fourth index is:

sql
CREATE INDEX INDEX_HISTORY_SESSIONID ON PUBLIC.HISTORY(SESSIONID)

The session data itself appears to be intact. On a copy of the session database, changing the fourth index root to -1 allowed HSQLDB to open the database. The other three HISTORY indexes all contained the expected 12012 readable rows, while INDEX_HISTORY_SESSIONID contained 0 readable rows.

Dropping and recreating only INDEX_HISTORY_SESSIONID restored all 12012 rows and the database then checkpointed and shut down cleanly.

After the failed persistence attempt, ZAP's database connection remains closed, causing continuous secondary connection exception: closed errors from components such as the WebSocket passive scanner.

This appears similar to #7920, but this occurrence is with ZAP 2.17.0 and HSQLDB 2.7.4.

Steps to reproduce the behavior:

I do not currently have a deterministic minimal reproduction.

The sequence which produced the failure was:

  1. Start ZAP 2.17.0 with an unpersisted session.
  2. Carry out a large/extensive scan.
  3. Allow the session database to grow to approximately:
    • .data: 8,422,162,432 bytes
    • .lobs: 5,915,246,592 bytes
    • .script: 15,215,434 bytes
  4. Select the option to persist/save the session.
  5. ZAP attempts to copy/move the session database.
  6. HSQLDB fails while reopening the copied database with RowInputBinary 54181390 / EOFException.
  7. The ZAP database connection is subsequently left closed and repeated secondary errors are logged.

Expected behavior:

The session should be persisted successfully.

If the copied database cannot be reopened, ZAP should ideally leave or restore the original session database connection so that the running session remains usable rather than being left with a closed database connection.

It may also be worth checking whether a corrupt secondary HSQLDB index can be detected and rebuilt automatically during session persistence/recovery.

Software Versions:

OWASP ZAP: 2.17.0 (all latest plugins)

HSQLDB bundled with ZAP: 2.7.4 (/opt/zaproxy/lib/hsqldb-2.7.4.jar)

OS: Ubuntu 26.04 (32GB machine with -Xxmx26g)

Java: openjdk 21.0.12 2026-07-21 OpenJDK Runtime Environment (build 21.0.12+8-1-26.04-Ubuntu) OpenJDK 64-Bit Server VM (build 21.0.12+8-1-26.04-Ubuntu, mixed mode, sharing)

Screenshots:

No response

Errors from the zap.log file:

java.sql.SQLException: error in script file line: 111 org.hsqldb.HsqlException: IO error: RowInputBinary 54181390
    at org.hsqldb.jdbc.JDBCUtil.sqlException(Unknown Source)
    at org.hsqldb.jdbc.JDBCUtil.sqlException(Unknown Source)
    at org.hsqldb.jdbc.JDBCConnection.<init>(Unknown Source)
    at org.hsqldb.jdbc.JDBCDriver.getConnection(Unknown Source)
    at org.hsqldb.jdbc.JDBCDriver.connect(Unknown Source)
    at org.parosproxy.paros.db.paros.ParosDatabaseServer.start(ParosDatabaseServer.java:128)
    at org.parosproxy.paros.db.paros.ParosDatabaseServer.<init>(ParosDatabaseServer.java:75)
    at org.parosproxy.paros.db.paros.ParosDatabase.open(ParosDatabase.java:156)
    at org.parosproxy.paros.model.Model.copySessionDb(Model.java:332)
    at org.parosproxy.paros.model.Model.moveSessionDb(Model.java:260)
    at org.parosproxy.paros.model.Session.save(Session.java:633)

Caused by: org.hsqldb.HsqlException: error in script file line: 111 org.hsqldb.HsqlException: IO error: RowInputBinary 54181390
    at org.hsqldb.error.Error.error(Unknown Source)
    at org.hsqldb.scriptio.ScriptReaderText.getError(Unknown Source)
    at org.hsqldb.scriptio.ScriptReaderText.readDDL(Unknown Source)
    at org.hsqldb.scriptio.ScriptReaderText.readAll(Unknown Source)
    at org.hsqldb.persist.Log.processScript(Unknown Source)
    at org.hsqldb.persist.Log.open(Unknown Source)
    at org.hsqldb.persist.Logger.open(Unknown Source)
    at org.hsqldb.Database.reopen(Unknown Source)
    at org.hsqldb.Database.open(Unknown Source)
    at org.hsqldb.DatabaseManager.getDatabase(Unknown Source)
    at org.hsqldb.DatabaseManager.newSession(Unknown Source)

Caused by: org.hsqldb.HsqlException: IO error: RowInputBinary 54181390
    at org.hsqldb.error.Error.error(Unknown Source)
    at org.hsqldb.rowio.RowInputBinary.readInt(Unknown Source)
    at org.hsqldb.index.NodeAVLDisk.<init>(Unknown Source)
    at org.hsqldb.RowAVLDisk.<init>(Unknown Source)
    at org.hsqldb.persist.RowStoreAVLDisk.get(Unknown Source)
    at org.hsqldb.persist.DataFileCache.getFromFile(Unknown Source)
    at org.hsqldb.persist.DataFileCache.get(Unknown Source)
    at org.hsqldb.persist.RowStoreAVLDisk.get(Unknown Source)
    at org.hsqldb.persist.RowStoreAVLDisk.setAccessor(Unknown Source)
    at org.hsqldb.persist.RowStoreAVLDisk.setAccessors(Unknown Source)
    at org.hsqldb.Table.setIndexRoots(Unknown Source)
    at org.hsqldb.StatementCommand.getResult(Unknown Source)
    at org.hsqldb.StatementCommand.execute(Unknown Source)
    at org.hsqldb.Session.executeCompiledStatement(Unknown Source)

Caused by: java.io.EOFException
    at org.hsqldb.lib.HsqlByteArrayInputStream.readInt(Unknown Source)

After this failure, repeated secondary errors are logged, for example:

WebSocketPassiveScanThread - Could not get messages from database

org.parosproxy.paros.db.DatabaseException:
java.sql.SQLNonTransientConnectionException: connection exception: closed

Additional context:

I copied the unpersisted session database before attempting recovery.

The HISTORY table has four indexes:

sql
PRIMARY KEY (HISTORYID)

CREATE INDEX HISTORY_INDEX
    ON PUBLIC.HISTORY(URI,METHOD,REQBODY,SESSIONID,HISTTYPE,HISTORYID,STATUSCODE);

CREATE INDEX INDEX_HISTORY_HISTTYPE
    ON PUBLIC.HISTORY(HISTTYPE);

CREATE INDEX INDEX_HISTORY_SESSIONID
    ON PUBLIC.HISTORY(SESSIONID);

The persisted index roots were:

sql
SET TABLE PUBLIC.HISTORY INDEX '18394196 853749 1774086 54181390 0 0 0 0 12012'

The failing RowInputBinary value, 54181390, is the fourth index root and therefore corresponds to INDEX_HISTORY_SESSIONID.

On a copy of the database I changed that root to -1:

sql
SET TABLE PUBLIC.HISTORY INDEX '18394196 853749 1774086 -1 0 0 0 0 12012'

The database then opened successfully using HSQLDB 2.7.4.

Running:

sql
PERFORM CHECK TABLE PUBLIC.HISTORY INDEX AND FIX;

showed:

PUBLIC.HISTORY                         rows 12012
SYS_IDX_SYS_PK_10091_10093             readable rows 12012
HISTORY_INDEX                          readable rows 12012
INDEX_HISTORY_HISTTYPE                 readable rows 12012
INDEX_HISTORY_SESSIONID                readable rows 0

Running the check again still left INDEX_HISTORY_SESSIONID at 0 rows.

I then ran:

sql
DROP INDEX INDEX_HISTORY_SESSIONID;

CREATE INDEX INDEX_HISTORY_SESSIONID
    ON PUBLIC.HISTORY(SESSIONID);

After recreating the index, all four indexes reported 12012 readable rows.

CHECKPOINT and SHUTDOWN then completed successfully.

This strongly suggests that the HISTORY row data was intact and the failure was limited to corruption/inconsistency of one persisted secondary index tree/root.

The session contained only 12012 HISTORY rows but was large on disk:

.data: 8,422,162,432 bytes .lobs: 5,915,246,592 bytes .script: 15,215,434 bytes

This may be relevant if the issue is related to very large HSQLDB cached-table/LOB storage.

Related previous issue: #7920

Would you like to help fix this issue?

  • Yes