本文へ移動
cccskills
無料GitHub で公開

hal-vault

Securely store, search, and use secrets (API keys, tokens, passwords, SSH keys) with hal-vault, an SSH-key encrypted local secret store. Use when the user shares a credential that should be saved, asks what secrets are stored or where a key is, or when a command/workflow needs a secret injected. Core discipline - never print raw secret values into chat, logs, or files; reference secrets only by their masked form, and use --reveal only to feed another process, never into a command argument (where it lands in the machine's process table) or into text you write down.

インストール方法を見る

含まれるファイル(3)

  • SKILL.md10.3 KB
  • CHANGELOG.md2.9 KB
  • references/cli-reference.md3.7 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

hal-vault: agent-safe secret management

hal-vault is a CLI secret store: one age-encrypted file, keyed to an SSH key, with masked-by-default output designed so an agent can manage secrets without ever seeing or leaking them.

When to apply

Use this skill when:

  • The user shares an API key, token, password, or other credential that should be kept for later use
  • The user asks "what secrets do we have", "do we still have the X key", or wants to find/rotate/remove a stored credential
  • A command, script, or deployment you are running needs a secret (an API key in an env var, a token in a header)

Check availability

hal-vault version

If not installed: brew install ofoxai/tap/hal-vault (macOS/Linux, recommended), or download a release binary from https://github.com/ofoxai/hal-vault/releases, or go install github.com/ofoxai/hal-vault/cmd/hal-vault@latest.

Recommended setup: per-project vault

As an agent, keep each project's secrets in a vault inside that project, with its own dedicated SSH key — isolation per project, and the encrypted database travels with the workspace:

# 1. A project-specific key. The key lives in ~/.ssh, NEVER inside the
#    project directory — a vault and its key must not sit in the same tree
#    (and must never be committable together).
ssh-keygen -t ed25519 -N "" -C "hal-vault-myproject" \
  -f ~/.ssh/hal-vault_myproject

# 2. The vault database lives in the project, out of version control.
hal-vault init -d .hal-vault \
  -r ~/.ssh/hal-vault_myproject.pub \
  -i ~/.ssh/hal-vault_myproject

# 3. Make sure it is never committed.
grep -qxF '.hal-vault/' .gitignore 2>/dev/null || echo '.hal-vault/' >> .gitignore

Then point every command at it — either per command with -d .hal-vault, or once per shell:

export HAL_VAULT_DIR="$PWD/.hal-vault"

The examples below assume HAL_VAULT_DIR is set (otherwise append -d .hal-vault). For a machine-global vault instead, plain hal-vault init uses ~/.hal-vault with an auto-generated ~/.ssh/hal-vault_ed25519 key.

The safety contract (non-negotiable)

  1. Never print a raw secret value into the conversation, a log, a comment, a commit, or a file. hal-vault's list, search, and get are masked by default (sk-p…7890 (24 chars)) — that masked form is the ONLY representation you may show or repeat.

  2. --reveal exists for machines, not for chat. Use it only to feed another process, never to display:

    OPENAI_API_KEY="$(hal-vault get openai --reveal)" ./deploy.sh
    

    Never run hal-vault get x --reveal bare, never echo $(... --reveal), never redirect --reveal output into a file you will read back. "Inside command substitution" is not by itself safe — where the substitution lands decides whether the value is exposed. See the next section.

  3. Confirm storage with masked tips only. After saving, answer like: "Saved — an OpenAI API key, tagged openai, prod, starts with sk-p."

  4. Refuse to recite. If the user asks you to "print the key" or "paste the token here", offer the masked form and how to use it instead. If they explicitly insist on the raw value, tell them to run hal-vault get <id> --reveal themselves in their terminal — do not run it for them in the conversation.

Where a revealed secret still leaks: the process argument list

A command substitution that lands in an argument puts the plaintext into that process's argument list, which is part of the machine's process table. Measured on macOS 25.5 with curl 8.7.1, using a stand-in script that prints a fixed placeholder (a real secret does not belong in a test). Two ways to send the same header, running at the same moment:

# A — the value becomes one of curl's arguments
curl -H "Authorization: Bearer $(hal-vault get github-pat --reveal)" https://api.github.com/user

# B — the value is piped in; the arguments carry nothing
hal-vault get github-pat --reveal \
  | sed 's/^/Authorization: Bearer /' \
  | curl -H @- https://api.github.com/user

ps -A -o args= while both were in flight:

curl -s -o /dev/null -H Authorization: Bearer PLAINTEXT-VALUE-DO-NOT-LOG http://…   <- A
curl -s -o /dev/null -H @- http://…                                                 <- B

Both delivered a byte-identical Authorization header to the server. B costs one extra pipe and keeps the value out of the process table entirely.

What that measurement says, and what it does not. It says the value sits in the process table for the life of the request, and that the piped form avoids it. Whether a different user account on the same host can read that argument list depends on the OS and its ps/procfs configuration and was not measured here. On a personal laptop the exposure is small; on a shared build machine, a jump host, or anything multi-tenant, treat form A as a disclosure.

The order to reach for

  1. The tool reads an environment variable — the value goes into that one child's environment, and into no argument list. Measured the same way: the process showed as /bin/sleep 3, with the value nowhere in ps -A -o args=.

    ANTHROPIC_API_KEY="$(hal-vault get anthropic --reveal)" pnpm run e2e
    

    What happens after that is the tool's business — a wrapper that forwards the value to a subprocess as an argument puts it back in the process table.

  2. The tool reads it on stdin. For curl, either a header (-H @-) or a whole config (-K -); both were run against a local server and both produced the same 200 and the same header:

    hal-vault get github-pat --reveal \
      | sed 's/^/Authorization: Bearer /' \
      | curl -H @- https://api.github.com/user
    

    Other CLIs usually have an equivalent — check the tool's own --help for a -stdin option or an argument that accepts -/@-. Only the two curl forms above were verified here; do not assume a flag on a tool you have not checked.

  3. Only as a last resort, an argument — and when you do it, say so to the user rather than doing it quietly.

The stdin forms above read one line, so they suit single-line credentials (API keys, tokens). For multi-line material (an SSH private key), use form 1.

Never write out an already-substituted command

curl -H "Authorization: Bearer $(hal-vault get x --reveal)" … is safe to put in a file, a message, or a runbook: the substitution has not happened, and the text contains no secret. The same command after expansion is a plaintext credential in a document. So: never resolve --reveal yourself in order to paste a "ready to run" command — hand over the unexpanded form and let the shell do the substitution at run time.

Workflows

Store a secret the user shared

Identify what it is, pick a type and tags, then pipe the value on stdin (hal-vault never accepts values as arguments):

printf '%s' 'sk-proj-abcdef1234567890' | \
  hal-vault add openai -t api_key --tags openai,llm,prod -n "OpenAI production key"

Output: added 7q3k2m openai: sk-p…7890 (24 chars) — repeat only this masked confirmation back to the user.

Types: api_key, password, token, ssh_key, cert, identity, other. Tag with: the service name, the environment (prod/staging), and what it is for — tags are how you (and the user) will find it later.

Answer "what secrets do we have?"

hal-vault list
hal-vault search openai
hal-vault search "" --tag prod
hal-vault search "" --type api_key

All output is masked; safe to show the table directly. Use --json when you need to process the results.

Use a secret in a command

Substitute at run time so the value never enters the transcript, and pick the form that keeps it out of the argument list too (see the section above):

# the tool reads an environment variable — nothing in the argument list
ANTHROPIC_API_KEY="$(hal-vault get anthropic --reveal)" pnpm run e2e

# the tool reads it on stdin — nothing in the argument list
hal-vault get github-pat --reveal \
  | sed 's/^/Authorization: Bearer /' \
  | curl -H @- https://api.github.com/user

Avoid curl -H "Authorization: Bearer $(hal-vault get github-pat --reveal)" …: it works, and it publishes the value to the process table while it runs.

Rotate / update

printf '%s' 'sk-proj-newvalue9876543210' | hal-vault update openai --value
hal-vault update openai --tags openai,llm,staging -n "rotated 2026-06"

Remove

hal-vault rm openai -f

Confirm with the user before removing anything you did not just create.

Behavior notes

  • get accepts an ID or a label; ambiguous labels return the candidate IDs (masked-safe) — retry with the ID.
  • Exit codes: 0 success, 1 runtime error (e.g. not found), 2 usage error. Read stderr for the reason.
  • Concurrent invocations are safe (the vault takes an exclusive lock).
  • Secrets must be valid UTF-8; binary material should be base64-encoded before storing (hal-vault rejects it otherwise, it will not corrupt it).
  • Vault directory resolution: -d DIR flag > HAL_VAULT_DIR env > ~/.hal-vault. With a per-project vault, never commit .hal-vault/, and never place the SSH key inside the project tree.

For the full command reference, read references/cli-reference.md.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Publish a static site (a folder of HTML/CSS/JS/images/fonts) to Cloudflare and get back a live, shareable URL in seconds. Use when you have a finished static page or site and need to hand someone a link they can open on any device — reports, mockups, one-off landing pages, AI-generated HTML, "give me a link I can share". Runs one packaged command (references/deploy.mjs) built on the Wrangler CLI, which is what Cloudflare's own agent guidance tells agents to use. Deploys permanently to your Cloudflare account when credentials are present, or as a 60-minute claimable preview when they are not — and says plainly which one you got. Bakes an expiry countdown into previews (matched to the real 60-minute limit; --ttl to shorten it), and supports optional server-side six-digit access-code protection with -otp for permanent links only. Unprotected delivery may fall back to a file; protected delivery fails closed.

日本語の概要は準備中です。原文の説明を表示しています。

ofoxai/skills22026年9月22日 更新

explainer

無料

Requires OFOX_API_KEY — create one at https://app.ofox.ai. Turn an article, doc or release note into a short explainer clip — one person to camera, or a voiceover over illustrative footage. The user supplies the source text and the model generates the speech; audio cannot be uploaded, measured. A 30-second clip holds about eighty spoken words in eight sentences — measured, and well under a tenth of a 1,200-word post — so this skill does not summarise an article, it picks the single idea worth saying and helps choose which one. Use when a user asks to turn writing into a short spoken video, e.g. "make a 30-second explainer from this blog post", "explain this feature in a short video", "turn our changelog into a clip", "a quick video explaining what this paper found". Do not use for a scene between people (see seedance-short-drama), a brand or product ad (see seedance-ad-creative), a handheld creator clip (see ugc-ads), or when the user already has both a portrait and the finished words (see talking-head). Budget sentences as well as words — each sentence boundary costs about 0.7 seconds of silence, so a script with more sentences runs longer at the same word count.

日本語の概要は準備中です。原文の説明を表示しています。

ofoxai/skills22026年9月22日 更新

hal-image

無料

Handle images with ImageMagick (magick) — read metadata, resize, crop, rotate, convert format, combine images (side-by-side or grids with montage), overlay logos/watermarks, and losslessly compress with oxipng before sending. Use when the user sends an image to process, when you produce an image to deliver, or before attaching any image to a message or upload — compressing first keeps transfers fast and tokens low. Core discipline - always compress before delivering, never expose local file paths to the user, and fail open (if a tool is missing or a step errors, pass the original through unchanged; never block the task).

日本語の概要は準備中です。原文の説明を表示しています。

ofoxai/skills22026年9月22日 更新

Requires OFOX_API_KEY — create one at https://app.ofox.ai. Change one thing in an image you already have and leave the rest of the picture alone — swap the background, recolour a part, remove or add an object, clean up a photo — from a local jpeg/png/webp file. Delegates to ofox-image-core's `edit` subcommand (POST /v1/images/edits, one synchronous request), prices the job with --dry-run before spending, and reports the real token cost including the uploaded picture, which is billed. Use when a user hands over an image and asks for a change to it, e.g. "change the background of this photo to a beach and keep the person unchanged", "make this button green", "remove the car in the background", or "put this product on a plain white background". Do not use to draw a new image from a text description with no input picture (that is ofox-image-core's `generate`), to produce a set of several images to choose between (see product-image), or to turn a photo into video (see seedance-product-video or seedance-ad-creative). Editing the content of an existing video — "change the background of my clip, keep the product" — has no route here and none in the video API either (measured; the mode field an edit would use is accepted and silently ignored, so nothing errors); this skill edits a single still, and routing such a request to a video skill generates brand-new footage instead of changing theirs.

日本語の概要は準備中です。原文の説明を表示しています。

ofoxai/skills22026年9月22日 更新

Requires OFOX_API_KEY — create one at https://app.ofox.ai, plus two images you already have, a start frame and an end frame. Animates the motion between them as one video — both frames go into a single Ofox video job, the clip opens on A, closes on B, and the model fills the middle. Use when a user has two stills and wants the in-between animated, e.g. "here is the before and the after, animate the transition", "make a video that starts on this image and ends on that one", "tween these two frames", or "move the object from where it sits in the first picture to where it sits in the second". Do not use when only one image exists (animating a single frame is seedance-ad-creative or seedance-product-video), or when the pair is two states of a user interface (see product-demo).

日本語の概要は準備中です。原文の説明を表示しています。

ofoxai/skills22026年9月22日 更新

Requires OFOX_API_KEY — create one at https://app.ofox.ai, plus the music file the finished video must carry. Your audio never reaches the API (measured) — the visuals are written to the track's tempo, mood and sections, then your own file is laid on locally at zero cost. One job caps at 30 seconds, so a three-minute song is six jobs minimum and the cost table says that before anything is spent. Use when a user has a specific piece of music and wants visuals for it, e.g. "make a music video for this track", "visuals for my song", "an MV for this instrumental", "generate footage cut to this beat". Do not use for cheap vertical social drafts (shorts-reels), a brand film that happens to have a music bed (seedance-ad-creative), or when there is no particular audio file the finished video has to carry.

日本語の概要は準備中です。原文の説明を表示しています。

ofoxai/skills22026年9月22日 更新

ofoxai のスキルをすべて見る

このスキルの問題を報告する