<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>owlwarden releases</title>
  <subtitle>Local security checks for Node apps and coding-agent configuration.</subtitle>
  <link href="https://suthat.github.io/owlwarden/changelog/feed.xml" rel="self"/>
  <link href="https://suthat.github.io/owlwarden/changelog/"/>
  <id>https://suthat.github.io/owlwarden/changelog/</id>
  <updated>2026-09-01T00:00:00Z</updated>
  <entry>
    <title>owlwarden 1.3.0</title>
    <link href="https://suthat.github.io/owlwarden/changelog/#1-3-0"/>
    <id>https://suthat.github.io/owlwarden/changelog/#1-3-0</id>
    <updated>2026-09-01T00:00:00Z</updated>
    <content type="html">&lt;p&gt;1.2 answered &lt;em&gt;which of these findings matters&lt;/em&gt;. This release answers the question a developer actually asks, dozens of times an hour: &lt;strong&gt;which of these did I just do?&lt;/strong&gt; (&lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/docs/adr/0032-turn-verdict.md&quot;&gt;ADR 0032&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No new rules, no new frameworks, no new agent hosts, and one new command.&lt;/strong&gt; The catalogue is still 25 rules, the matrix is still 16 frameworks and 7 hosts, and neither was the binding constraint. A finding you introduced thirty seconds ago and a finding you inherited from a repository&amp;#39;s first year are not the same object, and nothing in the tool could tell them apart.&lt;/p&gt;
&lt;h3&gt;Added&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden turn&lt;/code&gt;&lt;/strong&gt; - scans the files this turn touched, scans the same files at the base commit, and diffs the two. Findings are &lt;code&gt;introduced&lt;/code&gt;, &lt;code&gt;carried&lt;/code&gt;, or &lt;code&gt;fixed&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Carried findings never fail a turn, at any threshold.&lt;/strong&gt; There is no flag that changes it and no code path that could grow one: the gate is applied to the introduced set before it is consulted. A control that blocks on debt the turn did not create is a control that gets removed on the second day, and everything it would have caught goes with it.&lt;/p&gt;
&lt;p&gt;Everything introduced is &lt;em&gt;reported&lt;/em&gt; whether or not it blocks - a turn that adds a medium under a &lt;code&gt;high&lt;/code&gt; gate reads &lt;code&gt;clean at high - 1 introduced below the bar, shown anyway&lt;/code&gt;. &lt;code&gt;blocking&lt;/code&gt; is a separate field from &lt;code&gt;counts.introduced&lt;/code&gt; precisely so the verdict can never print &amp;quot;nothing introduced&amp;quot; over something the turn introduced.&lt;/p&gt;
&lt;pre class=&quot;code&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;  ◉ᴥ◉ turn / 1 file / since HEAD a8a6b93 / 0.16s
  ✘ blocked - 1 introduced at or above high
    1 carried (already at HEAD a8a6b93, not this turn&amp;#39;s)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;--base &amp;lt;REF&amp;gt;&lt;/code&gt;, &lt;code&gt;--record&lt;/code&gt;, &lt;code&gt;--hook &amp;lt;HOST&amp;gt;&lt;/code&gt;, &lt;code&gt;--fail-on&lt;/code&gt;, &lt;code&gt;--fail-on-exposure&lt;/code&gt;, &lt;code&gt;--no-surface&lt;/code&gt;, &lt;code&gt;--format pretty|json&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;turn --hook claude-code | cursor | generic&lt;/code&gt;&lt;/strong&gt; - the verdict in a host&amp;#39;s own hook shape, through the same three adapters &lt;code&gt;gate&lt;/code&gt; uses rather than a fourth encoder. The reason the model receives names what is new &lt;em&gt;and&lt;/em&gt; says the rest were already there: an agent told &amp;quot;there are eighteen findings&amp;quot; triages a backlog nobody asked it to touch.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;init --claude-code&lt;/code&gt; and &lt;code&gt;init --cursor&lt;/code&gt; now wire the &lt;strong&gt;Stop&lt;/strong&gt; hook to &lt;code&gt;turn --hook &amp;lt;host&amp;gt; --record&lt;/code&gt; instead of &lt;code&gt;gate --host &amp;lt;host&amp;gt; --since HEAD&lt;/code&gt;. The per-edit and pre-command hooks are unchanged - &lt;code&gt;gate&lt;/code&gt; still owns those.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;turn --record&lt;/code&gt;&lt;/strong&gt; - one JSON line per turn in &lt;code&gt;.owlwarden/turns.jsonl&lt;/code&gt;, bounded at the last 200. Every field but the timestamp and the stopwatch is derived from the two reports and the base, so two runs over an unchanged tree produce identical records. Asserted on both sides of the language boundary.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden.turn&lt;/code&gt;&lt;/strong&gt; in the SDK: &lt;code&gt;turnReportSchema&lt;/code&gt; and its types, held to &lt;code&gt;fixtures/golden/turn.json&lt;/code&gt; by the same cross-language contract as the scan report. Neither declaration can move alone.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;owlwarden_core::baseline::fingerprints()&lt;/code&gt; - the correct way to fingerprint more than one finding at a time, now that a second caller needs the occurrence walk. &lt;code&gt;owlwarden_core::report::fails_gate()&lt;/code&gt; - the gate predicate, extracted so &lt;code&gt;scan&lt;/code&gt; and &lt;code&gt;turn&lt;/code&gt; cannot drift into two definitions of &amp;quot;bad enough to stop&amp;quot;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Changed&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden --help&lt;/code&gt; is 41 lines, down from 161.&lt;/strong&gt; It lists four commands - &lt;code&gt;turn&lt;/code&gt;, &lt;code&gt;scan&lt;/code&gt;, &lt;code&gt;vet&lt;/code&gt;, &lt;code&gt;init&lt;/code&gt; - and the flags a first run needs. &lt;code&gt;owlwarden help --all&lt;/code&gt; prints everything, and nothing was removed. The cost of the wall landed on exactly the wrong person: someone who has run &lt;code&gt;npx owlwarden&lt;/code&gt; once and is looking for the command that answers their question.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;--ascii&lt;/code&gt; now means ASCII.&lt;/strong&gt; The summary line every reader sees carried a literal &lt;code&gt;/&lt;/code&gt; in both modes, so a flag whose whole job is a claim about the character set did not keep it. &lt;code&gt;Glyphs&lt;/code&gt; gains &lt;code&gt;separator&lt;/code&gt; and &lt;code&gt;dash&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;seal::command::current_surface&lt;/code&gt; and &lt;code&gt;seal::ChangeKind::word()&lt;/code&gt; are public: the turn verdict reports the agent execution surface without taking or comparing a seal, because an unsealed repository still has hooks and a reader deciding whether to seal wants to know how many.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Security&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;wasmtime floored at 36.0.14&lt;/strong&gt;, up from 1.2&amp;#39;s 36.0.13, which picked up RUSTSEC-2026-0269 / &lt;a href=&quot;https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-vqjp-4c8c-hfgg&quot; rel=&quot;noopener&quot;&gt;GHSA-vqjp-4c8c-hfgg&lt;/a&gt; - a filesystem sandbox escape through trailing slashes in paths and symlinks - in the interval between the two releases. The plugin host is the one crate in this workspace whose entire job is containment, so an open sandbox-escape advisory in it is the failure &lt;code&gt;AGENTS.md&lt;/code&gt; names: &lt;em&gt;the tool must not become the vulnerability it hunts&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nothing owlwarden ships was exploitable through it without a loaded plugin, and plugins are opt-in, source-only, and refused under &lt;code&gt;--ci&lt;/code&gt; without &lt;code&gt;--allow-plugins&lt;/code&gt;. It is floored anyway. &lt;code&gt;cargo deny check&lt;/code&gt; found it on this branch, which is the job that check exists to do - the 40-test sandbox-escape suite passes on the new version unchanged.&lt;/p&gt;
&lt;h3&gt;Known issues&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;verify&lt;/code&gt; still compares findings by &lt;code&gt;rule@location&lt;/code&gt;, so a patch that shifts lines can report one finding resolved and one introduced. &lt;code&gt;turn&lt;/code&gt; uses the baseline fingerprint and does not have this bug. Named here rather than fixed quietly: &lt;code&gt;verify&lt;/code&gt; compares two states of one file seconds apart, where the case is rare.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;/benchmark/&lt;/code&gt; still publishes nothing. The harness, the corpus discipline, and the threshold gate shipped in 1.2 and are tested; labelling real repositories is judgement work that has not been done, and a number computed from our own fixtures would be worse than no number.&lt;/li&gt;
&lt;/ul&gt;</content>
  </entry>
  <entry>
    <title>owlwarden 1.2.0</title>
    <link href="https://suthat.github.io/owlwarden/changelog/#1-2-0"/>
    <id>https://suthat.github.io/owlwarden/changelog/#1-2-0</id>
    <updated>2026-08-30T00:00:00Z</updated>
    <content type="html">&lt;p&gt;1.1 answered &lt;em&gt;what is here&lt;/em&gt;. This release answers the three questions a person asks immediately afterwards: &lt;strong&gt;what changed, what is reachable, and how often are we wrong?&lt;/strong&gt; (&lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/docs/adr/0027-workspace-seal.md&quot;&gt;ADR 0027&lt;/a&gt;, &lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/docs/adr/0028-effective-configuration.md&quot;&gt;ADR 0028&lt;/a&gt;, &lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/docs/adr/0029-exposure-model.md&quot;&gt;ADR 0029&lt;/a&gt;, &lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/docs/adr/0030-published-benchmark.md&quot;&gt;ADR 0030&lt;/a&gt;, &lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/docs/adr/0031-runtime-overlay.md&quot;&gt;ADR 0031&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;No new rules. The existing 25 became substantially more useful without one.&lt;/p&gt;
&lt;h3&gt;Added&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;exposure&lt;/code&gt;, a third axis on every application finding&lt;/strong&gt; - &lt;code&gt;internet&lt;/code&gt;, &lt;code&gt;authenticated&lt;/code&gt;, &lt;code&gt;internal&lt;/code&gt;, &lt;code&gt;unknown&lt;/code&gt; - computed by the engine from route resolution and auth-gate recognition per framework.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;It fails loud: &lt;code&gt;authenticated&lt;/code&gt; requires a positively identified gate, and absence of evidence yields &lt;code&gt;internet&lt;/code&gt;.&lt;/strong&gt; A module that does not resolve is not a gate, a name that does not read as one is not a gate, a session call whose result is never checked is not a gate, and a &lt;code&gt;config.matcher&lt;/code&gt; we could not parse covers nothing rather than everything. Everywhere else in owlwarden uncertainty resolves downward; here it resolves upward, because a finding wrongly marked as behind auth is a finding somebody deprioritises.&lt;/p&gt;
&lt;p&gt;Report order becomes exposure → severity → confidence, &lt;code&gt;--fail-on-exposure&lt;/code&gt; composes with &lt;code&gt;--fail-on&lt;/code&gt; as an OR, the Markdown reporter groups by exposure, and the summary line states the distribution. Exposure never raises severity.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden seal&lt;/code&gt;&lt;/strong&gt; - &lt;code&gt;.owlwarden/surface.lock&lt;/code&gt;, a committed record of every file the agent loads out of the working tree by semantic digest, with hooks, MCP servers and their pins, the permission set, marketplace sources, and instruction files extracted rather than merely hashed. &lt;code&gt;settings.json changed&lt;/code&gt; is a message people re-run past; &lt;em&gt;a &lt;code&gt;SessionStart&lt;/code&gt; hook was added&lt;/em&gt; is not.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;--verify&lt;/code&gt;, &lt;code&gt;--diff&lt;/code&gt;, and repeatable &lt;code&gt;--accept &amp;lt;fp&amp;gt; --reason &amp;quot;…&amp;quot;&lt;/code&gt;. Reformatting a config does not break the seal; changing one character of a hook command does. Sealing is never unattended, and refuses to write while an unaccepted high finding sits on the surface.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden effective&lt;/code&gt;&lt;/strong&gt; - the resolved agent configuration with provenance per key. &lt;code&gt;git config --show-origin&lt;/code&gt; for your agent.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;runtime_scope: shadowed&lt;/code&gt;&lt;/strong&gt; - a project key a higher configuration tier overrides, capped at &lt;code&gt;possible&lt;/code&gt; and reported rather than suppressed. Only under &lt;code&gt;--include-user-config&lt;/code&gt;; without it nothing outside the project root is opened and behaviour is identical to 1.1.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Runtime as an overlay.&lt;/strong&gt; Findings carry the runtime their file runs on, resolved per file, and five rules carry deltas where their base fix would not execute. The build asserts that the un-deltaed fix does not name an absent API, so the &lt;em&gt;absence&lt;/em&gt; of a delta is a positive claim.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Four frameworks, taking the matrix to sixteen&lt;/strong&gt;: SvelteKit, TanStack Start, SolidStart, Elysia - each with the full square fixture matrix and 13 remediation cells.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden bench&lt;/code&gt;&lt;/strong&gt; - precision, recall, per-rule false positives with their locations, and &lt;code&gt;authenticated&lt;/code&gt; precision published separately. The corpus discipline is enforced on load. &lt;strong&gt;The corpus is empty and the tooling says so&lt;/strong&gt;; &lt;code&gt;/benchmark/&lt;/code&gt; is not published until real repositories are labelled.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;owlwarden coverage [PATH]&lt;/code&gt; adds the exposure distribution, the unclassified rate, and the seal state.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
  &lt;li&gt;The Action gains &lt;code&gt;seal&lt;/code&gt;, &lt;code&gt;require-signed-seal&lt;/code&gt;, and &lt;code&gt;fail-on-exposure&lt;/code&gt;. The surface diff goes to the job summary and a &lt;code&gt;seal-diff&lt;/code&gt; output, silent when the surface has not moved.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Changed&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;open-redirect&lt;/code&gt; now recognises status-first &lt;code&gt;redirect(302, to)&lt;/code&gt;, status-last &lt;code&gt;Response.redirect(to, 302)&lt;/code&gt;, and &lt;code&gt;set.headers.Location = to&lt;/code&gt;. It was blind to all three, which made it blind to SvelteKit and Elysia redirects.&lt;/li&gt;
  &lt;li&gt;Sails gained route resolution; SolidStart&amp;#39;s profile now names &lt;code&gt;src/middleware.ts&lt;/code&gt;, where its headers are actually set.&lt;/li&gt;
  &lt;li&gt;Report JSON gains &lt;code&gt;exposure&lt;/code&gt;, &lt;code&gt;exposureEvidence&lt;/code&gt;, &lt;code&gt;runtime&lt;/code&gt;, &lt;code&gt;runtimeSource&lt;/code&gt;, and &lt;code&gt;exposureSummary&lt;/code&gt;; &lt;code&gt;target.runtime&lt;/code&gt; states the runtime and whether it was detected or defaulted. All additive - a 1.1 consumer is unaffected.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Security&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Gate evidence is clamped at one choke point. A module specifier is a string literal that can hold a newline or a bidirectional override, it resolves on any Unix filesystem, and it previously reached a terminal, a SARIF result, and a pull-request comment unclamped.&lt;/li&gt;
  &lt;li&gt;49 adversarial tests across the seal, tier resolution, the exposure classifier, and the benchmark. &lt;code&gt;SECURITY.md&lt;/code&gt; carries the seal&amp;#39;s limits - a detection and review control, not a containment one - and the exposure axis&amp;#39;s loud-direction rule.&lt;/li&gt;
&lt;/ul&gt;</content>
  </entry>
  <entry>
    <title>owlwarden 1.1.0</title>
    <link href="https://suthat.github.io/owlwarden/changelog/#1-1-0"/>
    <id>https://suthat.github.io/owlwarden/changelog/#1-1-0</id>
    <updated>2026-08-26T00:00:00Z</updated>
    <content type="html">&lt;p&gt;A second scan surface and a control that always runs (&lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/docs/adr/0025-agent-surface-and-supply-chain.md&quot;&gt;ADR 0025&lt;/a&gt;, &lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/docs/adr/0026-deterministic-agent-gate.md&quot;&gt;ADR 0026&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;owlwarden answered one question: &lt;em&gt;is the web application in this repository written safely?&lt;/em&gt; This release adds the second question the same repository now raises: &lt;em&gt;is the coding agent that works in it being told to do something hostile?&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Added&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Eleven rules on a new &lt;code&gt;agentWorkspace&lt;/code&gt; surface&lt;/strong&gt;, reading the agent and editor configuration a lockfile does not record. All cap at &lt;code&gt;likely&lt;/code&gt;, all carry a &lt;code&gt;runtimeScope&lt;/code&gt;, all map to CWE with OWASP ASI 2026 as a secondary reference: &lt;code&gt;agent-hook-autoexec&lt;/code&gt;, &lt;code&gt;agent-hook-untrusted-command&lt;/code&gt;, &lt;code&gt;agent-config-loader-script&lt;/code&gt;, &lt;code&gt;agent-config-env-redirect&lt;/code&gt;, &lt;code&gt;agent-config-secret-reachable&lt;/code&gt;, &lt;code&gt;agent-permission-wildcard&lt;/code&gt;, &lt;code&gt;agent-mcp-unpinned-remote&lt;/code&gt;, &lt;code&gt;agent-marketplace-untrusted&lt;/code&gt;, &lt;code&gt;agent-instructions-hidden-text&lt;/code&gt;, &lt;code&gt;agent-instructions-directive&lt;/code&gt;, and &lt;code&gt;install-lifecycle-script&lt;/code&gt; (which is &lt;code&gt;webApp&lt;/code&gt;, because it reads &lt;code&gt;package.json&lt;/code&gt;, and is the one with an OWASP Top 10 mapping - A08).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;Surface&lt;/code&gt;&lt;/strong&gt;, and the remediation matrix generalised over it. A &lt;code&gt;webApp&lt;/code&gt; rule owes twelve framework fixes; an &lt;code&gt;agentWorkspace&lt;/code&gt; rule owes seven agent host fixes; neither is checked against the other&amp;#39;s list, and a missing cell still fails the build.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden vet &amp;lt;path&amp;gt;&lt;/code&gt;&lt;/strong&gt; - the same engine with a fixed posture for a repository you did not write: agent rules only, offline, no plugins, and the target&amp;#39;s own config, baseline, and suppressions counted rather than honoured.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden gate --host &amp;lt;claude-code|cursor|generic&amp;gt;&lt;/code&gt;&lt;/strong&gt; - the hook entry point. Reads the host&amp;#39;s event on stdin, scans what it names, and returns a verdict in the host&amp;#39;s own shape. Fails closed before a command executes and open after an edit, because those have different consequences.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden verify --patch &amp;lt;file&amp;gt;&lt;/code&gt;&lt;/strong&gt; - applies a patch to a scratch copy, re-scans, and exits 0 only if the finding is gone &lt;em&gt;and&lt;/em&gt; nothing new appeared at or above the threshold.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;--since &amp;lt;ref&amp;gt;&lt;/code&gt; / &lt;code&gt;--staged&lt;/code&gt; / &lt;code&gt;--paths&lt;/code&gt;&lt;/strong&gt; - diff-scoped scanning. Project rules declare their own inputs, so a &lt;code&gt;package.json&lt;/code&gt;-only commit still fires the dependency rules. The scope is stated in every output format.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;--format agent&lt;/code&gt;&lt;/strong&gt; - the report on a token budget (default ~1500), with explicit truncation. Omits &lt;code&gt;why&lt;/code&gt;, which is written for a human.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden init --claude-code | --cursor | --generic&lt;/code&gt;&lt;/strong&gt; - wires the gate into a host&amp;#39;s lifecycle events.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;agent-surface&lt;/code&gt; preset&lt;/strong&gt;, &lt;code&gt;runtimeScope&lt;/code&gt; in every format, an ASI coverage table in &lt;code&gt;owlwarden coverage&lt;/code&gt; and &lt;code&gt;RULES.md&lt;/code&gt;, and the agent path allowlist in the coverage output so a reader can tell whether their host is in scope.&lt;/li&gt;
  &lt;li&gt;A generated documentation site: 211 pages, including one per (rule, framework) and (rule, agent host) cell that has a verified example.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Changed&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;honorSuppressions: boolean&lt;/code&gt; became a three-state &lt;code&gt;SuppressionPolicy&lt;/code&gt;. The gate needs &amp;quot;honour what the team committed, refuse what appeared during this session&amp;quot;, and a boolean had nowhere to put it.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;Report.target&lt;/code&gt; gained &lt;code&gt;configFilesScanned&lt;/code&gt; and &lt;code&gt;diffScope&lt;/code&gt;. A &lt;code&gt;vet&lt;/code&gt; reporting &amp;quot;0 files&amp;quot; over fourteen findings was describing the wrong number, and a diff-scoped clean result must never render as a clean repository.&lt;/li&gt;
  &lt;li&gt;The npm &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;keywords&lt;/code&gt;, &lt;code&gt;homepage&lt;/code&gt;, &lt;code&gt;funding&lt;/code&gt;, &lt;code&gt;license&lt;/code&gt;, and &lt;code&gt;publishConfig.provenance&lt;/code&gt; were rewritten for the registry&amp;#39;s own ranking inputs. The site URL now lives in one file, &lt;code&gt;site.url&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;owlwarden init&lt;/code&gt; with no flags is unchanged; the host flags are additive.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;core::agent_text&lt;/code&gt; is now &lt;code&gt;core::untrusted_text&lt;/code&gt;. It was named for the reader it was written for; the terminal turned out to be a fourth reader with the same requirement, and a module named &lt;code&gt;agent_text&lt;/code&gt; sanitising a human&amp;#39;s terminal is a name that lies to the next person.&lt;/li&gt;
  &lt;li&gt;The GitHub Action gained &lt;code&gt;preset&lt;/code&gt; and &lt;code&gt;since&lt;/code&gt; inputs. Without &lt;code&gt;preset&lt;/code&gt; the agent surface was unreachable from CI at all; &lt;code&gt;since&lt;/code&gt; was already in the README&amp;#39;s snippet and had never existed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Fixed&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;A &lt;code&gt;--since&lt;/code&gt; that could not resolve widened the scan instead of failing it.&lt;/strong&gt; It printed a note and scanned the whole project. On a shallow CI clone - which is what &lt;code&gt;actions/checkout&lt;/code&gt; gives you by default - that is the difference between three new findings and a red job full of debt the change did not introduce. &lt;code&gt;scan&lt;/code&gt; now exits &lt;code&gt;2&lt;/code&gt; and names &lt;code&gt;fetch-depth: 0&lt;/code&gt;; &lt;code&gt;gate&lt;/code&gt; still degrades, because a hook that bricks a session over a git hiccup gets uninstalled. A range (&lt;code&gt;main..HEAD&lt;/code&gt;) is refused rather than passed through: it would widen the scope a narrowing flag was asked to cut.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;A &lt;code&gt;--since&lt;/code&gt; value beginning with &lt;code&gt;-&lt;/code&gt; reached git as an option.&lt;/strong&gt; The ref sits in front of &lt;code&gt;git diff&lt;/code&gt;&amp;#39;s trailing &lt;code&gt;--&lt;/code&gt;, which separates paths from revisions and not options from anything, so &lt;code&gt;--since --output=&amp;lt;file&amp;gt;&lt;/code&gt; was argv git parsed - and &lt;code&gt;git diff --output=&lt;/code&gt; writes where it is pointed. The invocation passes &lt;code&gt;--end-of-options&lt;/code&gt; now, and the value is validated before it gets there.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;--report-suppressions&lt;/code&gt; printed repository text straight to the terminal.&lt;/strong&gt; The reason on a suppression is a comment somebody wrote, and the listing exists so a reviewer can audit what a tree has silenced. A reason containing &lt;code&gt;\x1b[2K\x1b[1A\x1b[2K&lt;/code&gt; clears its own line, moves up, and clears the entry above it - deleting a line from the audit, from inside the audit. Bidi overrides made a reason render as its opposite. Both output paths now render the reason, the path, and the rule id as data.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;The two sanitisers had drifted, in both directions.&lt;/strong&gt; &lt;code&gt;agent-safety.ts&lt;/code&gt; stripped the Unicode Tags block (U+E0000-E007F, which mirrors ASCII into zero-width code points, and is the channel current prompt-injection work actually uses) and &lt;code&gt;core&lt;/code&gt; did not, because they are category &lt;code&gt;Cf&lt;/code&gt; and &lt;code&gt;char::is_control&lt;/code&gt; only covers &lt;code&gt;Cc&lt;/code&gt;. Going the other way, &lt;code&gt;agent-safety.ts&lt;/code&gt; replaced &lt;code&gt;[INST]&lt;/code&gt; with the label &lt;code&gt;&amp;quot;[INST]&amp;quot;&lt;/code&gt; - the marker spelled exactly as it arrived - so that substitution ran on every MCP payload and changed nothing. &lt;code&gt;fixtures/untrusted-text-vectors.json&lt;/code&gt; now owns the list and both sides are tested against it.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;A plugin could supply the key that vouched for it.&lt;/strong&gt; Trust roots were read from the plugin&amp;#39;s own directory and from that directory&amp;#39;s parent - both inside the artifact being verified. Generate a key, sign the artifact, ship the public half beside the signature, and &lt;code&gt;--require-signed-plugins&lt;/code&gt; reported &lt;code&gt;verified&lt;/code&gt;. It refused nothing. Trust roots now come from &lt;code&gt;OWLWARDEN_PLUGIN_TRUST&lt;/code&gt; and from &lt;code&gt;.owlwarden/plugin-trust.json&lt;/code&gt; in the scan root, which is what &lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/docs/adr/0021-plugin-artifact-signing.md&quot;&gt;ADR 0021&lt;/a&gt; said in the first place.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden plugin inspect&lt;/code&gt; could never report &lt;code&gt;verified&lt;/code&gt;.&lt;/strong&gt; The TypeScript mirror imported public keys with &lt;code&gt;createPublicKey({ format: &amp;quot;raw&amp;quot; })&lt;/code&gt;, which Node rejects; a cast silenced the type error that said so. Every key threw, every key became &lt;code&gt;undefined&lt;/code&gt;, and every signature - valid or not - reported &lt;code&gt;untrusted&lt;/code&gt;. Failing in the safe direction is why it went a release unnoticed: an always-&lt;code&gt;untrusted&lt;/code&gt; line is indistinguishable from an unconfigured trust root. Keys are wrapped as SPKI DER now, and both implementations verify a shared test vector that neither of them generates.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;An unknown config key is refused rather than stripped.&lt;/strong&gt; zod drops unknown keys by default, so &lt;code&gt;failon: &amp;quot;high&amp;quot;&lt;/code&gt; parsed cleanly and the run used the default &lt;code&gt;info&lt;/code&gt; - a config that reads as if it tightens the gate, does not, and prints nothing either way. Both objects in the schema are strict now, and a near miss names the key it was probably meant to be.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;A symlinked config is reported instead of ignored in silence.&lt;/strong&gt; Not following it is right - a link is how a hostile tree points config resolution outside itself - but &lt;code&gt;owlwarden.config.json -&amp;gt; ../shared/config.json&lt;/code&gt; is an ordinary monorepo layout, and its author had no way to learn their &lt;code&gt;preset&lt;/code&gt; never applied.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;The GitHub Action refused every invocation it was ever given.&lt;/strong&gt; A guard written as &lt;code&gt;[[ &amp;quot;$value&amp;quot; == &lt;em&gt;$&amp;#39;\0&amp;#39;&lt;/em&gt; ]]&lt;/code&gt; was meant to reject NUL bytes; bash cannot hold a NUL in a string, so &lt;code&gt;$&amp;#39;\0&amp;#39;&lt;/code&gt; is the empty string and the pattern is &lt;code&gt;**&lt;/code&gt;. Every input matched, and the Action exited 2 before running anything. It shipped in 1.0 and nothing caught it, because nothing executed the Action: the &amp;quot;action smoke&amp;quot; workflow re-implements its command line rather than calling it. The check is gone - a NUL cannot reach a shell variable through &lt;code&gt;execve&lt;/code&gt; either - and the script now has 63 tests that run it with a stubbed CLI and assert on the argv it produces.&lt;/li&gt;
  &lt;li&gt;The Action snippets in both READMEs and on the site pointed at &lt;code&gt;suthat/owlwarden@v1&lt;/code&gt;, where there is no &lt;code&gt;action.yml&lt;/code&gt;. A check now validates every documented snippet against the Action&amp;#39;s real path and real inputs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Security&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden init&lt;/code&gt; never writes a &lt;code&gt;SessionStart&lt;/code&gt; hook&lt;/strong&gt;, and its MCP entry is &lt;code&gt;node_modules/.bin/owlwarden&lt;/code&gt; rather than &lt;code&gt;npx -y&lt;/code&gt;. Those are the two shapes &lt;code&gt;agent-hook-autoexec&lt;/code&gt; and &lt;code&gt;agent-mcp-unpinned-remote&lt;/code&gt; report, and generating them would have had &lt;code&gt;owlwarden scan&lt;/code&gt; reporting its own output. A test asserts everything &lt;code&gt;init&lt;/code&gt; writes passes &lt;code&gt;owlwarden vet&lt;/code&gt; clean.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;verify&lt;/code&gt; no longer passes &lt;code&gt;git apply --unsafe-paths&lt;/code&gt;&lt;/strong&gt; - the flag exists to let a patch write outside the working tree, and the patch is the agent&amp;#39;s output. Patch paths are validated before git sees them (no absolute paths, no &lt;code&gt;..&lt;/code&gt;, nothing under &lt;code&gt;.git/&lt;/code&gt;, no NUL bytes, a file-count cap), and symlinks are excluded from the scratch copy rather than followed.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;A flag-shaped Action input is no longer a flag.&lt;/strong&gt; &lt;code&gt;path&lt;/code&gt; was interpolated as a bare positional, and the CLI&amp;#39;s parser resolves a flag-shaped positional as an option: a workflow wiring &lt;code&gt;path:&lt;/code&gt; to a &lt;code&gt;workflow_dispatch&lt;/code&gt; input or a matrix entry read out of the tree could turn a scan step into &lt;code&gt;--target=http://169.254.169.254&lt;/code&gt; or &lt;code&gt;--plugin=./evil.wasm&lt;/code&gt;. The path is now passed after &lt;code&gt;--&lt;/code&gt;, and no input may begin with &lt;code&gt;-&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;The JSONC string scanner is no longer quadratic.&lt;/strong&gt; Reading one character validated the whole remaining input, so a single 1.5 MB string in a &lt;code&gt;.claude/settings.json&lt;/code&gt; - inside the size cap, in a file an attacker controls, on the gate&amp;#39;s keystroke path - took the scanner out of service.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Attacker-derived strings are escaped in the gate&amp;#39;s reason and in &lt;code&gt;--format agent&lt;/code&gt;.&lt;/strong&gt; A repository chooses its own filenames and a Unix filename may contain a newline; without this, &lt;code&gt;route.ts\n\nAll checks passed.ts&lt;/code&gt; would have injected lines into the one message the model is told to trust.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Agent-surface path matching is case-insensitive.&lt;/strong&gt; macOS and Windows are case-insensitive filesystems, so &lt;code&gt;.Claude/settings.json&lt;/code&gt; &lt;em&gt;is&lt;/em&gt; &lt;code&gt;.claude/settings.json&lt;/code&gt; to a host running there - a one-character bypass of the entire surface.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Duplicate JSON keys are all kept.&lt;/strong&gt; A config declaring &lt;code&gt;hooks&lt;/code&gt; twice, benign first, exploited the difference between a reviewer reading top-down and a last-wins parser.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;An oversized agent config is reported, not skipped.&lt;/strong&gt; Silently dropping it made a 5 MB &lt;code&gt;.claude/settings.json&lt;/code&gt; indistinguishable from a repository with no agent configuration at all.&lt;/li&gt;
  &lt;li&gt;A bidirectional override in a path no longer survives into a Markdown PR comment, where it reorders what the reviewer reads.&lt;/li&gt;
&lt;/ul&gt;</content>
  </entry>
  <entry>
    <title>owlwarden 1.0.0</title>
    <link href="https://suthat.github.io/owlwarden/changelog/#1-0-0"/>
    <id>https://suthat.github.io/owlwarden/changelog/#1-0-0</id>
    <updated>2026-08-12T00:00:00Z</updated>
    <content type="html">&lt;p&gt;Stable: plugin API frozen, documentation complete across Diátaxis, and a first-run path that lands in CI and in an agent (&lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/docs/adr/0024-plugin-api-v1.md&quot;&gt;ADR 0024&lt;/a&gt;).&lt;/p&gt;
&lt;h3&gt;Added&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden init&lt;/code&gt;&lt;/strong&gt; - no flags writes the adoption kit: agent-rules, a GitHub Action workflow, and &lt;code&gt;.cursor/mcp.json&lt;/code&gt;. &lt;code&gt;--agent-rules&lt;/code&gt; / &lt;code&gt;--workflow&lt;/code&gt; / &lt;code&gt;--mcp&lt;/code&gt; select a subset; &lt;code&gt;--force&lt;/code&gt; replaces foreign files.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;--format md&lt;/code&gt;&lt;/strong&gt; - Markdown grouped by severity, for PR comments. Repeatable with other formats (ADR 0022). GitHub Action accepts &lt;code&gt;md&lt;/code&gt; as well.&lt;/li&gt;
  &lt;li&gt;Tutorials, CLI and plugin-API reference, upgrade guide, comparison, RFC process, maintainer discoverability checklist. MCP registry descriptor at &lt;code&gt;mcp/server.json&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Changed&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Version &lt;strong&gt;1.0.0&lt;/strong&gt;. Plugin &lt;code&gt;schemaVersion: 1&lt;/code&gt; is frozen; breaking plugin-API changes go through &lt;code&gt;docs/rfc/&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Cosign/SBOM GitHub artifacts and &lt;code&gt;cargo-fuzz&lt;/code&gt; stay later - npm provenance and &lt;code&gt;cargo-deny&lt;/code&gt; already gate releases.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Security&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;--format md&lt;/code&gt; flattens newlines in untrusted prose (title, why, route, method) and lengthens snippet fences when source contains backticks, so a hostile tree cannot open a fake heading or break out of a code block in a PR comment.&lt;/li&gt;
&lt;/ul&gt;</content>
  </entry>
  <entry>
    <title>owlwarden 0.5.0</title>
    <link href="https://suthat.github.io/owlwarden/changelog/#0-5-0"/>
    <id>https://suthat.github.io/owlwarden/changelog/#0-5-0</id>
    <updated>2026-08-11T00:00:00Z</updated>
    <content type="html">&lt;p&gt;Depth beyond CI: first active detector, offline OSV index, plugin integrity, stackable formats, incremental watch (&lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/docs/adr/0019-first-party-active-detector.md&quot;&gt;ADR 0019&lt;/a&gt;-&lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/docs/adr/0023-incremental-watch.md&quot;&gt;0023&lt;/a&gt;).&lt;/p&gt;
&lt;h3&gt;Added&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;csrf-cross-origin-post&lt;/code&gt;&lt;/strong&gt; - canary cross-origin POST behind &lt;code&gt;--allow-active&lt;/code&gt; (ADR 0019). Staging only.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden osv update&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;--osv-db&lt;/code&gt;&lt;/strong&gt;, fail-closed &lt;strong&gt;&lt;code&gt;--osv --offline&lt;/code&gt;&lt;/strong&gt; (ADR 0020). Index is lockfile-scoped; not bundled in npm.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Plugin &lt;code&gt;artifact.sha256&lt;/code&gt;&lt;/strong&gt;, optional ed25519 &lt;code&gt;.sig&lt;/code&gt;, trust roots via &lt;code&gt;OWLWARDEN_PLUGIN_TRUST&lt;/code&gt; / &lt;code&gt;.owlwarden/plugin-trust.json&lt;/code&gt;, &lt;strong&gt;&lt;code&gt;--require-signed-plugins&lt;/code&gt;&lt;/strong&gt; (ADR 0021). Still not a hosted registry.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Repeated &lt;code&gt;--format&lt;/code&gt;&lt;/strong&gt; - one scan, N renders (ADR 0022).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Incremental &lt;code&gt;watch&lt;/code&gt;&lt;/strong&gt; - dirty-path re-parse + finding merge (ADR 0023).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Changed&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Version &lt;strong&gt;0.5.0&lt;/strong&gt;. Release-assurance (cosign/SBOM) moves to the next roadmap slice.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Security&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Offline OSV index ingest sanitises advisory ids, package names, versions, and summaries before they enter findings (control/ANSI stripping, shared with live OSV).&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;owlwarden osv update&lt;/code&gt; writes indexes via atomic &lt;code&gt;write_replacing&lt;/code&gt; so a partial write cannot leave CI with a truncated index file.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Fixed&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Incremental &lt;code&gt;watch&lt;/code&gt; refuses to hash files above the source size cap (2 MiB), matching bounded read limits elsewhere - giant blobs no longer blow the content-hash cache.&lt;/li&gt;
&lt;/ul&gt;</content>
  </entry>
  <entry>
    <title>owlwarden 0.4.0</title>
    <link href="https://suthat.github.io/owlwarden/changelog/#0-4-0"/>
    <id>https://suthat.github.io/owlwarden/changelog/#0-4-0</id>
    <updated>2026-08-11T00:00:00Z</updated>
    <content type="html">&lt;p&gt;CI-ready depth: pipelines teams already run, plus a deeper fixture corpus so those pipelines gate on findings worth trusting (&lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/docs/adr/0017-ci-reporting-surface.md&quot;&gt;ADR 0017&lt;/a&gt;, &lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/docs/adr/0018-corpus-depth-bar.md&quot;&gt;ADR 0018&lt;/a&gt;).&lt;/p&gt;
&lt;h3&gt;Added&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;--format sarif&lt;/code&gt;&lt;/strong&gt; - SARIF 2.1.0 rendering of the existing &lt;code&gt;Report&lt;/code&gt; (ADR 0017). For GitHub code scanning and similar consumers.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;--format junit&lt;/code&gt;&lt;/strong&gt; - JUnit XML, one failure per finding. Exit codes stay on the CLI.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;GitHub Action&lt;/strong&gt; at &lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/action/&quot;&gt;&lt;code&gt;action/&lt;/code&gt;&lt;/a&gt; - composite over the published CLI; preserves exit 0 / 1 / 2. See &lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/docs/how-to/ci.md&quot;&gt;docs/how-to/ci.md&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Corpus depth&lt;/strong&gt; - dialect tempting on every clean twin; &lt;code&gt;ssrf&lt;/code&gt; shapes now include &lt;code&gt;got.get&lt;/code&gt; and &lt;code&gt;https.get&lt;/code&gt;/&lt;code&gt;http.get&lt;/code&gt; (4); &lt;code&gt;open-redirect&lt;/code&gt; adds an extra/status-first shape (3); generic-profile fixtures outside the 144-cell matrix.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;More Safe autofix&lt;/strong&gt; - &lt;code&gt;weak-crypto&lt;/code&gt; HashedSecret (algorithm literal &lt;code&gt;&amp;#39;md5&amp;#39;&lt;/code&gt;/&lt;code&gt;&amp;#39;sha1&amp;#39;&lt;/code&gt; → &lt;code&gt;&amp;#39;sha256&amp;#39;&lt;/code&gt;, keeping &lt;code&gt;createHmac&lt;/code&gt; keys and &lt;code&gt;crypto.&lt;/code&gt; receivers) and &lt;code&gt;insecure-cookie&lt;/code&gt; options objects that only carry security keys (or &lt;code&gt;{}&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden plugin inspect &amp;lt;path&amp;gt;&lt;/code&gt;&lt;/strong&gt; - print capabilities from &lt;code&gt;owlwarden.plugin.json&lt;/code&gt; without loading WASM (local preview, not a signed registry).&lt;/li&gt;
  &lt;li&gt;Cold-scan performance baseline harness (&lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/docs/how-to/performance.md&quot;&gt;docs/how-to/performance.md&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Security&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;GitHub Action drops free-form &lt;code&gt;args&lt;/code&gt; (shell injection + &lt;code&gt;--allow-*&lt;/code&gt; bypass); typed &lt;code&gt;osv&lt;/code&gt; input replaces ad-hoc flags; path/out/version reject CR/LF/&lt;code&gt;..&lt;/code&gt;; &lt;code&gt;GITHUB_OUTPUT&lt;/code&gt; uses a heredoc delimiter.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;plugin inspect&lt;/code&gt; confines paths under cwd, requires &lt;code&gt;realpath&lt;/code&gt; under the working tree, and reads via &lt;code&gt;readFileBounded&lt;/code&gt; (no symlink leaf).&lt;/li&gt;
  &lt;li&gt;Action smoke workflow pins upstream actions by full commit SHA.&lt;/li&gt;
  &lt;li&gt;SARIF endpoint URIs strip control characters; &lt;code&gt;plugin inspect&lt;/code&gt; bounds JSON nesting depth after parse.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Fixed&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;MCP &lt;code&gt;scan_project&lt;/code&gt; / &lt;code&gt;scan_file&lt;/code&gt; send the flat NAPI request shape (nested &lt;code&gt;settings&lt;/code&gt; was rejected by &lt;code&gt;deny_unknown_fields&lt;/code&gt;, so agent scans failed).&lt;/li&gt;
  &lt;li&gt;NAPI rejects &lt;code&gt;--allow-active&lt;/code&gt; without &lt;code&gt;--target&lt;/code&gt;, matching the CLI gate.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;--fix&lt;/code&gt; refuses to apply when highlight text drifted since the scan (TOCTOU / dirty-tree safety).&lt;/li&gt;
  &lt;li&gt;Active request pacing reserves the next slot under a lock so concurrent detectors cannot bypass &lt;code&gt;ACTIVE_MIN_INTERVAL&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;OSV advisory summaries strip control / ANSI characters before they enter findings; package-lock line lookup is O(lines) not O(packages × lines).&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;weak-crypto&lt;/code&gt; Safe autofix no longer rewrites the whole &lt;code&gt;createHash&lt;/code&gt; / &lt;code&gt;createHmac&lt;/code&gt; call (which truncated HMAC keys and stripped &lt;code&gt;crypto.&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;</content>
  </entry>
  <entry>
    <title>owlwarden 0.3.0</title>
    <link href="https://suthat.github.io/owlwarden/changelog/#0-3-0"/>
    <id>https://suthat.github.io/owlwarden/changelog/#0-3-0</id>
    <updated>2026-08-10T00:00:00Z</updated>
    <content type="html">&lt;p&gt;Autofix (&lt;code&gt;--fix&lt;/code&gt;), &lt;code&gt;--allow-active&lt;/code&gt; scaffold, and opt-in Google OSV lookup. Folded in the 0.2.1 docs/MCP polish so one publish updates npm &lt;code&gt;homepage&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Added&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden scan --fix&lt;/code&gt;&lt;/strong&gt; - applies &lt;code&gt;Safe&lt;/code&gt;, single-line highlight replacements only. Never on &lt;code&gt;Possible&lt;/code&gt;. Clean git tree by default (&lt;code&gt;--allow-dirty&lt;/code&gt; to override). &lt;code&gt;--dry-run&lt;/code&gt; and &lt;code&gt;--fix-unsafe&lt;/code&gt;. Re-scans after writes. First Safe remediations: &lt;code&gt;stack-trace-leak&lt;/code&gt; and &lt;code&gt;weak-crypto&lt;/code&gt; (GuessableToken / &lt;code&gt;Math.random()&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;--allow-active&lt;/code&gt;&lt;/strong&gt; - with &lt;code&gt;--target&lt;/code&gt;, permits state-changing HTTP methods. Rate-limited, request audit log (method/URL/status). No first-party active detector yet. MCP cannot set the flag.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;--osv&lt;/code&gt;&lt;/strong&gt; - opt-in Google OSV QueryBatch for lockfile dependencies (&lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/docs/adr/0016-osv-advisory-lookup.md&quot;&gt;ADR 0016&lt;/a&gt;). New rule &lt;code&gt;known-vulnerable-dependency&lt;/code&gt; (A06 / CWE-1395). See &lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/docs/how-to/osv.md&quot;&gt;docs/how-to/osv.md&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;AdvisoryClient&lt;/code&gt; port and &lt;code&gt;Capabilities.advisory&lt;/code&gt;, distinct from target &lt;code&gt;Transport&lt;/code&gt; scope.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Changed&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;npm &lt;code&gt;homepage&lt;/code&gt; → &lt;code&gt;https://suthat.github.io/owlwarden/&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Token narrative: save tokens with local baseline scans; spend frontier models on hard judgment (site, README, npm README, agent-integration).&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;owlwarden mcp&lt;/code&gt; stderr ready banner / TTY how-to; stdout remains JSON-RPC-only.&lt;/li&gt;
&lt;/ul&gt;</content>
  </entry>
  <entry>
    <title>owlwarden 0.2.0</title>
    <link href="https://suthat.github.io/owlwarden/changelog/#0-2-0"/>
    <id>https://suthat.github.io/owlwarden/changelog/#0-2-0</id>
    <updated>2026-08-08T00:00:00Z</updated>
    <content type="html">&lt;p&gt;Plugins (source-only WASM), MCP for agents, and twelve Node frameworks. The formal v0.2 bar from &lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/ROADMAP.md&quot;&gt;ROADMAP.md&lt;/a&gt;. Autofix and active checks stay later work.&lt;/p&gt;
&lt;h3&gt;Added&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden-plugin-host&lt;/code&gt;&lt;/strong&gt; - sandboxed WASM plugin host (ROADMAP v0.2), ships partial: source-only. A plugin is a &lt;code&gt;.wasm&lt;/code&gt; module plus an &lt;code&gt;owlwarden.plugin.json&lt;/code&gt; manifest, loaded with &lt;code&gt;--plugin &amp;lt;path&amp;gt;&lt;/code&gt; (repeatable) and refused under &lt;code&gt;--ci&lt;/code&gt; unless &lt;code&gt;--allow-plugins&lt;/code&gt; is also passed. Every invocation runs in a fresh &lt;code&gt;wasmtime&lt;/code&gt; store bounded by fuel, a 64 MiB &lt;code&gt;StoreLimits&lt;/code&gt; memory cap, and a wall-clock deadline via epoch interruption; the only host function wired is &lt;code&gt;emit_finding&lt;/code&gt;, and every claim it receives is re-validated against the plugin&amp;#39;s own manifest before it becomes a finding. A manifest declaring &lt;code&gt;network&lt;/code&gt; or &lt;code&gt;active&lt;/code&gt; is refused at load time rather than silently downgraded - see &lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/docs/adr/0015-plugin-host-wasmtime.md&quot;&gt;ADR 0015&lt;/a&gt;. &lt;code&gt;wasmtime&lt;/code&gt; is a new dependency, confined to this one crate with default features disabled (only &lt;code&gt;cranelift&lt;/code&gt;/&lt;code&gt;runtime&lt;/code&gt;/&lt;code&gt;std&lt;/code&gt;); every other crate keeps &lt;code&gt;#![forbid(unsafe_code)]&lt;/code&gt;. Floored at 36.0.13 - every earlier release has an open RUSTSEC advisory, several of them sandbox escapes.&lt;/li&gt;
  &lt;li&gt;Sandbox-escape test suite (&lt;code&gt;crates/plugin-host/tests/sandbox_escape.rs&lt;/code&gt;): fuel exhaustion, oversized &lt;code&gt;memory.grow&lt;/code&gt; / &lt;code&gt;table.grow&lt;/code&gt;, a finding flood, an undeclared rule id, an oversized &lt;code&gt;why&lt;/code&gt;, and a benign positive control.&lt;/li&gt;
  &lt;li&gt;Error code &lt;strong&gt;&lt;code&gt;E_PLUGIN_INVALID&lt;/code&gt;&lt;/strong&gt; for a plugin that could not be loaded.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden mcp&lt;/code&gt;&lt;/strong&gt; - stdio MCP server with &lt;code&gt;scan_project&lt;/code&gt;, &lt;code&gt;scan_file&lt;/code&gt;, &lt;code&gt;explain_rule&lt;/code&gt;, and &lt;code&gt;list_rules&lt;/code&gt;. Static and read-only; no &lt;code&gt;--target&lt;/code&gt;, no file writes, paths sandboxed to the workspace root.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden init --agent-rules&lt;/code&gt;&lt;/strong&gt; - writes &lt;code&gt;.owlwarden/agent-rules.md&lt;/code&gt; from the compiled catalogue.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden plugin scaffold &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/strong&gt; - guest stub (&lt;code&gt;plugin.wat&lt;/code&gt;) plus a valid &lt;code&gt;owlwarden.plugin.json&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Plugin-authoring schemas in &lt;code&gt;@dointhai/owlwarden-sdk&lt;/code&gt; (&lt;code&gt;pluginManifestSchema&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Seven more Node frameworks&lt;/strong&gt; with first-class profiles, remediation on every catalogue rule, and square fixture coverage: Hono, Koa, Hapi, Sails.js, Astro, Remix, and Gatsby. Supported set is now twelve stacks (12 rules × 12 frameworks, locked in CI).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Richer fixture corpus&lt;/strong&gt; - each framework exercises two real-world shapes for &lt;code&gt;ssrf&lt;/code&gt; (fetch + axios), &lt;code&gt;open-redirect&lt;/code&gt; (redirect helper + &lt;code&gt;Location&lt;/code&gt; header), and &lt;code&gt;sensitive-data-logged&lt;/code&gt; (password + accessToken), plus tempting false-positive twins on every clean project.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;File-route mapping&lt;/strong&gt; for Astro (&lt;code&gt;src/pages/api&lt;/code&gt;), Remix flat routes, and Gatsby Functions (&lt;code&gt;src/api&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;Request-origin recognition for Hono’s &lt;code&gt;c&lt;/code&gt; context and Astro’s &lt;code&gt;Astro.request&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Changed&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;DetectorMeta.title&lt;/code&gt; / &lt;code&gt;.category&lt;/code&gt; / &lt;code&gt;.description&lt;/code&gt; are now &lt;code&gt;Cow&amp;lt;&amp;#39;static, str&amp;gt;&lt;/code&gt; (were &lt;code&gt;&amp;amp;&amp;#39;static str&lt;/code&gt;), so a &lt;code&gt;WasmDetector&lt;/code&gt; built from a parsed plugin manifest can own its strings. No change to the JSON wire shape or to first-party rules, which still write string literals.&lt;/li&gt;
  &lt;li&gt;README and npm package text rewritten in plain language: what it does, that it stays local, which frameworks it knows, and what v0.2 actually ships (plugins source-only, MCP read-only). States that local scans cover baseline checks without burning LLM tokens, and that deeper AI security review still belongs on high-impact work.&lt;/li&gt;
  &lt;li&gt;Plugin hardening after whitebox review: &lt;code&gt;O_NOFOLLOW&lt;/code&gt; + bounded reads for manifest/WASM load; &lt;code&gt;StoreLimits&lt;/code&gt; on tables; plugin rule ids must be namespaced under the plugin id; source-only plugins cannot declare &lt;code&gt;confirmed&lt;/code&gt;; &lt;code&gt;why&lt;/code&gt; capped; MCP JSON-RPC lines capped; &lt;code&gt;init&lt;/code&gt; / &lt;code&gt;plugin scaffold&lt;/code&gt; use symlink-safe writes under the working directory; napi re-checks &lt;code&gt;--ci&lt;/code&gt; + &lt;code&gt;--allow-plugins&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Fixture matrix tightened: every clean twin ships &lt;code&gt;*tempting*&lt;/code&gt; and &lt;code&gt;*safe-redirect*&lt;/code&gt; files; multi-fire rules are locked to named source shapes (fetch/axios, redirect/Location, …); the TypeScript e2e path asserts &lt;code&gt;SHARED_FIRES&lt;/code&gt; counts on all twelve frameworks, not only Next.js.&lt;/li&gt;
  &lt;li&gt;Cookie detection: nested setters (&lt;code&gt;ctx.cookies.set&lt;/code&gt;), Hapi &lt;code&gt;isHttpOnly&lt;/code&gt; / &lt;code&gt;isSecure&lt;/code&gt; / &lt;code&gt;isSameSite&lt;/code&gt;, and dropped false cookie matches on &lt;code&gt;c.header&lt;/code&gt; / &lt;code&gt;res.setHeader&lt;/code&gt; / bare &lt;code&gt;serialize&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Stack-trace rule recognises Koa-style &lt;code&gt;ctx.body = …&lt;/code&gt; assignments.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;secureHeaders&lt;/code&gt; counts as header middleware for Hono.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Fixed&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;cargo deny&lt;/code&gt; CI gate: allow &lt;code&gt;CDLA-Permissive-2.0&lt;/code&gt; for &lt;code&gt;webpki-roots&lt;/code&gt; (Mozilla CA data via rustls/reqwest), and give the dynamic-engine dev-dep on &lt;code&gt;owlwarden-transport&lt;/code&gt; a workspace version so it is not a path-only wildcard.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Security&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Prompt-injection hardening for MCP / agents / plugins.&lt;/strong&gt; MCP tool results are wrapped in an &lt;code&gt;OWLWARDEN_TOOL_RESULT&lt;/code&gt; trust-boundary envelope; free text is stripped of control/invisible characters and common chat role markers. Plugin &lt;code&gt;why&lt;/code&gt; is sanitised at emit time; &lt;code&gt;init --agent-rules&lt;/code&gt; tells agents to treat findings as evidence, not instructions.&lt;/li&gt;
&lt;/ul&gt;</content>
  </entry>
  <entry>
    <title>owlwarden 0.1.0</title>
    <link href="https://suthat.github.io/owlwarden/changelog/#0-1-0"/>
    <id>https://suthat.github.io/owlwarden/changelog/#0-1-0</id>
    <updated>2026-09-01T00:00:00Z</updated>
    <content type="html">&lt;p&gt;Passive dynamic engine and correlation. &lt;code&gt;Confirmed&lt;/code&gt; is reachable for the first time, without opening active (state-changing) checks.&lt;/p&gt;
&lt;h3&gt;Added&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;--target &amp;lt;URL&amp;gt;&lt;/code&gt;&lt;/strong&gt; - probe a live origin with passive methods only (GET/HEAD/OPTIONS). Operator intent from the command line; never read from project config, so a hostile PR cannot point CI at an internal host (&lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/docs/adr/0014-passive-dynamic-and-correlation.md&quot;&gt;ADR 0014&lt;/a&gt;).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;--scope &amp;lt;URL&amp;gt;&lt;/code&gt;&lt;/strong&gt; (repeatable) - deny-by-default allowlist. When omitted, the allowlist is exactly the origin of &lt;code&gt;--target&lt;/code&gt;. Localhost is not special.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden-transport&lt;/code&gt;&lt;/strong&gt; - &lt;code&gt;ReqwestTransport&lt;/code&gt; that enforces scope on every redirect hop, streams body bytes under the cap, and refuses state-changing methods without &lt;code&gt;--allow-active&lt;/code&gt; (no active detectors ship yet).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden-dynamic&lt;/code&gt;&lt;/strong&gt; - passive header probe for &lt;code&gt;security-headers-missing&lt;/code&gt; and a correlation post-pass that raises agreeing static+dynamic findings to &lt;code&gt;confirmed&lt;/code&gt;, or clears a static gap when the live response already sets the headers.&lt;/li&gt;
  &lt;li&gt;Error code &lt;strong&gt;&lt;code&gt;E_TARGET_INVALID&lt;/code&gt;&lt;/strong&gt; for bad target/scope.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Square static fixture matrix&lt;/strong&gt; - every catalogue rule has a vulnerable fixture and a silent clean twin on all five frameworks (12 × 5 = 60 cells). &lt;code&gt;weak-crypto&lt;/code&gt; fires three shapes on every framework. Locked by &lt;code&gt;SHARED_FIRES&lt;/code&gt; in &lt;code&gt;crates/detectors/tests/fixtures.rs&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Framework dynamic matrix&lt;/strong&gt; - &lt;code&gt;crates/dynamic-engine/tests/framework_matrix.rs&lt;/code&gt; correlates &lt;code&gt;security-headers-missing&lt;/code&gt; against each framework&amp;#39;s fixture plus a live header probe (confirmed / cleared / clean stays silent).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;CLI live e2e&lt;/strong&gt; - &lt;code&gt;packages/cli/test/run.test.ts&lt;/code&gt; drives &lt;code&gt;--target&lt;/code&gt; through the npm CLI against an in-process server on all five frameworks.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Changed&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Confidence filtering runs &lt;strong&gt;after&lt;/strong&gt; correlation, so a &lt;code&gt;possible&lt;/code&gt; static finding can still become &lt;code&gt;confirmed&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;watch&lt;/code&gt; refuses &lt;code&gt;--target&lt;/code&gt; / &lt;code&gt;--scope&lt;/code&gt; (static-only; re-probing on save is hostile to the developer&amp;#39;s own server).&lt;/li&gt;
  &lt;li&gt;Help text no longer claims every scan is offline - only scans without &lt;code&gt;--target&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;napi &lt;code&gt;scan&lt;/code&gt; is async&lt;/strong&gt; (&lt;code&gt;spawn_blocking&lt;/code&gt;) so a live probe cannot deadlock the Node event loop while an in-process test (or user) server is accepting connections. The TypeScript CLI awaits the Promise.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Security&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Scope deny-by-default, including redirect hops (SSRF-bait case).&lt;/li&gt;
  &lt;li&gt;Credentials in &lt;code&gt;--target&lt;/code&gt; / &lt;code&gt;--scope&lt;/code&gt; URLs are refused.&lt;/li&gt;
  &lt;li&gt;Automatic response decompression is off; body caps apply while streaming.&lt;/li&gt;
  &lt;li&gt;No path from scanned-tree config to the request URL.&lt;/li&gt;
  &lt;li&gt;Redirect &lt;code&gt;Location&lt;/code&gt; re-validated as a target (blocks &lt;code&gt;user@host&lt;/code&gt; confusion, non-http(s) schemes, control characters, oversized URLs).&lt;/li&gt;
  &lt;li&gt;Headers-only probes (&lt;code&gt;max_body_bytes = 0&lt;/code&gt;) do not pull a response body into memory - a hostile HEAD payload cannot inflate the scanner.&lt;/li&gt;
  &lt;li&gt;Oversized response header values are dropped, not truncated; outbound request headers reject CRLF/NUL (request-smuggling footgun for future detectors).&lt;/li&gt;
&lt;/ul&gt;</content>
  </entry>
  <entry>
    <title>owlwarden 0.0.2</title>
    <link href="https://suthat.github.io/owlwarden/changelog/#0-0-2"/>
    <id>https://suthat.github.io/owlwarden/changelog/#0-0-2</id>
    <updated>2026-09-01T00:00:00Z</updated>
    <content type="html">&lt;p&gt;Static slice of the trust-and-noise work planned for v0.1: suppressions, baseline, three gap-closing rules, and &lt;code&gt;watch&lt;/code&gt;. Still no network - the dynamic engine that makes &lt;code&gt;Confirmed&lt;/code&gt; reachable remains later.&lt;/p&gt;
&lt;h3&gt;Added&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Inline suppressions&lt;/strong&gt; with a mandatory reason: &lt;code&gt;// owlwarden-disable-next-line &amp;lt;rule&amp;gt; -- &amp;lt;reason&amp;gt;&lt;/code&gt;. Directives without a reason never hide a finding. &lt;code&gt;--report-suppressions&lt;/code&gt; lists every directive and flags stale or missing-reason ones. &lt;code&gt;suppressedCount&lt;/code&gt; in the JSON report is now wired for real.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Baseline mode.&lt;/strong&gt; &lt;code&gt;--baseline &amp;lt;file&amp;gt;&lt;/code&gt; reports only findings new since the file was written; &lt;code&gt;--write-baseline &amp;lt;file&amp;gt;&lt;/code&gt; records current debt. Fingerprints key on rule id, normalised path, whitespace-collapsed evidence, and an occurrence index so a formatter pass does not reopen accepted findings and two identical findings in one file stay distinct (&lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/docs/adr/0013-suppressions-and-baseline.md&quot;&gt;ADR 0013&lt;/a&gt;).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Three rules&lt;/strong&gt; closing the static-reachable OWASP gaps:&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;unpinned-dependency&lt;/code&gt; (A06) - &lt;code&gt;*&lt;/code&gt; / &lt;code&gt;latest&lt;/code&gt; in &lt;code&gt;package.json&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;ci-unpinned-action&lt;/code&gt; (A08) - GitHub Actions not pinned to a commit SHA&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;sensitive-data-logged&lt;/code&gt; (A09) - passwords/tokens written to a log sink Each ships remediation for all five frameworks, with vulnerable and should-not-fire fixtures.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden watch&lt;/code&gt;&lt;/strong&gt; - re-scan on change, static only. Never opens a network path.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Changed&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;The filesystem walker now reads &lt;code&gt;.github/&lt;/code&gt; (still skips other hidden directories), so CI integrity rules can see workflow files.&lt;/li&gt;
  &lt;li&gt;Report JSON gains &lt;code&gt;suppressions&lt;/code&gt; and &lt;code&gt;baselineHiddenCount&lt;/code&gt; (additive under schema &lt;code&gt;1.0&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Security hardening (hostile scan target):&lt;/strong&gt; executable project config (&lt;code&gt;owlwarden.config.{js,mjs,ts,mts}&lt;/code&gt;) is opt-in via &lt;code&gt;--allow-config-js&lt;/code&gt; (default loads JSON only); &lt;code&gt;--ci&lt;/code&gt; ignores project &lt;code&gt;preset&lt;/code&gt; / &lt;code&gt;failOn&lt;/code&gt; / &lt;code&gt;minConfidence&lt;/code&gt; unless &lt;code&gt;--allow-project-config&lt;/code&gt;, ignores inline suppressions unless &lt;code&gt;--allow-suppressions&lt;/code&gt;, and refuses &lt;code&gt;--baseline&lt;/code&gt; unless &lt;code&gt;--allow-baseline&lt;/code&gt;; truncated reports fail CI; &lt;code&gt;--out&lt;/code&gt; / &lt;code&gt;--write-baseline&lt;/code&gt; refuse symlinked parent directories and write via temp+rename (&lt;code&gt;create_new&lt;/code&gt; / &lt;code&gt;wx&lt;/code&gt;); source reads are bounded and use &lt;code&gt;O_NOFOLLOW&lt;/code&gt; on Unix; parser nesting guard skips comments/strings and counts generics/JSX and brackets inside templates; &lt;code&gt;hardcoded-secret&lt;/code&gt; redacts values in snippets before line truncation; baseline/config loads refuse symlinks and oversized inputs; CI workflow evidence is length-capped. See &lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/SECURITY.md&quot;&gt;SECURITY.md&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;</content>
  </entry>
  <entry>
    <title>owlwarden 0.0.1</title>
    <link href="https://suthat.github.io/owlwarden/changelog/#0-0-1"/>
    <id>https://suthat.github.io/owlwarden/changelog/#0-0-1</id>
    <updated>2026-09-01T00:00:00Z</updated>
    <content type="html">&lt;p&gt;First release. A static engine, a rule set, and honest reporting about what it does and does not reach.&lt;/p&gt;
&lt;h3&gt;Added&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Static engine.&lt;/strong&gt; Sandboxed &lt;code&gt;SourceProvider&lt;/code&gt;, oxc parsing with bounded recursion, and per-file and whole-project rule traits.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Five frameworks&lt;/strong&gt; via the &lt;code&gt;FrameworkProfile&lt;/code&gt; registry: Next.js, Nuxt, NestJS, Express, and Fastify. Detection is package-based with specificity tie-breaking, so a NestJS project that also depends on Express is treated as NestJS.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Nine rules&lt;/strong&gt; across six OWASP Top 10 (2021) categories: &lt;code&gt;open-redirect&lt;/code&gt;, &lt;code&gt;weak-crypto&lt;/code&gt;, &lt;code&gt;sql-injection&lt;/code&gt;, &lt;code&gt;cors-permissive&lt;/code&gt;, &lt;code&gt;insecure-cookie&lt;/code&gt;, &lt;code&gt;security-headers-missing&lt;/code&gt;, &lt;code&gt;stack-trace-leak&lt;/code&gt;, &lt;code&gt;hardcoded-secret&lt;/code&gt;, and &lt;code&gt;ssrf&lt;/code&gt;. Full catalogue in &lt;a href=&quot;https://github.com/suthat/owlwarden/blob/main/RULES.md&quot;&gt;RULES.md&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden coverage&lt;/code&gt;&lt;/strong&gt; - what the rules reach, and what they do not, computed from the compiled-in rules rather than maintained by hand.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Confidence on every finding&lt;/strong&gt;, and a &lt;code&gt;RequestOrigin&lt;/code&gt; analysis that the injection-shaped rules use to distinguish &lt;code&gt;Likely&lt;/code&gt; from &lt;code&gt;Possible&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Remediation tables&lt;/strong&gt;, so every rule ships a framework-specific fix for each supported framework rather than generic advice.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;pretty&lt;/code&gt; and &lt;code&gt;json&lt;/code&gt; reporters&lt;/strong&gt;, with code frames, ASCII and no-colour fallbacks, and a stable JSON contract validated against zod schemas in CI.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;owlwarden explain &amp;lt;id&amp;gt;&lt;/code&gt;&lt;/strong&gt; - the full write-up for a rule with no network access, because the reader may be an agent with no browser.&lt;/li&gt;
  &lt;li&gt;npm CLI plus a standalone Rust binary, on Linux, macOS, and Windows.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Security&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Passive only. v0.0 reads source and sends no requests, so it cannot change the state of anything it scans.&lt;/li&gt;
  &lt;li&gt;No telemetry, of any kind, opt-in or otherwise.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;#![forbid(unsafe_code)]&lt;/code&gt; in every crate.&lt;/li&gt;
  &lt;li&gt;Bounded file count, file size, total bytes, and parser recursion depth, so a hostile repository cannot exhaust memory or the stack.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;[Unreleased]: https://github.com/suthat/owlwarden/compare/v1.3.0...HEAD [1.3.0]: https://github.com/suthat/owlwarden/compare/v1.2.0...v1.3.0 [1.2.0]: https://github.com/suthat/owlwarden/compare/v1.1.0...v1.2.0 [1.1.0]: https://github.com/suthat/owlwarden/compare/v1.0.0...v1.1.0 [1.0.0]: https://github.com/suthat/owlwarden/compare/v0.5.0...v1.0.0 [0.5.0]: https://github.com/suthat/owlwarden/compare/v0.4.0...v0.5.0 [0.4.0]: https://github.com/suthat/owlwarden/compare/v0.3.0...v0.4.0 [0.3.0]: https://github.com/suthat/owlwarden/compare/v0.2.0...v0.3.0 [0.2.0]: https://github.com/suthat/owlwarden/compare/v0.1.0...v0.2.0 [0.1.0]: https://github.com/suthat/owlwarden/compare/v0.0.2...v0.1.0 [0.0.2]: https://github.com/suthat/owlwarden/compare/v0.0.1...v0.0.2 [0.0.1]: https://github.com/suthat/owlwarden/releases/tag/v0.0.1&lt;/p&gt;</content>
  </entry>
</feed>
