From 9443a266e1e3e02248fa13b67c8e99a157042597 Mon Sep 17 00:00:00 2001 From: Dave Jong Date: Thu, 27 Aug 2026 15:51:16 +0200 Subject: [PATCH 1/2] Add repository hygiene, contributor docs, and honest package metadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first tranche of the packaging review: everything that does not change the shape of the published artifact, so the risky items can be done next with a safety net rather than alongside these. ## Metadata The description covered only lockfile scanning, which has been a fraction of what the package does for several releases — it now names the dependency inventory, the attack-surface map, the runtime guard and response hardening. Keywords gained the runtime-security terms someone would actually search (`runtime-security`, `rasp`, `virtual-patching`, `nodejs-security`, `application-security`, plus `waf`, `ssrf`, `secret-redaction`). `publishConfig` is explicit rather than relying on a registry default or an `--access` flag at the call site. A scoped package defaults to restricted, and a publish that quietly succeeds as private is indistinguishable from one that worked until somebody tries to install it. ## Line endings and editors `.gitattributes` pins LF. That matters more here than in most repositories: several committed artifacts are compared BYTE for byte across repositories — the vendored rule contract, the capability manifest, the canary map and rule — and CRLF slipping into one turns a cross-repository agreement check into a diff nobody can read. `*.lockb` is marked binary so nothing tries to normalise Bun's lockfile. `git add --renormalize .` produced no content change, which is the useful result: the tree was already LF-clean, so this is a guard against future drift rather than a mass rewrite hiding in a hygiene commit. `.editorconfig` matches it, and leaves trailing whitespace alone in Markdown: two trailing spaces are a hard line break, and trimming them silently rewrites shipped prose that agents read when auditing the package. `.node-version` records 24 — the version the release workflow builds with. `engines.node` stays `>=18`, which is the consumer contract and a different question. No `export-ignore` rules: `files` in `package.json` remains the single authoritative allowlist of what ships, and a second mechanism that can disagree with it is worse than none. ## Contributor-facing `CONTRIBUTING.md` is the human counterpart to `AGENTS.md` — setup, the loop, what to verify before opening a pull request, and the public-repository boundary in the short form, including that third-party confidential material is a separate obligation rather than a special case. It also tabulates the generated artifacts that must stay in step with their sources, because changing one without regenerating leaves two files disagreeing and the check that notices is not always the one that fails first. The bug-report form asks for the versions and reproduction that make a report actionable — Connect, Node, package manager, framework, OS — and requires confirming the report carries no credentials, customer data, third-party proprietary material or private implementation details. Blank issues are disabled so that prompt cannot be bypassed: this repository is public, and a pasted log with a token in it is not recoverable by deleting the issue. Nothing new ships: `npm pack --dry-run` still reports 39 files, 995 kB packed. Co-Authored-By: Claude Opus 5 (1M context) --- .editorconfig | 20 ++++++ .gitattributes | 9 +++ .github/ISSUE_TEMPLATE/bug-report.yml | 100 ++++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 14 ++++ .node-version | 1 + CONTRIBUTING.md | 81 +++++++++++++++++++++ package.json | 19 ++++- 7 files changed, 241 insertions(+), 3 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/ISSUE_TEMPLATE/bug-report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .node-version create mode 100644 CONTRIBUTING.md diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..a0c6b2a7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# Shared with .gitattributes: LF, and a final newline, because committed artifacts are compared byte for +# byte and an editor's default is not a good reason for a fixture to differ. +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +[*.md] +# Two trailing spaces are a hard line break in Markdown. Trimming them silently rewrites shipped prose — +# and README.md and AGENT-INSTALL.md are read by agents auditing the package. +trim_trailing_whitespace = false + +[*.{lockb}] +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..475ffc79 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# One line ending in the repository, whatever a contributor's platform does locally. +# +# This matters more here than in most repositories: several committed artifacts are compared BYTE for byte +# — the vendored rule contract, the capability manifest, the canary map and rule — and CRLF slipping into +# one of them turns a cross-repository agreement check into a diff nobody can read. +* text=auto eol=lf + +# Bun's binary lockfile. Marked so a diff never mangles it and `text=auto` never guesses. +*.lockb binary diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 00000000..602bc119 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,100 @@ +name: Bug report +description: Something in @patchstack/connect does not work as documented. +labels: [bug] +body: + - type: markdown + attributes: + value: | + **This repository is public, and so is everything you paste here.** + + Before you fill this in: redact tokens, site UUIDs, customer hostnames and internal URLs. A + deleted issue is not a retracted one — it stays in notification emails, caches and forks. + + Found a security vulnerability in this package? Do not file it here. Contact Patchstack directly so + it can be fixed before it is described in public. + + - type: input + id: connect-version + attributes: + label: "@patchstack/connect version" + description: "`npx @patchstack/connect --version`, or the version in your lockfile." + placeholder: "0.3.31" + validations: + required: true + + - type: input + id: node-version + attributes: + label: Node version + description: "`node --version`. Supported from 18." + placeholder: "v22.12.0" + validations: + required: true + + - type: input + id: package-manager + attributes: + label: Package manager and version + description: npm, pnpm, Yarn or Bun — the resolution differs between them, and so do the bugs. + placeholder: "pnpm 9.12.0" + validations: + required: true + + - type: input + id: framework + attributes: + label: Framework or runtime + description: Next.js, SvelteKit, Astro, NestJS, Fastify, Express, TanStack Start, Cloudflare Workers, Deno, Bun — and its version. + placeholder: "Next.js 15.1.0 (app router), Node runtime" + validations: + required: true + + - type: input + id: os + attributes: + label: Operating system + placeholder: "macOS 15.1 (arm64) / Ubuntu 24.04 / Windows 11" + validations: + required: true + + - type: textarea + id: reproduction + attributes: + label: Steps to reproduce + description: The commands you ran, in order. A repository or a minimal project that shows it is worth more than a description. + placeholder: | + 1. npm install --save-dev @patchstack/connect + 2. npx patchstack-connect scan + 3. ... + validations: + required: true + + - type: textarea + id: expected + attributes: + label: What you expected + description: And where it says so — the README, AGENT-INSTALL.md, the CLI's own output. A documented promise that is wrong is a different bug from a surprising behaviour. + validations: + required: true + + - type: textarea + id: actual + attributes: + label: What happened instead + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Output and logs, sanitized + description: Command output, stack traces, the `patchstack:` lines. Replace tokens, site UUIDs and hostnames with placeholders. + render: shell + + - type: checkboxes + id: disclosure + attributes: + label: Before you submit + options: + - label: This report contains no credentials or tokens, no customer data, no third-party proprietary material such as copied system prompts, and no private implementation details. + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..6d583454 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,14 @@ +# Blank issues are disabled deliberately. +# +# The bug-report form asks for the versions and the reproduction that make a report actionable, and asks +# the reporter to confirm it carries nothing private. A blank issue bypasses both prompts, and this +# repository is public: the cost of a pasted log with a token or a customer hostname in it is not +# recoverable by deleting the issue. +blank_issues_enabled: false +contact_links: + - name: Security vulnerability in this package + url: https://patchstack.com/contact/ + about: Do not open a public issue. Contact Patchstack directly so it can be fixed before it is described. + - name: Help with your Patchstack account, sites or billing + url: https://patchstack.com/contact/ + about: Anything about your account rather than this package. diff --git a/.node-version b/.node-version new file mode 100644 index 00000000..a45fd52c --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +24 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..1a3902e0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,81 @@ +# Contributing to `@patchstack/connect` + +The human counterpart to `AGENTS.md`. Same boundary, same verification; this one explains the setup. + +## Before anything else: this repository is public + +Its history, code comments, test output, commit messages and pull-request descriptions are all public, and +so is anything you paste into an issue. + +`AGENTS.md` carries the boundary in full and is worth reading once. The two rules that catch people: + +- **Nothing from private Patchstack systems** unless it is already in this repository's public + documentation or public API contract — no private repository names or paths, no internal topology or + endpoints, no customer or vulnerability-review data, no local filesystem paths. +- **Nothing third-party confidential**, which is a separate obligation and not a special case of the + first: copied system prompts, private transcripts, unpublished documentation. Material can be entirely + free of Patchstack internals and still not be ours to publish. Verify that something is public rather + than assuming it. + +Fixtures are synthetic or explicitly public. Do not copy one from a private repository to make a test more +realistic — write it. + +## Setup + +```bash +nvm use # reads .node-version (24). Consumers are supported from 18; see engines.node. +npm ci +``` + +## The loop + +```bash +npm run typecheck # tsc --noEmit, plus the guard templates, which are typechecked separately +npm test # vitest +npm run build # tsup -> dist/ (gitignored; built on publish) +``` + +`dist/` is not committed. A few tests only run once it exists — they skip in a plain checkout and are +required in CI, so run `npm run build` before trusting a green local run of the whole suite. + +## Before you open a pull request + +- `npm run typecheck`, `npm test`, `npm run build` all clean. +- `npm pack --dry-run` if you touched anything about packaging. `files` in `package.json` is the + authoritative allowlist of what ships. +- Read your own diff, commit messages and pull-request description against the boundary above. The + pull-request template asks you to confirm this; it is the only control that covers prose, because secret + scanning finds credentials and nothing finds a paragraph of architecture. + +Changing onboarding, the install prompt or the setup guide? Read `MAINTAINING.md` first — it maps which +files are load-bearing — and note that the install prompt is a tested artifact with its own gate in +`field-test/`. + +## Generated artifacts that must stay in step + +Each of these is generated from a source of truth and committed. Changing the source without regenerating +leaves two files disagreeing, and the check that notices is not always the one that fails first: + +| artifact | generated from | regenerate with | +|---|---|---| +| `capabilities.json` | `src/map/capabilities.ts` | `node scripts/emit-capabilities.mjs` | +| `rule-contract.json` | the engine's match types and actions | see `src/protect/rules/contract.js` | +| the canary attack-surface map | `tests/map/canary-case.ts` | `PS_CANARY_EMIT_DIR= npx vitest run tests/map/canary-emit` | + +A vocabulary change to `capabilities.json` also needs `CAPABILITY_VERSION` moved; CI checks that. + +## Comments and prose + +- Comments describe the code that is there, not the code that used to be, and not how a fix was reasoned + about. That belongs in the pull request. +- No ticket references anywhere in the tree. They go in the pull-request title and description. +- Plain words, short sentences. Lead with what changed and why it matters. + +## Reporting a bug + +Use the bug-report form, which asks for the versions and the reproduction that make a report actionable — +and asks you to confirm it carries no credentials, customer data or private implementation details. If a +report cannot be written without those, contact Patchstack support instead of filing it here. + +Think you have found a security vulnerability in this package? Do not open an issue — see `SECURITY.md` +if present, or contact Patchstack support directly. diff --git a/package.json b/package.json index 17ccb8b7..c9cb4c7d 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,24 @@ { "name": "@patchstack/connect", "version": "0.3.6", - "description": "Patchstack connector for JavaScript applications. Scans your lockfile and reports installed packages to Patchstack for vulnerability monitoring.", + "description": "Runtime application security for JavaScript and Node.js: dependency inventory, attack-surface mapping, and an in-process guard that virtually patches known vulnerabilities and hardens responses.", "keywords": [ "patchstack", "security", + "application-security", + "nodejs-security", + "runtime-security", + "rasp", + "virtual-patching", + "waf", "vulnerability", "cve", - "npm-audit", "sca", - "dependency-scanning" + "dependency-scanning", + "npm-audit", + "attack-surface", + "ssrf", + "secret-redaction" ], "license": "MIT", "type": "module", @@ -61,6 +70,10 @@ "engines": { "node": ">=18" }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, "devDependencies": { "@types/node": "^20.11.0", "fastify": "^5.12.1", From b60d84af01a4978c7cf1f0e9b83db3d963ded4d5 Mon Sep 17 00:00:00 2001 From: Dave Jong Date: Thu, 27 Aug 2026 16:13:26 +0200 Subject: [PATCH 2/2] Correct the contributor instructions and the report form MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six corrections, all to prose that stated something untrue. `CONTRIBUTING.md` told contributors that how a fix was reasoned about belongs in the pull request. A pull request is public, and `AGENTS.md` places that material in the private engineering record. The section now says what it should: a comment and a public pull-request description state the current invariant and the externally observable reason it matters; investigation history is kept privately. Setup said `nvm use` reads `.node-version`. nvm looks for `.nvmrc`. The command is now `nvm use "$(cat .node-version)"`, with fnm and nodenv noted as reading the file directly, and one file stays the source of truth. Consumer support from Node 18 is called out as a separate contract. The loop ran the tests before the build. Several tests only run once `dist/` exists, so in that order they skip — which is indistinguishable from passing. Build first. The generated-artifact table pointed at a source file for the rule contract instead of naming `npm run rule-contract`, and the two check commands CI runs are now named. In the bug-report form, `npx @patchstack/connect --version` prints the help text, because no version flag exists; it asks for `npm ls @patchstack/connect --depth=0`. The reproduction example installed with `--save-dev`, while the runtime guard is imported in production and deployments prune dev dependencies — it matches the README's `--save`. Every command in the changed text was run: `nvm use "$(cat .node-version)"` resolves 24, `npm run rule-contract:check` and `npm run capabilities:check` exit 0, and `npm ls` prints the version from a consumer project. Co-Authored-By: Claude Opus 5 (1M context) --- .github/ISSUE_TEMPLATE/bug-report.yml | 6 +++--- CONTRIBUTING.md | 26 ++++++++++++++++++-------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 602bc119..01e6cc66 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -17,7 +17,7 @@ body: id: connect-version attributes: label: "@patchstack/connect version" - description: "`npx @patchstack/connect --version`, or the version in your lockfile." + description: "`npm ls @patchstack/connect --depth=0` in your project, or the version in your lockfile." placeholder: "0.3.31" validations: required: true @@ -63,8 +63,8 @@ body: label: Steps to reproduce description: The commands you ran, in order. A repository or a minimal project that shows it is worth more than a description. placeholder: | - 1. npm install --save-dev @patchstack/connect - 2. npx patchstack-connect scan + 1. npm install --save @patchstack/connect + 2. npx patchstack-connect setup 3. ... validations: required: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1a3902e0..3d3bedd9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,20 +23,26 @@ realistic — write it. ## Setup ```bash -nvm use # reads .node-version (24). Consumers are supported from 18; see engines.node. +# .node-version records the version releases are built with (24). fnm, nodenv and asdf read it directly; +# nvm looks for .nvmrc, so it needs the file passed in. +nvm use "$(cat .node-version)" # or: fnm use / nodenv local npm ci ``` +Consumers are supported from Node 18 — `engines.node` is that contract, and it is a different question +from the version this repository is developed and released on. + ## The loop ```bash npm run typecheck # tsc --noEmit, plus the guard templates, which are typechecked separately -npm test # vitest npm run build # tsup -> dist/ (gitignored; built on publish) +npm test # vitest ``` -`dist/` is not committed. A few tests only run once it exists — they skip in a plain checkout and are -required in CI, so run `npm run build` before trusting a green local run of the whole suite. +Build before testing, in that order. `dist/` is not committed, and several tests only run once it exists — +they skip in a plain checkout and are required in CI. Run in the other order and those tests skip, which +reads exactly like passing. ## Before you open a pull request @@ -59,15 +65,19 @@ leaves two files disagreeing, and the check that notices is not always the one t | artifact | generated from | regenerate with | |---|---|---| | `capabilities.json` | `src/map/capabilities.ts` | `node scripts/emit-capabilities.mjs` | -| `rule-contract.json` | the engine's match types and actions | see `src/protect/rules/contract.js` | +| `rule-contract.json` | the engine's match types and actions | `npm run rule-contract` | | the canary attack-surface map | `tests/map/canary-case.ts` | `PS_CANARY_EMIT_DIR= npx vitest run tests/map/canary-emit` | -A vocabulary change to `capabilities.json` also needs `CAPABILITY_VERSION` moved; CI checks that. +A vocabulary change to `capabilities.json` also needs `CAPABILITY_VERSION` moved. `npm run +capabilities:check` and `npm run rule-contract:check` are what CI runs. ## Comments and prose -- Comments describe the code that is there, not the code that used to be, and not how a fix was reasoned - about. That belongs in the pull request. +- Comments describe the code that is there and the invariant it holds — not the code that used to be, and + not how a fix was reasoned about. +- **The same applies to a pull-request description**, which is public. State what changed and the + externally observable reason it matters. "What used to happen", "how we found it" and the mutations you + tried belong in the private engineering record, not here — see the boundary in `AGENTS.md`. - No ticket references anywhere in the tree. They go in the pull-request title and description. - Plain words, short sentences. Lead with what changed and why it matters.