first commit
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
# `allowlist`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The allow-list module encapsulates the per-plugin scoping rule that
|
||||
defines the package's security boundary: a plugin can only read secrets
|
||||
that its manifest's `secrets[]` block declared, no matter what
|
||||
`Bun.secrets` happens to hold under the host's service namespace. Every
|
||||
`get` / `require` call routes through `assertAllowed` BEFORE any
|
||||
keystore round-trip, so a leaked plugin cannot probe key existence by
|
||||
name even when the OS daemon is reachable.
|
||||
|
||||
The interface is intentionally minimal — a frozen ordered `names` array
|
||||
plus an O(1) `has` probe and the throwing `assertAllowed` guard — so the
|
||||
adapter can compose it without re-implementing the membership semantics.
|
||||
|
||||
## Features
|
||||
|
||||
- Frozen `names` array preserving manifest declaration order; safe to
|
||||
return directly from the adapter's `list()`.
|
||||
- `has(name)` boolean probe for branchy callers (e.g. selective writes).
|
||||
- `assertAllowed(name)` guard that throws `SECRETS_BUN.NOT_ALLOWED`
|
||||
with `context.name` populated for the audit log.
|
||||
- Silent de-duplication so a manifest that imports the same secret
|
||||
twice does not crash at construction.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { createAllowlist } from "@suaveplan/api-secrets-bun/allowlist";
|
||||
|
||||
const al = createAllowlist(["STRIPE_SECRET_KEY", "JWT_SIGNING_KEY"]);
|
||||
|
||||
al.assertAllowed("STRIPE_SECRET_KEY"); // no-op
|
||||
al.assertAllowed("AWS_SECRET_KEY"); // throws SECRETS_BUN.NOT_ALLOWED
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Compose the allow-list with the adapter to gate `set` as well as `get`:
|
||||
|
||||
```ts
|
||||
import { createAllowlist } from "@suaveplan/api-secrets-bun/allowlist";
|
||||
|
||||
const al = createAllowlist(manifest.secrets.map((s) => s.name));
|
||||
|
||||
function writeIfAllowed(name: string, value: string): void {
|
||||
al.assertAllowed(name);
|
||||
return Bun.secrets.set({ service: "my-plugin", name, value });
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `createAllowlist(names: readonly string[]): Allowlist` — build a
|
||||
frozen allow-list from a manifest-derived name list.
|
||||
- `Allowlist.names: readonly string[]` — frozen ordered view.
|
||||
- `Allowlist.has(name: string): boolean` — membership probe.
|
||||
- `Allowlist.assertAllowed(name: string): void` — throws
|
||||
`SecretsBunError` with code `SECRETS_BUN.NOT_ALLOWED` on miss.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The allow-list owns its `Set<string>` once at construction so per-call
|
||||
lookup is O(1). The `names` array is frozen at the top level only; the
|
||||
strings inside are primitives and already immutable. `assertAllowed`
|
||||
populates `context.name` so the audit trail can route a denied lookup
|
||||
back to the offending plugin without surfacing the value (there is no
|
||||
value yet — the call is rejected before keystore consultation).
|
||||
Reference in New Issue
Block a user