security: protect local data at rest (issue #146)
All checks were successful
CI Code / Check spelling (pull_request) Successful in 15s
CI Code / Check coding style (pull_request) Successful in 26s
CI Code / Linux (debian) (pull_request) Successful in 5m3s
CI Code / Linux (arch) (pull_request) Successful in 6m25s
CI Code / Linux (ubuntu) (pull_request) Successful in 8m1s
CI Code / Code Coverage (pull_request) Successful in 9m27s

T03 — keep secrets out of profanity.log:
- new redact_secrets() helper (common.c) masks the content of SASL
  auth/response/challenge/success and <password> elements; applied in
  the libstrophe logger (_xmpp_file_logger) and the stderr-to-log
  bridge (REQ-DAR-03, REQ-LOG-06)
- _add_to_db() no longer logs decrypted message bodies or the full
  INSERT statement (REQ-DAR-03)

T07 — owner-only permissions on key material and history (REQ-AUTH-01):
- chmod 0600 on chatlog.db after open (journal/WAL files inherit)
- chmod 0600 on OTR keys.txt and fingerprints.txt after every write
- parent dirs are already 0700 (create_dir); this is defense in depth

T08 — integrity before trust:
- PRAGMA quick_check(1) gates every chatlog.db open; on failure the
  DB stays closed, the user is warned once in the console and the
  session continues without history (REQ-RES-02)
- OMEMO aesgcm downloads decrypt into a 0600 tempfile next to the
  target and rename it into place only after the GCM tag verifies;
  the open-command hook no longer runs on failed decryption
  (REQ-CRY-06)

Tests: redact_secrets unit tests; AES-256-GCM roundtrip and
tampered-tag/ciphertext unit tests (crypto.c now linked into
unittests under BUILD_OMEMO); functional test planting a corrupt
chatlog.db and asserting graceful degradation.

Closes #146
This commit is contained in:
2026-07-21 10:07:53 +03:00
parent 2be16df905
commit b63a9d29f8
17 changed files with 370 additions and 12 deletions

View File

@@ -255,6 +255,9 @@ main(int argc, char* argv[])
PROF_FUNC_TEST(message_db_history_verify),
PROF_FUNC_TEST(message_db_history_lmc),
PROF_FUNC_TEST(message_db_history_multi_resource),
#ifdef HAVE_SQLITE
PROF_FUNC_TEST(message_db_corrupt_database_degrades_gracefully),
#endif
/* Basic message send/receive */
PROF_FUNC_TEST(message_send),

View File

@@ -536,3 +536,41 @@ message_db_history_multi_resource(void** state)
assert_true(prof_output_regex("Buddy1/laptop"));
assert_true(prof_output_regex("Buddy1/tablet"));
}
/*
* Test: corrupt chatlog.db degrades gracefully (issue #146, REQ-RES-02).
*
* A chatlog.db with a valid SQLite magic but garbage content is planted
* before connecting. Database init must fail cleanly: the user gets a
* console warning, the session stays up, and the client stays responsive.
*/
void
message_db_corrupt_database_degrades_gracefully(void** state)
{
const char* xdg_data = getenv("XDG_DATA_HOME");
assert_non_null(xdg_data);
GString* db_file = g_string_new(xdg_data);
g_string_append(db_file, "/profanity/database/stabber_at_localhost");
assert_int_equal(0, g_mkdir_with_parents(db_file->str, 0700));
g_string_append(db_file, "/chatlog.db");
/* valid 16-byte SQLite header magic followed by garbage: sqlite3_open
* succeeds (lazy open), the integrity gate must catch it */
FILE* db = fopen(db_file->str, "wb");
assert_non_null(db);
assert_int_equal(16, fwrite("SQLite format 3", 1, 16, db));
for (int i = 0; i < 4096; i++) {
fputc(0xA5, db);
}
fclose(db);
g_string_free(db_file, TRUE);
prof_connect();
assert_true(prof_output_exact("Chat history storage is unavailable for this session"));
/* client is still alive and responsive after the failed DB init */
prof_input("/autoping set 60");
assert_true(prof_output_exact("Autoping interval set to 60 seconds."));
}

View File

@@ -11,3 +11,4 @@ void message_db_history_service_chars(void** state);
void message_db_history_verify(void** state);
void message_db_history_lmc(void** state);
void message_db_history_multi_resource(void** state);
void message_db_corrupt_database_degrades_gracefully(void** state);