ai-saas-guard 0.36.0 → 0.38.0

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
@@ -37,6 +37,8 @@ Start with the 30-second copy-paste demo: `npx ai-saas-guard@latest demo --summa
37
37
  npx ai-saas-guard@latest scan --root /path/to/your-saas --summary
38
38
  ```
39
39
 
40
+ For AI-heavy PRs, run it in GitHub Actions to turn auth, billing, data, deploy, and test changes into a reviewer queue before merge.
41
+
40
42
  The output is meant to answer three practical questions before you invite users:
41
43
 
42
44
  - **Can a real user get access they should not have?** Check auth, tenant ownership, Supabase RLS, and Stripe entitlement paths first.
@@ -168,7 +170,7 @@ For a concise comparison with Semgrep, zizmor, OpenSSF Scorecard, Snyk, and GitH
168
170
  | --- | --- | --- |
169
171
  | Local CLI | Private code, first local launch review, founder or reviewer workflow | Published on npm; local-first, read-only, no code upload, no LLM calls |
170
172
  | GitHub Action | CI review queue, SARIF upload, PR summary artifacts, controlled fail thresholds | Available through `zr9959/ai-saas-guard@v0` and fixed version tags |
171
- | Hosted GitHub App | Future hosted Check Run experience for selected repositories | Limited trial gate only; not the complete hosted SaaS, not a public hosted scanner |
173
+ | Hosted GitHub App | Future hosted Check Run experience for selected repositories | Limited trial gate with staging ingress, public install-info, compact Check Runs, and cleanup handling; not the complete hosted SaaS, not a public hosted scanner |
172
174
 
173
175
  ## Quick Start
174
176
 
@@ -231,19 +233,19 @@ The CLI is published on npm as `ai-saas-guard`, and the GitHub Action is availab
231
233
  | Area | Status |
232
234
  | --- | --- |
233
235
  | Public GitHub repository | Available |
234
- | npm CLI | `ai-saas-guard@0.36.0` |
235
- | GitHub Action | `zr9959/ai-saas-guard@v0` or fixed tag `v0.36.0` |
236
+ | npm CLI | `ai-saas-guard@0.38.0` |
237
+ | GitHub Action | `zr9959/ai-saas-guard@v0` or fixed tag `v0.38.0` |
236
238
  | Outputs | Launch decision queue, short summary, terminal, JSON, SARIF, and PR-focused markdown |
237
239
  | Project config | `.ai-saas-guard.json` rule toggles, severity overrides, suppressions, and fail thresholds |
238
240
  | Privacy model | Local-first, read-only scan commands, no LLM calls, no code upload |
239
- | Versioned Action tags | `v0.36.0`, `v0` |
240
- | Current release | `0.36.0` turns Markdown and summary output into a clearer launch decision queue with ranking explanations, reviewer checklists, case-study flow, and local trust/resource statements |
241
+ | Versioned Action tags | `v0.38.0`, `v0` |
242
+ | Current release | `0.38.0` closes more of the hosted GitHub App staging loop with public install guidance, selected-repository Check Run wording, signed installation cleanup, and updated hosted docs/tests |
241
243
  | npm publishing | Trusted Publisher/OIDC, no long-lived publish token |
242
244
  | Repository trust hardening | Strict branch protection, Dependabot, CodeQL, fast-check fuzzing, signed release provenance assets, private vulnerability reporting, secret scanning, and push protection |
243
245
  | Cloudflare hosted ingress | Deployed at `https://ai-saas-guard-hosted.zr9959.workers.dev`; signed GitHub App webhook delivery and compact Check Run smoke now pass in staging |
244
246
  | Hosted GitHub App staging | Private App `ai-saas-guard-hosted` (`3834787`) installed on `zr9959/ai-saas-guard`; hosted operations evidence is in [docs/hosted-operations-evidence.md](docs/hosted-operations-evidence.md) |
245
247
  | OpenSSF Best Practices | Passing badge, project `12955`; `.bestpractices.json` remains the conservative evidence record |
246
- | Next roadmap | v0.36.0 plan is tracked in [docs/v0.36-roadmap.md](docs/v0.36-roadmap.md) |
248
+ | Previous roadmap | v0.36.0 plan is tracked in [docs/v0.36-roadmap.md](docs/v0.36-roadmap.md) |
247
249
 
248
250
  ## Example Finding
249
251
 
@@ -361,7 +363,7 @@ The hosted staging harness is documented in [docs/hosted-staging-harness.md](doc
361
363
 
362
364
  Deployed worker staging evidence is documented in [docs/hosted-deployed-worker-staging.md](docs/hosted-deployed-worker-staging.md). It exports `createHostedDeployedWorkerStagingEvidenceAutomation`, `createHostedDeployedWorkerStagingEvidenceBundle`, and `evaluateHostedDeployedWorkerStagingReleaseGate` from `ai-saas-guard/hosted/deployed-staging`. It validates safe log samples, then turns public HTTPS health, signed webhook replay, deployed worker cleanup, and external CI/scan/rollback evidence into the hosted release gate for a Node/container read-only checkout worker candidate. It does not deploy cloud resources or claim production hosted exposure.
363
365
 
364
- The first live hosted ingress is deployed on Cloudflare Workers at `https://ai-saas-guard-hosted.zr9959.workers.dev` and documented in [hosted/cloudflare-worker/README.md](hosted/cloudflare-worker/README.md). It exposes `/healthz`, `/github/app/manifest-callback`, and signed `/github/webhook` intake backed by Cloudflare KV. A private staging GitHub App, `ai-saas-guard-hosted`, is installed on `zr9959/ai-saas-guard` with selected-repository access and the first-slice permission contract. The Worker verifies signatures, stores compact pull request identity records, exchanges a scoped installation token, fetches PR file metadata from GitHub, classifies PR-risk hotspots, and publishes a bounded Check Run summary. Current deployed evidence is tracked in [docs/hosted-operations-evidence.md](docs/hosted-operations-evidence.md): health, signed webhook delivery, compact KV records, cleanup, and Check Run publication pass for the staging smoke. The Cloudflare Worker still does not run a full source checkout scan worker or store raw webhook payloads, PR title/body text, raw diffs, source, secrets, checkout paths, or installation tokens.
366
+ The first live hosted ingress is deployed on Cloudflare Workers at `https://ai-saas-guard-hosted.zr9959.workers.dev` and documented in [hosted/cloudflare-worker/README.md](hosted/cloudflare-worker/README.md). It exposes `/healthz`, `/github/app/install-info`, `/github/app/manifest-callback`, and signed `/github/webhook` intake backed by Cloudflare KV. A private staging GitHub App, `ai-saas-guard-hosted`, is installed on `zr9959/ai-saas-guard` with selected-repository access and the first-slice permission contract. The Worker verifies signatures, stores compact pull request identity records, exchanges a scoped installation token, fetches PR file metadata from GitHub, classifies PR-risk hotspots, and publishes a bounded selected-repository hosted check with a review queue and manual proof prompt. Signed installation deletion and repository removal events delete matching compact records. Current deployed evidence is tracked in [docs/hosted-operations-evidence.md](docs/hosted-operations-evidence.md): health, signed webhook delivery, compact KV records, cleanup, and Check Run publication pass in staging. The Cloudflare Worker still does not run a full source checkout scan worker or store raw webhook payloads, PR title/body text, raw diffs, source, secrets, checkout paths, or installation tokens.
365
367
 
366
368
  The hosted operational release gate is documented in [docs/hosted-operational-release-gate.md](docs/hosted-operational-release-gate.md). It defines the hosted-specific CI, replay, queue, worker cleanup, privacy, monitoring, rollback, and incident-response evidence required before any hosted environment is exposed to users. The pure gate evaluator exported from `ai-saas-guard/hosted/contracts` blocks hosted exposure unless every P0 evidence item is fresh, a container digest is recorded, and release notes avoid pentest, certification, and full-audit claims.
367
369
 
@@ -415,7 +417,7 @@ Use `suppressions` for narrower false-positive handling when one rule is noisy o
415
417
 
416
418
  ## GitHub Action
417
419
 
418
- The repo includes a composite Action. Use `v0` for the latest compatible pre-1.0 Action, a specific release tag such as `v0.36.0` for controlled upgrades, or pin a reviewed commit SHA for stricter supply-chain control:
420
+ The repo includes a composite Action. Use `v0` for the latest compatible pre-1.0 Action, a specific release tag such as `v0.38.0` for controlled upgrades, or pin a reviewed commit SHA for stricter supply-chain control:
419
421
 
420
422
  ```yaml
421
423
  name: ai-saas-guard
@@ -485,7 +485,7 @@ export function createHostedCheckRunSummary(input) {
485
485
  conclusion,
486
486
  output: {
487
487
  title: formatCheckRunTitle(totalFindings, conclusion, input.failOnSeverity),
488
- summary: `Launch gate: ${launchGate}. Review first: What changed at the launch boundary? Manual proof required before release; it is not a full security audit, pentest, or certification.`,
488
+ summary: `Launch-risk gate: ${launchGate}. Launch gate: ${launchGate}. Review first: What changed at the launch boundary? Manual proof required before release. This is not an AI reviewer and not a full security audit, pentest, or certification.`,
489
489
  text: truncateMarkdown(formatCheckRunMarkdown(report, conclusion, localCliCommand, launchGate), input.maxMarkdownChars)
490
490
  },
491
491
  annotations: report.evidence.slice(0, MAX_CHECK_RUN_ANNOTATIONS).map((finding) => {
@@ -1146,21 +1146,31 @@ function formatCheckRunMarkdown(report, conclusion, localCliCommand, launchGate)
1146
1146
  ...report.evidence.map((finding) => `| ${escapeMarkdownTableCell(finding.severity)} | ${escapeMarkdownTableCell(finding.ruleId)} | ${escapeMarkdownTableCell(formatFindingLocation(finding))} |`)
1147
1147
  ];
1148
1148
  return [
1149
- "### AI SaaS Guard",
1149
+ "### AI SaaS Guard Launch-risk gate",
1150
1150
  "",
1151
- "Review first: verify findings locally before launch. This hosted check is not a full security audit, pentest, or certification.",
1151
+ "Review first: verify findings locally before launch or merge. Not an AI reviewer or full security audit.",
1152
1152
  "",
1153
1153
  `Launch gate: ${launchGate}`,
1154
1154
  `Conclusion: ${conclusion}`,
1155
1155
  `Local CLI: \`${localCliCommand}\``,
1156
- `Retention: compact report ${report.retentionDays} days; raw source, raw diffs, secrets, and customer payloads are not retained.`,
1157
- "",
1158
- "Summary:",
1159
- ...severityOrder.map((severity) => `- ${capitalize(severity)}: ${report.summaryCounts[severity] ?? 0}`),
1156
+ `Retention: compact report ${report.retentionDays} days; no raw source, diffs, secrets, or customer payloads.`,
1160
1157
  "",
1161
1158
  "Review categories:",
1162
1159
  ...(categories.length === 0 ? ["- None"] : categories.map((category) => `- ${category}`)),
1163
1160
  "",
1161
+ "Verification steps:",
1162
+ "- Review listed files before release or merge.",
1163
+ "- Reproduce locally with the CLI command above.",
1164
+ "- Confirm behavior with app-specific tests.",
1165
+ "",
1166
+ "Launch decision queue:",
1167
+ "- Can a real user get access they should not have?",
1168
+ "- Can the app claim success when something failed?",
1169
+ "- Can launch infrastructure do too much damage?",
1170
+ "",
1171
+ "Summary:",
1172
+ ...severityOrder.map((severity) => `- ${capitalize(severity)}: ${report.summaryCounts[severity] ?? 0}`),
1173
+ "",
1164
1174
  "Files to review first:",
1165
1175
  ...(filesToReview.length === 0 ? ["- None"] : filesToReview.map((file) => `- ${file}`)),
1166
1176
  "",
@@ -1169,11 +1179,6 @@ function formatCheckRunMarkdown(report, conclusion, localCliCommand, launchGate)
1169
1179
  "- Why this auth billing data or deploy decision is safe?",
1170
1180
  "- What manual test proves it fails closed?",
1171
1181
  "",
1172
- "Verification steps:",
1173
- "- Review each listed file before release or merge.",
1174
- "- Reproduce locally with the CLI command above.",
1175
- "- Treat findings as review prompts; confirm behavior with app-specific tests.",
1176
- "",
1177
1182
  "Findings:",
1178
1183
  ...findingLines
1179
1184
  ].join("\n");
@@ -165,7 +165,7 @@ Next steps
165
165
  | --- | --- | --- |
166
166
  | 本地 CLI | 私有代码、本机首次上线 review、founder 或 reviewer 自查 | 已发布到 npm;本地优先、只读、不上传代码、不调用 LLM |
167
167
  | GitHub Action | CI 里的 review queue、SARIF、PR summary artifact、可控 fail threshold | 可通过 `zr9959/ai-saas-guard@v0` 或固定版本标签使用 |
168
- | Hosted GitHub App | 未来面向 selected repositories 的 hosted Check Run 体验 | 目前只是 limited trial gate,不是完整 hosted SaaS,也不是公开 hosted scanner |
168
+ | Hosted GitHub App | 未来面向 selected repositories 的 hosted Check Run 体验 | 当前是 staging ingress,已有公开 install-info、compact Check Run 和 cleanup handling;不是完整 hosted SaaS,也不是公开 hosted scanner |
169
169
 
170
170
  ## 快速开始
171
171
 
@@ -211,24 +211,24 @@ node dist/cli.js scan --root /path/to/your-saas
211
211
 
212
212
  这个仓库是公开 GitHub 仓库。
213
213
 
214
- CLI 已发布到 npm:`ai-saas-guard@0.36.0`。GitHub Action 支持 `v0` 浮动标签,也支持固定版本标签,例如 `v0.36.0`。
214
+ CLI 已发布到 npm:`ai-saas-guard@0.38.0`。GitHub Action 支持 `v0` 浮动标签,也支持固定版本标签,例如 `v0.38.0`。
215
215
 
216
216
  | 模块 | 状态 |
217
217
  | --- | --- |
218
218
  | 公开 GitHub 仓库 | 已可用 |
219
- | npm CLI | `ai-saas-guard@0.36.0` |
220
- | GitHub Action | `zr9959/ai-saas-guard@v0` 或固定标签 `v0.36.0` |
219
+ | npm CLI | `ai-saas-guard@0.38.0` |
220
+ | GitHub Action | `zr9959/ai-saas-guard@v0` 或固定标签 `v0.38.0` |
221
221
  | 输出格式 | 上线决策队列、短 summary、Terminal、JSON、SARIF 和 PR markdown |
222
222
  | 项目配置 | `.ai-saas-guard.json` 支持规则开关、severity 覆盖、suppressions 和 fail threshold |
223
223
  | 隐私模型 | 本地优先、只读扫描、不调用 LLM、不上传代码 |
224
- | 当前版本 | `0.36.0` Markdown summary 输出升级成更清楚的上线决策队列,加入排序解释、reviewer checklistcase-study flow 和本地 trust/resource statement |
225
- | Action 标签 | `v0.36.0`、`v0` |
224
+ | 当前版本 | `0.38.0` 补齐更多 hosted GitHub App staging 闭环:公开 install-infoselected-repository Check Run 文案、签名 installation cleanup,以及对应 hosted 文档和测试 |
225
+ | Action 标签 | `v0.38.0`、`v0` |
226
226
  | npm 发布 | GitHub Actions Trusted Publisher/OIDC,无需长期 npm token |
227
227
  | 仓库可信度加固 | 严格 branch protection、Dependabot、CodeQL、fast-check fuzzing、signed release provenance assets、private vulnerability reporting、secret scanning 和 push protection |
228
- | Cloudflare hosted ingress | 已部署到 `https://ai-saas-guard-hosted.zr9959.workers.dev`;签名 GitHub App webhook deliverycompact Check Run staging smoke 已通过 |
228
+ | Cloudflare hosted ingress | 已部署到 `https://ai-saas-guard-hosted.zr9959.workers.dev`;提供 `/github/app/install-info`,签名 GitHub App webhook deliverycompact Check Run 和 installation cleanup staging smoke 已通过 |
229
229
  | Hosted GitHub App staging | 私有 App `ai-saas-guard-hosted`(`3834787`)已安装到 `zr9959/ai-saas-guard`;hosted operations evidence 见 [docs/hosted-operations-evidence.md](hosted-operations-evidence.md) |
230
230
  | OpenSSF Best Practices | 已获得 passing badge,项目 `12955`;`.bestpractices.json` 继续作为保守证据记录 |
231
- | 下一版路线 | v0.36.0 计划见 [v0.36-roadmap.md](v0.36-roadmap.md) |
231
+ | 上一版路线 | v0.36.0 计划见 [v0.36-roadmap.md](v0.36-roadmap.md) |
232
232
 
233
233
  ## 主要命令
234
234
 
@@ -379,7 +379,7 @@ GitHub Marketplace wrapper 决策见 [docs/github-marketplace-wrapper-decision.m
379
379
 
380
380
  ## Hosted GitHub App 设计
381
381
 
382
- 当前仓库已经包含未来 Hosted GitHub App 的设计文档、纯契约测试、第一个真实 Cloudflare hosted ingress,以及 Node/container read-only checkout scan runner。私有 staging GitHub App `ai-saas-guard-hosted` 已安装到 `zr9959/ai-saas-guard`,Cloudflare 已配置所需的云端凭据绑定。Worker 代码已经能接收签名 webhook、写入 KV 队列、换取 scoped installation token、读取 GitHub PR file metadata、做 compact PR-risk classification,并发布有长度上限的 Check Run summary;当前端到端 GitHub App webhook delivery smoke 已通过,证据记录在 [docs/hosted-operations-evidence.md](hosted-operations-evidence.md)。Cloudflare ingress 本身仍不是完整 source checkout scan worker。
382
+ 当前仓库已经包含未来 Hosted GitHub App 的设计文档、纯契约测试、第一个真实 Cloudflare hosted ingress,以及 Node/container read-only checkout scan runner。私有 staging GitHub App `ai-saas-guard-hosted` 已安装到 `zr9959/ai-saas-guard`,Cloudflare 已配置所需的云端凭据绑定。Worker 代码已经能接收签名 webhook、写入 KV 队列、换取 scoped installation token、读取 GitHub PR file metadata、做 compact PR-risk classification,并发布有长度上限的 selected-repository Check Run summary;`/github/app/install-info` 会返回公开安全的安装说明、权限、事件、隐私边界和卸载说明。签名 installation deletion 和 repository removal 事件会删除匹配的 compact records。当前端到端 GitHub App webhook delivery smoke 已通过,证据记录在 [docs/hosted-operations-evidence.md](hosted-operations-evidence.md)。Cloudflare ingress 本身仍不是完整 source checkout scan worker。
383
383
 
384
384
  相关文档:
385
385
 
@@ -412,7 +412,7 @@ GitHub Marketplace wrapper 决策见 [docs/github-marketplace-wrapper-decision.m
412
412
  - Hosted staging deployment planner:`ai-saas-guard/hosted/staging` 导出 `planHostedProviderBinding`、`planHostedStagingDeployment` 和 `planHostedGitHubAppPromotion`,把真实 provider 引用、Node/container deployment plan、hosted operational release-gate evidence 和 GitHub App deployment planning 组合起来;缺少 queue、store、worker sandbox、Check Run publisher、logs、metrics、rollback 或 incident-response 引用时,会阻止 staging exposure 和 production promotion;它本身仍然不会调用云平台、创建 GitHub App 或暴露公开 hosted 服务
413
413
  - Hosted staging harness:`ai-saas-guard/hosted/staging-harness` 导出 `createFileBackedHostedStagingHarness`、`createHostedStagingHarnessEvidence`、`createHostedStagingReleaseEvidenceBundle`、`evaluateHostedStagingReleaseEvidenceBundle` 和 `validateHostedLogBoundary`,可以在本地用 file-backed queue、compact report、Check Run request 和 worker sandbox 跑通签名 webhook replay、worker tick 和 cleanup 校验,把 success/failure cleanup probes 与 log-boundary samples 转成 release-gate evidence,并直接执行 hosted release gate 判断;它只是 staging 演练工具,不会调用云平台、创建 GitHub App、写真实 Check Run 或暴露公开 hosted 服务
414
414
  - Deployed worker staging evidence:`ai-saas-guard/hosted/deployed-staging` 导出 `createHostedDeployedWorkerStagingEvidenceAutomation`、`createHostedDeployedWorkerStagingEvidenceBundle` 和 `evaluateHostedDeployedWorkerStagingReleaseGate`,先验证 safe log samples,再把 public HTTPS health、signed webhook replay、deployed worker cleanup 以及外部 CI/scan/rollback evidence 转成 hosted release gate evidence;它不会部署云资源,也不会宣称 production hosted exposure
415
- - Cloudflare hosted ingress:`hosted/cloudflare-worker` 已部署到 `https://ai-saas-guard-hosted.zr9959.workers.dev`,提供 `/healthz`、`/github/app/manifest-callback` 和签名 `/github/webhook` intake;Worker 已具备 compact pull request identity、file/category risk signal Check Run metadata 路径;staging GitHub App ID 为 `3834787`,installation ID 为 `135085075`;真实 GitHub App webhook delivery 和 Check Run smoke 已通过;完整 source checkout worker deployment、monitoring、rollback 和 incident-response evidence 仍需要通过 hosted operational release gate
415
+ - Cloudflare hosted ingress:`hosted/cloudflare-worker` 已部署到 `https://ai-saas-guard-hosted.zr9959.workers.dev`,提供 `/healthz`、`/github/app/install-info`、`/github/app/manifest-callback` 和签名 `/github/webhook` intake;Worker 已具备 compact pull request identity、file/category risk signal、selected-repository Check Run metadata 和签名 installation cleanup 路径;staging GitHub App ID 为 `3834787`,installation ID 为 `135085075`;真实 GitHub App webhook delivery 和 Check Run smoke 已通过;完整 source checkout worker deployment、monitoring、rollback 和 incident-response evidence 仍需要通过 hosted operational release gate
416
416
  - Hosted GitHub App limited trial gate:`ai-saas-guard/hosted/contracts` 导出 `createHostedGitHubAppTrialGate`,确保 trial 只作用于 selected repositories,并要求 Check Run publication、compact report、worker cleanup 和 safe log-boundary evidence;它不宣称完整 hosted SaaS 已可用
417
417
  - Case-study fixture:`examples/case-study-ai-saas` 和 [case-study-ai-saas.md](case-study-ai-saas.md) 展示一个更接近真实 AI SaaS 的 auth、billing、Supabase、Next/Vercel 和 GitHub Actions 风险组合
418
418
  - Resource budget:`createLocalScanResourceBudget` 暴露大仓库本地扫描的保守预算:文件数、单文件字节、总字节、忽略 build/dependency 目录、不上传代码、不调用 LLM
@@ -6,6 +6,48 @@ Use `zr9959/ai-saas-guard@v0` for the latest compatible pre-1.0 Action. Use a sp
6
6
 
7
7
  The Action runs the same local scanner inside the GitHub-hosted runner. It reads the checked-out repository, does not call an LLM, and does not upload source code. For `pr-risk`, always use `actions/checkout` with `fetch-depth: 0` so the base branch comparison is available.
8
8
 
9
+ ## Copy-paste PR launch gate workflow
10
+
11
+ Use this when you want one PR job to act as the launch-risk middle layer: Markdown goes to `$GITHUB_STEP_SUMMARY` for reviewers, while SARIF goes to GitHub code scanning for alert tracking. This is not an AI reviewer and it does not approve a PR; it translates trust-boundary changes into a reviewer queue.
12
+
13
+ ```yaml
14
+ name: ai-saas-guard-pr-launch-gate
15
+
16
+ on:
17
+ pull_request:
18
+
19
+ permissions:
20
+ contents: read
21
+ security-events: write
22
+
23
+ jobs:
24
+ launch-gate:
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - uses: actions/checkout@v6.0.2
28
+ with:
29
+ fetch-depth: 0
30
+ - uses: zr9959/ai-saas-guard@v0
31
+ with:
32
+ command: pr-risk
33
+ root: ${{ github.workspace }}
34
+ base: origin/main
35
+ config: .ai-saas-guard.json
36
+ format: markdown
37
+ output: ai-saas-guard-pr.md
38
+ - run: cat ai-saas-guard-pr.md >> "$GITHUB_STEP_SUMMARY"
39
+ - uses: zr9959/ai-saas-guard@v0
40
+ with:
41
+ command: scan
42
+ root: ${{ github.workspace }}
43
+ config: .ai-saas-guard.json
44
+ format: sarif
45
+ output: ai-saas-guard.sarif
46
+ - uses: github/codeql-action/upload-sarif@v3
47
+ with:
48
+ sarif_file: ai-saas-guard.sarif
49
+ ```
50
+
9
51
  ## PR Summary
10
52
 
11
53
  Use markdown when reviewers need a short, evidence-first launch decision queue: risky files, required verification, reviewer checklist, ranking explanation, and suggested PR split.
@@ -89,4 +89,6 @@ The repository can now produce and validate the deployment plan, and a private s
89
89
 
90
90
  This is now a first-slice staging Worker deployment, not a complete hosted scanner. The Worker code verifies signatures, queues compact pull request identity records, exchanges scoped installation tokens, fetches PR file metadata, classifies PR-risk hotspots, and publishes bounded Check Runs. Current operations evidence is tracked in [hosted-operations-evidence.md](hosted-operations-evidence.md); health, signed webhook delivery, compact KV records, cleanup, and Check Run publication pass in staging. It still does not run full source checkout scan workers inside the Cloudflare Worker, store raw diffs, store source code, or expose a production hosted service.
91
91
 
92
+ The Worker also exposes public-safe installation guidance at `https://ai-saas-guard-hosted.zr9959.workers.dev/github/app/install-info`. That response documents the install URL, selected-repository boundary, first-slice permissions, subscribed events, privacy model, and uninstall cleanup wording without returning private keys, webhook secrets, installation tokens, source, diffs, or customer payloads. Signed installation deletion and repository removal events delete matching compact records from KV.
93
+
92
94
  The next deployment stage should wire the hosted service runtime, production adapters, [Node/container app skeleton](hosted-node-container-app.md), and [staging deployment planner](hosted-staging-deployment.md) to a real platform queue, compact report store, GitHub installation authentication, worker isolation layer, Checks API publisher, logs, metrics, rollback, and incident-response evidence.
@@ -6,10 +6,14 @@ Passing these checks does not make the project a pentest, certification, or full
6
6
 
7
7
  ## Current Evidence
8
8
 
9
- Recorded on 2026-05-24 from the deployed Cloudflare Worker and a temporary no-file-change GitHub PR smoke.
9
+ Recorded on 2026-05-25 from the deployed Cloudflare Worker plus the earlier temporary no-file-change GitHub PR smoke.
10
10
 
11
11
  | Check | Evidence | Result |
12
12
  | --- | --- | --- |
13
+ | Cloudflare Worker health, v0.38.0 | `GET https://ai-saas-guard-hosted.zr9959.workers.dev/healthz` returned `ok: true`, routes including `/github/app/install-info`, `checkRunPublisher: "configured"`, `scannerVersion: "0.38.0"`, and all privacy flags set to false for raw payloads, PR text, source, diffs, secrets, customer payloads, checkout paths, and installation tokens | Passed |
14
+ | Public install guidance, v0.38.0 | `GET https://ai-saas-guard-hosted.zr9959.workers.dev/github/app/install-info` returned the `ai-saas-guard-hosted` install URL, selected-repository boundary wording, first-slice permissions `checks: write`, `contents: read`, `metadata: read`, `pull_requests: read`, subscribed events `pull_request`, `installation`, and `installation_repositories`, uninstall cleanup wording, `scannerVersion: "0.38.0"`, and no private keys, webhook secrets, installation tokens, source, diffs, or customer payloads | Passed |
15
+ | Deployed Worker version, v0.38.0 | `wrangler deploy` uploaded 36.35 KiB / gzip 9.30 KiB and deployed version `5999ccce-c64d-4f3f-96c9-b46cff5a2aed` at `2026-05-25T10:51:30Z` verification time | Passed |
16
+ | Staging KV cleanup, v0.38.0 | `wrangler kv bulk delete` removed 104 old `delivery:` and `scan:` staging records, then `wrangler kv key list --namespace-id fa5344fbd7944de6a776bf8731d58460 --remote` returned `[]` | Passed |
13
17
  | Cloudflare Worker health | `GET https://ai-saas-guard-hosted.zr9959.workers.dev/healthz` returned `ok: true`, `checkRunPublisher: "configured"`, `scannerVersion: "0.28.0"`, and all privacy flags set to false for raw payloads, PR text, source, diffs, secrets, customer payloads, checkout paths, and installation tokens | Passed |
14
18
  | Deployed Worker version | `wrangler deployments list` showed current version `531d2286-86c6-4327-bfd0-67cad8693c10`, deployed at `2026-05-24T09:01:25.706Z` | Passed |
15
19
  | KV cleanup | `wrangler kv key list --namespace-id fa5344fbd7944de6a776bf8731d58460 --remote` returned `[]` after smoke cleanup | Passed |
@@ -5,11 +5,11 @@
5
5
  ## Current State
6
6
 
7
7
  - Package name: `ai-saas-guard`
8
- - Current published version: `0.36.0`
8
+ - Current published version: `0.38.0`
9
9
  - Next source candidate: none
10
10
  - npm registry state: published at <https://www.npmjs.com/package/ai-saas-guard>
11
11
  - First npm-published version: `0.1.1`
12
- - GitHub Release: `v0.36.0`
12
+ - GitHub Release: `v0.38.0`
13
13
  - Publish workflow: `.github/workflows/npm-publish.yml`
14
14
  - Trusted Publisher: GitHub Actions, `zr9959/ai-saas-guard`, workflow `npm-publish.yml`, allowed action `npm publish`
15
15
  - Long-lived npm publish token: not required
@@ -18,7 +18,7 @@
18
18
 
19
19
  Use GitHub Actions with npm Trusted Publisher/OIDC:
20
20
 
21
- 1. Create and review a release tag such as `v0.36.0`.
21
+ 1. Create and review a release tag such as `v0.38.0`.
22
22
  2. Publish from the GitHub Release or run the `Publish npm` workflow manually with `ref` set to that tag.
23
23
  3. Keep `permissions.id-token: write` in the workflow so npm can exchange the GitHub Actions OIDC identity for a short-lived publish credential.
24
24
  4. Run `npm publish --access public` from the workflow. Trusted publishing automatically generates provenance for this public package from this public repository.
@@ -66,7 +66,7 @@ Implemented surfaces:
66
66
  - hosted Node/container app skeleton document and helpers for safe health and webhook HTTP ingress, one-job worker ticks, in-memory provider adapters, provider reference validation, and the chosen `node_container` roles `webhook-ingress` and `scan-worker`
67
67
  - hosted staging deployment planner document and helpers for provider binding, staging release-gate evidence, Node/container deployment composition, and production GitHub App promotion gating
68
68
  - hosted staging harness document and helpers for local signed webhook replay, file-backed queue/report/Check Run artifacts, worker sandbox cleanup verification, and release-gate evidence fixtures without cloud calls
69
- - live Cloudflare hosted ingress at `https://ai-saas-guard-hosted.zr9959.workers.dev` with `/healthz`, `/github/app/manifest-callback`, signed `/github/webhook` intake, Cloudflare KV storage, private staging GitHub App `ai-saas-guard-hosted` (`3834787`) installed on `zr9959/ai-saas-guard`, Worker code for scoped installation-token exchange, PR file metadata fetching, compact PR-risk classification, and bounded Check Run publishing, plus hosted operations evidence in `docs/hosted-operations-evidence.md`
69
+ - live Cloudflare hosted ingress at `https://ai-saas-guard-hosted.zr9959.workers.dev` with `/healthz`, `/github/app/install-info`, `/github/app/manifest-callback`, signed `/github/webhook` intake, Cloudflare KV storage, private staging GitHub App `ai-saas-guard-hosted` (`3834787`) installed on `zr9959/ai-saas-guard`, Worker code for public-safe install guidance, scoped installation-token exchange, PR file metadata fetching, compact PR-risk classification, bounded selected-repository Check Run publishing, and signed installation deletion/repository removal cleanup, plus hosted operations evidence in `docs/hosted-operations-evidence.md`
70
70
  - resource caps for repository text collection, including per-file, total-file, and total-byte scan budgets to reduce worst-case memory use
71
71
  - hosted pre-implementation contracts document, hosted compact report fixture, and pure helpers for pull request webhook intake planning, durable scan queue upsert planning, worker read-only scan planning, Check Run publication planning, queue-safe pull request event parsing from trusted GitHub event fields, bounded check-run summary rendering, idempotent queue cleanup planning, worker checkout cleanup planning, retention/deletion cleanup planning, and operational release gate evaluation
72
72
  - implementation-ready hosted GitHub App permission contract for required permissions, optional PR comment permissions, selected repository installation, and out-of-scope broad permissions
@@ -5,11 +5,14 @@ This directory contains the first live hosted ingress for `ai-saas-guard`.
5
5
  It is intentionally narrow:
6
6
 
7
7
  - `GET /healthz` returns public-safe service health.
8
+ - `GET /github/app/install-info` returns public-safe installation guidance, first-slice permissions, subscribed events, privacy boundaries, and uninstall wording.
8
9
  - `GET /github/app/manifest-callback` acknowledges the GitHub App manifest redirect without storing the one-time code.
9
10
  - `POST /github/webhook` verifies GitHub `sha256` webhook signatures before JSON parsing or storage.
10
11
  - Requests over 1 MiB are rejected before JSON parsing or KV writes.
11
12
  - Signed `pull_request` events are reduced to trusted GitHub identity fields and stored in Cloudflare KV.
12
13
  - When GitHub App bindings are configured, the Worker exchanges a scoped installation token, fetches PR file metadata from GitHub, runs compact PR-risk classification, and publishes a bounded Check Run summary.
14
+ - The Check Run summary names the selected-repository hosted check, the Review queue, and a Manual proof prompt so reviewers know which trust-boundary files to inspect before merge.
15
+ - Signed `installation` deletion events and `installation_repositories` `repositories_removed` events delete matching compact `scan:<installation>:...` records from KV when KV list/delete bindings are available.
13
16
  - Duplicate GitHub delivery IDs are accepted idempotently.
14
17
  - Responses and KV records do not include raw webhook payloads, PR title/body text, source code, diffs, secrets, customer payloads, checkout paths, or installation tokens.
15
18
 
@@ -20,7 +23,7 @@ This Worker is a real hosted ingress with first-slice Check Run publishing code,
20
23
  - `HOSTED_EVENTS`: Cloudflare KV namespace for compact delivery and queued scan records.
21
24
  - `WEBHOOK_SECRET`: Worker secret matching the GitHub App webhook secret.
22
25
  - `GITHUB_APP_PRIVATE_KEY`: Worker secret for the staging GitHub App private key, used only in memory to sign short-lived GitHub App JWTs.
23
- - `SCANNER_VERSION`: public version string, currently `0.28.0`.
26
+ - `SCANNER_VERSION`: public version string, currently `0.38.0`.
24
27
  - `GITHUB_APP_ID`, `GITHUB_APP_SLUG`, `GITHUB_APP_INSTALLATION_ID`: public staging identifiers for the private GitHub App installation.
25
28
 
26
29
  ## Deployment
@@ -39,6 +42,7 @@ After creating the KV namespace, replace the placeholder namespace ID in `wrangl
39
42
  Current public staging endpoint:
40
43
 
41
44
  - Worker URL: `https://ai-saas-guard-hosted.zr9959.workers.dev`
45
+ - Install-info URL: `https://ai-saas-guard-hosted.zr9959.workers.dev/github/app/install-info`
42
46
  - KV namespace binding: `HOSTED_EVENTS`
43
47
  - KV namespace ID: `fa5344fbd7944de6a776bf8731d58460`
44
48
  - GitHub App slug: `ai-saas-guard-hosted`
@@ -47,6 +51,39 @@ Current public staging endpoint:
47
51
  - Installed repository: `zr9959/ai-saas-guard`
48
52
  - Mode: signed webhook ingress, compact queueing, PR file metadata classification, and bounded Check Run publishing
49
53
 
54
+ ## Public Install Guidance
55
+
56
+ `GET /github/app/install-info` is designed for the first screen a repository admin sees before installation. It returns only public-safe fields:
57
+
58
+ - install URL for `ai-saas-guard-hosted`
59
+ - first-slice permissions: `checks: write`, `contents: read`, `pull_requests: read`, and `metadata: read`
60
+ - subscribed events: `pull_request`, `installation`, and `installation_repositories`
61
+ - selected-repository boundary and explicit wording that the hosted check is not an AI reviewer, pentest, full audit, or certification
62
+ - uninstall/data deletion wording for compact records
63
+
64
+ Do not add raw app private keys, webhook secrets, installation tokens, source, diffs, customer payloads, or checkout paths to this response.
65
+
66
+ ## Check Run Shape
67
+
68
+ The Check Run is intentionally compact. It should answer:
69
+
70
+ - What changed at a launch-risk boundary?
71
+ - Which files are in the Review queue?
72
+ - What Manual proof should block merge until it passes?
73
+ - What selected-repository permissions did the hosted check use?
74
+
75
+ The Check Run must not include patch text, source snippets, PR title/body text, secrets, installation tokens, customer data, or private checkout paths.
76
+
77
+ ## Uninstall And Repository Removal
78
+
79
+ The Worker handles signed GitHub cleanup events, including installation deletion:
80
+
81
+ - `installation` with action `deleted` deletes compact scan records for the installation.
82
+ - `installation_repositories` with action `removed` deletes compact scan records for removed repository IDs.
83
+ - repeated cleanup is safe because deleting an already-removed compact record is a no-op.
84
+
85
+ Delivery audit records may remain for the normal KV TTL. They must not contain source, diffs, secrets, customer payloads, PR-authored text, checkout paths, or installation tokens.
86
+
50
87
  ## Release Boundary
51
88
 
52
89
  Do not expose this as the full product. The hosted operational release gate still requires deployed evidence for Check Run publication, worker cleanup, monitoring, rollback, incident response, dependency and deployment artifact scanning, and GitHub App installation behavior.
@@ -17,6 +17,13 @@ const EVENT_TTL_SECONDS = 60 * 60 * 24 * 30;
17
17
  export const MAX_WEBHOOK_PAYLOAD_BYTES = 1024 * 1024;
18
18
  const MAX_PR_FILES_PAGES = 3;
19
19
  const MAX_PATCH_CHARS_PER_FILE = 20_000;
20
+ const HOSTED_APP_PERMISSIONS = {
21
+ checks: "write",
22
+ contents: "read",
23
+ metadata: "read",
24
+ pull_requests: "read"
25
+ };
26
+ const HOSTED_APP_EVENTS = ["pull_request", "installation", "installation_repositories"];
20
27
 
21
28
  const CATEGORY_WEIGHTS = {
22
29
  "auth/session": 30,
@@ -38,6 +45,10 @@ export default {
38
45
  return jsonResponse(200, createHostedWorkerHealth(env));
39
46
  }
40
47
 
48
+ if (request.method === "GET" && url.pathname === "/github/app/install-info") {
49
+ return jsonResponse(200, createHostedInstallInfo(env));
50
+ }
51
+
41
52
  if (request.method === "GET" && url.pathname === "/github/app/manifest-callback") {
42
53
  return jsonResponse(200, createGitHubAppManifestCallback(url));
43
54
  }
@@ -129,6 +140,38 @@ export default {
129
140
  });
130
141
  }
131
142
 
143
+ if (eventName === "installation" || eventName === "installation_repositories") {
144
+ let installationPayload;
145
+ try {
146
+ installationPayload = JSON.parse(payload);
147
+ } catch {
148
+ return jsonResponse(400, {
149
+ accepted: false,
150
+ stage: "payload",
151
+ reason: "invalid_json",
152
+ deliveryId,
153
+ privacy: HOSTED_WORKER_PRIVACY
154
+ });
155
+ }
156
+
157
+ const cleanup = await handleInstallationCleanupEvent({
158
+ kv: env.HOSTED_EVENTS,
159
+ deliveryKey,
160
+ deliveryId,
161
+ eventName,
162
+ payload: installationPayload
163
+ });
164
+ return jsonResponse(202, {
165
+ accepted: true,
166
+ stage: cleanup.cleaned ? "cleanup" : "ignored",
167
+ reason: cleanup.reason,
168
+ deliveryId,
169
+ deletedRecords: cleanup.deletedRecords,
170
+ shouldCreateCheckRun: false,
171
+ privacy: HOSTED_WORKER_PRIVACY
172
+ });
173
+ }
174
+
132
175
  if (eventName !== "pull_request") {
133
176
  await storeJson(env.HOSTED_EVENTS, deliveryKey, {
134
177
  deliveryId,
@@ -275,7 +318,7 @@ export function createHostedWorkerHealth(env = {}) {
275
318
  ok: true,
276
319
  service: "ai-saas-guard-hosted",
277
320
  mode: "webhook-ingress",
278
- routes: ["/healthz", "/github/app/manifest-callback", "/github/webhook"],
321
+ routes: ["/healthz", "/github/app/install-info", "/github/app/manifest-callback", "/github/webhook"],
279
322
  storage: "cloudflare_kv",
280
323
  checkRunPublisher: hasGitHubCheckRunConfig(env) ? "configured" : "not_configured",
281
324
  scannerVersion: env.SCANNER_VERSION || "unknown",
@@ -283,6 +326,24 @@ export function createHostedWorkerHealth(env = {}) {
283
326
  };
284
327
  }
285
328
 
329
+ export function createHostedInstallInfo(env = {}) {
330
+ const slug = stringValue(env.GITHUB_APP_SLUG) ?? "ai-saas-guard-hosted";
331
+
332
+ return {
333
+ ok: true,
334
+ service: "ai-saas-guard-hosted",
335
+ installUrl: `https://github.com/apps/${encodeURIComponent(slug)}/installations/new`,
336
+ permissions: HOSTED_APP_PERMISSIONS,
337
+ events: HOSTED_APP_EVENTS,
338
+ boundary:
339
+ "Install on selected repositories only. The hosted check turns PR trust-boundary changes into a review queue; it is not an AI reviewer, pentest, full audit, or certification.",
340
+ uninstall:
341
+ "Uninstall or repository removal deletes compact records for that installation or repository when GitHub sends the signed event. Local CLI use does not depend on hosted installation.",
342
+ scannerVersion: env.SCANNER_VERSION || "unknown",
343
+ privacy: HOSTED_WORKER_PRIVACY
344
+ };
345
+ }
346
+
286
347
  export function createGitHubAppManifestCallback(url) {
287
348
  return {
288
349
  ok: true,
@@ -387,6 +448,97 @@ async function storeJson(kv, key, value) {
387
448
  await kv.put(key, JSON.stringify(value), { expirationTtl: EVENT_TTL_SECONDS });
388
449
  }
389
450
 
451
+ async function handleInstallationCleanupEvent({ kv, deliveryKey, deliveryId, eventName, payload }) {
452
+ const cleanup = resolveInstallationCleanup(payload, eventName);
453
+ await storeJson(kv, deliveryKey, {
454
+ deliveryId,
455
+ eventName,
456
+ accepted: true,
457
+ reason: cleanup.reason,
458
+ installationId: cleanup.installationId,
459
+ repositoryIds: cleanup.repositoryIds,
460
+ receivedAt: new Date().toISOString()
461
+ });
462
+
463
+ if (!cleanup.cleaned || cleanup.installationId === undefined) {
464
+ return { ...cleanup, deletedRecords: 0 };
465
+ }
466
+
467
+ const deletedRecords = await deleteCompactRecordsForInstallation({
468
+ kv,
469
+ installationId: cleanup.installationId,
470
+ repositoryIds: cleanup.repositoryIds
471
+ });
472
+ return { ...cleanup, deletedRecords };
473
+ }
474
+
475
+ function resolveInstallationCleanup(payload, eventName) {
476
+ const installationId = integerValue(payload?.installation?.id);
477
+ if (installationId === undefined) {
478
+ return {
479
+ cleaned: false,
480
+ reason: "missing_installation_id",
481
+ installationId,
482
+ repositoryIds: []
483
+ };
484
+ }
485
+
486
+ if (eventName === "installation" && payload?.action === "deleted") {
487
+ return {
488
+ cleaned: true,
489
+ reason: "installation_deleted",
490
+ installationId,
491
+ repositoryIds: []
492
+ };
493
+ }
494
+
495
+ if (eventName === "installation_repositories" && payload?.action === "removed") {
496
+ const repositoryIds = Array.isArray(payload?.repositories_removed)
497
+ ? payload.repositories_removed.map((repo) => integerValue(repo?.id)).filter((id) => id !== undefined)
498
+ : [];
499
+ return {
500
+ cleaned: repositoryIds.length > 0,
501
+ reason: repositoryIds.length > 0 ? "repositories_removed" : "no_removed_repositories",
502
+ installationId,
503
+ repositoryIds
504
+ };
505
+ }
506
+
507
+ return {
508
+ cleaned: false,
509
+ reason: "installation_event_ignored",
510
+ installationId,
511
+ repositoryIds: []
512
+ };
513
+ }
514
+
515
+ async function deleteCompactRecordsForInstallation({ kv, installationId, repositoryIds }) {
516
+ if (typeof kv?.list !== "function" || typeof kv?.delete !== "function") {
517
+ return 0;
518
+ }
519
+
520
+ let deleted = 0;
521
+ const prefixes =
522
+ repositoryIds.length > 0
523
+ ? repositoryIds.map((repositoryId) => `scan:${installationId}:${repositoryId}:`)
524
+ : [`scan:${installationId}:`];
525
+
526
+ for (const prefix of prefixes) {
527
+ let cursor;
528
+ do {
529
+ const page = await kv.list({ prefix, cursor });
530
+ for (const key of page.keys ?? []) {
531
+ if (typeof key?.name !== "string") continue;
532
+ await kv.delete(key.name);
533
+ deleted += 1;
534
+ }
535
+ cursor = page.list_complete === false ? page.cursor : undefined;
536
+ } while (cursor);
537
+ }
538
+
539
+ return deleted;
540
+ }
541
+
390
542
  async function runHostedPrRiskCheck({ env, identity, scanKey, scannerVersion }) {
391
543
  try {
392
544
  await storeJson(env.HOSTED_EVENTS, scanKey, {
@@ -669,19 +821,27 @@ function summarizeFindings(findings) {
669
821
 
670
822
  function renderCheckRunSummary({ identity, report, scannerVersion }) {
671
823
  const lines = [
672
- `Review first: ai-saas-guard found ${report.summary.total} PR risk signal(s) for ${identity.repositoryFullName}#${identity.pullRequestNumber}.`,
824
+ `Launch-risk gate: ai-saas-guard found ${report.summary.total} PR risk signal(s) for ${identity.repositoryFullName}#${identity.pullRequestNumber}. Review first: inspect the listed trust-boundary files before merge.`,
673
825
  `Scanner version: ${scannerVersion}.`,
674
826
  "",
675
- "This is not a pentest, certification, or full security audit. Review the listed files before merge.",
827
+ "Selected-repository hosted check: this App uses checks:write, contents:read, pull_requests:read, and metadata:read for the installed repository only.",
828
+ "",
829
+ "This is not an AI reviewer, pentest, certification, or full security audit. Review the listed files before merge.",
830
+ "",
831
+ "Launch decision queue:",
832
+ "- Can a real user get access they should not have?",
833
+ "- Can the app claim success when something failed?",
834
+ "- Can launch infrastructure do too much damage?",
676
835
  "",
677
836
  "Privacy: this Check Run stores compact file/category signals only. It does not store webhook payload bodies, PR title/body text, diff contents, source, secrets, checkout paths, or installation tokens."
678
837
  ];
679
838
 
680
839
  if (report.topRiskyFiles.length > 0) {
681
- lines.push("", "Top files:");
840
+ lines.push("", "Review queue:");
682
841
  for (const file of report.topRiskyFiles.slice(0, 5)) {
683
842
  lines.push(`- ${file.path}: ${file.categories.join(", ")} (${file.added}+/${file.removed}-)`);
684
843
  }
844
+ lines.push("", "Manual proof:", "- Review the listed files locally and prove auth, billing, data, deploy, or test changes fail closed before merge.");
685
845
  }
686
846
 
687
847
  if (report.truncated) {
@@ -7,7 +7,7 @@
7
7
  "enabled": true
8
8
  },
9
9
  "vars": {
10
- "SCANNER_VERSION": "0.28.0",
10
+ "SCANNER_VERSION": "0.38.0",
11
11
  "GITHUB_APP_ID": "3834787",
12
12
  "GITHUB_APP_SLUG": "ai-saas-guard-hosted",
13
13
  "GITHUB_APP_INSTALLATION_ID": "135085075"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-saas-guard",
3
- "version": "0.36.0",
3
+ "version": "0.38.0",
4
4
  "description": "Local-first CLI that catches launch blockers in AI-built Next.js/Supabase/Stripe SaaS apps.",
5
5
  "readmeFilename": "README.md",
6
6
  "type": "module",