5 Commits
Author SHA1 Message Date
a3408bb198 fix(shares): a folder is not a sync problem (#169)
bdrive share <folder> answered "not synced to this project yet" on a fully
synced folder, so users went looking for a sync fault that wasn't there.
snap.files maps FILES, so a folder always missed the existence check and
landed in the same 404 as a path that doesn't exist — and the CLI bolted
"wait a few seconds for the daemon" onto it.

Tell the two apart in the handler (any key under p+"/" — never bare p, or
notes-archive/x.md makes "notes" a folder): 400 with "share links are
per-file" naming the lexicographically smallest file inside, 404 with
today's wording otherwise. The CLI's daemon hint was already 404-only, so
it stops appearing on its own; the only CLI change is printing a 400 body
without httpBodyError's status prefix. The web UI mints through the same
handler, so it gets the same distinction.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 10:41:28 -07:00
Snow Lee (Sungwon)andGitHub a3dfa73fef Catch a credential when it syncs, not only when you share it (#162)
* 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.
2026-08-18 15:08:44 -07:00
d3d92bf904 feat(webapp): refuse to share a file that looks like it holds credentials (BEA-111) (#137)
Minting a share link ran zero content checks: a file holding an AWS-shaped
key became a public URL on one click, and the CLI printed nothing but the
link. handleShareCreate now reads the first 1 MiB and runs six anchored
rules between the synced-path check and Shares.Create, answering 409 with
rule ids and line numbers unless the request carries confirm: true.

The matched text never leaves scanSecrets — not into the body, not into a
log line. TestShareSecretNeverEchoed greps both for the planted string,
because a 409 body is the easiest place in this codebase to leak it.

Both callers carry the override, since the gate alone would turn any false
positive into a hard block with no way out: `bdrive share --force`, and the
browser's Share-anyway dialog on modalConfirm (no new component). A path
that already has a live link skips the scan — its content is public
already, so withholding the URL protects nobody — but alreadyPublic drops
links whose creator left the org, since those 404 at /s/ and would
otherwise wave a secrets file straight through.

A failed blob read is 503, not a silent pass: the repo's "degrade rather
than fail" posture is for sync cycles, and a check that skips itself on a
storage hiccup is the false confidence this exists to remove.

Every user-facing string says the file was checked at the moment you shared
it. A link serves the file's LATEST content forever, so a key written into
an already-shared file is never caught — that open loop stays open, and the
copy is the only thing stopping v1 from claiming otherwise.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 04:33:05 +09:00
fd4f5c7964 feat(onboarding): folder name follows the project name, and project ids are UUIDs (#89)
The paste prompt now carries the project's name so an agent recommends a
folder of that name; with no project at all the recommendation is `shared/`
(and `bdrive init shared` names the new project after the folder), replacing
the old `wiki/` default.

New project ids are UUIDs instead of `p-` + 8 hex chars. The route validator
still accepts the legacy shape — ids are permanent — and the client-side URL
parsers (remote/http.go, bdrive share) now only check the shape of a URL
segment, leaving the hub as the single authority on which ids are valid.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 13:42:07 +09:00
Snow LeeandClaude Fable 5 c0e785a4ba feat: public share links and /beardrive:install for Claude Code teams
Share links (bdrive share <file>, or the web UI's Share button):
- Mint an unguessable public URL (/s/<token>) anyone can open — no
  account. HTML renders as a page, markdown gets a standalone shell,
  PDFs open inline; ?download=1 attaches.
- Sandboxed: /s/* responses carry CSP `sandbox allow-scripts` + nosniff
  and never see auth cookies, so shared content's scripts run in an
  opaque origin and can't touch hub sessions.
- Links serve the file's LATEST synced content and live until revoked;
  --expires makes self-destructing ones; --list/--revoke manage them.
  Re-sharing a file returns the same link. File-backed shares.json.
- CLI resolves the project by walking up to .bdrive/ from the shared
  file, warns when the hub address is private (LAN-only links), and
  hints when the file hasn't synced yet.

/beardrive:install (plugin command) — team onboarding driven by Claude:
- Ensures the bdrive binary, signs in (bdrive login), runs bdrive init
  (whole folder or a shared subfolder like wiki/).
- Asks before appending a CLAUDE.md section that teaches agents to put
  shareable artifacts in the shared folder and mint URLs with bdrive
  share; asks before registering project-level hooks in
  .claude/settings.json: blocking pull at UserPromptSubmit, async push
  on PostToolUse Write|Edit — teammates sync with or without the plugin.
- Fix: the plugin hook script still checked for the old `.bdrive` file
  and was a silent no-op since the directory change; now checks -d.

Tests: share creation gating, public access + sandbox headers, dedupe,
latest-content semantics, revoke, expiry, markdown/download variants,
list filtering, registry persistence. Docs updated (README sharing +
Claude Code sections, SKILL.md, CLAUDE.md).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 15:02:13 -07:00