Commit Graph
21 Commits
Author SHA1 Message Date
dsql 581ff712e0 fix: fingerprint_data is deterministic for sets and rejects identity-repr objects
set/frozenset values fell through to repr() (hash-randomized member order), and objects
with the default <X at 0x..> repr embedded a per-process memory address - both made
fingerprint_data return a different digest across restarts despite its 'deterministic'
contract (a fix-wave d446f50 turned a loud TypeError into a silent unstable hash).
_fingerprint_normalize now canonically sorts set/frozenset members, and the default=
handler raises TypeError on an identity-based repr instead of hashing an address.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-06 16:39:34 -04:00
dsql 8dffb6bc61 docs: correct reencrypt's list-nested-blob docstring to match current behavior
the docstring said a list-nested blob is "not covered by the depth-limit raise
below", but the cutoff check uses _has_encrypted_field, which does walk lists -
so a list-nested blob reached via the cutoff dict scan DOES raise, while the
same blob one level shallower (hit during normal traversal instead) is what's
actually skipped silently. doc-only, no behavior change.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-06 00:17:18 -04:00
dsql dc8f80c690 fix: is_encrypted_record never false-negatives at odd traversal_level; encrypt_data rejects nested non-str keys
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 19:03:53 -04:00
dsql dfd794159e refactor: derive __version__ from package metadata (single source)
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 17:00:26 -04:00
dsql d0c3bcd7c8 refactor: hoist shared RSA-OAEP padding to a constant; restore encrypt_aes_key_with_rsa docstring
hoists the byte-identical OAEP(MGF1(SHA256), SHA256, label=None) construction out
of encrypt_aes_key_with_rsa and decrypt_aes_key_with_rsa into a module-level
_OAEP_PADDING constant so a future scheme change lands in one place instead of
two in lockstep. also restores encrypt_aes_key_with_rsa's Args/Returns/Raises
docstring block, matching its four siblings, after Wave-1 over-stripped it to
bare prose.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 16:46:20 -04:00
dsql 857e9380c6 docs: bump install pin to v0.1.8
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 16:24:02 -04:00
dsql abf7491d26 fix: is_encrypted_record misses blobs nested inside a list or tuple
both the bounded pass and the unbounded _has_encrypted_field fallback
descended only through dict values, so a blob nested inside a list at
any depth was invisible and the function returned False. reencrypt()
already skips list-nested blobs (documented gotcha), so after rotation
such a blob was stranded under the old key while this audit reported
the record clean - a rotation-data-loss trap once the old wrapped-key
record is deleted. both traversal passes now walk list/tuple items in
addition to dict values; the blob-detection predicate is unchanged.

bump 0.1.7 -> 0.1.8

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 16:14:34 -04:00
dsql 7287a52947 docs: compress prose/module docstrings, em-dash->hyphen (de-bloat wave 1)
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 00:16:42 -04:00
dsql d446f50942 fix: EC-5..EC-8 error-message inversion, RSA-only wrap guard, 32-byte key guard, fingerprint_data robustness
EC-5: _load_private_key branches on whether a password was given so the normalized
ValueError matches the actual cryptography TypeError case (was always claiming
"encrypted but no password" even when a password was given for an unencrypted key).
EC-6: encrypt_aes_key_with_rsa/decrypt_aes_key_with_rsa now raise a clear ValueError
via _require_rsa for a non-RSA key (e.g. Ed25519/EC), instead of crashing raw with
AttributeError at wrap/unwrap — this lib is RSA-envelope only.
EC-7: initialize() requires exactly 32 bytes (isinstance bytes, len==32), rejecting a
16/24-byte key (silent AES-128/192 downgrade) or a str instead of failing late and
opaquely at first encrypt.
EC-8: fingerprint_data gains a default= handler (datetime/date/time, bytes/bytearray,
and a type-tagged repr fallback) plus a key-type-tagging pre-pass so datetime/bytes/
ObjectId-like values no longer TypeError and int-vs-str dict keys no longer collide
to the same fingerprint. Never logs the data being fingerprinted.

Also compresses the essay-length docstrings (module + several methods) to cut
narration while keeping the load-bearing footgun notes (RSA-only, AES-256 key length,
never-log-key-material) intact — zero behavior change, re-verified after.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-02 23:24:06 -04:00
dsql d827dca30f fix: is_file drift on decrypt_aes_key_with_rsa and non-str dict key coercion
self_test(is_file=False) only forwarded is_file to the public-key wrap;
decrypt_aes_key_with_rsa had no is_file parameter and always open()'d its
argument, so a PEM string was opened as a filename, misdiagnosing a good
keypair as non-pairing and leaking the private key PEM into the
FileNotFoundError traceback. decrypt_aes_key_with_rsa now takes is_file
(default True, preserving current callers), and self_test threads it
through to both key loads.

encrypt_data json.dumps a dict without checking key types, silently
stringifying int/float/bool/None keys (e.g. snowflake-int-keyed dicts),
so a decrypt round-trip silently lost the original key. encrypt_data now
raises TypeError on a non-str key instead of coercing it.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-02 17:17:44 -04:00
dsql 49af2a1143 fix: reencrypt/is_encrypted_record/decrypt_record miss self-blob and deep-nested records
Two silent-data-loss paths in the record-level functions: (1) reencrypt's
traversal_level cutoff silently left blobs nested deeper than the default
under the old key with no signal, contradicting its own documented fail-loud
rotation contract, and is_encrypted_record shared the cutoff so a
post-rotation audit couldn't detect the leftover; (2) all three record
functions inspected only record.values(), never the record itself, so a bare
{secure, iv, data} blob used as the whole document (the README's file-storage
pattern) was invisible to is_encrypted_record and passed through reencrypt
unchanged under the old key.

reencrypt now raises when a blob sits deeper than traversal_level instead of
silently truncating, and detects/handles the record-itself-is-a-blob case;
is_encrypted_record falls back to an unbounded-depth scan past
traversal_level so it reliably flags leftovers regardless of nesting depth;
decrypt_record likewise handles a record that is itself a blob. Bumped to
v0.1.4.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-02 16:40:37 -04:00
dsql e03622b175 chore: ignore .claude/ dir (CLAUDE.md now lives under .claude/)
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 21:55:13 -04:00
dsql 306e5b8057 fix: EC-1 single key load for fingerprint; encrypt/decrypt type guards
EC-1: factor _fingerprint_of(public_key) so encrypt_aes_key_with_rsa fingerprints the
already-loaded key instead of re-opening/parsing the file. encrypt_data rejects a
non-dict/str with a clear TypeError (was: opaque .encode() AttributeError); decrypt_data
raises ValueError on a malformed blob (was: raw KeyError); a wrong-password PEM load gives
a clearer message; reencrypt's dict-only traversal (list-nested blobs skipped) documented.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 21:35:04 -04:00
dsql 254826f86c docs: pin install line to release, note unpinned-latest option
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 18:13:53 -04:00
dsql 113a3e6949 docs: show unpinned install line; note tag-pinning for reproducibility
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 18:07:38 -04:00
dsql 72c7aa936e fix: normalize key-load exceptions; type-faithful decrypt_data (v0.1.3)
- encrypted OpenSSH private key with no password now raises ValueError (not a raw
  TypeError from load_ssh_private_key), matching the PEM path and the docstring (L14)
- a non-PEM/non-SSH public key raises a clear ValueError instead of cryptography's
  UnsupportedAlgorithm, consistent with the private-key paths (L15)
- decrypt_data only treats a json-OBJECT plaintext as a dict, so json-shaped strings
  ('123','true','[1,2]') round-trip as strings; existing dict blobs unaffected (L16)
- both key loads route through shared _load_private_key/_load_public_key helpers
- document reencrypt's fail-loud (vs decrypt_record's per-field swallow) asymmetry (nit).

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 17:58:26 -04:00
dsql 5de8b5d736 fix: OpenSSH private-key fingerprint fallback + clean error on missing password
get_rsa_key_fingerprint(is_private=True) only loaded PEM private keys, so an OpenSSH-format private key raised — unlike decrypt_aes_key_with_rsa, which already had the fallback. mirrored it: on a PEM load failure, an OPENSSH-marked key is loaded via load_ssh_private_key. also normalized the encrypted-key-without-password case: cryptography raises TypeError there, which now becomes a clear ValueError('private key is encrypted but no password was provided') in both methods instead of leaking the raw TypeError.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 01:39:21 -04:00
dsql 16205e810a fix: deepcopy in reencrypt/decrypt_record so input is not mutated
both used record.copy() (shallow), leaving unencrypted mutable fields shared between the input and the returned dict, violating the documented 'input is not mutated' contract. switched to copy.deepcopy.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-28 17:18:28 -04:00
dsql 313b0c7d56 fix: forward password to private-key fingerprinting (v0.1.1)
get_rsa_key_fingerprint(is_private=True) called load_pem_private_key(password=None),
so an encrypted private key raised a raw TypeError. add an optional password param
forwarded to the load; unencrypted keys ignore it.

verified: encrypted private key fingerprints with its password and matches the
public key's fingerprint; missing password still raises.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-28 15:53:04 -04:00
dsql 659aa7849d add package: pyproject + src
EnvelopeCrypto: hybrid envelope encryption for dict records — a random
AES-256-GCM data key (DEK) encrypts the data, wrapped per-system via
RSA-OAEP (SHA-256) for distribution. config-free (DEK + key paths
injected), storage-agnostic, object-only. covers bootstrap/self_test,
authorize/deauthorize, rotate + reencrypt, and record-level decrypt.
src/ layout, hatchling build, cryptography backend.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-24 21:36:43 -04:00
dsql 0b708cdf9a init: envelope encryption (RSA-OAEP + AES-256-GCM) for dict records
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-24 21:25:27 -04:00