Skip to content

Security hardening pass: credential hygiene, identifier sinks, secure RNG#92

Merged
albertzhzhou-droid merged 1 commit into
mainfrom
security-hardening-pass
Jun 12, 2026
Merged

Security hardening pass: credential hygiene, identifier sinks, secure RNG#92
albertzhzhou-droid merged 1 commit into
mainfrom
security-hardening-pass

Conversation

@albertzhzhou-droid

Copy link
Copy Markdown
Owner

Summary

Project-wide bug/security scan of the high-risk surfaces — network clients,
importers, local/Firestore/web databases, auth services, operator tooling, and
Firestore rules — with six fixes applied. No behaviour change for
well-formed inputs; the educational safety boundary is untouched.

Fixes

# File(s) Defect Fix
F1 lib/core/state/app_state.dart Record ids generated with a per-call, non-cryptographic Random() Static Random.secure() (seeded fallback only where no entropy source exists)
F2 lib/data/datasources/remote/fdc_p0_importer.dart FDC API key embedded in the URL query string → leaked into error messages, logs, and cache-metadata keys Key sent as the X-Api-Key header (supported by api.data.gov/FDC); URL carries no query
F3 lib/data/datasources/remote/source_fetch_client.dart Fetch error messages echoed full URLs, including query strings that may carry credentials All three error paths (non-HTTPS rejection, HTTP failure, byte-limit) now echo only scheme://host/path
F4 dailymed_p0_importer.dart, health_canada_dpd_p0_importer.dart Upstream-derived values (SPL set ids, DPD drug codes) interpolated unencoded into request paths/queries Uri.encodeComponent / Uri.encodeQueryComponent before interpolation
F5 lib/core/db/cdss_database*.dart Table names flowed unvalidated into dynamic identifier sinks that cannot use parameter binding: SQL identifiers (native), Firestore path segments (users/{uid}/cdss_tables/{table}/rows), web storage keys New requireValidCdssTableName guard (strict snake_case) enforced in all three implementations
F6 lib/core/db/cdss_database_firestore.dart A staging row with no identifier key upserted into a shared "null" document — every such row silently overwrote the previous one (data loss) Fails loudly with a StateError

Audited and found already sound (no change needed)

  • HttpSourceFetchClient: HTTPS-only, redirects disabled, userInfo rejected, streaming byte cap enforced even without Content-Length.
  • Local AI adapter: localhost-only endpoint enforcement (scheme/host/userInfo/query/fragment checks), redirects disabled, banned-phrase scrubbing.
  • Firestore rules: deny-all fallback, owner scoping, safeId pattern on ids, admin/importer claims gating.
  • Operator token tooling: token files written 0600 under build/; no token values logged.
  • Tool scripts: spawnSync only with fixed argv arrays (no shell interpolation).
  • Local SQL: row queries use ? parameter binding throughout.
  • No credential/email logging in auth or backend services.

Tests

New test/security_hardening_test.dart (6 cases) pins:

  • the table-name guard (valid identifiers pass; path/SQL-shaping values throw),
  • query-string redaction on all three fetch-client error paths,
  • the FDC key travelling only in the X-Api-Key header (captured request asserted).

Validation (all run, all green)

  • dart format --set-exit-if-changed . clean; flutter analyze no issues
  • flutter test --concurrency=1751 passed
  • npm run public:preflight → 0 BLOCKER; npm run privacy:preflight → 0 blocker
  • git diff --check clean

Educational prototype only; synthetic fixtures; no real credentials, PHI, or
medical-meaning changes.

🤖 Generated with Claude Code

… RNG

Project-wide scan of the high-risk surfaces (network clients, importers, local
databases, auth, operator tooling, Firestore rules) with six fixes. No
behaviour change for well-formed inputs; educational boundary untouched.

F1 app_state.dart — record ids now come from a static Random.secure()
   (fallback to seeded Random only where no entropy source exists) instead of
   a per-call non-cryptographic Random().

F2 fdc_p0_importer.dart — the FDC API key moves from the URL query string to
   the X-Api-Key header (supported by api.data.gov/FDC), so it can no longer
   leak through URLs in error messages, logs, or cache-metadata keys.

F3 source_fetch_client.dart — fetch error messages (non-HTTPS rejection, HTTP
   status failures, byte-limit failures) now echo only scheme://host/path,
   never the query string, as defense in depth for any credentialed URL.

F4 dailymed_p0_importer.dart / health_canada_dpd_p0_importer.dart — upstream-
   derived values (SPL set ids, DPD drug codes) are URI-encoded before being
   interpolated into request paths/queries, so malformed values cannot reshape
   the request.

F5 cdss_database*.dart — CDSS table identifiers are validated against a strict
   snake_case pattern (`requireValidCdssTableName`) before reaching dynamic
   identifier sinks that cannot use parameter binding: SQL table names
   (native), Firestore path segments (users/{uid}/cdss_tables/{table}/rows),
   and web storage keys.

F6 cdss_database_firestore.dart — a staging row without any identifier key now
   fails loudly instead of upserting every such row into one shared "null"
   document (silent data loss).

Audited and found already sound (no change): HTTPS-only/no-redirect/byte-capped
fetch client streaming; Local AI adapter localhost-only endpoint enforcement
with redirects disabled; Firestore rules deny-all fallback + owner scoping +
safeId; operator token files written 0600 under build/; tool spawn calls use
fixed argv arrays; SQL rows use ? parameter binding; no credential logging.

New test/security_hardening_test.dart pins the table-name guard, the
query-string redaction (three error paths), and the X-Api-Key behaviour.

Gates: dart format clean, flutter analyze clean, flutter test 751 passed,
public:preflight 0 BLOCKER, privacy:preflight 0 blocker, git diff --check
clean. Synthetic fixtures only; no real credentials.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@albertzhzhou-droid albertzhzhou-droid merged commit 39cdd65 into main Jun 12, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant