fix(01-foundation): address all checker blockers and warnings in phase plans
This commit is contained in:
@@ -20,7 +20,7 @@ must_haves:
|
||||
- "AES-256-GCM Encrypt/Decrypt roundtrip produces the original plaintext"
|
||||
- "Argon2id DeriveKey with the same passphrase and salt always returns the same 32-byte key"
|
||||
- "A Finding can be saved to the database with the key_value stored encrypted and retrieved as plaintext"
|
||||
- "The raw database file does NOT contain plaintext API key values"
|
||||
- "The raw database file does NOT contain plaintext API key values — verified by querying raw bytes from the BLOB column"
|
||||
artifacts:
|
||||
- path: "pkg/storage/encrypt.go"
|
||||
provides: "Encrypt(plaintext, key) and Decrypt(ciphertext, key) using AES-256-GCM"
|
||||
@@ -271,7 +271,7 @@ func NewSalt() ([]byte, error) {
|
||||
- Test 3: DeriveKey(passphrase, salt) twice returns identical 32 bytes
|
||||
- Test 4: NewSalt() twice returns different slices
|
||||
- Test 5: SaveFinding stores finding → ListFindings decrypts and returns KeyValue == "sk-proj-test"
|
||||
- Test 6: Database file (when not :memory:) does NOT contain literal "sk-proj-test" in raw bytes
|
||||
- Test 6: Raw BLOB bytes retrieved directly from the database do NOT contain the plaintext key string
|
||||
</behavior>
|
||||
<action>
|
||||
Create **pkg/storage/schema.sql**:
|
||||
@@ -473,6 +473,7 @@ Fill **pkg/storage/db_test.go** (replacing stubs from Plan 01):
|
||||
package storage_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/salvacybersec/keyhunter/pkg/storage"
|
||||
@@ -567,9 +568,10 @@ func TestSaveFindingEncrypted(t *testing.T) {
|
||||
// Derive a test key
|
||||
key := storage.DeriveKey([]byte("testpassphrase"), []byte("testsalt1234xxxx"))
|
||||
|
||||
plainKey := "sk-proj-test1234567890abcdefghijklmnopqr"
|
||||
f := storage.Finding{
|
||||
ProviderName: "openai",
|
||||
KeyValue: "sk-proj-test1234567890abcdefghijklmnopqr",
|
||||
KeyValue: plainKey,
|
||||
Confidence: "high",
|
||||
SourcePath: "/test/file.env",
|
||||
SourceType: "file",
|
||||
@@ -583,10 +585,17 @@ func TestSaveFindingEncrypted(t *testing.T) {
|
||||
findings, err := db.ListFindings(key)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, findings, 1)
|
||||
assert.Equal(t, "sk-proj-test1234567890abcdefghijklmnopqr", findings[0].KeyValue)
|
||||
assert.Equal(t, plainKey, findings[0].KeyValue)
|
||||
assert.Equal(t, "openai", findings[0].ProviderName)
|
||||
// Verify masking
|
||||
assert.Equal(t, "sk-proj-...opqr", findings[0].KeyMasked)
|
||||
|
||||
// Verify encryption contract: raw BLOB bytes in the database must NOT contain the plaintext key.
|
||||
// This confirms Encrypt() was called before INSERT, not that the key was stored verbatim.
|
||||
var rawBlob []byte
|
||||
require.NoError(t, db.SQL().QueryRow("SELECT key_value FROM findings WHERE id = ?", id).Scan(&rawBlob))
|
||||
assert.False(t, bytes.Contains(rawBlob, []byte(plainKey)),
|
||||
"raw database BLOB must not contain plaintext key — encryption was not applied")
|
||||
}
|
||||
```
|
||||
</action>
|
||||
@@ -601,12 +610,12 @@ func TestSaveFindingEncrypted(t *testing.T) {
|
||||
- TestDecryptWrongKey passes — wrong key causes error
|
||||
- TestArgon2KeyDerivation passes — 32 bytes, deterministic
|
||||
- TestNewSalt passes — 16 bytes, non-deterministic
|
||||
- TestSaveFindingEncrypted passes — stored and retrieved with correct KeyValue and KeyMasked
|
||||
- TestSaveFindingEncrypted passes — stored and retrieved with correct KeyValue, KeyMasked, AND raw BLOB does not contain plaintext
|
||||
- `grep -q 'go:embed.*schema' pkg/storage/db.go` exits 0
|
||||
- `grep -q 'modernc.org/sqlite' pkg/storage/db.go` exits 0
|
||||
- `grep -q 'journal_mode=WAL' pkg/storage/db.go` exits 0
|
||||
</acceptance_criteria>
|
||||
<done>Storage layer complete — SQLite opens with schema, AES-256-GCM encrypt/decrypt works, Argon2id key derivation works, SaveFinding/ListFindings encrypt/decrypt transparently. All 7 tests pass.</done>
|
||||
<done>Storage layer complete — SQLite opens with schema, AES-256-GCM encrypt/decrypt works, Argon2id key derivation works, SaveFinding/ListFindings encrypt/decrypt transparently. Raw BLOB bytes verified to not contain plaintext. All 7 tests pass.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
@@ -619,6 +628,7 @@ After both tasks:
|
||||
- `grep -q 'cipher\.NewGCM' pkg/storage/encrypt.go` exits 0
|
||||
- `grep -q 'journal_mode=WAL' pkg/storage/db.go` exits 0
|
||||
- schema.sql contains CREATE TABLE for findings, scans, settings
|
||||
- TestSaveFindingEncrypted asserts raw BLOB does not contain plaintext key
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
@@ -626,6 +636,7 @@ After both tasks:
|
||||
- AES-256-GCM column encryption works: Encrypt + Decrypt roundtrip returns original (STOR-02)
|
||||
- Argon2id key derivation: DeriveKey deterministic, 32 bytes, RFC 9106 params (STOR-03)
|
||||
- FindingCRUD: SaveFinding encrypts before INSERT, ListFindings decrypts after SELECT
|
||||
- Raw BLOB in database does not contain plaintext key — verified by automated test
|
||||
- All 7 storage tests pass
|
||||
</success_criteria>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user