Security & LGPD · Data inventory

Security and personal data

What the app processes, the legal basis under the LGPD, and how each item is protected. Core principle: minimization — we only keep what a feature truly needs, for the shortest time possible, and never in cleartext when we can avoid it.

← Back to documentation

Keep this document accurate as the code evolves. It is the project's data-processing inventory.

Data inventory

DataWhere / whyLegal basisProtection
Device token (pseudonymous credential, no login) Identifies the device to save lists in the cloud and record consent. Consent (toggle "Save lists to the cloud"). In the app: secure storage/Keystore. On the server: never stored in cleartext — Redis keys use a salted SHA-256 hash of the token (like a password hash). A Redis dump reveals neither a usable token nor links a known token to its data. Never logged.
Anonymous measurement id Estimate unique users (audience). Legitimate interest, with opt-out. Only enters salted into a HyperLogLog (aggregated cardinality, irreversible). No per-device record. Details in the LIA.
Location (lat/lon) Find prices near the user (radius). Performance of the service requested by the user. Not stored. Used transiently in the SEFAZ call and rounded (~11 m) only in the cache key. Not written to logs (search is POST). No analytics event stores coordinates.
Shopping lists Shareable links and cloud history (when consented). Consent (cloud) / performance of the service (link). Stored under UUID, linked to the device only via the hash above. Deletable (right to erasure). Idle TTL of 30/90 days.
IP address Daily search rate limit (anti-abuse). Legitimate interest (security/abuse). Never in cleartext: the limit key stores only a salted hash of the IP, with a 24 h TTL. Buckets the client without keeping addresses on disk.
Feedback text (👍/👎, "wrong item", free-form note) Detect AI normalization mistakes. Legitimate interest. Anonymous (device token is not stored with it). Lives in a capped stream (1000 items) visible only in the admin panel.
Search terms Ranking of most searched / not found items. Legitimate interest. Aggregated by count (sorted set), not linked to a user. The event feed stores only item count, match rate, and source — not per-user terms, no token, no IP.

Third-party secrets (SEFAZ token, keys)

Credentials the backend uses to talk to third parties (SEFAZ AppToken is the current case) are not kept in the repository, in .env, in logs, or in any API response. They are entered once in the admin panel (Settings), encrypted with Fernet (AES-128-CBC + HMAC), and stored in Redis. The panel only shows a short fingerprint so the operator can confirm which value is active — the value itself is never returned. Rotation takes effect immediately, without a restart.

Honest threat model

The backend must send the token to SEFAZ in cleartext, so the process must hold the value in memory. No scheme hides a secret from someone with root on the same machine (they can read process memory or the decryption key). Hashing also does not work: it is one-way, and we need the value back. What this architecture actually guarantees:

The only residual key is SECRET_ENCRYPTION_KEY (Fernet key), in the server .env. Ciphertext (Redis) and key (env) live in separate places — neither alone leaks the secret. Truly hiding from root would require an external KMS/HSM, which this single VPS does not have.

Data subject rights

References