Skip to content
Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

Repository files navigation

Web App Security Skill

Scope, audit, harden, and retest web projects with AI coding agents and reproducible evidence.

latest tag CI CodeQL npm version MIT license SPDX 2.3 SBOM

Demo · v0.8.1 · v0.8.0 · Install · First project · Tutorial · GitHub Action · Project journeys · 中文

For web product owners and builders using AI coding agents; no offensive-security background is required. Run a local source-only first pass from the root of a project you may inspect.

npx --yes web-app-security-skill audit . --fail-on never

--fail-on never lets the first report finish without turning a suspected lead into a failing CI claim. This command reads local project files, does not contact a deployment and does not edit code. For each actionable result, the report gives you:

  • the security term, a plain-language explanation and a realistic consequence;
  • what the evidence proves and what still needs human or runtime confirmation;
  • a reviewable change, likely product side effects, rollback conditions, and separate security and normal-behavior retests.

For supported JavaScript/TypeScript frameworks, the same command also writes route-security.json, route-security.md and a SHA-256 sidecar. It inventories HTTP routes and Next.js Server Actions separately, lists application-wide controls once, and shows what to review next:

Security term Plain meaning What the route view can say
Application control What was registered for the whole app? A global guard or middleware is listed once; this does not prove it protects every route.
Authentication (authn) Who is making the request? A supported login/session source was observed, was not observed, or could not be resolved.
Route-level authorization (authz) May this identity call this operation? A supported policy/guard was observed, or a custom route control still needs review.
Object-level authorization (BOLA/IDOR) May this identity access this specific record? A caller-selected ID can be followed through at most four exact project-local call edges into supported Prisma/Drizzle operations. Query constraints, post-load comparisons, missing supported constraints and incomplete paths stay distinct.

review_first, review_next and review_later are work-order labels, not severity scores. A missing visible control is never converted into a confirmed vulnerability.

In plain language, the access-chain view can say: "this route takes a project ID, obtains the current user through Auth.js, carries both values through two exact local functions, and reaches a Prisma query whose visible filter does not include that user." It can also distinguish an exact owner/tenant query predicate from a supported post-load comparison. It cannot prove runtime reachability, control-flow dominance, deployed denial behavior or database policy. Supabase results always say that external RLS policy evidence is still required.

Route inventory coverage and bounded access-path coverage are separate counters. A completed path means only that this static model reached a supported operation; it does not mean the route is safe or vulnerable. Route-security v1/v2 baselines are not comparable with v3 and must be replaced with a new v3 baseline before route-regression results can be interpreted.

For Express, stable inventory covers direct ESM/CommonJS express() and Router() receivers, direct require('express').Router(), inline route calls, exact static mounts and exact local CommonJS router mounts. An imported local route-registration function is reported as express_registration_function_unresolved and makes coverage partial. In that case exit code 3 means evidence is incomplete and the tool refuses to report a clean route result; it does not mean that three vulnerabilities were found. A finding policy failure can still take precedence with exit code 1.

For the widest maintained local pass, select the no-download deep profile. It uses the built-in rules and calls pinned Checkov, Gitleaks, Opengrep and OSV-Scanner binaries already installed by the user. A missing tool becomes unknown evidence with setup guidance:

npx --yes web-app-security-skill audit . --profile deep --fail-on never

Owned local source fixture: a suspected HIGH OS command injection lead is explained, changed from shell execution to argument-separated execution, then security and normal product behavior are retested

Read the generated reports and patch behind this demo.

See the result

Audit an intentionally unsafe local source file, inspect the explanation and proposed change, then run both the security retest and the fixture's normal behavior test. Nothing reaches the network and no project dependency is installed.

Input Finding Evidence Reviewable change Retest
src/export-report.mjs OS command injection lead (CWE-78), HIGH suspected; input flow and reachability are not proven replace shell parsing with execFile and separate arguments; quoting/platform behavior may change security fixed; functional passed
git clone https://github.com/parousia8888/web-app-security-skill.git
cd web-app-security-skill
npm run demo -- --out ./demo-output

Read the generated before / proposed change / retest evidence, then inspect demo-output/demo-result.json, summary.md, before.json, hardening.patch, after.json, and functional-retest.txt. Every public demo fact is derived from demo-result.json; the repository check reruns the fixture and fails if any surface disagrees.

For the complete install-to-uninstall path, follow the tested first project tutorial.

What's new in v0.8.1

v0.8.1 makes existing boundaries enforceable; it does not add a detector family. A persisted auditBoundary.sourceRoots plus excludedDirectories now compiles into one file-read policy used by the built-in analyzer, route/access review, --since/--staged snapshots and every selected external adapter. Excluded source is not opened by a scanner that claims this scope. Missing, unsafe or invalid roots stop the run instead of producing a clean report. Checkov and OSV receive only exact governing inputs; Opengrep and working-tree Gitleaks receive private scoped snapshots. Restricted Gitleaks history is explicitly unknown / history_scope_not_supported, because a broad history scan followed by output filtering would violate the read boundary. See the adapter scope matrix, scope implementation and scope contract test.

An optional root-level webapp-security.suppressions.json can disposition one exact adapter/rule/path/fingerprint match. The finding stays in JSON, Markdown, HTML, SARIF and JUnit with its original evidence and baseline states; suppression is visible policy, not proof of safety or a fifth evidence state. Unknown and evidence-integrity findings cannot be suppressed. CI/release gates and all external-adapter suppressions require an owner and expiry; expired, drifted, malformed or unmatched entries stay active and produce diagnostics. The format and example are in the tutorial, finding schema and false-positive policy.

The repository's required self-audit now uses an explicit production-only scope and reviewed exact suppressions, while retaining fixture counts and every disposition in its artifact. It blocks active HIGH and unavailable evidence; a green result is bounded static evidence, not proof that the repository is vulnerability-free. Node package export patterns now replace every right-hand-side * literally, ambiguous conditional exports remain partial, and finite allowlisted usage counters such as usage.tokens: 17 stay numeric while credentials remain redacted. Focused regressions are linked from the v0.8.1 engineering plan.

Release publication is now a trusted-main manual workflow: a read-only job verifies the signed annotated tag, signer policy, exact candidate and hosted checks before dependency installation; a separate release-environment job owns publication permissions. Moving v1 has explicit pending and final states and remains less immutable than a full commit pin. The solo-maintainer administrator bypass is documented and is not independent review. WSL2 remains unsupported. The signed v0.8.1 tag, GitHub Release, npm package and verified installer are public and identify source commit 6e581adcac7a0433ec6428d8080d20761dfc3a93. The signed moving v1 Action now identifies the same source after its immutable consumer and guarded pending-state consumers passed.

What's new in v0.8.0

v0.8.0 is a bounded interprocedural access-control release. It extends the route-security surface from one local call to at most four exact project-local call edges. It supports exact route/query/body/Server Action selectors, carries object, principal and tenant facts separately, and distinguishes Prisma/Drizzle query predicates from supported post-load comparisons. Ambiguous calls, argument/return transforms, unsupported provider construction and exhausted budgets remain partial instead of being guessed.

At four fixed public commits, the frozen 14-path evaluation completed 13 paths: Drizzle 6/6 and Prisma 7/8. The sole miss is Formbricks ACTION getMembershipRole, retained as partial for argument_mapping_ambiguous and call_target_unresolved. Four completed paths retain visible supporting limitations. These are bounded fixed-corpus effectiveness facts, not production precision/recall, vulnerability confirmation or proof of deployed enforcement. Read the review, provenance, real-world regression and engineering plan.

The stable rule inventory remains 25 built-in risk rules, 3 evidence-integrity rules and 16 opt-in external-adapter risk rules, for 44 total. Access paths are a separate capability, not additional vulnerability rules. Pattern matches remain suspected; incomplete analysis remains unknown and may exit 3. The signed v0.8.0 tag, GitHub Release, npm package and verified installer are public and identify source commit 119cbcc7f8d327482df8abfa50a4af0b69fcceee. The moving v1 Action now identifies the same v0.8.0 source after its immutable consumer and guarded promotion lease passed. The combined public consumer also passed, and its byte-matched durable live-verification record is published with the release.

Install

Zero-install CLI trial

Try the CLI without keeping an installation:

npx --yes web-app-security-skill audit . --fail-on never

Claude Code plugin

Install the Claude Code plugin from this repository marketplace in one shell line:

claude plugin marketplace add parousia8888/web-app-security-skill --scope user && claude plugin install web-app-security-skill@web-app-security --scope user

Inside an existing Claude Code session, the equivalent commands are:

/plugin marketplace add parousia8888/web-app-security-skill
/plugin install web-app-security-skill@web-app-security

Verified multi-surface installation

For a checksum-verified multi-surface installation with optional GitHub attestation, the command below installs the skill for Claude Code and Codex, plus the ordinary CLI under ~/.local/bin. Existing installs are refused unless you explicitly pass --force, which creates timestamped backups before replacement. It downloads an immutable bootstrap, verifies its SHA-256 before execution, then verifies the selected release manifest, checksums, SBOM, source commit and archive before installation. Attestation runs when GitHub CLI is installed and authenticated; pass --attestation required to make an unavailable or failed attestation stop installation.

( set -eu; p="$(mktemp "${TMPDIR:-/tmp}/web-app-security-bootstrap.XXXXXX")"; trap 'rm -f "$p"' EXIT HUP INT TERM; curl --proto '=https' --proto-redir '=https' --tlsv1.2 --fail --silent --show-error --location --output "$p" 'https://raw.githubusercontent.com/parousia8888/web-app-security-skill/0d488226ac55036b8871ff12b5572e697ec37bb7/scripts/bootstrap-install.sh?immutable=0d488226ac55036b8871ff12b5572e697ec37bb7'; node -e 'const c=require("node:crypto"),f=require("node:fs"),p=process.argv[1],e=process.argv[2],a=c.createHash("sha256").update(f.readFileSync(p)).digest("hex");if(a!==e){console.error(`bootstrap SHA-256 mismatch: ${a}`);process.exit(1)}' "$p" '0b9c43d22c886f1f5394613800701eeeb1919a858168c5ca678f227ba0306c95'; sh "$p" )

Select a surface when needed:

sh bootstrap-install.sh --target claude
sh bootstrap-install.sh --target codex
sh bootstrap-install.sh --target cli
sh bootstrap-install.sh --target both   # Claude Code + Codex

The shortened examples assume you already downloaded and verified bootstrap-install.sh using the command above. Explicit-version, offline/manual, attestation and trust-anchor details are in verified installation. Supported environments and current limits are recorded in the compatibility matrix.

Check, upgrade, or remove an installation:

webapp-security version
# Run the verified bootstrap with --mode upgrade for a recognized installation.
sh bootstrap-install.sh --mode upgrade
webapp-security uninstall

upgrade replaces only installations carrying a recognized Web App Security Skill marker (or the documented legacy Skill identity), and keeps timestamped backups. uninstall removes recognized current installs but preserves those backups. Unknown directories and launchers are refused even with install --force.

Run the first project

Open the target repository in Claude Code or Codex and send this prompt:

webapp-security start .

This creates a private project identity plus .webapp-security/runs/<run-id>/security-scope.yml, records detected framework, package manager, lockfile and deployment/config paths, and performs no network access. Review the scope, then send:

Use $web-app-security on this repository. Start with source and local checks only. Record scope and assumptions. Classify every result as confirmed, suspected, unknown, or not_applicable. Prepare the smallest reviewable hardening patch, do not apply risky or production changes without approval, retest every applied fix, and finish with fixed, remaining, and unreached risks.

The deterministic source path can then run as:

webapp-security audit .webapp-security/runs/<run-id> --fail-on high
webapp-security explain <finding-id> --report .webapp-security/runs/<run-id>/report.json
webapp-security repair-plan <finding-id> \
  --report .webapp-security/runs/<run-id>/report.json --out ./repair-review
webapp-security start . --run-id <retest-run-id>
webapp-security retest .webapp-security/runs/<retest-run-id> \
  --baseline .webapp-security/runs/<run-id>/report.json

# Review-noise filters for the built-in adapter only
webapp-security audit . --since HEAD~1 --fail-on never
webapp-security audit . --staged --fail-on never

--since excludes untracked files. --staged reads the Git index, not unstaged working-tree content. Neither mode can be combined with external adapters or baseline/retest comparison.

The default is the bundled, network-free source adapter. Optional external adapters are explicit:

webapp-security doctor . --adapter all --json
webapp-security audit . --profile deep --fail-on never

Tested versions are Checkov 3.3.9, Gitleaks 8.30.1, Opengrep 1.27.0 and OSV-Scanner 2.5.0. The CLI and Action do not download them. Checkov runs only three fixed root Dockerfile/GitHub Actions rules with --skip-download; it may query PyPI for version metadata but does not upload project source. Opengrep uses only the bundled, digest-pinned ten-rule local ruleset and makes no network request; OSV-Scanner may query the public OSV database. Project dependencies are not executed. Compose, Terraform, Kubernetes and the rest of Checkov are not stable coverage. A blocking external-adapter run additionally requires --acknowledge-alert-policy after the consuming repository accepts the responsibilities in docs/alert-policy.md. See the adapter protocol for failure, redaction and version semantics.

Each source audit writes v3 JSON, Markdown, HTML, SARIF, JUnit, a SHA-256 sidecar and proposed.patch. Every source finding keeps the professional term and adds plain-language meaning, consequence, evidence limits, a reviewable proposal, side effects, separate security and functional retests, rollback criteria and user decisions. A direct project audit is allowed for one-off review but has ephemeral identity and cannot be a retest baseline. fixed requires the same persisted subject and scope, a compatible rule, completed current coverage and affirmative absence of the condition. The patch is never applied by this command. None of these commands grants permission to probe a deployment.

Reports summarize by risk domain, then evidence state, then severity. The default CI policy gates HIGH confirmed and suspected findings in security_exposure and supply_chain. A suspected lead remains suspected in every artifact; the gate means it needs review before CI can pass, not that exploitability was proved. Use --fail-on never for a non-blocking first report. Existing --fail-on behavior continues to set those two domains; opt into another domain explicitly, for example:

webapp-security crawl --site https://example.com --out ./security-report \
  --fail-on high --fail-on-domain search_discoverability=high

Multiple --fail-on-domain <domain=threshold> options may be combined. Effective thresholds are recorded in the report. The generated rule taxonomy separates source rule kind, family, language, domain, severity, default evidence state and standards. Exact stable source counts and complete explanation metadata come from the machine-readable stable-source-rules.json: 25 built-in risk rules, 3 built-in evidence-integrity rules and 16 external adapter risk rules on main, for 44 stable source and deployment-policy rules. Ten JavaScript/TypeScript and ten Python built-in rules are bounded lexical leads for execution, unsafe browser or framework configuration, transport, authentication/session settings and deserialization; five shared checks cover repository and project configuration. Their exact detection and false-positive boundaries are recorded in the JS/TS and Python decisions. Pattern matches do not prove input flow or runtime reachability and remain suspected until independently reproduced; only a narrow rule-specific observable fact can be confirmed. Per-file token and operation budgets plus a whole-run operation budget bound the built-in lexical analysis. A limit produces source-evidence-incomplete / unknown, partial or unavailable coverage, and recorded effective limits; it is never reported as a clean scan.

Capability boundary

Capabilities use two independent dimensions so support tooling is not counted as vulnerability coverage:

  • Category: Detection; Evidence and reporting; Lifecycle and distribution; or Agent-guided methodology.
  • Maturity: stable, experimental, agent_guided, or planned.

The current stable Detection families are the narrow built-in source audit, opt-in Checkov, Gitleaks, Opengrep and OSV-Scanner adapters, crawl-boundary audit, crawler identity verification, edge verification, and the read-only AWS inventory helper. Project discovery, the demo, report renderers, retest infrastructure, installer, and GitHub Action are tested product capabilities, but are not additional detector families. API authorization, business logic, LLM/OAuth, data-layer and broader AWS reviews remain Agent-guided methodology until a named adapter earns regression evidence.

The generated capability matrix links every category and maturity statement to evidence. Results are confirmed, suspected, unknown, or not_applicable; a check that could not run is never a pass. Installing the Skill does not prove a project secure.

Current detector and workflow constraints are listed in KNOWN_LIMITATIONS.md. The MCP and stable-rule expansion decision is a future gate, not shipped behavior.

Deterministic tools

Ask Claude Code or Codex to use web-app-security, or run the same deterministic tools directly:

# Network-free project discovery and versioned scope
webapp-security start .

# Source-only audit, explain and required-baseline retest
webapp-security audit .webapp-security/runs/<run-id> --fail-on high
webapp-security audit . --since HEAD~1 --fail-on never
webapp-security audit . --staged --fail-on never
webapp-security doctor . --adapter all
webapp-security audit . --profile deep --fail-on never
webapp-security explain <finding-id> --report <report.json>
webapp-security start . --run-id <retest-run-id>
webapp-security retest .webapp-security/runs/<retest-run-id> \
  --baseline <report.json> --fail-on high

# Historical v1 reports stay non-comparable; moved/cloned projects require explicit binding
webapp-security migrate-report <v1-report.json> --scope <security-scope.yml> \
  --acknowledge-subject <subject-id> --out <new-directory>
webapp-security rebind <moved-project> --scope <security-scope.yml> \
  --acknowledge-subject <subject-id>

# Passive crawl-boundary and crawler accessibility audit
webapp-security crawl --site https://example.com --out ./security-report

# Localhost/RFC1918 targets are blocked unless the authorized operator opts in
webapp-security crawl --site http://127.0.0.1:3000 --out ./security-report \
  --allow-private-network

# Active sensitive-path probes require both ownership/written authorization and an explicit gate
webapp-security crawl --site https://example.com --out ./security-report \
  --active-probe --acknowledge-authorization

# Crawler identity: exact product ranges or FCrDNS, never a user-agent string alone
webapp-security verify-crawler --ip 66.249.66.1 --ua Googlebot --ranges

# Passive headers, redirect, certificate and TLS policy verification
webapp-security verify-edge --site https://example.com

# Read-only AWS posture inventory
webapp-security aws --profile default --region us-east-1 --out ./security-report

Active rate-limit verification also requires --acknowledge-authorization. Network or evidence failure is unknown and exits non-zero; it is never rendered as safe. The crawl client stays on the initial origin, validates and pins DNS on every redirect hop, blocks local/private/link-local/reserved destinations by default, and enforces request plus compressed and decoded response-byte budgets. --allow-private-network permits only an explicitly selected localhost/private origin; link-local metadata and reserved ranges remain blocked.

Source conclusions use finding/report v3, including the before/after source reports inside the new demo. Crawl, crawler identity, edge and AWS remain on v2; the demo's small demo-result.json fact schema is separate from either report schema. Both report versions preserve the same coverage, evidence-state, policy and exit-code semantics. Report bundles and their tool-specific observations are sanitized in memory, staged as private files in the target directory, and committed together without overwriting prior evidence. A renderer or handled write failure is rolled back without leaving a partial new bundle. Historical v1 reports remain readable only for display, release verification and explicit non-comparable migration; they are never accepted as a comparable baseline. Compatible persisted v2 source baselines remain readable and are upgraded in memory for v3 comparison without rewriting their bytes.

GitHub Action

The composite Action keeps the v0.3 crawl inputs and outputs. Crawl mode is passive by default and requires deployment authorization acknowledgement:

- name: Audit public crawl boundary
  uses: parousia8888/web-app-security-skill@6e581adcac7a0433ec6428d8080d20761dfc3a93
  with:
    site: https://example.com
    acknowledge-authorization: true
    active-probe: false
    fail-on: high

For repeatable CI, use the immutable v0.8.1 commit above. The signed stable major-version alias now identifies the same v0.8.1 source, and remains intentionally movable:

uses: parousia8888/web-app-security-skill@v1

Source mode defaults to the bundled adapter. The immutable v0.8.1 Action runs the v3 source contract, 25 built-in risk rules, 3 evidence-integrity rules, bounded Express/NestJS/Next.js route and Server Action inventory, and bounded access-control-chain review. External binaries must be installed and pinned by the caller; the Action never downloads them:

- name: Audit source
  uses: parousia8888/web-app-security-skill@6e581adcac7a0433ec6428d8080d20761dfc3a93
  with:
    mode: source
    project: .
    adapters: builtin
    fail-on: high

The moving v1 tag was promoted to v0.8.1 with an exact prior-tag-object lease after the immutable and moving-alias consumers passed. Its tracked state used separate pending and final commits. Review release notes before accepting an update; use the full commit above when the workflow must not move.

Trust and release evidence

  • CI runs Ubuntu/macOS x Node 22/24, deterministic HTTP/HTTPS fixtures and Bash 3.2 smoke tests.
  • Third-party Actions in release and CodeQL workflows are pinned to full commit SHAs.
  • Tagged releases require matching VERSION, changelog and a versioned evidence note. The tag is signed and the release records its source commit.
  • Release assets contain a reproducible source archive, SPDX 2.3 SBOM, SHA256SUMS and GitHub build-provenance attestation. CI builds the archive twice, compares every byte, then runs the lifecycle from the extracted archive in an isolated home with network access denied.
  • Public v0.8.1 verification bound the successful immutable and signed-v1 consumers to final tracked state, required installer attestation, and published one byte-matched live-verification record as a workflow artifact and Release asset (SHA-256 0fae8eaa68bafe35b52e8ed2c3b22b92e49c7b91fc58f9d83e6ed99210f2c6fa).
  • SECURITY.md, threat model, false-positive policy and compatibility matrix make the trust boundary reviewable.

Verify downloaded release assets:

sha256sum -c SHA256SUMS
gh attestation verify web-app-security-skill-*.tar.gz \
  --repo parousia8888/web-app-security-skill
git -c gpg.ssh.allowedSignersFile=.github/release-signers verify-tag v0.8.1

.github/release-signers is a repository-local signer policy: a successful local check proves that the tag matches the policy in the checked-out repository, but it does not independently prove GitHub account ownership. Check GitHub's verification for the exact tag object separately. npm OIDC provenance is a separate signal for the npm package. The exact boundaries and the cross-channel source identity check are in release trust boundaries.

5 ordinary project journeys

The original v0.4.0 journeys preserve the complete v2 built-in/Gitleaks/OSV evidence. The separate v0.5.0 built-in review reruns the same fixed commits through the broader v3 JavaScript/TypeScript and Python rules and manually classifies every finding. No hosted instance or project dependency was executed in either pass.

Project Evidence outcome Manual outcome
Linkwarden v3: 6 suspected 6 expected benign matches after JSDOM, DOMPurify and constant-content review
Healthchecks v3: 5 suspected 4 useful response-encoding leads; 1 expected benign opt-in shell match
Open WebUI v3: 6 suspected; 1 unknown 3 useful leads; 3 expected benign; tokenizer failure stays unknown
Uptime Kuma v3: 4 confirmed facts; 21 suspected 4 useful leads; 17 expected benign; confirmed items are lockfile hygiene, not four app vulnerabilities
Mealie v3: 0 findings No configured pattern matched; this does not establish security

Read the structured v0.5.0 classification and the historical journey method. Confirmed source facts, scanner leads and false-positive outcomes are kept visible; this is not a precision score. Uptime Kuma and Mealie overlap with the methodology corpus below at the same commits, so these are two evidence views rather than ten distinct projects.

The 5 earlier source methodology studies remain as a separate corpus: three intentionally vulnerable benchmarks and two production projects.

Project Evidence outcome
OWASP Juice Shop Confirmed intentional SQL injection plus upstream prepared-statement repair
OWASP NodeGoat Confirmed intentional server-side eval, IDOR and open redirect
DVWA Confirmed low/impossible SQLi, XSS and command-injection control pairs
Uptime Kuma SSRF-shaped outbound sinks closed as product behavior; no vulnerability counted
Mealie URL-fetch lead traced to auth and private-IP guard; no vulnerability counted

Read the method and corpus limits. These are evidence for the methodology, not a fabricated precision score for a CLI that is not yet a general SAST engine.

Program map

Phase Focus Active?
0 Scope, ownership and authorization anchor gate
1 Frontend exposure no
2 API: IDOR/BOLA, auth, limits, races, SSRF yes
3 LLM abuse and OAuth/OIDC yes
4 Server-side source audit source access
5 Database and tenant isolation yes
6 Supply chain, SBOM, SCA and SRI partial
7 Blue-team detection no
8 Report, patch evidence and retest no

Cross-cutting references cover crawl boundaries, verified crawler identity, source-map/dotfile exposure, enforcement placement, AWS hardening, overlooked surfaces, regression gates and safe deployment. Start from SKILL.md.

Contributing

The roadmap separates correctness work from adoption work. New contributors can start from bounded good-first issues, the issue forms, and CONTRIBUTING.md. False-positive reports need a sanitized minimal fixture and expected classification; sensitive details go through private vulnerability reporting.

The generated launch evidence collects only reproducible capability, demo, project-journey, methodology-study and release facts. The publication kit provides evidence-linked drafts and a reusable public/private case-study workflow without claiming that external publication has occurred.

MIT licensed.

Releases

Packages

Used by

Contributors

Languages