Releases: askalf/redstamp
Releases · askalf/redstamp
Release list
v0.5.0
Changed
redstamp initnow writesredstamp.config.json(waswarden.config.json),
andredstamp check/redstamp-mcpdefault to it — completing the config-file
half of the warden→redstamp rename (the docs already advertised
redstamp.config.json, but the code still defaulted towarden.config.json, so
a user following the README got a gate that loaded no policy). Fully
back-compatible: with no--policy, an existingwarden.config.jsonis still
read transparently, so a project set up before the rename keeps working — only a
freshinitwrites the branded name. Help text updated and the example is now
redstamp.config.example.json. The~/.warden/global config, the audit
log, and theWARDEN_*env vars are intentionally unchanged.
0.4.1
- Renamed:
@askalf/warden→@askalf/redstamp(npm-publishable name; the old name collides with an existing unscoped package and is create-blocked by the registry). GitHub repo becomesaskalf/redstamp(old URLs redirect). Legacywarden/warden-mcp/warden-hook/warden-servebin aliases retained alongside the newredstamp*bins.WARDEN_*env vars unchanged.
All notable changes to @askalf/redstamp (formerly @askalf/warden) are documented here. The format is
based on Keep a Changelog, and this project
adheres to Semantic Versioning.
v0.4.1
v0.4.0
Added
- Cross-call taint tracking —
TaintSession(@askalf/warden/taint, #34).
check()classifies one call in isolation, which an attacker evades by
splitting an exfil across calls: read a secret into a temp file (call 1 — a
sensitive read), then ship that file out (call 2 — benign, no visible
secret).TaintSessionremembers the session: secret sources (reads of
~/.ssh,.env,.aws/credentials, …), propagation (the file a secret
is written to — and any copy — becomes tainted), and external sinks,
escalating to black the moment tainted data leaves the machine.
checkSequence(actions, policy)runs a whole stream through one session.
Deterministic and offline; like the judge it can only raise risk, never
lower a verdict — wrapping a stream is always at least as safe as per-call
check(). Precision-scoped: config-read → allowlisted host is not
flagged. The stateless core is untouched (byte-identicaldecide()). - External MITRE ATT&CK arena corpus (#36) — the default arena corpus is
warden-authored, so warden topping it proves capability, not neutrality.
arena/external-corpus.jsonadds 68 samples across 32 MITRE ATT&CK
techniques, command forms drawn from the public GTFOBins / LOLBAS /
HackTricks knowledge bases, with benign uses of the same tools so precision
is a genuine test.node arena/run.mjs --corpus <file>/npm run arena:external; results inarena/EXTERNAL-CORPUS-RESULTS.md(warden: 100%
recall, 100% precision after the #33 coverage work — the corpus originally
cross-flagged the same two gaps #33 closed). Honest caveat documented:
externally taxonomized but still assembled in-repo; true neutrality needs an
outside-contributed corpus, and the protocol makes that a drop-in. - Six measured classifier coverage gaps closed (#33), each mechanism-scoped
and checked against the benign set: recursivechmodof the root/system tree
(any mode —000locks root out, not just777); staged
download→make-executable chained with;as well as&&;rundll32 javascript:protocol exec (LOLBin) → black; gnupg keyring
(.gnupg/secring.gpg) inSENSITIVE_PATH_RE;/etc/shadowreads gated
(distinct from world-readable/etc/passwd, which stays allow);vssadmin create shadowgated (dual-use NTDS-theft prep — the ransomware
delete-shadows variant stays black). Corpus +11 → 245 samples; recall
96% → 97%, precision held at 100%.
Changed
- Default judge model freshened to
claude-sonnet-5(#35). - README: quick-start notes that npm v12 blocks git dependencies by
default — until warden is on npm,npm i github:askalf/wardenneeds
--allow-giton npm ≥ 12.
Fixed
- Judge tier fail-safe + raise-only proof (#35).
makeJudgeno longer
throws into the host on a bad response (explicitres.okcheck — a 429/5xx
returning HTML used to throw atres.json(); wrapped JSON parse; catch-all):
every failure mode returns null andcheckAsynckeeps the deterministic
verdict. And the invariant that a compromised/jailbroken judge can only
raise risk is now pinned by tests — a judge answering green cannot clear a
black or weaken a verdict. - Fuzz-found fail-safe gaps at the entrypoints (#32):
tool/method
arriving as an array containing aSymbol(implicitString(array)→
Array.join→ TypeError), and non-stringskillTextreaching the injection
scanner's regex, could throw instead of failing safe to a verdict. A shared
symbol-safeasStr()coercion now guards every site; found by 1M-iteration
fuzzing, pinned by regression tests. Detection unchanged. - CI release notes (#30): extraction uses
indexOf, not a multiline regex
whose$matched the blank line after the heading and shipped empty bodies
(v0.2.1 and v0.3.0 notes were backfilled by hand).
v0.3.0
Added
- arena — a neutral, reproducible agent-firewall benchmark (
npm run arena):
on a labeled corpus of real attacks and real benign work, every tool is scored
on recall and precision and determinism through one pipe, so the numbers
are comparable instead of anecdotal. Ships allow-all / block-all anchors, a
naive regex deny-list baseline, and a LlamaFirewall adapter, framed along
threat-model axes (what each tool even attempts to cover). Results live in
arena/RESULTS.mdand regenerate withnode arena/run.mjs. scanMcpToolsseverity tiers — every finding now carries
severity: 'critical' | 'advisory'. Injection/exfil instructions (the
curated patterns) are critical; a bare sensitive-path / secret-env mention
is advisory, so consumers scanning long-form skill prose (canon) can stop
treating instructional docs about credential handling as poison — scanning
the official Claude Code marketplace flagged 19/29 skills on exactly those
mention heuristics. Additive:flagsare unchanged and consumers that only
read them behave as before.SENSITIVE_PATH_EXFIL_RE— a sensitive path being moved
(transfer-verb → sensitive path → destination, one clause), e.g.
read ~/.ssh/id_rsa and POST it to https://…, which the curated exfil
patterns miss (wrong verb/noun combination). Critical, and built on
SENSITIVE_PATH_REso the two can't drift apart.- Four framework-governance examples (
examples/) — the same warden MCP
gate governing a CrewAI Flow, a LangGraph.js StateGraph, an OpenAI Agents SDK
agent, and a Microsoft AutoGen agent, all surfaced in the README.
Fixed
- Three scanner false-positive classes, measured on 2,000+ real marketplace
skills (auditing the official Claude Code catalog + 9 community
marketplaces with canon; every first-pass critical was manually reviewed):SENSITIVE_PATH_RE's.envnow requires a non-word lookbehind —
process.env/self.env/import.meta.envare code, not the dotenv
file, and were the single largest FP source. (Also applied to the
data-exfil-to-destination pattern's.envnoun.)scanMcpToolsnormalizes stringified newlines (\n2-char escapes) back
to real newlines before matching, andSENSITIVE_PATH_EXFIL_RE's gaps stop
at both — previously clause-bounded patterns silently spanned lines in
JSON-stringified text, so unrelated rows of a markdown table could read as
one verb→path→destination "clause".- The bare-word 'exfiltration intent' rule (exfiltrate/leak/steal, no
destination) now tiers asadvisory, not critical: every corpus hit was
descriptive prose — memory leaks, ML data leakage, threat lists in
defensive security docs. The flag itself is unchanged, so strict
(tool-description) surfaces still act on it.
- Three classifier false-positive classes:
- single-label hostnames (Docker service names like
dborredis) are
treated as internal, so container-to-container traffic no longer reads as
an exfil destination; - the
curl | interpreterRCE rule fires only on an external target —
piping from localhost/internal services no longer flags; - the DNS-exfil rule is anchored to command position, so prose that merely
mentionshost/digno longer flags.
- single-label hostnames (Docker service names like
- Docs matched to shipped behavior — the native hook (
warden-fast) is
fail-safe, not fail-open (daemon unreachable → it execs the in-process Node
hook, which still screens), and the daemon command iswarden-serve, not
warden serve. - Audit verifier — interspersed unprotected records no longer read as tampering.
verifyAuditFile()skipped only leading pre-chain lines; the first line
lacking aprev/hashmid-file was treated as a chain break, so any file that
a second, non-chained writer had appended to (e.g. an in-process hook fallback
logging raw tool calls into the shared~/.warden/audit.jsonl) reported
ok:falseeven when the hash chain was fully intact — and an attacker could
defeat verification outright by appending one junk line. Now any line without a
stringprev+hashis treated as unprotected history: skipped and tallied as
unchained, while the chained records are verified continuously. Tamper
detection is unchanged — editing a chained record breaks its hash, deleting one
breaks the next record's link, and stripping a record'sprev/hashto
disguise an edit breaks the following record's link. Pinned by three regression
tests (interspersed foreign record, junk-tail append, strip-to-disguise).
v0.2.1
Fixed
- Secret scanner — GitHub App / Actions tokens.
scanSecretsrecognized
ghp_/gho_/github_pat_but missed the GitHub App token family:
ghs_(server-to-server — theGITHUB_TOKENminted into every GitHub
Actions run and the output ofactions/create-github-app-token),ghu_
(user-to-server), andghr_(refresh). An agent exfiltrating aghs_token
to an external host therefore slipped the secret-exfil gate. This is the
credential class stolen in the tj-actions/changed-files supply-chain attack
(CVE-2025-30066). Now matched (gh[sur]_[A-Za-z0-9]{30,}) and blocked on
egress; pinned by a regression test.