@hasna/secrets
v0.2.7
Published
A local secrets vault for AI agents — store API keys, passwords, tokens, and any credentials
Maintainers
Readme
@hasna/secrets
A local encrypted secrets vault for AI agents, CLIs, and developer machines. Store API keys, passwords, tokens, and other credentials without committing them to source control.
Install
bun install -g @hasna/secretsCLI Usage
secrets --help
secrets docsCommon Commands
Store a secret:
secrets set example/anthropic/test/api_key "$ANTHROPIC_API_KEY" \
--type api_key \
--label "Anthropic API Key (test)"Read a secret value:
secrets get example/anthropic/test/api_keyList and search without printing secret values:
secrets list
secrets list example/anthropic
secrets search anthropicInspect audit history:
secrets audit example/anthropic/test/api_keyInspect metadata-only secret reference health:
secrets status --jsonThe status contract reports package version, redacted local data paths, env override names, and aggregate counts only. It does not include secret values, secret key names, raw env values, provider inventory, or private key material.
import { getSecretReferenceStatus } from "@hasna/secrets/status";
const status = getSecretReferenceStatus();
console.log(status.counts.byType.api_key);Secret References For Automations
Deterministic automations and action manifests must reference secrets by key,
not embed raw values. In @hasna/actions manifests this appears as
secrets[].ref; in lower-level payloads it may be named secretRef. The
reference value is the same slash-delimited key used by this vault:
{
"secretRef": "hasna/xyz/opensource/connectors/prod/github",
"scope": "repo:issues:write",
"reason": "Create a GitHub issue from an approved automation action"
}Minimum contract for OpenAutomations and @hasna/actions consumers:
- automation specs, queued action payloads, todos comments, and run evidence
store
secretRefstrings only - the runtime that resolves a reference must check the requested scope before handing a value to a connector or command
- audit records may include the secret key, type, scope, resolver package, and decision, but never the decrypted value
- redaction must treat keys named
secretRef,secret,token,apiKey,authorization, and provider credential fields as sensitive - replay should re-resolve the reference at execution time instead of storing a historical secret value in the automation queue
Export redacted compact JSON for review:
secrets exportExport plaintext only when a local restorable artifact is explicitly needed:
secrets export --show --pretty > secrets-backup.jsonScan the current workspace or bounded git history for exposed credentials:
secrets scan workspace --limit 50
secrets scan history --max-commits 200 --limit 50Create secure loop evidence and deduped Todos tasks for unsafe sensitive-file permissions:
secrets security permissions \
--report-dir ~/.hasna/loops/evidence/secret-file-permissions \
--upsert-tasks \
--todos-project ~/.hasna/loops \
--task-list secret-file-permissions \
--max-task-actions 20 \
--jsonThe permissions report is written with private file permissions and contains paths, modes, fingerprints, and task routing metadata only. It does not include secret values.
Delete a secret:
secrets delete example/anthropic/test/api_keyKey Format
Use slash-delimited keys:
<division>/<service>/<env>/<name>Examples:
example/anthropic/test/api_key
example/local/dev-workstation/tool/exa-api-key
example-app/oauth/youtube_client_secretHasna XYZ Canonical Keys
Generic keys still work as before. Keys under hasna/xyz/ are validated so
new Hasna XYZ app resources follow the canonical migration shape:
hasna/{division}/{app_type}/{app}/{env}/{component}
hasna/{division}/infra/{resource_group}/{env}/{component}[/role]Allowed app types are:
opensource, internalapp, companywebsite, projectinfra is reserved for shared infrastructure ownership. Deprecated migration
taxonomies such as connector, website, and platform are rejected under
hasna/xyz/. App names should omit repo prefixes such as open-, iapp-,
cweb-, and project-.
Examples:
hasna/xyz/opensource/files/prod/rds
hasna/xyz/internalapp/news/prod/env
hasna/xyz/infra/apps/prod/postgres/masterOperator runbook:
docs/hasna-xyz-canonical-secrets-runbook-2026-06-08.md.
AWS Secrets Manager Sync
secrets aws can use the legacy static-key config written by
secrets aws configure, or AWS profile/default-chain credentials:
AWS_PROFILE=hasna-xyz-infra secrets aws sync --dry-run
secrets aws push hasna/xyz/opensource/files/prod/s3 --profile hasna-xyz-infra --dry-run
secrets aws sync --credential-mode role --role-arn arn:aws:iam::123456789012:role/example --source-profile hasna-xyz-infra --dry-runCredential source precedence is command flags, HASNA_SECRETS_AWS_*
environment variables, ~/.hasna/secrets/aws.json, then the standard AWS
provider chain. If aws.json contains static keys and no explicit override is
provided, static-key behavior is preserved for compatibility.
Use --dry-run or --plan before live sync. Plan output is metadata-only JSON:
it reports names, regions, prefixes, credential source descriptors, and
intended actions without printing secret values or writing AWS/local vault
state.
Secret Types
Supported types:
api_key, password, token, credential, otherStructured Vault Items
The generic key/value store remains supported. For browser autofill and LastPass-like records, use structured vault items. Item payloads are encrypted; titles, domains, tags, and item kind are stored as searchable metadata.
Create a login item:
secrets items add-login \
--title "GitHub" \
--url "https://github.com" \
--username "[email protected]" \
--password "$GITHUB_PASSWORD"Create an address item:
secrets items add-address \
--title "Home" \
--name "Example User" \
--line1 "1 Main St" \
--city "New York" \
--state "NY" \
--postal-code "10001" \
--country "US" \
--email "[email protected]"List, search, inspect, or delete items:
secrets items list
secrets items list login
secrets items search github
secrets items get <id> # redacted payload
secrets items get <id> --show # decrypted payload
secrets items delete <id>Supported item kinds:
login, address, identity, payment_card, secure_note, api_key, customChrome Extension
The extension/ directory contains a Manifest V3 Chrome extension that can fill
logins, addresses, identities, and payment-card fields from structured vault
items. Legacy username/password pairs still work when stored as slash-delimited
secrets with a common prefix.
Run the local bridge:
secrets serveThen load extension/ from chrome://extensions with Developer Mode enabled,
open the extension settings, and paste the token from secrets serve token.
The extension talks only to http://127.0.0.1:27462 and asks for decrypted item
payloads only when you click Fill or Copy.
Optional TTL values can be attached when setting a secret:
secrets set temp/session "$TOKEN" --type token --ttl 24h
secrets gcMCP Usage
Install the MCP server into local AI agents:
secrets mcp install --target codex
secrets mcp install --target claude
secrets mcp install --target geminiAgents connect over stdio by running:
secrets mcpStart the shared Streamable HTTP MCP server explicitly:
secrets mcp http --port 8848The MCP exposes these tools:
list_secrets(namespace?)
search_secrets(query)
get_secret(key)
set_secret(key, value, type?, label?, ttl?)
delete_secret(key)
list_vault_items(kind?)
search_vault_items(query)
get_vault_item(id)
set_vault_item(kind, title, data, id?, subtitle?, domains?, tags?, favorite?)
delete_vault_item(id)
audit_log(key?, limit?)
register_user(id, name, type?)
list_users(type?)
storage_status()
storage_push(tables?)
storage_pull(tables?)
storage_sync(tables?)
scan_workspace_exposures(root?, limit?, maxFileBytes?, maxFiles?, maxBytesScanned?, timeoutMs?)
scan_history_exposures(root?, limit?, maxCommits?, timeoutMs?)list_secrets and search_secrets return metadata only and do not decrypt
stored values. get_secret returns the raw value, so use it only when the agent
needs to pass the secret into a tool or command.
The scan tools return compact JSON with a stable schema, bounded redacted findings, and path/line/commit references only. They do not return raw matching values. MCP scan roots are constrained to the server working directory, and workspace scans include hard file, byte, and timeout bounds.
Env-File Bridge
The vault can import from and export to the conventional machine-local
~/.secrets tree:
~/.secrets/{division}/{service}/live.env
~/.secrets/{division}/{business}/{service}/live.envImport .env files into the vault:
secrets import-env --dir ~/.secrets --dry-run
secrets import-env --dir ~/.secrets --overwriteExport vault entries back to .env files:
secrets export-env --dir ~/.secrets --dry-run
secrets export-env --dir ~/.secrets --forceStorage Sync
This package supports optional remote storage sync directly against a Postgres/RDS database. Local SQLite remains the default.
export HASNA_SECRETS_DATABASE_URL=postgres://...
secrets storage status
secrets storage push
secrets storage pull
secrets storage syncThe remote storage URL can also be provided as the short non-deprecated fallback
SECRETS_DATABASE_URL.
Canonical production storage uses database secrets on RDS instance
hasna-xyz-infra-apps-prod-postgres. Runtime credentials are stored in AWS
Secrets Manager at hasna/xyz/opensource/secrets/prod/rds; load its
database_url value into HASNA_SECRETS_DATABASE_URL. SECRETS_DATABASE_URL
remains supported as a rollback/local fallback. Do not print rows or values from
the canonical database; status commands expose only redacted URLs, table names,
and non-secret metadata.
MCP exposes the same flow through storage_status, storage_push,
storage_pull, and storage_sync.
Data Directory
Data is stored in ~/.hasna/secrets/.
secrets path
secrets keyThe vault database lives at ~/.hasna/secrets/vault.db. Key material lives in
~/.hasna/secrets/vault.key for local-key mode or
~/.hasna/secrets/vault.key.enc for KMS envelope-encryption mode.
Safety Notes
list,search,export, andscando not decrypt or print secret values by default.export --showandexport --plaintextare explicit plaintext escape hatches for local restorable backups.- CLI import refuses redacted export bundles so placeholders do not overwrite real secrets by accident.
getand MCPget_secretreturn raw secret values.- Never paste secret values into commits, logs, issues, PRs, or chat messages.
- Keep
.env,.env.local,.secrets/, and.connect/out of git.
Cloud service (self_hosted)
Beyond the local vault, @hasna/secrets ships a deployable HTTP service and a
typed SDK. Four surfaces cover the same core:
secrets— the CLI.secrets-mcp— the MCP server (stdio by default,--httpfor Streamable HTTP).secrets-serve— the HTTP API. Unauthenticated probesGET /health,/ready,/version({status, version, mode}) andGET /openapi.json; a versioned/v1surface (secrets + vault-item CRUD, search, audit, users) behind strict API-key auth (@hasna/contracts). Scopes:secrets:read,secrets:write.@hasna/secrets/sdk— a typed, dependency-free fetch client generated from the serve OpenAPI. Clientself_hostedmode usesSECRETS_API_URL+SECRETS_API_KEY(never a DSN).
Storage is PURE REMOTE (Amendment A1) in cloud mode: secrets-serve reads
and writes the shared Postgres directly (no cache, no local mirror). Secret and
vault-item values are encrypted at rest (AES-256-GCM) with a master key
injected via HASNA_SECRETS_MASTER_KEY — the service fails closed without it.
# migrate the cloud database (one-shot), then serve
export HASNA_SECRETS_STORAGE_MODE=cloud
export HASNA_SECRETS_DATABASE_URL=postgres://… # or DATABASE_URL
export HASNA_SECRETS_API_SIGNING_KEY=$(openssl rand -hex 32)
export HASNA_SECRETS_MASTER_KEY=$(openssl rand -base64 32)
secrets-serve db migrate
secrets-serve # listens on $PORT (default 8080)
# issue an API key (@hasna/contracts issuer), then call the SDK
bunx @hasna/contracts issue-key --app secrets --agent my-agent --scopes 'secrets:read,secrets:write'import { createSecretsClientFromEnv } from "@hasna/secrets/sdk";
const client = createSecretsClientFromEnv(); // SECRETS_API_URL + SECRETS_API_KEY
await client.putSecret({ key: "openai/api_key", value: "sk-…", type: "api_key" });
const secret = await client.getSecret({ key: "openai/api_key" });Migrations live in migrations/ (canonical checksummed set in
src/server/cloud-migrations.ts). Container image: Dockerfile.package
(ARM64/bun); local stack: docker-compose.yml.
License
Apache-2.0 -- see LICENSE
