Files
Agent-System/internal/artifactvault/crypto.go
T
weichenandCursor c7e3e99303 feat(artifactvault): extract audited FAV with union-deny reads
Goose 00036 is a renumbered copy of the vault tables. Degraded read stays off by default and only runs after authorization; tombstones and unpublished artifacts fail closed.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-14 15:59:36 +00:00

54 lines
1.3 KiB
Go

package artifactvault
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
)
// ProjectKey derives a per-project envelope key. The master secret never enters manifests.
func ProjectKey(master []byte, env, projectExternalKey string) (key []byte, ref string, err error) {
if len(master) < 16 {
return nil, "", fmt.Errorf("artifactvault: vault master key too short")
}
sum := sha256.Sum256(append(append(master, []byte(env)...), []byte(projectExternalKey)...))
ref = "proj:" + env + ":" + projectExternalKey + ":" + hex.EncodeToString(sum[:8])
return sum[:], ref, nil
}
func Encrypt(key, plaintext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
return gcm.Seal(nonce, nonce, plaintext, nil), nil
}
func Decrypt(key, envelope []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
ns := gcm.NonceSize()
if len(envelope) < ns {
return nil, fmt.Errorf("artifactvault: short envelope")
}
return gcm.Open(nil, envelope[:ns], envelope[ns:], nil)
}