ai-saas-guard 0.37.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 +7 -7
- package/docs/README.zh-CN.md +9 -9
- package/docs/github-app-deployment.md +2 -0
- package/docs/hosted-operations-evidence.md +5 -1
- package/docs/npm-publishing.md +3 -3
- package/docs/project-handoff.md +1 -1
- package/hosted/cloudflare-worker/README.md +38 -1
- package/hosted/cloudflare-worker/src/index.js +157 -2
- package/hosted/cloudflare-worker/wrangler.jsonc +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -170,7 +170,7 @@ For a concise comparison with Semgrep, zizmor, OpenSSF Scorecard, Snyk, and GitH
|
|
|
170
170
|
| --- | --- | --- |
|
|
171
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 |
|
|
172
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 |
|
|
173
|
-
| Hosted GitHub App | Future hosted Check Run experience for selected repositories | Limited trial gate
|
|
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 |
|
|
174
174
|
|
|
175
175
|
## Quick Start
|
|
176
176
|
|
|
@@ -233,13 +233,13 @@ The CLI is published on npm as `ai-saas-guard`, and the GitHub Action is availab
|
|
|
233
233
|
| Area | Status |
|
|
234
234
|
| --- | --- |
|
|
235
235
|
| Public GitHub repository | Available |
|
|
236
|
-
| npm CLI | `ai-saas-guard@0.
|
|
237
|
-
| GitHub Action | `zr9959/ai-saas-guard@v0` or fixed tag `v0.
|
|
236
|
+
| npm CLI | `ai-saas-guard@0.38.0` |
|
|
237
|
+
| GitHub Action | `zr9959/ai-saas-guard@v0` or fixed tag `v0.38.0` |
|
|
238
238
|
| Outputs | Launch decision queue, short summary, terminal, JSON, SARIF, and PR-focused markdown |
|
|
239
239
|
| Project config | `.ai-saas-guard.json` rule toggles, severity overrides, suppressions, and fail thresholds |
|
|
240
240
|
| Privacy model | Local-first, read-only scan commands, no LLM calls, no code upload |
|
|
241
|
-
| Versioned Action tags | `v0.
|
|
242
|
-
| Current release | `0.
|
|
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 |
|
|
243
243
|
| npm publishing | Trusted Publisher/OIDC, no long-lived publish token |
|
|
244
244
|
| Repository trust hardening | Strict branch protection, Dependabot, CodeQL, fast-check fuzzing, signed release provenance assets, private vulnerability reporting, secret scanning, and push protection |
|
|
245
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 |
|
|
@@ -363,7 +363,7 @@ The hosted staging harness is documented in [docs/hosted-staging-harness.md](doc
|
|
|
363
363
|
|
|
364
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.
|
|
365
365
|
|
|
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/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
|
|
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.
|
|
367
367
|
|
|
368
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.
|
|
369
369
|
|
|
@@ -417,7 +417,7 @@ Use `suppressions` for narrower false-positive handling when one rule is noisy o
|
|
|
417
417
|
|
|
418
418
|
## GitHub Action
|
|
419
419
|
|
|
420
|
-
The repo includes a composite Action. Use `v0` for the latest compatible pre-1.0 Action, a specific release tag such as `v0.
|
|
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:
|
|
421
421
|
|
|
422
422
|
```yaml
|
|
423
423
|
name: ai-saas-guard
|
package/docs/README.zh-CN.md
CHANGED
|
@@ -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 体验 |
|
|
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,21 +211,21 @@ node dist/cli.js scan --root /path/to/your-saas
|
|
|
211
211
|
|
|
212
212
|
这个仓库是公开 GitHub 仓库。
|
|
213
213
|
|
|
214
|
-
CLI 已发布到 npm:`ai-saas-guard@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.
|
|
220
|
-
| GitHub Action | `zr9959/ai-saas-guard@v0` 或固定标签 `v0.
|
|
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.
|
|
225
|
-
| Action 标签 | `v0.
|
|
224
|
+
| 当前版本 | `0.38.0` 补齐更多 hosted GitHub App staging 闭环:公开 install-info、selected-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
|
|
228
|
+
| Cloudflare hosted ingress | 已部署到 `https://ai-saas-guard-hosted.zr9959.workers.dev`;提供 `/github/app/install-info`,签名 GitHub App webhook delivery、compact 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
231
|
| 上一版路线 | v0.36.0 计划见 [v0.36-roadmap.md](v0.36-roadmap.md) |
|
|
@@ -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
|
|
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
|
|
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
|
|
@@ -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-
|
|
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 |
|
package/docs/npm-publishing.md
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
## Current State
|
|
6
6
|
|
|
7
7
|
- Package name: `ai-saas-guard`
|
|
8
|
-
- Current published version: `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.
|
|
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.
|
|
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.
|
package/docs/project-handoff.md
CHANGED
|
@@ -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,
|
|
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.
|
|
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, {
|
|
@@ -672,6 +824,8 @@ function renderCheckRunSummary({ identity, report, scannerVersion }) {
|
|
|
672
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
|
"",
|
|
827
|
+
"Selected-repository hosted check: this App uses checks:write, contents:read, pull_requests:read, and metadata:read for the installed repository only.",
|
|
828
|
+
"",
|
|
675
829
|
"This is not an AI reviewer, pentest, certification, or full security audit. Review the listed files before merge.",
|
|
676
830
|
"",
|
|
677
831
|
"Launch decision queue:",
|
|
@@ -683,10 +837,11 @@ function renderCheckRunSummary({ identity, report, scannerVersion }) {
|
|
|
683
837
|
];
|
|
684
838
|
|
|
685
839
|
if (report.topRiskyFiles.length > 0) {
|
|
686
|
-
lines.push("", "
|
|
840
|
+
lines.push("", "Review queue:");
|
|
687
841
|
for (const file of report.topRiskyFiles.slice(0, 5)) {
|
|
688
842
|
lines.push(`- ${file.path}: ${file.categories.join(", ")} (${file.added}+/${file.removed}-)`);
|
|
689
843
|
}
|
|
844
|
+
lines.push("", "Manual proof:", "- Review the listed files locally and prove auth, billing, data, deploy, or test changes fail closed before merge.");
|
|
690
845
|
}
|
|
691
846
|
|
|
692
847
|
if (report.truncated) {
|
package/package.json
CHANGED