mirror of
https://github.com/runbear-io/beardrive.git
synced 2026-08-25 08:08:08 +02:00
* refactor(secrets): lift the share-time credential rules into internal/secrets The rules only ever ran on the rarest path a file takes. Moving them out of internal/webapp is what lets internal/syncer run the same six rules on the path every file takes, without inverting the dependency. Pure move plus one addition: Label(), the six human strings that until now lived only in the frontend's SECRET_LABELS — so 'bdrive share' stops printing a bare rule id where the web dialog says 'an AWS access key'. Rule ids and the rule/line JSON tags are unchanged: Browser.tsx keys off them, so they are a wire contract. * feat(sync): warn when a synced file looks like it holds a credential The six share-time rules now run on the path every file takes. A file with an AWS key in it used to ride a normal sync to the hub, to every teammate's disk and into every future agent's context with no badge and no warning — while the Share dialog one click later blocked that exact file. Warn, never block: the op is journaled and pushed exactly as before. A hold arm would mean a false positive silently parks someone's changes, and it would break the cycle's degrade-to-offline posture. - scan() reads the blob PutBlobFile just wrote (the bytes that were actually journaled), only on the branches that wrote one — an unchanged file is still never re-read. - Findings persist per path in secrets-<mount>.json, merged rather than replaced: nearly every cycle scans zero files, and a whole-set rewrite would erase the warning seconds after it appeared. Fixing the file clears it. - bdrive status grows a secrets block; the agent hook appends one advisory sentence. Rule ids and line numbers only, never the matched bytes. - SaveSecrets failing logs and continues: advisory telemetry never gets a veto over convergence. * docs: the credential check now runs on sync, not only on share README, the CLI reference and project-files get the new bdrive status block and the warn-never-block posture, with the three limits stated (checked when it changes, first 1 MiB, writing device only). Diagrams: internal/secrets is a package of its own in the overview, secretLog joins the sync engine, and the share-gate class notes that it no longer owns the rules. * test(sync): assert an unchanged file is never re-read for credentials The check must ride the branch that already reads the file. Clearing the record by hand and cycling proves it: a scan that re-read unchanged files would put the finding back, and the daemon's 3-second tick would pay for it on every file.
58 lines
2.1 KiB
Go
58 lines
2.1 KiB
Go
package store
|
|
|
|
import (
|
|
"github.com/runbear-io/beardrive/internal/secrets"
|
|
)
|
|
|
|
// The secrets record is what a cycle's credential findings survive in. The scan
|
|
// flags a file the moment it changes; the two surfaces that report it —
|
|
// `bdrive status` and the agent hook — run minutes later, long after that file
|
|
// stopped changing and the cheap size+mtime path stopped reading it. So the
|
|
// record has to outlive the cycle that made it, for the same reason the inbound
|
|
// spool does (see inbound.go).
|
|
//
|
|
// It is merged PER PATH, never replaced wholesale: nearly every cycle scans
|
|
// zero changed files, so a whole-set rewrite would erase the warning three
|
|
// seconds after it appeared. A finding stands until the file changes again
|
|
// without it — which is what makes fixing the file the only thing needed to
|
|
// clear the warning, no command and no flag.
|
|
//
|
|
// It is advisory telemetry about paths this device already journaled and
|
|
// pushed. It must never be able to fail a cycle.
|
|
|
|
// secretsPath names the per-mount findings file, validated exactly like
|
|
// cachePath: the mount id comes from a folder's .bdrive/config.json, so a
|
|
// separator in it would put this file wherever its author chose.
|
|
func (s *Store) secretsPath(mountID string) (string, error) {
|
|
return s.mountStatePath("secrets-", mountID)
|
|
}
|
|
|
|
// LoadSecrets returns the findings recorded for each mount-relative path.
|
|
func (s *Store) LoadSecrets(mountID string) (map[string][]secrets.Finding, error) {
|
|
p, err := s.secretsPath(mountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := map[string][]secrets.Finding{}
|
|
if err := readJSON(p, &out); err != nil {
|
|
return nil, err
|
|
}
|
|
// Same treatment as the cache's keys: plain JSON in $BDRIVE_HOME that
|
|
// anything running as the user can write, rendered into a terminal and
|
|
// joined against cache keys. An out-of-volume path is dropped, not shown.
|
|
for rel, f := range out {
|
|
if !cleanRel(rel) || len(f) == 0 {
|
|
delete(out, rel)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (s *Store) SaveSecrets(mountID string, f map[string][]secrets.Finding) error {
|
|
p, err := s.secretsPath(mountID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return WriteJSONAtomic(p, f)
|
|
}
|