chore: canonical npm name, OIDC publish workflow, release docs - #53
Conversation
Rename remaining `pharn-cli` -> `pharn` references across docs, config, dev artifacts, and source comments: repo/bugs/homepage URLs in package.json, plus README, THREAT-MODEL, LIMITS, SECURITY, CHANGELOG, CONSTITUTION, CONTRIBUTING, docs/, and .dev/ plan artifacts. Set publishConfig.provenance to false so `npm publish` runs from a local machine. npm's automatic provenance requires a supported CI provider (GitHub Actions / GitLab OIDC) and aborts with "provider: null" when run locally. A follow-up CI workflow re-enables provenance via an explicit --provenance flag that overrides this default. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe PR aligns the repository and feature records from ChangesProduct naming and public documentation
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…ments Rename the npm package `pharn` -> the scoped `@pharn-dev/pharn` across all user-facing surfaces: package.json `name`, README (badge + npx install), docs/, CONTRIBUTING, SECURITY, CHANGELOG, the GitHub issue templates, the `pharn -h` USAGE text (src/index.ts), and the git-prereq error message (src/steps/prereqs.ts). The installed bin stays `pharn` (unscoped); only the npm package identity is scoped. Also correct stale in-code comments left by the module-model removal: src/commands/init.ts (no manifest fallback), src/lib/constitution.ts (test-only now that wizard.ts is gone), and src/lib/repo.ts (capability index, not manifest.json). Fix a CHANGELOG garble the earlier repo-wide pharn-cli->pharn rename introduced (`pharn` -> `pharn`), restoring the historical `pharn-cli`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Publish to npm on a GitHub Release (or manual dispatch). Uses OIDC (id-token: write) + `npm publish --provenance` for a signed provenance attestation; the flag overrides package.json's `provenance: false`, so CI gets provenance while local `npm publish` stays functional. Requires an NPM_TOKEN repository secret (npm automation/granular token). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
89c5f88 to
815acf2
Compare
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
CONSTITUTION.md (1)
20-24: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winAlign capability-document wording with the install artifacts.
README.mddescribes capabilities as the install unit and says there’s no module catalog/manifest.json, butCONSTITUTION.mdandLIMITS.mdstill saypharnfetches manifests/module manifests and installs modules. These security-document sections should use the README’s capability-frontmatter/artifact wording, otherwise the same trust boundary is documented in two different systems.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@CONSTITUTION.md` around lines 20 - 24, The capability-document wording is inconsistent with README.md: update CONSTITUTION.md lines 20-24 and LIMITS.md lines 21-31 to describe capability frontmatter and install artifacts as the trust boundary, removing references to manifests, module manifests, module catalogs, and module-based installation while preserving the stated security principles and limitations.
🧹 Nitpick comments (1)
CLAUDE.md (1)
48-49: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDocument the fetch invariant for
repo.tstoo.This section names only
skills-version.ts, but the repository rule applies to bothsrc/lib/skills-version.tsandsrc/lib/repo.ts;repo.tsowns the commit-metadata fetch. List both files so future edits preserve redirect rejection, the 8-second timeout, and the 256 KB body cap.As per coding guidelines, remote fetches in
src/lib/{skills-version,repo}.tsmust use redirect errors, an 8-second timeout, and a 256 KB body limit.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@CLAUDE.md` around lines 48 - 49, Update the remote-fetch invariant documentation in CLAUDE.md to name both skills-version.ts and repo.ts, explicitly requiring redirect rejection, an 8-second timeout, and a 256 KB response-body cap for each. Preserve the existing validation and path-containment guidance.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.dev/features/canonical-npm-name/PLAN.md:
- Around line 1-5: Reconcile the verification records with the current scoped
package identity: in .dev/features/canonical-npm-name/PLAN.md lines 1-5, replace
the unscoped pharn objective or mark the plan superseded; in
.dev/features/canonical-npm-name/GRILL.md lines 29-34, update tarball and
package assertions to `@pharn-dev/pharn`; in
.dev/features/canonical-npm-name/REVIEW.md lines 1-5, align the reviewed rename
direction with the scoped identity; and in
.dev/features/npm-publish-metadata/SHIP.md line 21, replace the pharn→pharn
statement with the actual `@pharn-dev/pharn` rename.
In @.dev/features/canonical-npm-name/SHIP.md:
- Around line 3-4: Correct the ship record’s package identity and release
direction: update the increment description to rename `pharn-cli` to
`@pharn-dev/pharn` while preserving the installed `pharn` binary, and remove the
unscoped-package/deprecation guidance. If this is stale historical output rather
than an active record, remove the artifact instead.
In @.dev/features/canonical-npm-name/verify-report.json:
- Around line 8-12: Update the verification artifact and its underlying
validation flow so the validate gate passes rather than leaving verdict as FAIL;
exclude generated test-*/ fixtures from validation as appropriate. Ensure the
resulting verify.verdict is PASS and failing_gates is empty, preserving the
.dev/floor/check-ship.mjs contract and requiring all CI gates to pass.
In @.dev/features/npm-publish-metadata/PLAN.md:
- Around line 10-13: The plan should use the scoped npm package name
`@pharn-dev/pharn` throughout package metadata and installation instructions,
while retaining pharn as the executable/bin name. Update the package.json,
README.md, CHANGELOG.md, and CLAUDE.md entries to replace unscoped package
references and change the npx command to target `@pharn-dev/pharn`.
- Around line 24-27: Align the repository metadata claim with the actual package
configuration: in .dev/features/npm-publish-metadata/PLAN.md lines 24-27, either
update the documented repository URL to pharn-dev/pharn-cli or change
package.json consistently to pharn-dev/pharn; in
.dev/features/npm-publish-metadata/GRILL.md line 51, remove the “EXACT”
assertion unless the package metadata is updated to match.
In @.github/workflows/publish.yml:
- Around line 3-8: Add a pre-publish validation step in the publish workflow
that compares the GitHub Release tag (removing the v prefix as needed) with the
version in package.json and fails on mismatch. Restrict workflow_dispatch
publishing to the intended release ref, while preserving the existing
release-triggered publishing behavior.
- Around line 21-24: Update the actions/setup-node configuration in the
privileged publish job to disable automatic package-manager caching by setting
package-manager-cache to false, and remove the cache: npm setting. Leave
dependency caching enabled only in the non-publishing CI jobs.
In `@CHANGELOG.md`:
- Around line 96-97: Update the initial release note to remove the duplicate bin
names, restoring the historical second binary name or clearly stating that only
the pharn binary is exposed.
In `@SECURITY.md`:
- Around line 3-9: The security documentation understates the installation write
surface. In SECURITY.md, update the description to cover .claude/, the mirrored
capability layout, root product surfaces, and pharn.config.json; in
THREAT-MODEL.md, expand Surface A beyond .claude/ to include every installed
location. Preserve the existing remote-input and safe-write security model while
ensuring both documents describe the current layout consistently.
---
Outside diff comments:
In `@CONSTITUTION.md`:
- Around line 20-24: The capability-document wording is inconsistent with
README.md: update CONSTITUTION.md lines 20-24 and LIMITS.md lines 21-31 to
describe capability frontmatter and install artifacts as the trust boundary,
removing references to manifests, module manifests, module catalogs, and
module-based installation while preserving the stated security principles and
limitations.
---
Nitpick comments:
In `@CLAUDE.md`:
- Around line 48-49: Update the remote-fetch invariant documentation in
CLAUDE.md to name both skills-version.ts and repo.ts, explicitly requiring
redirect rejection, an 8-second timeout, and a 256 KB response-body cap for
each. Preserve the existing validation and path-containment guidance.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 883be253-0cb0-4632-a400-bb3af6d26fe4
📒 Files selected for processing (76)
.claude/commands/pharn-dev-build.md.claude/hooks/enforce-writes-scope.cjs.claude/hooks/enforce-writes-scope.test.cjs.dev/features/archetype-enum-align/PLAN.md.dev/features/archetype-file-tree-scan/PLAN.md.dev/features/archetype-io-boundary/PLAN.md.dev/features/archetype-missing-signal/PLAN.md.dev/features/archetype-path-context/PLAN.md.dev/features/canonical-npm-name/GRILL.md.dev/features/canonical-npm-name/PLAN.md.dev/features/canonical-npm-name/REGRESSION.md.dev/features/canonical-npm-name/REVIEW.md.dev/features/canonical-npm-name/SHIP.md.dev/features/canonical-npm-name/VERIFY.md.dev/features/canonical-npm-name/regression-report.json.dev/features/canonical-npm-name/verify-report.json.dev/features/capability-resolver/PLAN.md.dev/features/commands-off-manifest/PLAN.md.dev/features/commands-off-manifest/REVIEW.md.dev/features/commands-off-manifest/SHIP.md.dev/features/commands-off-manifest/VERIFY.md.dev/features/commands-off-manifest/verify-report.json.dev/features/config-loader-honest-errors/GRILL.md.dev/features/config-loader-honest-errors/PLAN.md.dev/features/fresh-check-git-rce/PLAN.md.dev/features/harden-install-path/GRILL.md.dev/features/harden-install-path/PLAN.md.dev/features/harden-install-path/REVIEW.md.dev/features/harden-install-path/VERIFY.md.dev/features/init-archetype-default/PLAN.md.dev/features/init-archetype-default/REVIEW.md.dev/features/init-install-capabilities/GRILL.md.dev/features/init-install-capabilities/PLAN.md.dev/features/installer-layout-mirror/PLAN.md.dev/features/installer-layout-mirror/SHIP.md.dev/features/list-archetype-aware/PLAN.md.dev/features/model-routing-config/PLAN.md.dev/features/npm-publish-metadata/GRILL.md.dev/features/npm-publish-metadata/PLAN.md.dev/features/npm-publish-metadata/REGRESSION.md.dev/features/npm-publish-metadata/SHIP.md.dev/features/npm-publish-metadata/VERIFY.md.dev/features/regress/PLAN.md.dev/features/remove-dead-docs-url/GRILL.md.dev/features/remove-dead-docs-url/PLAN.md.dev/features/remove-dead-docs-url/REGRESSION.md.dev/features/remove-vendor-skill/PLAN.md.dev/features/remove-vendor-skill/REGRESSION.md.dev/floor/lens-scanner-map.json.dev/floor/lens-scanner-map.test.mjs.github/ISSUE_TEMPLATE/bug_report.md.github/ISSUE_TEMPLATE/feature_request.md.github/PULL_REQUEST_TEMPLATE.md.github/workflows/publish.yml.pharn/pharn-dev-regress/base-results.json.pharn/pharn-dev-regress/head-results.json.pharn/pharn-dev-verify/results.json.pharn/writes-scope.jsonCHANGELOG.mdCLAUDE.mdCODE_OF_CONDUCT.mdCONSTITUTION.mdCONTRIBUTING.mdLIMITS.mdREADME.mdSECURITY.mdTHREAT-MODEL.mddocs/README.mddocs/commands/init.mddocs/contributing.mddocs/getting-started.mdpackage.jsonsrc/commands/init.tssrc/lib/constitution.tssrc/lib/repo.tssrc/types.ts
| # PLAN — canonical-npm-name (rename npm package `@pharn-dev/pharn` → `pharn`) | ||
|
|
||
| - spec_content_hash: bca940a5ad247c120e6d8a3acba119d0d8df51dca275964d0e54c48d729d3c4e # fix #4 (ARCHITECTURE.md) | ||
| - increment: Publish the package under the canonical **unscoped** name `pharn` by setting `package.json.name` and aligning every npm-name reference in the docs/repo surface — no version bump, no CLI behavior change. | ||
| - layer(s): packaging (`package.json`) + docs/repo-surface (`*.md`, `docs/**`, `.github/ISSUE_TEMPLATE/**`) — **no `src/` code-behavior layer** (ARCHITECTURE.md §4). See OQ1 for the one src edge case. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Reconcile the package identity in the verification records.
These artifacts describe publishing the unscoped pharn package, while this PR targets scoped @pharn-dev/pharn. That mismatch can mislead release operators and invalidate the recorded tarball/package checks. Update the records to the scoped identity, or clearly mark them as superseded historical artifacts.
.dev/features/canonical-npm-name/PLAN.md#L1-L5: replace the unscoped package objective or mark this plan superseded..dev/features/canonical-npm-name/GRILL.md#L29-L34: update the tarball/package assertions to match the scoped package..dev/features/canonical-npm-name/REVIEW.md#L1-L5: align the reviewed rename direction with the current package identity..dev/features/npm-publish-metadata/SHIP.md#L21-L21: correct the nonsensicalpharn→pharnmetadata statement and record the actual rename.
🧰 Tools
🪛 LanguageTool
[uncategorized] ~5-~5: The official name of this software platform is spelled with a capital “H”.
Context: ...+ docs/repo-surface (*.md, docs/**, .github/ISSUE_TEMPLATE/**) — **no src/ code-...
(GITHUB)
📍 Affects 4 files
.dev/features/canonical-npm-name/PLAN.md#L1-L5(this comment).dev/features/canonical-npm-name/GRILL.md#L29-L34.dev/features/canonical-npm-name/REVIEW.md#L1-L5.dev/features/npm-publish-metadata/SHIP.md#L21-L21
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.dev/features/canonical-npm-name/PLAN.md around lines 1 - 5, Reconcile the
verification records with the current scoped package identity: in
.dev/features/canonical-npm-name/PLAN.md lines 1-5, replace the unscoped pharn
objective or mark the plan superseded; in
.dev/features/canonical-npm-name/GRILL.md lines 29-34, update tarball and
package assertions to `@pharn-dev/pharn`; in
.dev/features/canonical-npm-name/REVIEW.md lines 1-5, align the reviewed rename
direction with the scoped identity; and in
.dev/features/npm-publish-metadata/SHIP.md line 21, replace the pharn→pharn
statement with the actual `@pharn-dev/pharn` rename.
| Increment: rename the npm package `@pharn-dev/pharn` → `pharn` (canonical unscoped name), no version | ||
| bump, no behavior change. Run via `/pharn-dev-ship` (gated mode). |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Correct the package identity in this ship record.
This record says the package is changing from @pharn-dev/pharn to unscoped pharn, and advises deprecating the scoped package. The PR objective is the opposite: pharn-cli → @pharn-dev/pharn, with the installed binary remaining pharn. Update this artifact to describe the scoped publish, or remove it if it is stale historical output; otherwise the active ship record can mislead release and deprecation steps.
Also applies to: 45-47
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.dev/features/canonical-npm-name/SHIP.md around lines 3 - 4, Correct the
ship record’s package identity and release direction: update the increment
description to rename `pharn-cli` to `@pharn-dev/pharn` while preserving the
installed `pharn` binary, and remove the unscoped-package/deprecation guidance.
If this is stale historical output rather than an active record, remove the
artifact instead.
| "validate": 1 | ||
| }, | ||
| "verdict": "FAIL", | ||
| "failing_gates": [ | ||
| "validate" |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Do not leave the ship verification verdict as FAIL.
The ship helper computes a green floor only when verify.verdict === "PASS". With validate: 1, this artifact blocks shipping despite describing the failure as pre-existing. Either make validate pass by excluding the generated test-*/ fixtures, or update the ship policy and report contract to explicitly accept pre-existing failures.
Based on the supplied .dev/floor/check-ship.mjs contract, floorGreen requires a PASS verification verdict. Based on learnings, all CI gates must pass.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.dev/features/canonical-npm-name/verify-report.json around lines 8 - 12,
Update the verification artifact and its underlying validation flow so the
validate gate passes rather than leaving verdict as FAIL; exclude generated
test-*/ fixtures from validation as appropriate. Ensure the resulting
verify.verdict is PASS and failing_gates is empty, preserving the
.dev/floor/check-ship.mjs contract and requiring all CI gates to pass.
Source: Learnings
| - `package.json` — npm publish metadata (see concrete values below): `name` pharn→**pharn**, `description`, `keywords`, `repository`/`bugs`/`homepage`, `bin`→`{pharn}`, `publishConfig{access,provenance}`, `scripts.prepublishOnly` + `scripts.prepack`, `engines` (decision Q1). `version` **stays 0.2.0** (metadata, not behavior). — layer: repo packaging | ||
| - `README.md` — add an **Install** section (`npx pharn@latest init`); update the npm badge `pharn`→`pharn` and the "npm package is `pharn` / both bins" note (line ~43) to the new name + single bin. **No** module-prose rewrite (see Q4). — layer: docs | ||
| - `CHANGELOG.md` — one `[Unreleased]` entry: rename to `pharn` + publish-readiness metadata, Keep-a-Changelog format. — layer: docs | ||
| - `CLAUDE.md` — update the "Published as `pharn-cli`, exposing both `pharn` and `pharn-cli` bins" line to the new `pharn` package name + single bin. — layer: project guidance | ||
| - `CLAUDE.md` — update the "Published as `pharn`, exposing both `pharn` and `pharn` bins" line to the new `pharn` package name + single bin. — layer: project guidance |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Use the scoped npm package name from the PR contract.
The objective requires @pharn-dev/pharn; this plan still describes the package as unscoped pharn and implies npx pharn@latest. Keep the executable name pharn, but use the scoped package name in metadata and install instructions.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.dev/features/npm-publish-metadata/PLAN.md around lines 10 - 13, The plan
should use the scoped npm package name `@pharn-dev/pharn` throughout package
metadata and installation instructions, while retaining pharn as the
executable/bin name. Update the package.json, README.md, CHANGELOG.md, and
CLAUDE.md entries to replace unscoped package references and change the npx
command to target `@pharn-dev/pharn`.
| - `repository`: `{ "type": "git", "url": "git+https://github.com/pharn-dev/pharn.git" }` (EXACT — provenance attestation validates this against the building repo) | ||
| - `bugs`: `{ "url": "https://github.com/pharn-dev/pharn/issues" }` | ||
| - `homepage`: `"https://github.com/pharn-dev/pharn#readme"` | ||
| - `bin`: `{ "pharn": "dist/index.js" }` (Q2 — drop the `pharn` alias) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Align repository metadata claims with the actual package configuration.
The plan and grill record pharn-dev/pharn as the exact repository URL, but the supplied package.json evidence still points to pharn-dev/pharn-cli. This must be either an intentional metadata migration or an inaccurate verification record.
.dev/features/npm-publish-metadata/PLAN.md#L24-L27: update the concrete metadata claim or changepackage.jsonconsistently..dev/features/npm-publish-metadata/GRILL.md#L51-L51: remove the “EXACT” assertion until it matches the actual package metadata.
📍 Affects 2 files
.dev/features/npm-publish-metadata/PLAN.md#L24-L27(this comment).dev/features/npm-publish-metadata/GRILL.md#L51-L51
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.dev/features/npm-publish-metadata/PLAN.md around lines 24 - 27, Align the
repository metadata claim with the actual package configuration: in
.dev/features/npm-publish-metadata/PLAN.md lines 24-27, either update the
documented repository URL to pharn-dev/pharn-cli or change package.json
consistently to pharn-dev/pharn; in .dev/features/npm-publish-metadata/GRILL.md
line 51, remove the “EXACT” assertion unless the package metadata is updated to
match.
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v6 | ||
| with: | ||
| node-version: 20 | ||
| cache: npm |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== workflow files =="
git ls-files .github/workflows || true
echo
echo "== publish workflow =="
if [ -f .github/workflows/publish.yml ]; then
cat -n .github/workflows/publish.yml
fi
echo
echo "== zizmor references / secrets usage in publish workflow =="
rg -n "npm_cache|cache|NPM_TOKEN|npm publish|setup-node|permissions|contents|contents:|github-token|id-token|GITHUB_TOKEN|zizmor" .github/workflows/publish.yml || true
echo
echo "== all github token / npm cache references in workflows =="
rg -n "github-token:|GITHUB_TOKEN|NPM_TOKEN|npm_cache|actions/cache|setup-node|permissions:" .github/workflows || trueRepository: pharn-dev/pharn-cli
Length of output: 3071
🌐 Web query:
zizmor npm cache poisoning setup-node cache npm publish NPM_TOKEN
💡 Result:
The connection between zizmor, npm, and cache poisoning arises primarily from the default behavior of actions/setup-node (v5 and later), which automatically caches package manager dependencies [1][2][3]. If a repository's cache is poisoned by a low-privileged workflow—such as one triggered by an untrusted event—this poisoned cache can be consumed by a high-privileged release workflow, potentially leading to the injection of malicious code into a published npm package [4][3][5]. Zizmor, a static analysis tool for GitHub Actions, includes a cache-poisoning audit specifically designed to detect this risk [1][6][7]. Key security considerations and mitigations include: 1. Disable Automatic Caching in Sensitive Workflows: For workflows with elevated privileges (such as those performing npm publishing), it is strongly recommended to explicitly disable automatic npm caching by setting package-manager-cache: false in your actions/setup-node step [4][2][8][3]. 2. Use Trusted Publishing: Avoid manual npm tokens (NPM_TOKEN) in CI/CD secrets whenever possible [9][10]. Instead, use npm's Trusted Publishing mechanism, which allows GitHub Actions to securely authenticate with npm via short-lived, OIDC-based identity tokens [9][5]. Zizmor can detect and flag workflows that still rely on manual tokens despite having the necessary permissions for trusted publishing [9]. 3. GitHub Actions Cache Security: GitHub has implemented improvements to mitigate cache-based privilege escalation, including issuing read-only cache tokens for untrusted triggers on the default branch [11]. However, this does not eliminate the need for proper configuration in your own workflows [3]. 4. Zizmor Usage: Zizmor automatically scans for these patterns [7][12]. If you encounter a false positive or have unique requirements, you can review its audit rules [13] and documentation [14]. Zizmor also tracks various npm publishing patterns to ensure they align with security best practices [9][10]. For further guidance, consult the official documentation for actions/setup-node regarding cache poisoning mitigation [8] and the zizmor documentation for audit specifics [14][13].
Citations:
- 1: [BUG]:
cache-poisoningrule doesn't support breaking change ofactions/setup-nodev5 zizmorcore/zizmor#1152 - 2: docs: Update caching recommendations to mitigate cache poisoning risks actions/setup-node#1567
- 3: npm/documentation@cad232f
- 4: ci: disable setup-node caching in JS release workflow grafana/augurs#517
- 5: https://github.com/lullu57/gh-actions-demo-cache-poisoning
- 6: [BUG]: cache-poisoning rule doesn't support breaking change of actions/setup-node v6 zizmorcore/zizmor#1485
- 7: https://zizmor.sh/
- 8: https://github.com/actions/setup-node/blob/main/docs/advanced-usage.md
- 9: feat: add npm trusted publishing detection zizmorcore/zizmor#1161
- 10: Feature: more publishing patterns for JS zizmorcore/zizmor#1493
- 11: https://github.blog/changelog/2026-06-26-read-only-actions-cache-for-untrusted-triggers/
- 12: https://github.com/zizmorcore/zizmor
- 13: https://docs.zizmor.sh/audits/
- 14: https://docs.zizmor.sh/
Remove npm caching from the privileged publish job.
actions/setup-node caches dependencies automatically with cache: npm, and this job also runs with ids: write for provenance while publishing authenticated with NPM_TOKEN. Keep caching only in non-publishing CI jobs; disable it here with package-manager-cache: false.
🧰 Tools
🪛 zizmor (1.26.1)
[error] 21-21: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): this step
(cache-poisoning)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/publish.yml around lines 21 - 24, Update the
actions/setup-node configuration in the privileged publish job to disable
automatic package-manager caching by setting package-manager-cache to false, and
remove the cache: npm setting. Leave dependency caching enabled only in the
non-publishing CI jobs.
Source: Linters/SAST tools
| Initial published release. `pharn` bootstraps the PHARN stack into an existing | ||
| Next.js project. Exposes both `pharn` and `pharn` bins. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix the duplicate bin names.
The release note currently says the release exposes both pharn and pharn bins. Restore the historical name or state that only the pharn binary is exposed.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@CHANGELOG.md` around lines 96 - 97, Update the initial release note to remove
the duplicate bin names, restoring the historical second binary name or clearly
stating that only the pharn binary is exposed.
| PHARN is an audit-grade methodology — taking security seriously is part of the brand, not an afterthought. `pharn` is the bootstrapper that fetches and installs that methodology, so it sits at a trust boundary: it pulls remote content over the network and writes it into a user's project. We welcome coordinated disclosure of any vulnerability in this repository. | ||
|
|
||
| ## What `pharn-cli` is, and its security surface | ||
| ## What `pharn` is, and its security surface | ||
|
|
||
| This repository **is `pharn-cli`** — an ESM-only Node CLI (`"type": "module"`, NodeNext, `engines.node >= 20`) that runs a wizard, fetches the selected PHARN modules from `pharn-dev/pharn-oss` via `degit`, copies them into the user's `.claude/`, and writes `pharn.config.json`. It has a small, thin dependency set (`@clack/prompts`, `degit`, `minimist`, `picocolors`), no bundled runtime services, and no telemetry. Its security-relevant surface is exactly the two things that cross a trust boundary: **remote input** (the `manifest.json` / `module.json` it reads and the repo content it clones) and **file-system writes** (everything it copies into `.claude/` and the config it writes). | ||
| This repository **is `pharn`** — an ESM-only Node CLI (`"type": "module"`, NodeNext, `engines.node >= 20`) that runs a wizard, fetches the selected PHARN modules from `pharn-dev/pharn-oss` via `degit`, copies them into the user's `.claude/`, and writes `pharn.config.json`. It has a small, thin dependency set (`@clack/prompts`, `degit`, `minimist`, `picocolors`), no bundled runtime services, and no telemetry. Its security-relevant surface is exactly the two things that cross a trust boundary: **remote input** (the `manifest.json` / `module.json` it reads and the repo content it clones) and **file-system writes** (everything it copies into `.claude/` and the config it writes). | ||
|
|
||
| The CLI's security model is **deterministic, not model-driven**: it never asks an AI to decide what is safe. Every value that arrives from the network is validated against strict regex allowlists, rejected for `..` and control characters, and every copy is confined with a `safeJoin` guard so nothing can escape its intended target — checks that hold regardless of what the fetched content says. Preserve that shape: a security fix that relies on "the content will be well-behaved" is not a fix. |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Align the security boundary with the current installation layout.
Both documents understate the write surface by treating .claude/ as the destination for installed methodology. The current flow also writes capabilities and fixed product surfaces outside .claude/; the threat model and security policy should name or clearly abstract all of those paths.
- SECURITY.md#L3-L9: describe writes to
.claude/, the mirrored capability layout, root product surfaces, andpharn.config.json. - THREAT-MODEL.md#L18-L22: expand Surface A beyond
.claude/so the residual analysis covers every installed location.
🧰 Tools
🪛 LanguageTool
[style] ~7-~7: Consider an alternative for the overused word “exactly”.
Context: ...metry. Its security-relevant surface is exactly the two things that cross a trust bound...
(EXACTLY_PRECISELY)
📍 Affects 2 files
SECURITY.md#L3-L9(this comment)THREAT-MODEL.md#L18-L22
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@SECURITY.md` around lines 3 - 9, The security documentation understates the
installation write surface. In SECURITY.md, update the description to cover
.claude/, the mirrored capability layout, root product surfaces, and
pharn.config.json; in THREAT-MODEL.md, expand Surface A beyond .claude/ to
include every installed location. Preserve the existing remote-input and
safe-write security model while ensuring both documents describe the current
layout consistently.
Convert publish.yml to pure OIDC Trusted Publishing — no secrets in the workflow: - delete the env NODE_AUTH_TOKEN / NPM_TOKEN block (auth is now OIDC) - gate the publish job behind the `npm-publish` environment - bump Node to 24 and install npm@latest (Trusted Publishing needs npm >= 11.5.1) - drop the workflow_dispatch trigger (releases only) - guard: the release tag must equal package.json version before publish Requires an out-of-band `npm-publish` GitHub environment and an npmjs.org Trusted Publisher for publish.yml + that environment; the first publish stays a manual local `npm publish`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The PHARN dev-loop record for the publish.yml OIDC conversion (commit 7b84868): PLAN, GRILL, REGRESSION, VERIFY, REVIEW, SHIP + the two machine reports. It records the floor verdicts read at each stage — build `validate` RED and verify FAIL were the same pre-existing untracked test-app artifact (CI-equivalent green), proceeded past only by explicit human decision at the build / GATE-2 gates. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
What
Three commits:
af34d3a— finalize thepharn-cli→pharnrename across docs, config, dev artifacts, and source comments; setpublishConfig.provenance: false(automatic provenance needs a CI OIDC provider and aborts withprovider: nullon a localnpm publish).9b889fc— rename the npm packagepharn→ the scoped@pharn-dev/pharnacross all user-facing surfaces (package.jsonname, README badge + npx install,docs/, CONTRIBUTING, SECURITY, CHANGELOG, issue templates,pharn -husage, git-prereq error message). The installed bin stayspharn(unscoped) — only the package identity is scoped. Also syncs stale in-code comments left by the module-model removal (init.ts,constitution.ts,repo.ts).815acf2— add.github/workflows/publish.yml: publishes to npm on a GitHub Release (or manual dispatch) with provenance viaid-token: write+ an explicit--provenanceflag, which overrides thefalsedefault so local and CI publishing both work.CI green locally: format, lint, typecheck, 378 tests.
Maintainer follow-ups
NPM_TOKENrepo secret (an npm automation/granular token that bypasses 2FA) for the publish workflow.@pharn-dev/pharnneeds thepharn-devnpm org/scope (+--access public, already set inpublishConfigand the workflow).originis stillpharn-dev/pharn-cliwhilepackage.jsonpoints atpharn-dev/pharn— rename the GitHub repo (or revert the URLs) to reconcile. README CI/CodeQL badges still point atpharn-cli(correct until the repo is renamed).Deliberately NOT touched (pre-existing, out of scope)
SECURITY.md(manifest.ts,MODULE_NAME_RE,schemaVersiongate) andCHANGELOG.md(pharn remove <module>,pharn-core) that predate the module-model removal.CHANGELOGstill describespublishConfigas "(public access + provenance)"; withprovenance: false+ CI-based provenance you may want to reword.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
pharnname with a singlepharncommand.npx pharn init.Bug Fixes
pharn init.Documentation
pharnand describe the current workflow.