Skip to content

docs: fix audit DB filename and document placeholder sentinel#9

Closed
8enji wants to merge 15 commits into
mainfrom
claude/hopeful-diffie-76bf63
Closed

docs: fix audit DB filename and document placeholder sentinel#9
8enji wants to merge 15 commits into
mainfrom
claude/hopeful-diffie-76bf63

Conversation

@8enji
Copy link
Copy Markdown
Collaborator

@8enji 8enji commented May 18, 2026

Summary

Two small docs fixes from pre-launch E2E review:

  • F9 (audit filename drift). CHANGELOG.md entries for v0.1.0 and v0.1.1 referenced .veil/audit.db, but the on-disk file has always been .veil/audit.sqlite (internal/config/project.go:75). Both entries corrected. docs/ had no remaining audit.db references.
  • F10 (placeholder sentinel not documented). Every Veil-generated placeholder embeds a literal VEIL substring as a fail-closed leak sentinel (see internal/placeholder/engine.goconst Sentinel = "VEIL"). An agent that scans its inputs for this substring can identify which .env / MCP-config values are placeholders, though it learns nothing about the real secret. Added a bullet to docs/THREAT_MODEL.md's "What Veil does NOT protect against" section calling this out as consistent with the cooperative-but-curious model.

F16 (--yes flag for veil list --reveal on non-TTY) was investigated and found to already be wired in internal/cli/list.go:34 with a clear Usage string — no change needed. Verified end-to-end below.

Test plan

  • rg 'audit\.db' docs/ returns no hits
  • rg 'audit\.db' --type md returns no hits (CHANGELOG clean)
  • docs/THREAT_MODEL.md reads cleanly (new bullet at line 19, list structure intact)
  • veil list --help shows: --yes bypass TTY safety check for --reveal (scripted use)
  • veil list --reveal --yes | cat prints the table without error
  • veil list --reveal | cat errors with "Pipe or redirect detected. Re-run with --yes to override."
  • go build ./... clean
  • go vet ./... clean

🤖 Generated with Claude Code

8enji and others added 15 commits May 18, 2026 17:38
IsSecretLike previously flagged any env var whose name matched the
secret regex (key/secret/token/password/auth/credential/dsn) regardless
of value, producing false-positive vaulting (LOG_LEVEL_AUTH=info,
DB_PASSWORD_PROMPT=true) and false-positive run refusals.

Now name-pattern matches must also clear len>=12 AND distinct>=6.
Provider patterns, URL-with-password, and the length+entropy path are
unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
No behavior change. Adds the data shape needed by the upcoming
basicCorrelator and the init flow's buildEnvFileCredentials basic
branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pairs <prefix>USER(NAME)?<suffix> with <prefix>(PASSWORD|PASS)<suffix>
using strict decoration matching. Password partner must clear a
length+distinct value-shape floor (12/6) so fixture-style trivial
values like DB_USER=test, DB_PASSWORD=test do not pair.

Correlator is not yet registered in DetectAll — wired up next.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AWS triples are still claimed first; remaining candidates pass through
the basic correlator. Integration test covers mixed input with both
schemes plus a loose bearer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
basicCorrelator needs to see non-secret-shaped USER/USERNAME candidates
that ScanEnviron drops via IsSecretLike. ScanEnvironForPairs keeps the
denylist filter but omits the value-shape check, returning every
plausibly-non-system env var for the correlator to pair against.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
processEnvFile now feeds every KV line into correlate.DetectAll (not
just IsSecretLike-filtered) so USERNAME halves with non-secret-shaped
values reach the basic correlator. Detected pairs become one
basic-scheme credential (Username/UsernamePlaceholder fields set), and
the .env file is rewritten with placeholders for both halves.

The interactive prompt now groups basic pairs under a [basic] tag and
counts them alongside any AWS triples.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
processShellEnv now uses the broader ScanEnvironForPairs pool for the
correlator while keeping the existing IsSecretLike-filtered set for the
loose-bearer path. Basic groups are vaulted as a single credential with
Username + UsernamePlaceholder set; the interactive prompt labels them
under [basic] and counts them in the header alongside AWS triples.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pulls the Basic-scheme decode out of tryRewriteBasic so the upcoming
ClassifyBasicLeak diagnostic can reuse it. Behavior unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diagnostic helper for the leak handler: when both halves of a Basic
header decode to placeholders belonging to two different credentials,
returns a "how to fix" hint pointing at `veil add --scheme basic` or
`veil init --force`. Returns "" otherwise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the leak guard fires on a Basic header whose two halves point to
two different vault credentials, the 502 response now carries a
targeted hint identifying the cred names and a `veil add --scheme
basic` / `veil init --force` recipe. Header X-Veil-Error: basic_unpaired
distinguishes this case for tooling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pins the runtime fail-closed behavior to the new value-shape gate:
trivial-value name matches no longer trigger the unvaulted warning,
real-shaped tokens still do.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
gofmt prefers single spaces between aligned struct field types and
between value-and-trailing-comment columns. Also replace empty
backticks in basic.go's regex-explanation comment with prose so Go's
doc-comment formatter doesn't smart-quote them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The hint suggested `veil add NAME --scheme basic --user X --value-stdin`,
but `veil add` doesn't accept --scheme basic — basic is implied by
setting --user. A user copying the hint verbatim would hit
"unknown --scheme basic".

Lead with `veil init --force` as the primary recipe (the basic-pair
correlator picks them up automatically); demote `veil add` to a
fallback with corrected syntax. The cred NAME passed to `veil add` is
the password var (the existing canonical record), not the username var.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
vault.Credential now persists UsernameVar (the source env-var name for
the username half of a basic credential) so recoverPendingEnvRewrite
can check the username line against c.Username, the same way it
already checks the password line against c.Real.

Before this fix, the divergence check only matched line.Key against
c.Name (the password var). For a basic cred, the username var
(e.g. GH_USERNAME) is never bound to a separate vault entry, so a
user editing only the username between a crash and the re-run had
their edit silently overwritten when recovery replayed the placeholder
rewrite.

UsernameVar is populated in both init flows (buildEnvFileCredentials
and vaultShellBasicGroup); it's empty for manually-added basic creds
(`veil add --user ...`) and non-basic schemes, which makes the new
divergence check a no-op for those — matching the existing aws-scheme
opt-out pattern.

Regression test: TestRecoverPendingEnvRewriteDetectsBasicUsernameEdit
asserts the recovery path surfaces an actionable error when only the
username half has been edited.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CHANGELOG entries for v0.1.0 and v0.1.1 referenced `.veil/audit.db`, but
the on-disk file has always been `.veil/audit.sqlite` (see
internal/config/project.go:75). Correct both entries.

Add a "What Veil does NOT protect against" bullet to THREAT_MODEL.md
calling out that every Veil-generated placeholder embeds a literal
`VEIL` substring as a fail-closed leak sentinel — an agent scanning for
this substring can identify which inputs are placeholders, though it
learns nothing about the real secret. This is consistent with the
cooperative-but-curious model and the design tradeoff already documented
in internal/placeholder/engine.go.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@8enji
Copy link
Copy Markdown
Collaborator Author

8enji commented May 18, 2026

Merging locally instead.

@8enji 8enji closed this May 18, 2026
@8enji 8enji deleted the claude/hopeful-diffie-76bf63 branch May 18, 2026 23:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant