ai-saas-guard 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,6 +10,7 @@
10
10
 
11
11
  <p align="center">
12
12
  <a href="https://github.com/zr9959/ai-saas-guard/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/zr9959/ai-saas-guard/actions/workflows/ci.yml/badge.svg"></a>
13
+ <a href="https://www.npmjs.com/package/ai-saas-guard"><img alt="npm" src="https://img.shields.io/npm/v/ai-saas-guard.svg"></a>
13
14
  <a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
14
15
  <a href="package.json"><img alt="Node.js >=20" src="https://img.shields.io/badge/node-%3E%3D20-339933.svg"></a>
15
16
  <a href="docs/release-quality-knowledge-base.md"><img alt="Release gate documented" src="https://img.shields.io/badge/release%20gate-documented-0f766e.svg"></a>
@@ -40,18 +41,45 @@ It is intentionally evidence-first. Findings include a rule ID, severity, file e
40
41
 
41
42
  This repository is public on GitHub.
42
43
 
43
- The first GitHub release and Action tag are `v0.1.0`; the npm-ready patch release is `v0.1.1`. The npm package is not published yet, so run the CLI from source for now. If you need stricter supply-chain pinning in CI, pin the GitHub Action to a reviewed commit SHA instead of a mutable tag.
44
+ The CLI is published on npm as `ai-saas-guard`, and the GitHub Action is available through versioned release tags. If you need stricter supply-chain pinning in CI, pin the GitHub Action to a reviewed commit SHA instead of a mutable tag.
44
45
 
45
46
  | Area | Status |
46
47
  | --- | --- |
47
48
  | Public GitHub repository | Available |
48
- | Local CLI from source | Available |
49
+ | npm CLI | Published as `ai-saas-guard` |
50
+ | Local CLI from source | Available for development |
49
51
  | JSON and SARIF output | Available |
50
52
  | Composite GitHub Action | Available |
51
- | Versioned Action tags | `v0.1.1` |
52
- | npm package | Not published yet |
53
+ | Versioned Action tags | `v0.1.3` |
54
+ | npm package | `ai-saas-guard@0.1.3` |
55
+ | npm publishing | Trusted Publisher/OIDC, no long-lived publish token |
53
56
 
54
- ## Quick Start From Source
57
+ ## Quick Start
58
+
59
+ Run the published CLI without installing it globally:
60
+
61
+ ```bash
62
+ npx ai-saas-guard@latest scan --root /path/to/your-saas
63
+ ```
64
+
65
+ Run focused checks:
66
+
67
+ ```bash
68
+ npx ai-saas-guard@latest pr-risk --root /path/to/your-saas --base origin/main
69
+ npx ai-saas-guard@latest check-supabase --root /path/to/your-saas
70
+ npx ai-saas-guard@latest check-stripe --root /path/to/your-saas
71
+ npx ai-saas-guard@latest check-mcp --root /path/to/your-saas
72
+ ```
73
+
74
+ Machine-readable output:
75
+
76
+ ```bash
77
+ npx ai-saas-guard@latest scan --root /path/to/your-saas --json
78
+ npx ai-saas-guard@latest scan --root /path/to/your-saas --sarif > ai-saas-guard.sarif
79
+ npx ai-saas-guard@latest scan --root /path/to/your-saas --fail-on high
80
+ ```
81
+
82
+ For local development:
55
83
 
56
84
  ```bash
57
85
  git clone https://github.com/zr9959/ai-saas-guard.git
@@ -70,14 +98,6 @@ node dist/cli.js check-stripe --root /path/to/your-saas
70
98
  node dist/cli.js check-mcp --root /path/to/your-saas
71
99
  ```
72
100
 
73
- Machine-readable output:
74
-
75
- ```bash
76
- node dist/cli.js scan --root /path/to/your-saas --json
77
- node dist/cli.js scan --root /path/to/your-saas --sarif > ai-saas-guard.sarif
78
- node dist/cli.js scan --root /path/to/your-saas --fail-on high
79
- ```
80
-
81
101
  ## Example Finding
82
102
 
83
103
  Terminal output is designed to be useful to a reviewer, not just a scanner dashboard.
@@ -127,11 +147,14 @@ AI-generated PRs often combine unrelated work:
127
147
  - review-first checklist
128
148
  - suggested PR split
129
149
  - required tests or manual verification
150
+ - explicit git-diff diagnostics when a base ref or shallow checkout prevents PR classification
130
151
 
131
152
  ```bash
132
153
  node dist/cli.js pr-risk --root /path/to/your-saas --base origin/main --json
133
154
  ```
134
155
 
156
+ If `--base` cannot be resolved, `pr-risk` emits `pr-risk.diff-unavailable` instead of silently reporting a clean or empty diff. In GitHub Actions, use `actions/checkout` with `fetch-depth: 0` when you need merge-base comparison against `origin/main`.
157
+
135
158
  ## Commands
136
159
 
137
160
  | Command | Purpose |
@@ -162,7 +185,7 @@ jobs:
162
185
  - uses: actions/checkout@v6.0.2
163
186
  with:
164
187
  fetch-depth: 0
165
- - uses: zr9959/ai-saas-guard@v0.1.1
188
+ - uses: zr9959/ai-saas-guard@v0.1.3
166
189
  with:
167
190
  command: pr-risk
168
191
  root: ${{ github.workspace }}
@@ -173,7 +196,7 @@ jobs:
173
196
  For SARIF upload:
174
197
 
175
198
  ```yaml
176
- - uses: zr9959/ai-saas-guard@v0.1.1
199
+ - uses: zr9959/ai-saas-guard@v0.1.3
177
200
  with:
178
201
  command: scan
179
202
  format: sarif
@@ -183,7 +206,7 @@ For SARIF upload:
183
206
  sarif_file: ai-saas-guard.sarif
184
207
  ```
185
208
 
186
- For maximum reproducibility, replace `v0.1.1` with the full commit SHA from the release notes.
209
+ For maximum reproducibility, replace `v0.1.3` with the full commit SHA from the release notes.
187
210
 
188
211
  ## Ignore File
189
212
 
@@ -257,7 +280,6 @@ Open-source core:
257
280
 
258
281
  Near-term priorities:
259
282
 
260
- - npm trusted publishing and provenance
261
283
  - PR comment summary mode
262
284
  - configurable severity and rule toggles
263
285
  - expanded Supabase RLS fixtures
@@ -282,4 +304,4 @@ Please read [SECURITY.md](SECURITY.md) before reporting vulnerabilities. Do not
282
304
 
283
305
  ## npm Publishing
284
306
 
285
- The package name is prepared but not published yet. See [docs/npm-publishing.md](docs/npm-publishing.md) for the GitHub Actions provenance workflow and the required `NPM_TOKEN` or trusted-publisher setup.
307
+ The package is published as [`ai-saas-guard`](https://www.npmjs.com/package/ai-saas-guard). See [docs/npm-publishing.md](docs/npm-publishing.md) for the GitHub Actions Trusted Publisher workflow, provenance notes, and first-publish token history.
@@ -195,6 +195,13 @@ export const RULE_CATALOG = {
195
195
  why: "AI-generated PRs often bury trust-boundary changes inside larger diffs.",
196
196
  stability: "default"
197
197
  },
198
+ "pr-risk.diff-unavailable": {
199
+ ruleId: "pr-risk.diff-unavailable",
200
+ severity: "info",
201
+ title: "Git diff could not be read",
202
+ why: "PR classification can be misleading when the requested base ref or Git history is unavailable.",
203
+ stability: "default"
204
+ },
198
205
  "pr-risk.no-diff": {
199
206
  ruleId: "pr-risk.no-diff",
200
207
  severity: "info",
@@ -14,10 +14,13 @@ const categoryWeights = {
14
14
  "large AI-generated/refactor-like diff": 18
15
15
  };
16
16
  export async function classifyPrRisk(options) {
17
- const diffText = options.diffText ?? (await readGitDiff(options.rootDir, options.base));
17
+ const diffResult = options.diffText === undefined
18
+ ? await readGitDiff(options.rootDir, options.base)
19
+ : { diffText: options.diffText, diagnostics: [] };
20
+ const { diffText } = diffResult;
18
21
  const files = parseDiffFiles(diffText);
19
22
  const categories = new Set();
20
- const findings = [];
23
+ const findings = [...diffResult.diagnostics];
21
24
  for (const file of files) {
22
25
  for (const category of file.categories) {
23
26
  categories.add(category);
@@ -44,7 +47,7 @@ export async function classifyPrRisk(options) {
44
47
  suggestedFix: "Split unrelated UI/refactor work away from trust-boundary changes and add focused tests before merge."
45
48
  }));
46
49
  }
47
- if (diffText.trim().length === 0) {
50
+ if (diffText.trim().length === 0 && diffResult.diagnostics.length === 0) {
48
51
  findings.push(finding({
49
52
  ruleId: "pr-risk.no-diff",
50
53
  title: "No git diff found",
@@ -68,13 +71,17 @@ async function readGitDiff(rootDir, base) {
68
71
  if (base) {
69
72
  try {
70
73
  const { stdout } = await execFileAsync("git", ["diff", `${base}...HEAD`], { cwd: rootDir, maxBuffer: 20 * 1024 * 1024 });
71
- return stdout;
74
+ return { diffText: stdout, diagnostics: [] };
72
75
  }
73
- catch {
74
- return "";
76
+ catch (error) {
77
+ return {
78
+ diffText: "",
79
+ diagnostics: [buildGitDiffFailureFinding(rootDir, ["git", "diff", `${base}...HEAD`], error, base)]
80
+ };
75
81
  }
76
82
  }
77
83
  const parts = [];
84
+ const failures = [];
78
85
  for (const args of [
79
86
  ["diff", "--cached"],
80
87
  ["diff"]
@@ -83,11 +90,62 @@ async function readGitDiff(rootDir, base) {
83
90
  const { stdout } = await execFileAsync("git", args, { cwd: rootDir, maxBuffer: 20 * 1024 * 1024 });
84
91
  parts.push(stdout);
85
92
  }
86
- catch {
87
- continue;
93
+ catch (error) {
94
+ failures.push({ args: ["git", ...args], error });
88
95
  }
89
96
  }
90
- return parts.join("\n");
97
+ if (parts.length === 0 && failures.length > 0) {
98
+ return {
99
+ diffText: "",
100
+ diagnostics: [buildGitDiffFailureFinding(rootDir, failures[0].args, failures[0].error)]
101
+ };
102
+ }
103
+ return { diffText: parts.join("\n"), diagnostics: [] };
104
+ }
105
+ function buildGitDiffFailureFinding(rootDir, command, error, base) {
106
+ const errorText = redactRootPath(getGitErrorText(error), rootDir);
107
+ const lowerError = errorText.toLowerCase();
108
+ let suggestedVerification = "Run `git status` and confirm the target path is inside a Git repository.";
109
+ let suggestedFix = "Run `pr-risk` from a Git checkout, or pass explicit diff text through the API.";
110
+ if (base) {
111
+ suggestedVerification = `Run \`${buildFetchCommand(base)}\`, then \`git rev-parse --verify ${base}\` to confirm the base ref exists locally.`;
112
+ suggestedFix = "Fetch the branch or pass an existing local base ref, for example `--base origin/main`.";
113
+ }
114
+ if (base && (lowerError.includes("no merge base") || lowerError.includes("shallow"))) {
115
+ suggestedVerification = "Run `git rev-parse --is-shallow-repository` and confirm CI checks out full history before `pr-risk`.";
116
+ suggestedFix = "Use `fetch-depth: 0` in `actions/checkout`, or run `git fetch --unshallow` before invoking `pr-risk`.";
117
+ }
118
+ return finding({
119
+ ruleId: "pr-risk.diff-unavailable",
120
+ title: base ? `Could not read git diff for base ${base}` : "Could not read git diff",
121
+ severity: "info",
122
+ evidence: [
123
+ {
124
+ file: ".",
125
+ match: command.join(" "),
126
+ snippet: errorText.slice(0, 500)
127
+ }
128
+ ],
129
+ why: "PR risk classification needs a readable git diff, but the git command failed for the target repository.",
130
+ suggestedVerification,
131
+ suggestedFix
132
+ });
133
+ }
134
+ function buildFetchCommand(base) {
135
+ const remoteRef = /^([^/\s]+)\/(.+)$/.exec(base);
136
+ if (remoteRef)
137
+ return `git fetch ${remoteRef[1]} ${remoteRef[2]}`;
138
+ return `git fetch origin ${base}`;
139
+ }
140
+ function redactRootPath(text, rootDir) {
141
+ return rootDir ? text.replaceAll(rootDir, ".") : text;
142
+ }
143
+ function getGitErrorText(error) {
144
+ const candidate = error;
145
+ return [candidate.stderr, candidate.stdout, candidate.message]
146
+ .filter((value) => Boolean(value?.trim()))
147
+ .join("\n")
148
+ .trim() || "git diff exited with a non-zero status.";
91
149
  }
92
150
  function parseDiffFiles(diffText) {
93
151
  const files = [];
@@ -1,31 +1,29 @@
1
1
  # npm Publishing
2
2
 
3
- `ai-saas-guard` is prepared for npm publication, but the package is not published yet.
3
+ `ai-saas-guard` is published on npm and should be released only from reviewed GitHub tags.
4
4
 
5
5
  ## Current State
6
6
 
7
7
  - Package name: `ai-saas-guard`
8
- - Current version: `0.1.1`
9
- - npm registry state: not published at the time of this document update
10
- - GitHub Release: `v0.1.0`
8
+ - Current version: `0.1.3`
9
+ - npm registry state: published at <https://www.npmjs.com/package/ai-saas-guard>
10
+ - First npm-published version: `0.1.1`
11
+ - GitHub Release: `v0.1.3`
11
12
  - Publish workflow: `.github/workflows/npm-publish.yml`
13
+ - Trusted Publisher: GitHub Actions, `zr9959/ai-saas-guard`, workflow `npm-publish.yml`, allowed action `npm publish`
14
+ - Long-lived npm publish token: not required
12
15
 
13
16
  ## Preferred Path
14
17
 
15
- Use GitHub Actions with npm provenance:
18
+ Use GitHub Actions with npm Trusted Publisher/OIDC:
16
19
 
17
- 1. Create an npm automation token with publish rights for this package.
18
- 2. Add it to this GitHub repository as `NPM_TOKEN`.
19
- 3. Run the `Publish npm` workflow manually with `ref` set to `v0.1.1`.
20
- 4. After the first publish succeeds, configure npm Trusted Publisher for future releases:
21
- - Provider: GitHub Actions
22
- - Organization or user: `zr9959`
23
- - Repository: `ai-saas-guard`
24
- - Workflow filename: `npm-publish.yml`
25
- - Allowed action: `npm publish`
26
- 5. Once trusted publishing is verified, remove or rotate any long-lived npm publish token.
20
+ 1. Create and review a release tag such as `v0.1.3`.
21
+ 2. Publish from the GitHub Release or run the `Publish npm` workflow manually with `ref` set to that tag.
22
+ 3. Keep `permissions.id-token: write` in the workflow so npm can exchange the GitHub Actions OIDC identity for a short-lived publish credential.
23
+ 4. Run `npm publish --access public` from the workflow. Trusted publishing automatically generates provenance for this public package from this public repository.
24
+ 5. Keep npm package publishing access set to require 2FA and disallow traditional tokens. Trusted publishers continue to work because they use OIDC instead of npm auth tokens.
27
25
 
28
- The workflow sets `id-token: write` and runs `npm publish --provenance --access public`, so token-based first publish can include provenance and future trusted-publisher publishes can use OIDC.
26
+ The first npm publish used a temporary granular access token because npm requires a 2FA-bypass token until trusted publishing is configured. That temporary automation token and the GitHub `NPM_TOKEN` secret were removed after the Trusted Publisher migration.
29
27
 
30
28
  ## Release Gate
31
29
 
@@ -45,9 +45,11 @@ Implemented surfaces:
45
45
  - MCP config side-effect and secret-bearing risk inventory
46
46
  - Next/Vercel deploy and runtime footguns
47
47
  - PR diff risk triage for auth, billing, RLS, env, tests removed, and large mixed diffs
48
+ - PR diff diagnostics when a base ref or shallow checkout prevents comparison
48
49
  - JSON output
49
50
  - SARIF output
50
51
  - composite GitHub Action wrapper
52
+ - npm publishing through GitHub Actions Trusted Publisher/OIDC
51
53
 
52
54
  Existing commands:
53
55
 
@@ -111,6 +113,14 @@ CI:
111
113
  - Uses `permissions: contents: read`
112
114
  - Latest verified run after setup succeeded
113
115
 
116
+ Publishing:
117
+
118
+ - npm package: `ai-saas-guard`
119
+ - Current release line: `v0.1.3`
120
+ - Publish workflow: `.github/workflows/npm-publish.yml`
121
+ - Trusted Publisher: GitHub Actions for `zr9959/ai-saas-guard`, workflow `npm-publish.yml`
122
+ - Long-lived npm publish tokens should not be required.
123
+
114
124
  ## Repository Boundaries
115
125
 
116
126
  Allowed in this public repository:
@@ -133,14 +143,13 @@ Not allowed:
133
143
 
134
144
  Recommended order:
135
145
 
136
- 1. Prepare npm publishing plan with trusted publishing/provenance.
137
- 2. Add GitHub Action release packaging and example workflow.
138
- 3. Add PR comment summary mode.
139
- 4. Add configurable severity and rule toggles.
140
- 5. Expand Supabase RLS fixtures and ownership patterns.
141
- 6. Write Stripe webhook replay cookbook.
142
- 7. Add SARIF upload workflow example.
143
- 8. Improve false-positive suppression and rule stability labels.
146
+ 1. Add PR comment summary mode.
147
+ 2. Add configurable severity and rule toggles.
148
+ 3. Expand Supabase RLS fixtures and ownership patterns.
149
+ 4. Write Stripe webhook replay cookbook.
150
+ 5. Add SARIF upload workflow example.
151
+ 6. Improve false-positive suppression and rule stability labels.
152
+ 7. Add a GitHub App design note for the potential hosted layer.
144
153
 
145
154
  For every feature, keep the scanner evidence-first:
146
155
 
package/docs/rules.md CHANGED
@@ -61,6 +61,7 @@ Rule metadata is centralized in `src/rules/catalog.ts` and covered by tests so S
61
61
  | Rule ID | Severity | Why it exists |
62
62
  | --- | --- | --- |
63
63
  | `pr-risk.sensitive-surface` | medium/high | Highlights files reviewers should inspect before cosmetic or refactor files. |
64
+ | `pr-risk.diff-unavailable` | info | Explains when the requested base ref or Git history prevents PR diff classification. |
64
65
  | `pr-risk.no-diff` | info | Explains that PR classification needs a diff. |
65
66
 
66
67
  ## Adding Rules
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-saas-guard",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Repo-local launch-readiness scanner for AI-built SaaS apps.",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/zr9959/ai-saas-guard#readme",