plugins/src/base/skills/lisa-secrets-access/SKILL.md
Vendor-neutral access layer for secrets. Every skill and script that needs an API key MUST resolve it through this skill rather than reading a keychain, an .env file, or a provider CLI directly. Resolves the environment first so CI injection wins, then the configured provider — Bitwarden Secrets Manager, 1Password, AWS Secrets Manager, Doppler or Vault — looked up by key name. Enforces one store per secret, reads usage metadata from the provider's own note field, and never writes.
npx skillsauth add codyswanngt/lisa lisa-secrets-accessInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Single chokepoint for reading credentials. Caller skills MUST go through this — they MUST NOT read the OS keychain, parse a .env, or invoke a provider CLI themselves.
The rule this exists to enforce: a secret lives in exactly one store. Every local cache is a copy that will eventually drift from its source, and a drifted copy is indistinguishable from a valid one until something fails in production.
operation: get name: ATTIO_API_KEY
operation: list # names only, never values
operation: describe name: ATTIO_API_KEY # the usage note, not the value
operation: verify # every declared secret resolves
get returns the value on stdout and nothing else. list and describe never emit a secret value.
In .lisa.config.json:
{
"secrets": {
"provider": "bitwarden",
"bootstrap": { "sources": ["env", "keychain"], "key": "BWS_ACCESS_TOKEN" },
"require": ["ATTIO_API_KEY", "SLACK_WEBHOOK_URL"]
}
}
provider — bitwarden | 1password | aws | doppler | vault | env.
bootstrap — how to obtain the one credential that unlocks the rest. sources is ordered: environment first, so CI injection wins over any local copy. This is the only credential permitted in a keychain; it is a bootstrap, not a cache.
require — optional. Omit it and every secret the provider grants is available, which is correct when the provider already scopes access per project. Present, it narrows to exactly those names and asserts them: a listed name that does not resolve is a startup error, not a late surprise. Use it when a repo needs a subset of a deliberately broader project.
There is no map of secret IDs, deliberately. Copying an ID per secret is the same duplication in a smaller costume, and lookup is by name.
A secret's key is the exact environment-variable name. No inference, no fuzzy matching, no case folding. A secret named attio-prod will not resolve for ATTIO_API_KEY, and the error says so rather than silently returning nothing.
Enforce it: doctor should warn on any key that is not a valid UPPER_SNAKE_CASE identifier.
If $<NAME> is set and non-empty, return it. This is how CI injects secrets, and it means a scheduled run never touches the provider or a local store.
Resolve the bootstrap credential by walking bootstrap.sources in order. Fail with an actionable message naming where it was looked for.
| Provider | Read |
| --- | --- |
| bitwarden | bws secret list → index by key |
| 1password | op read "op://<vault>/<name>/credential" |
| aws | aws secretsmanager get-secret-value --secret-id <name> |
| doppler | doppler secrets get <name> --plain |
| vault | vault kv get -field=<name> <path> |
| env | environment only; the provider is the environment |
Cache in-process only. Never write a resolved value to disk — that recreates the problem this skill exists to remove.
A missing name reports what is visible, so the caller can see immediately whether the secret is absent or merely misnamed:
GOOGLE_SERVICE_ACCOUNT_JSON is not available to this account.
Visible: APOLLO_API_KEY, ATTIO_API_KEY, SLACK_WEBHOOK_URL
A secret's key must be the exact environment variable name.
Every provider has a description field — Bitwarden note, 1Password notes, AWS Description, Doppler notes, Vault custom metadata. That is where a secret's usage documentation belongs, not in a config file: a note travels with the secret and therefore cannot drift from it, which is precisely the property config lacks.
Format — first line prose, then key: value lines:
Attio CRM - system of record for the sales funnel.
scope: object_configuration, record, list_entry - read-write
owner: <name>
ci: yes - injected by <mechanism>
docs: <path>
describe returns this. When a note is empty, infer purpose from the name, mark it inferred, and report the gap — infer and warn, never instead of. A silent fallback that works well enough guarantees the notes stay empty forever.
An inferred mapping must never authorise a write. It orients a reader; it does not pick which credential calls a production API. ATTIO_API_KEY versus ATTIO_API_KEY_STAGING is exactly the guess that silently writes to the wrong system.
No create, no update, no rotate. Writing secrets or their notes requires an authority a CI credential should not hold, and a read-only path cannot be turned against the vault if it leaks.
Where a rotating credential must persist a new value — an OAuth refresh token that the issuer replaces on every use — that is a separate, deliberately-scoped writer, and exactly one process may hold that loop. Two refreshers race, and each invalidates the other's token silently.
require resolves.^[A-Z][A-Z0-9_]*$..env, or provider CLI outside this skill. One chokepoint is what makes the single-store rule enforceable.development
Prepare a machine — a fresh laptop or a throwaway container — to run coding agents, before any repository exists. Detects which of Lisa's supported agents (Claude Code, Codex, Cursor, OpenCode, Antigravity, Copilot) are already installed, asks which credential manager the machine uses (Bitwarden, 1Password, Doppler, Vault, AWS, or none), and installs only what is missing, each by its vendor's own preferred method. Idempotent, headless by default, and emits a Dockerfile for a spin-up/spin-down environment. Run it on a new machine, in a container, or before cloning anything.
tools
Provision and verify a remote execution environment for a host project — Codex Cloud today, other remote surfaces as they are added. Generates a repository-owned setup script that installs the declared toolchain, materializes secrets through lisa-secrets-access, and runs the project's own hook. Provisions by API where one exists, by driving the vendor console where one does not, and by emitting exact config otherwise — then proves the result with the same read-back regardless of which tier did the work. Use before dispatching any work with executionEnv.
tools
Bring a developer's machine in line with the toolchain the project declares. Reports every tool in remoteEnv.tools that is missing, outdated, or unpinned for this platform, and installs the missing ones into ~/.local/bin from the same pinned, checksummed entries the remote surfaces use — but only when asked. Same manifest, same pins, same installers as lisa-setup-remote-env; what differs is consent and that the pin is a floor rather than an equality. Run it on a fresh checkout, after a manifest change, or when a tool fails at the moment of use.
tools
Route one unit of work to a remote execution surface. Reads the executionEnv parameter (local by default, codex-cloud or claude-web today), verifies the environment is provisioned and bound to this repository, submits a thin skill invocation, records the task identifier to .lisa/remote-dispatch.json, and exits without polling. Routing only — the remote runs the identical skill from the identical repository. Composable and inline: other skills invoke it via the Skill tool rather than users calling it directly.