Changelog
34 releases shipped - every feature, fix, and security improvement on the record. Latest: v0.19.0 on 2026-11-05.
Showing 34 releases matching any change
v0.19.0
2026-11-05
Latest15 changes- addedRedaction catches encoded and sliced secrets, and names the key - the shared engine behind `qring exec`, `qring run` and the MCP airlock now also matches each secret's base64 (standard and URL-safe, any alignment), hex, URL-encoded and JSON-escaped forms, and any run of 16 or more characters of its credential part: the `user:password` of a connection string, the path and query of a webhook URL, the signature of a JWT, the body lines of a PEM, the string values of a JSON credential, the whole value otherwise. Every hit becomes `[QRING:REDACTED:<KEY>]`; `[QRING:REDACTED]` stays the fallback when the key is unknown. Limits: encoded forms for values up to 256 characters, slices for credential parts up to 1024, repeated-character runs never sliced. The airlock matches whole values and their encoded forms but does not slice, so an agent cannot read a secret out one character per call through a tool that echoes its input. Streaming output now flushes as soon as it is written
- addedMCP resources for introspection, zero new tools - `qring://policy` (the project's policy as the agent may see it), `qring://rotation` (the `rotate:due` list, most overdue first) and `qring://canaries` join `qring://sessions`. Canaries are hidden unless the server starts with `QRING_MCP_EXPOSE_CANARIES=1`, because which keys are canaries is the thing the feature protects. The tool count stays at 46
- addedThe 1.0 stability contract - `docs/stability.md` lists what is frozen as of 0.19 (the CLI, the 46 tool schemas, resource URIs, the `.q-ring.json` policy format, the ring format, environment variables, exit codes, both redaction tokens), what stays experimental, the semver promise from 1.0, the deprecation policy and the support window
- addedClaude Code plugin: always-on rules - plugins have no rules component and a plugin-root CLAUDE.md is not loaded, so the secret-hygiene and q-ring-workflow rules now arrive through the SessionStart hook on startup, `/clear` and compaction (not on resume, where the transcript already has them). Ready-made copies for `~/.claude/rules/` ship under `claude-code-plugin/rules/`; both are generated from the Cursor rules and CI fails on drift
- addedAudit pass 2 report - `docs/audits/2026-11-audit-pass-2.md` covers the second internal adversarial review: forty findings (C1 to C40, seven rated High), each fixed with a regression test, the surfaces examined and found sound, and the open design items carried to 0.20. `QRING_ALLOW_PRIVATE_HOOKS` is now the canonical name of the private-address hook override, and `QRING_SILENCE_DEPRECATIONS=1` silences deprecation warnings
- changedThe redaction token names the key (`[QRING:REDACTED:DATABASE_URL]`) - match the `[QRING:REDACTED` prefix if you grep for it. When no redaction set can be built, the airlock withholds the tool result and `qring exec` / `qring run` refuse to spawn instead of passing output through; a single unreadable secret is skipped with a counts-only note instead of disabling redaction for every other secret
- changedA corrupt `.q-ring.json` (not valid JSON, not an object, unreadable) fails closed everywhere policy is enforced, and the diagnostic surfaces report it instead of crashing: `qring doctor` (which now honours `--project-path`), `qring policy` (exit 1, `{ policyError }` with --json), `qring status` and the dashboard, the `check_policy` tool and every MCP resource
- changed`exec_with_secrets` over MCP injects only the keys the project manifest declares, or, without a manifest, only credential-shaped names (`*_KEY`, `*_TOKEN`, `*_SECRET`, `*_PASSWORD`, `*_URL`, `*_DSN` and similar); `MY_CONFIG` and `LOG_LEVEL` are skipped and listed by name in the result. Declare the keys you need under `secrets` in `.q-ring.json`. The CLI is unchanged
- changedShell and signal hooks registered over MCP start disabled - arm one with `qring hook enable <id>`. With a `readableKeys` allowlist, MCP writes are limited to those keys too. `qring exec` / `qring run` refuse an unknown `--profile`, `qring ci:validate --json` exits 1 when a secret fails, and `qring mcp approvals` honours the root `--json` flag
- changedDeprecated, removed in 1.0 (each prints one `q-ring: deprecated:` line on stderr per process): `qring approve <key> --list` (use `qring approvals`), the bare `--json` shapes of `context`, `ci:validate` and `policy` (1.0 moves them to the `{ ok, data }` wrapper), teleport v1 bundles without an `iter` field, agent memory stores under the pre-0.14 machine-derived key, `QRING_CANARY_ALERTS_PATH` (use `QRING_CONFIG_DIR`) and `Q_RING_ALLOW_PRIVATE_HOOKS` (use `QRING_ALLOW_PRIVATE_HOOKS`)
- fixedA stale policy could be served - the `.q-ring.json` cache was keyed on mtime alone, so two writes inside one millisecond (Windows) returned the previous policy; both caches now key on the file's content, per path. `qring://canaries` no longer ties a re-planted canary to its predecessor's trip and deletion events, and the Claude Code SessionStart hook builds its JSON with `jq` instead of `printf %b`
- securityPolicy holds on every MCP write path - `policy.mcp.deniedTags` was validated but never evaluated, so a secret with a denied tag stayed readable, listable, exportable and deletable over MCP. MCP writes (`set_secret`, `import_dotenv`, `teleport_unpack`, `generate_secret --saveAs`, `promote_secret`, `rotate_secret`, `lint_files --fix`, `agent_scan` auto-rotation) now respect key and tag policy and cannot shadow a protected key from another scope; bulk writers skip protected keys instead of aborting halfway; entanglement honours deniedTags in both directions and shares deny tags across a link
- securityExec never injects process-control variables - `NODE_OPTIONS`, `LD_PRELOAD`, `PATH`, the `GIT_*`, `SSH_*`, `MAKE*` and `CARGO_*` families, interpreter start-up, proxy and TLS trust-store variables and more are never injected over MCP, and from the CLI only when named with `--keys`; credentials inside a family (`NPM_TOKEN`, `SSH_PRIVATE_KEY`) still inject. A key whose name is not a plain environment variable name (`BASH_FUNC_ls%%`, `stripe-key`) is never injected, and `QRING_FILE_PASSPHRASE` / `QRING_MEMORY_PASSPHRASE` are stripped from every child. Exec profiles are still not a sandbox
- securityRe-setting a secret keeps its tags and description - `qring set KEY value` without `--tags` used to drop them, and with them any deniedTags protection. `qring set --clear-tags` (and `clearTags` on `set_secret`) removes them on purpose; combining it with new tags is refused
- securityCanary identity no longer leaks through audit hash-chain gaps, blocked alert deliveries, `get_project_context`, agent-started dashboards or silent `ci_validate_secrets` reads; `canary plant --force` starts fresh. The airlock redacts errors, progress and URIs as well as results, `qring push` refuses values that would inject variables into fly or netlify, and the file backend writes atomically. Known limitation, documented: on Linux an exec or run child can read the parent's environment through `/proc/<ppid>/environ`; a fix is planned for 0.20
v0.18.0
2026-09-27
7 changes- addedEnvironment promotion - `qring diff staging prod` compares two environments key by key (same / different / missing on one side / single-value) without printing a value and exits 1 on drift, so it works as a CI gate; `qring promote KEY --from staging --to prod` copies one superposition state into another through the normal write path (policy, audit, hooks), is a no-op when the target already matches, and asks (or needs --force) before overwriting a different value. MCP: `diff_environments`, `promote_secret` - 46 tools
- addedThree more push targets - `qring push fly --app`, `qring push railway --service --railway-env`, `qring push netlify --site` on the existing architecture: each platform's own authenticated CLI, values over stdin (flyctl secrets import, railway variable set --stdin) or, for Netlify's import-only CLI, a 0600 temp file inside a private temp dir removed even when the CLI fails - never argv
- addedRotation reminders - every secret records when its value last changed (rotatedAt, set by set, rotate and auto-rotation alike). `qring set KEY --rotate-every 90` schedules a reminder: due soon inside the last 20% of the interval (or 7 days, whichever is shorter), overdue past it. `qring inspect` shows the status, `qring rotate:due [--all] [--json]` lists what needs attention most-overdue first, the dashboard gains a Rotate-soon card, KPI and health pill, and set_secret / inspect_secret carry rotateEveryDays / rotation
- addedTeleport v2 recipient packs - `qring teleport keygen` mints an X25519 identity (private key in the OS keyring, never a file) and prints a qring1… recipient string; `pack --to <recipient…>` encrypts a scoped secret set to any number of recipients (a random content key under AES-256-GCM, wrapped per recipient with HKDF-SHA256 over an ephemeral X25519 agreement, node:crypto only) so a teammate unpacks with nothing but their own keyring. unpack detects v1 vs v2; --dry-run shows the addressed recipient ids and whether you are one. teleport_pack accepts recipients, teleport_unpack needs no passphrase for v2. Passphrase bundles keep working
- fixedRegistries never race or leak into your real config from tests - hooks, entanglement, canary webhooks, approvals, agent memory and observer state resolve their directory through the new QRING_CONFIG_DIR (default unchanged); the test suite gives every worker its own, ending the intermittent "registry was corrupt" CI failure
- fixedA locked or unreachable OS keyring now says so - @napi-rs/keyring 2.x throws where 1.x returned "not found"; q-ring translates those into a BackendUnavailableError that explains how to unlock or switch to the file backend
- changedDependencies: @napi-rs/keyring 2.1, vitest 5, MCP SDK 1.30.1. Test suite 383 → 461
v0.17.6
2026-09-16
3 changes- fixedCanary values can no longer read as placeholders - CSPRNG noise occasionally spelled a placeholder marker (`xxx` turns up in a 74-char body roughly once every few hundred draws), and `scan` would then report the planted canary as a placeholder instead of a secret. Every canary format now regenerates until the value passes the same heuristic `scan` uses
- fixedTest suite: the canary alert registry's 0600 mode assertion is skipped on Windows, where chmod is a no-op - the Windows compat job had been failing on it since 0.17.5
- securityDependency floors: hono ≥ 4.13.5 (three moderate advisories in the transitive MCP transport dependency); in-range bumps for zod, @types/node, eslint, typescript-eslint
v0.17.5
2026-09-11
5 changes- addedWrap policy - `.q-ring.json` gains a `policy.wrap` block for the airlock: allowTools / denyTools with `*` globs (deny wins), approveTools that run only while a `qring mcp approve <tool>` grant is live, a global rateLimit plus per-tool toolRateLimits (sliding windows), and a redactResults switch; denied tools are hidden from tools/list and refused with a policy_deny audit event, and an unparseable policy fails closed like the rest of the engine
- addedAirlock v2 - `qring mcp wrap` now proxies resources (list, templates, read, subscribe) and prompts (list, get), relays list_changed / updated notifications, advertises exactly the capabilities the wrapped server has, wraps remote Streamable HTTP servers with `--url` (`--header`, `--auth-secret KEY` for a Bearer token read from q-ring), and scrubs known secret values from every tool result, resource, and prompt before they reach the transcript (`--no-redact` or `wrap.redactResults: false` opts out)
- addedCanary v2 - honeytokens imitate thirteen real issuer shapes (aws, aws-secret, github, github-pat, openai, openai-project, anthropic, stripe, gitlab, slack, google, npm, generic); trips can page you via `qring canary alert add --discord | --slack | --ntfy | --url` webhook channels, SSRF-guarded, throttled with the desktop alert and never carrying the fake value; `qring canary plant KEY --push github` (or vercel / cloudflare) plants a tripwire and seeds it into a deployment platform's secrets without reading it back
- addedAgent session timeline - audit events folded into one timeline per MCP client process (pid + clientInfo label) and per airlock session (correlation id, wrapped command as the label), with window, event and denial counts, and key names touched - never values; surfaced as an expandable Agent Sessions (24h) dashboard card, `qring audit:sessions [--agent] [--since 24h|7d] [-v]`, and two MCP resources, `qring://sessions` and `qring://sessions/{id}`, which strip canary trips and go dark when policy denies the audit_log tool
- changed`qring mcp wrap` no longer requires a tools capability on the wrapped server - at least one of tools, resources, or prompts is enough
v0.17.0
2026-09-11
4 changes- addedMCP tool annotations - all 44 tools advertise readOnlyHint, destructiveHint, idempotentHint, and openWorldHint from a single table, so hosts can auto-approve the 27 read-only tools and confirm destructive or open-world ones (delete_secret, rotate_secret, exec_with_secrets, …); a test lists tools over a real MCP session and checks every tool carries every hint
- fixedClaude Code plugin always-on rules - a plugin install never loaded the plugin-root CLAUDE.md, so `/plugin install qring@q-ring` shipped commands, agents, and skills but none of the secret-hygiene rules; they now ship as a generated `secret-hygiene-rules` skill built from the Cursor rules, and the validator passes clean
- fixedREADME MCP config paths (Kiro reads `.kiro/settings/mcp.json`; Claude Code reads a project `.mcp.json`, not Claude Desktop's config) and keychain-first descriptions on npm, the MCP Registry, and the Cursor and Claude plugin manifests
- fixedSmithery listing now shows all 44 tools - bundles publish through the registry API with the server's live tools/list attached instead of the CLI, which could not carry tool schemas