ai-saas-guard 0.40.0 → 0.41.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 +6 -6
- package/dist/hosted/contracts.js +2 -2
- package/dist/hosted/worker.d.ts +69 -0
- package/dist/hosted/worker.js +99 -0
- package/docs/README.zh-CN.md +6 -6
- package/docs/hosted-operational-release-gate.md +9 -0
- package/docs/hosted-operations-evidence.md +5 -1
- package/docs/npm-publishing.md +3 -3
- package/hosted/cloudflare-worker/README.md +1 -1
- package/hosted/cloudflare-worker/src/index.js +15 -17
- package/hosted/cloudflare-worker/wrangler.jsonc +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -235,13 +235,13 @@ The CLI is published on npm as `ai-saas-guard`, and the GitHub Action is availab
|
|
|
235
235
|
| Area | Status |
|
|
236
236
|
| --- | --- |
|
|
237
237
|
| Public GitHub repository | Available |
|
|
238
|
-
| npm CLI | `ai-saas-guard@0.
|
|
239
|
-
| GitHub Action | `zr9959/ai-saas-guard@v0` or fixed tag `v0.
|
|
238
|
+
| npm CLI | `ai-saas-guard@0.41.0` |
|
|
239
|
+
| GitHub Action | `zr9959/ai-saas-guard@v0` or fixed tag `v0.41.0` |
|
|
240
240
|
| Outputs | Launch decision queue, short summary, terminal, JSON, SARIF, and PR-focused markdown |
|
|
241
241
|
| Project config | `.ai-saas-guard.json` rule toggles, severity overrides, suppressions, and fail thresholds |
|
|
242
242
|
| Privacy model | Local-first, read-only scan commands, no LLM calls, no code upload |
|
|
243
|
-
| Versioned Action tags | `v0.
|
|
244
|
-
| Current release | `0.
|
|
243
|
+
| Versioned Action tags | `v0.41.0`, `v0` |
|
|
244
|
+
| Current release | `0.41.0` adds source-checkout trial planning/evidence contracts, compresses hosted Check Run reviewer output, and documents the next hosted source-checkout gate |
|
|
245
245
|
| npm publishing | Trusted Publisher/OIDC, no long-lived publish token |
|
|
246
246
|
| Repository trust hardening | Strict branch protection, Dependabot, CodeQL, fast-check fuzzing, signed release provenance assets, private vulnerability reporting, secret scanning, and push protection |
|
|
247
247
|
| Cloudflare hosted ingress | Deployed at `https://ai-saas-guard-hosted.zr9959.workers.dev`; public install/privacy notes are in [docs/hosted-install-privacy.md](docs/hosted-install-privacy.md); signed GitHub App webhook delivery and compact Check Run smoke now pass in staging |
|
|
@@ -367,7 +367,7 @@ Deployed worker staging evidence is documented in [docs/hosted-deployed-worker-s
|
|
|
367
367
|
|
|
368
368
|
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.
|
|
369
369
|
|
|
370
|
-
The next hosted source-checkout step is intentionally narrow: deploy the existing read-only checkout worker behind the same selected-repository identity, keep the fixed `pr-risk --json` command, write only compact findings to the Check Run, and require deployed cleanup/log-boundary/rollback evidence before broader trial use.
|
|
370
|
+
The next hosted source-checkout step is intentionally narrow: deploy the existing read-only checkout worker behind the same selected-repository identity, keep the fixed `pr-risk --json` command, write only compact findings to the Check Run, and require deployed cleanup/log-boundary/rollback evidence before broader trial use. The v0.41 contract layer adds `createHostedSourceCheckoutTrialPlan` and `createHostedSourceCheckoutEvidence` so checkout start/end, token removal, CLI start/end, compact report write, Check Run write, and cleanup status can be reviewed before public exposure.
|
|
371
371
|
|
|
372
372
|
Hosted install and privacy details are summarized in [docs/hosted-install-privacy.md](docs/hosted-install-privacy.md): selected-repository permissions, supported events, Check Run data boundaries, uninstall cleanup, and why the local CLI remains the private/offline path.
|
|
373
373
|
|
|
@@ -423,7 +423,7 @@ Use `suppressions` for narrower false-positive handling when one rule is noisy o
|
|
|
423
423
|
|
|
424
424
|
## GitHub Action
|
|
425
425
|
|
|
426
|
-
The repo includes a composite Action. Use `v0` for the latest compatible pre-1.0 Action, a specific release tag such as `v0.
|
|
426
|
+
The repo includes a composite Action. Use `v0` for the latest compatible pre-1.0 Action, a specific release tag such as `v0.41.0` for controlled upgrades, or pin a reviewed commit SHA for stricter supply-chain control:
|
|
427
427
|
|
|
428
428
|
```yaml
|
|
429
429
|
name: ai-saas-guard
|
package/dist/hosted/contracts.js
CHANGED
|
@@ -487,7 +487,7 @@ export function createHostedCheckRunSummary(input) {
|
|
|
487
487
|
title: formatCheckRunTitle(totalFindings, conclusion, input.failOnSeverity),
|
|
488
488
|
summary: [
|
|
489
489
|
`Launch-risk gate: ${launchGate}. Launch gate: ${launchGate}.`,
|
|
490
|
-
"Review task: inspect
|
|
490
|
+
"Review task: inspect risk areas and files before merge.",
|
|
491
491
|
"Manual proof: prove changed auth, billing, data, deploy, or tests fail closed.",
|
|
492
492
|
"Boundary: selected repository only; not an AI reviewer, pentest, full audit, or certification."
|
|
493
493
|
].join(" "),
|
|
@@ -1154,7 +1154,7 @@ function formatCheckRunMarkdown(report, conclusion, localCliCommand, launchGate)
|
|
|
1154
1154
|
return [
|
|
1155
1155
|
"### AI SaaS Guard Launch-risk gate",
|
|
1156
1156
|
"",
|
|
1157
|
-
"Review task: inspect
|
|
1157
|
+
"Review task: inspect risk areas and files before merge.",
|
|
1158
1158
|
"Manual proof: prove changed auth, billing, data, deploy, or tests fail closed.",
|
|
1159
1159
|
"Boundary: selected repository only; not an AI reviewer, pentest, full audit, or certification.",
|
|
1160
1160
|
"",
|
package/dist/hosted/worker.d.ts
CHANGED
|
@@ -27,6 +27,73 @@ export interface HostedReadOnlyCheckoutScanRunnerOptions {
|
|
|
27
27
|
installationTokenProvider: HostedInstallationTokenProvider;
|
|
28
28
|
commandRunner?: HostedReadOnlyCheckoutCommandRunner;
|
|
29
29
|
}
|
|
30
|
+
export interface HostedSourceCheckoutTrialPlanInput {
|
|
31
|
+
requestedAt: string;
|
|
32
|
+
repositoryFullName: string;
|
|
33
|
+
selectedRepositoryOnly: boolean;
|
|
34
|
+
permissions: {
|
|
35
|
+
contents: "read" | "write" | "none";
|
|
36
|
+
checks: "write" | "read" | "none";
|
|
37
|
+
};
|
|
38
|
+
command: string[];
|
|
39
|
+
storesRawSource: boolean;
|
|
40
|
+
storesRawDiffs: boolean;
|
|
41
|
+
storesInstallationToken: boolean;
|
|
42
|
+
exposesPublicScanner: boolean;
|
|
43
|
+
}
|
|
44
|
+
export interface HostedSourceCheckoutTrialPlan {
|
|
45
|
+
readyForTrial: boolean;
|
|
46
|
+
blockedReasons: Array<"repository_not_selected" | "contents_read_required" | "checks_write_required" | "fixed_pr_risk_json_command_required" | "raw_source_storage_blocked" | "raw_diff_storage_blocked" | "installation_token_storage_blocked" | "public_scanner_claim_blocked">;
|
|
47
|
+
requestedAt: string;
|
|
48
|
+
repositoryFullName: string;
|
|
49
|
+
permissions: {
|
|
50
|
+
contents: "read";
|
|
51
|
+
checks: "write";
|
|
52
|
+
};
|
|
53
|
+
fixedCommand: ["ai-saas-guard", "pr-risk", "--root", "<worker-checkout>", "--base", "<trusted-base-sha>", "--json"];
|
|
54
|
+
stages: Array<{
|
|
55
|
+
id: "checkout_start" | "token_remove" | "cli_start" | "cli_end" | "compact_report_write" | "check_run_write" | "cleanup_end";
|
|
56
|
+
evidence: string;
|
|
57
|
+
}>;
|
|
58
|
+
privacy: {
|
|
59
|
+
storesRawSource: false;
|
|
60
|
+
storesRawDiffs: false;
|
|
61
|
+
storesInstallationToken: false;
|
|
62
|
+
exposesPublicScanner: false;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export interface HostedSourceCheckoutEvidenceInput {
|
|
66
|
+
requestedAt: string;
|
|
67
|
+
jobKey: string;
|
|
68
|
+
stages: Array<{
|
|
69
|
+
id: HostedSourceCheckoutTrialPlan["stages"][number]["id"];
|
|
70
|
+
ok: boolean;
|
|
71
|
+
at: string;
|
|
72
|
+
}>;
|
|
73
|
+
summaryCounts: Record<string, number>;
|
|
74
|
+
compactFindingCount: number;
|
|
75
|
+
cleanupStatus: "deleted" | "failed" | "unknown";
|
|
76
|
+
rawSource?: string;
|
|
77
|
+
rawDiff?: string;
|
|
78
|
+
checkoutPath?: string;
|
|
79
|
+
installationToken?: string;
|
|
80
|
+
}
|
|
81
|
+
export interface HostedSourceCheckoutEvidence {
|
|
82
|
+
readyForReleaseGate: boolean;
|
|
83
|
+
blockedReasons: Array<"missing_checkout_start" | "missing_token_remove" | "missing_cli_start" | "missing_cli_end" | "missing_compact_report_write" | "missing_check_run_write" | "missing_cleanup_end" | "cleanup_not_deleted">;
|
|
84
|
+
requestedAt: string;
|
|
85
|
+
jobKey: string;
|
|
86
|
+
stageResults: HostedSourceCheckoutEvidenceInput["stages"];
|
|
87
|
+
summaryCounts: Record<string, number>;
|
|
88
|
+
compactFindingCount: number;
|
|
89
|
+
cleanupStatus: "deleted" | "failed" | "unknown";
|
|
90
|
+
privacy: {
|
|
91
|
+
includesRawSource: false;
|
|
92
|
+
includesRawDiffs: false;
|
|
93
|
+
includesPrivateCheckoutPath: false;
|
|
94
|
+
includesInstallationToken: false;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
30
97
|
export interface HostedReadOnlyCheckoutScanGateInput {
|
|
31
98
|
requestedAt: string;
|
|
32
99
|
jobKey: string;
|
|
@@ -74,5 +141,7 @@ export declare class HostedReadOnlyCheckoutScanError extends Error {
|
|
|
74
141
|
constructor(safeReason: HostedReadOnlyCheckoutScanSafeReason);
|
|
75
142
|
}
|
|
76
143
|
export declare function createHostedReadOnlyCheckoutScanRunner(options: HostedReadOnlyCheckoutScanRunnerOptions): HostedServiceScanRunner;
|
|
144
|
+
export declare function createHostedSourceCheckoutTrialPlan(input: HostedSourceCheckoutTrialPlanInput): HostedSourceCheckoutTrialPlan;
|
|
145
|
+
export declare function createHostedSourceCheckoutEvidence(input: HostedSourceCheckoutEvidenceInput): HostedSourceCheckoutEvidence;
|
|
77
146
|
export declare function evaluateHostedReadOnlyCheckoutScanGate(input: HostedReadOnlyCheckoutScanGateInput): HostedReadOnlyCheckoutScanGate;
|
|
78
147
|
export declare function runHostedReadOnlyCheckoutScan(input: HostedServiceScanRunnerInput, options: HostedReadOnlyCheckoutScanRunnerOptions): Promise<HostedServiceScanRunnerResult>;
|
package/dist/hosted/worker.js
CHANGED
|
@@ -28,6 +28,105 @@ export class HostedReadOnlyCheckoutScanError extends Error {
|
|
|
28
28
|
export function createHostedReadOnlyCheckoutScanRunner(options) {
|
|
29
29
|
return (input) => runHostedReadOnlyCheckoutScan(input, options);
|
|
30
30
|
}
|
|
31
|
+
export function createHostedSourceCheckoutTrialPlan(input) {
|
|
32
|
+
const blockedReasons = [];
|
|
33
|
+
const expectedCommand = [
|
|
34
|
+
"ai-saas-guard",
|
|
35
|
+
"pr-risk",
|
|
36
|
+
"--root",
|
|
37
|
+
"<worker-checkout>",
|
|
38
|
+
"--base",
|
|
39
|
+
"<trusted-base-sha>",
|
|
40
|
+
"--json"
|
|
41
|
+
];
|
|
42
|
+
if (!input.selectedRepositoryOnly)
|
|
43
|
+
blockedReasons.push("repository_not_selected");
|
|
44
|
+
if (input.permissions.contents !== "read")
|
|
45
|
+
blockedReasons.push("contents_read_required");
|
|
46
|
+
if (input.permissions.checks !== "write")
|
|
47
|
+
blockedReasons.push("checks_write_required");
|
|
48
|
+
if (!arraysEqual(input.command, expectedCommand)) {
|
|
49
|
+
blockedReasons.push("fixed_pr_risk_json_command_required");
|
|
50
|
+
}
|
|
51
|
+
if (input.storesRawSource)
|
|
52
|
+
blockedReasons.push("raw_source_storage_blocked");
|
|
53
|
+
if (input.storesRawDiffs)
|
|
54
|
+
blockedReasons.push("raw_diff_storage_blocked");
|
|
55
|
+
if (input.storesInstallationToken)
|
|
56
|
+
blockedReasons.push("installation_token_storage_blocked");
|
|
57
|
+
if (input.exposesPublicScanner)
|
|
58
|
+
blockedReasons.push("public_scanner_claim_blocked");
|
|
59
|
+
return {
|
|
60
|
+
readyForTrial: blockedReasons.length === 0,
|
|
61
|
+
blockedReasons,
|
|
62
|
+
requestedAt: input.requestedAt,
|
|
63
|
+
repositoryFullName: input.repositoryFullName,
|
|
64
|
+
permissions: {
|
|
65
|
+
contents: "read",
|
|
66
|
+
checks: "write"
|
|
67
|
+
},
|
|
68
|
+
fixedCommand: [
|
|
69
|
+
"ai-saas-guard",
|
|
70
|
+
"pr-risk",
|
|
71
|
+
"--root",
|
|
72
|
+
"<worker-checkout>",
|
|
73
|
+
"--base",
|
|
74
|
+
"<trusted-base-sha>",
|
|
75
|
+
"--json"
|
|
76
|
+
],
|
|
77
|
+
stages: [
|
|
78
|
+
{ id: "checkout_start", evidence: "trusted selected-repository identity and contents:read token requested" },
|
|
79
|
+
{ id: "token_remove", evidence: "temporary askpass and token material removed before CLI starts" },
|
|
80
|
+
{ id: "cli_start", evidence: "fixed pr-risk --json command starts in temporary worker checkout" },
|
|
81
|
+
{ id: "cli_end", evidence: "CLI exits with bounded output and compact JSON only" },
|
|
82
|
+
{ id: "compact_report_write", evidence: "compact findings stored without source, diff, secrets, or checkout paths" },
|
|
83
|
+
{ id: "check_run_write", evidence: "bounded Check Run written from compact findings only" },
|
|
84
|
+
{ id: "cleanup_end", evidence: "worker checkout, generated artifacts, and credential material deleted" }
|
|
85
|
+
],
|
|
86
|
+
privacy: {
|
|
87
|
+
storesRawSource: false,
|
|
88
|
+
storesRawDiffs: false,
|
|
89
|
+
storesInstallationToken: false,
|
|
90
|
+
exposesPublicScanner: false
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export function createHostedSourceCheckoutEvidence(input) {
|
|
95
|
+
const requiredStages = [
|
|
96
|
+
"checkout_start",
|
|
97
|
+
"token_remove",
|
|
98
|
+
"cli_start",
|
|
99
|
+
"cli_end",
|
|
100
|
+
"compact_report_write",
|
|
101
|
+
"check_run_write",
|
|
102
|
+
"cleanup_end"
|
|
103
|
+
];
|
|
104
|
+
const observed = new Map(input.stages.map((stage) => [stage.id, stage]));
|
|
105
|
+
const blockedReasons = [];
|
|
106
|
+
for (const stage of requiredStages) {
|
|
107
|
+
if (observed.get(stage)?.ok !== true) {
|
|
108
|
+
blockedReasons.push(`missing_${stage}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (input.cleanupStatus !== "deleted")
|
|
112
|
+
blockedReasons.push("cleanup_not_deleted");
|
|
113
|
+
return {
|
|
114
|
+
readyForReleaseGate: blockedReasons.length === 0,
|
|
115
|
+
blockedReasons,
|
|
116
|
+
requestedAt: input.requestedAt,
|
|
117
|
+
jobKey: input.jobKey,
|
|
118
|
+
stageResults: input.stages.map((stage) => ({ ...stage })),
|
|
119
|
+
summaryCounts: { ...input.summaryCounts },
|
|
120
|
+
compactFindingCount: input.compactFindingCount,
|
|
121
|
+
cleanupStatus: input.cleanupStatus,
|
|
122
|
+
privacy: {
|
|
123
|
+
includesRawSource: false,
|
|
124
|
+
includesRawDiffs: false,
|
|
125
|
+
includesPrivateCheckoutPath: false,
|
|
126
|
+
includesInstallationToken: false
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
31
130
|
export function evaluateHostedReadOnlyCheckoutScanGate(input) {
|
|
32
131
|
const requiredStages = [
|
|
33
132
|
"git_init",
|
package/docs/README.zh-CN.md
CHANGED
|
@@ -213,18 +213,18 @@ node dist/cli.js scan --root /path/to/your-saas
|
|
|
213
213
|
|
|
214
214
|
这个仓库是公开 GitHub 仓库。
|
|
215
215
|
|
|
216
|
-
CLI 已发布到 npm:`ai-saas-guard@0.
|
|
216
|
+
CLI 已发布到 npm:`ai-saas-guard@0.41.0`。GitHub Action 支持 `v0` 浮动标签,也支持固定版本标签,例如 `v0.41.0`。
|
|
217
217
|
|
|
218
218
|
| 模块 | 状态 |
|
|
219
219
|
| --- | --- |
|
|
220
220
|
| 公开 GitHub 仓库 | 已可用 |
|
|
221
|
-
| npm CLI | `ai-saas-guard@0.
|
|
222
|
-
| GitHub Action | `zr9959/ai-saas-guard@v0` 或固定标签 `v0.
|
|
221
|
+
| npm CLI | `ai-saas-guard@0.41.0` |
|
|
222
|
+
| GitHub Action | `zr9959/ai-saas-guard@v0` 或固定标签 `v0.41.0` |
|
|
223
223
|
| 输出格式 | 上线决策队列、短 summary、Terminal、JSON、SARIF 和 PR markdown |
|
|
224
224
|
| 项目配置 | `.ai-saas-guard.json` 支持规则开关、severity 覆盖、suppressions 和 fail threshold |
|
|
225
225
|
| 隐私模型 | 本地优先、只读扫描、不调用 LLM、不上传代码 |
|
|
226
|
-
| 当前版本 | `0.
|
|
227
|
-
| Action 标签 | `v0.
|
|
226
|
+
| 当前版本 | `0.41.0` 增加 source-checkout trial plan/evidence 契约,压缩 hosted Check Run reviewer 输出,并记录下一阶段 hosted source-checkout gate |
|
|
227
|
+
| Action 标签 | `v0.41.0`、`v0` |
|
|
228
228
|
| npm 发布 | GitHub Actions Trusted Publisher/OIDC,无需长期 npm token |
|
|
229
229
|
| 仓库可信度加固 | 严格 branch protection、Dependabot、CodeQL、fast-check fuzzing、signed release provenance assets、private vulnerability reporting、secret scanning 和 push protection |
|
|
230
230
|
| Cloudflare hosted ingress | 已部署到 `https://ai-saas-guard-hosted.zr9959.workers.dev`;安装和隐私说明见 [hosted-install-privacy.md](hosted-install-privacy.md);提供 `/github/app/install-info`,签名 GitHub App webhook delivery、compact Check Run 和 installation cleanup staging smoke 已通过 |
|
|
@@ -383,7 +383,7 @@ GitHub Marketplace wrapper 决策见 [docs/github-marketplace-wrapper-decision.m
|
|
|
383
383
|
|
|
384
384
|
当前仓库已经包含未来 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。
|
|
385
385
|
|
|
386
|
-
下一步 hosted source checkout 仍然要保持窄边界:把现有 read-only checkout worker 放到同一个 selected-repository identity 后面,继续固定 `pr-risk --json` 命令,只把 compact findings 写入 Check Run,并在扩大 trial 前要求 deployed cleanup、log-boundary 和 rollback evidence。
|
|
386
|
+
下一步 hosted source checkout 仍然要保持窄边界:把现有 read-only checkout worker 放到同一个 selected-repository identity 后面,继续固定 `pr-risk --json` 命令,只把 compact findings 写入 Check Run,并在扩大 trial 前要求 deployed cleanup、log-boundary 和 rollback evidence。v0.41 契约层新增 `createHostedSourceCheckoutTrialPlan` 和 `createHostedSourceCheckoutEvidence`,用于在公开暴露前检查 checkout start/end、token removal、CLI start/end、compact report write、Check Run write 和 cleanup status。
|
|
387
387
|
|
|
388
388
|
Hosted 安装、权限和隐私边界见 [hosted-install-privacy.md](hosted-install-privacy.md):selected-repository 权限、支持的 GitHub 事件、Check Run 数据边界、卸载清理,以及为什么本地 CLI 仍然是私有/离线路径。
|
|
389
389
|
|
|
@@ -170,6 +170,15 @@ Required proof:
|
|
|
170
170
|
- failure cleanup covers clone failure, timeout, CLI failure, malformed JSON output, Check Run write failure, cancellation, and process interruption.
|
|
171
171
|
- cleanup failures create an operator-review event without returning raw source, raw diffs, installation tokens, checkout paths, private URLs, or low-level filesystem errors to users.
|
|
172
172
|
|
|
173
|
+
### Source Checkout Trial Evidence Contract
|
|
174
|
+
|
|
175
|
+
The v0.41 source-checkout trial boundary is executable as a pure contract before public exposure. The `ai-saas-guard/hosted/worker` export now includes:
|
|
176
|
+
|
|
177
|
+
- `createHostedSourceCheckoutTrialPlan`: accepts the proposed selected-repository trial shape and blocks anything wider than `contents: read`, `checks: write`, fixed `ai-saas-guard pr-risk --root <worker-checkout> --base <trusted-base-sha> --json`, no raw source storage, no raw diff storage, no installation-token storage, and no public scanner claim.
|
|
178
|
+
- `createHostedSourceCheckoutEvidence`: records stage-level evidence for `checkout_start`, `token_remove`, `cli_start`, `cli_end`, `compact_report_write`, `check_run_write`, and `cleanup_end`, then blocks release-gate readiness unless every stage passed and cleanup status is `deleted`.
|
|
179
|
+
|
|
180
|
+
The evidence object must stay compact and privacy-safe. It may contain job key, stage IDs, timestamps, summary counts, compact finding count, cleanup status, and safe blocked reasons. It must not include source, diffs, checkout paths, installation tokens, PR-authored commands, private URLs, or low-level filesystem errors.
|
|
181
|
+
|
|
173
182
|
### Log Boundary Evidence
|
|
174
183
|
|
|
175
184
|
Before exposure, sample ingress, queue, worker, report, and Check Run logs for the release candidate. The sample may contain scan key, installation ID, repository ID, PR number, head SHA, scanner version, duration, summary counts, error class, and cleanup status.
|
|
@@ -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-25 from the deployed Cloudflare Worker plus
|
|
9
|
+
Recorded on 2026-05-25 from the deployed Cloudflare Worker plus temporary GitHub PR smokes.
|
|
10
10
|
|
|
11
11
|
| Check | Evidence | Result |
|
|
12
12
|
| --- | --- | --- |
|
|
13
|
+
| Cloudflare Worker health, v0.41.0 | `GET https://ai-saas-guard-hosted.zr9959.workers.dev/healthz` returned `ok: true`, routes including `/github/app/install-info`, `checkRunPublisher: "configured"`, `scannerVersion: "0.41.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.41.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.41.0"`, and no private keys, webhook secrets, installation tokens, source, diffs, or customer payloads | Passed |
|
|
15
|
+
| Deployed Worker version, v0.41.0 | `wrangler deploy` uploaded 38.57 KiB / gzip 9.86 KiB and deployed version `fb0b4726-ac75-4577-942b-fdeed7752979` at `2026-05-25T13:22:28Z` verification time | Passed |
|
|
16
|
+
| Real hosted PR smoke, v0.41.0 | `node scripts/hosted-pr-smoke.mjs --evidence-file /tmp/ai-saas-guard-hosted-smoke-v0.41.json` opened temporary PR `#87`, waited for Check Run `77718782535` on head SHA `83a341dcba63ad9a30aabdfec1de4f874a3c0b11`, received conclusion `success`, closed the PR, restored the original branch, deleted the local branch, and deleted 9 staging KV records with `remainingSmokeKeys: 0`; `gh pr view 87` returned `state: "CLOSED"`, `git ls-remote --heads origin codex/hosted-smoke-20260525132327` returned no remote branch, and `wrangler kv key list --namespace-id fa5344fbd7944de6a776bf8731d58460 --remote` returned `[]` after cleanup | Passed |
|
|
13
17
|
| Cloudflare Worker health, v0.40.0 | `GET https://ai-saas-guard-hosted.zr9959.workers.dev/healthz` returned `ok: true`, routes including `/github/app/install-info`, `checkRunPublisher: "configured"`, `scannerVersion: "0.40.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
|
| Public install guidance, v0.40.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.40.0"`, and no private keys, webhook secrets, installation tokens, source, diffs, or customer payloads | Passed |
|
|
15
19
|
| Deployed Worker version, v0.40.0 | `wrangler deploy` uploaded 38.99 KiB / gzip 10.01 KiB and deployed version `47e90d1c-0d7b-455f-b1a4-1ec7ee10d58b` at `2026-05-25T12:47:28Z` verification time | 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.41.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.41.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.41.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.
|
|
@@ -23,7 +23,7 @@ This Worker is a real hosted ingress with first-slice Check Run publishing code,
|
|
|
23
23
|
- `HOSTED_EVENTS`: Cloudflare KV namespace for compact delivery and queued scan records.
|
|
24
24
|
- `WEBHOOK_SECRET`: Worker secret matching the GitHub App webhook secret.
|
|
25
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.
|
|
26
|
-
- `SCANNER_VERSION`: public version string, currently `0.
|
|
26
|
+
- `SCANNER_VERSION`: public version string, currently `0.41.0`.
|
|
27
27
|
- `GITHUB_APP_ID`, `GITHUB_APP_SLUG`, `GITHUB_APP_INSTALLATION_ID`: public staging identifiers for the private GitHub App installation.
|
|
28
28
|
|
|
29
29
|
## Deployment
|
|
@@ -822,29 +822,19 @@ function summarizeFindings(findings) {
|
|
|
822
822
|
function renderCheckRunSummary({ identity, report, scannerVersion }) {
|
|
823
823
|
const riskAreas = summarizeHostedRiskAreas(report.topRiskyFiles);
|
|
824
824
|
const lines = [
|
|
825
|
-
`Launch-risk gate:
|
|
826
|
-
|
|
827
|
-
"",
|
|
828
|
-
"Review task: inspect the files below before merge.",
|
|
825
|
+
`Launch-risk gate: ${report.summary.total} PR risk signal(s) for ${identity.repositoryFullName}#${identity.pullRequestNumber}.`,
|
|
826
|
+
"Review task: inspect risk areas and files before merge.",
|
|
829
827
|
"Manual proof: prove changed auth, billing, data, deploy, or tests fail closed.",
|
|
830
|
-
"Boundary: selected repository only; not an AI reviewer, pentest, full audit, or certification.",
|
|
831
|
-
"",
|
|
832
|
-
"Selected-repository hosted check: this App uses checks:write, contents:read, pull_requests:read, and metadata:read for the installed repository only.",
|
|
833
828
|
"",
|
|
834
|
-
"
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
829
|
+
"Risk areas:",
|
|
830
|
+
...(riskAreas.length === 0
|
|
831
|
+
? ["- None"]
|
|
832
|
+
: riskAreas.slice(0, 5).map((area) => `- ${area.name}: ${area.count} file(s). Proof: ${area.proof}`)),
|
|
838
833
|
"",
|
|
839
|
-
"
|
|
834
|
+
"Review queue:"
|
|
840
835
|
];
|
|
841
836
|
|
|
842
837
|
if (report.topRiskyFiles.length > 0) {
|
|
843
|
-
lines.push("", "Risk areas:");
|
|
844
|
-
for (const area of riskAreas.slice(0, 5)) {
|
|
845
|
-
lines.push(`- ${area.name}: ${area.count} file(s). Proof: ${area.proof}`);
|
|
846
|
-
}
|
|
847
|
-
lines.push("", "Review queue:");
|
|
848
838
|
for (const file of report.topRiskyFiles.slice(0, 5)) {
|
|
849
839
|
lines.push(`- ${file.path}: ${file.categories.join(", ")} (${file.added}+/${file.removed}-)`);
|
|
850
840
|
}
|
|
@@ -855,8 +845,16 @@ function renderCheckRunSummary({ identity, report, scannerVersion }) {
|
|
|
855
845
|
"- Why is this auth, billing, data, deploy, or test decision safe?",
|
|
856
846
|
"- What manual proof shows it fails closed?"
|
|
857
847
|
);
|
|
848
|
+
} else {
|
|
849
|
+
lines.push("- None");
|
|
858
850
|
}
|
|
859
851
|
|
|
852
|
+
lines.push(
|
|
853
|
+
"",
|
|
854
|
+
`Boundary: selected repository only; scanner ${scannerVersion}; not an AI reviewer, pentest, full audit, or certification.`,
|
|
855
|
+
"Privacy: compact file/category signals only; no webhook bodies, PR text, source, diffs, secrets, checkout paths, or installation tokens."
|
|
856
|
+
);
|
|
857
|
+
|
|
860
858
|
if (report.truncated) {
|
|
861
859
|
lines.push("", "Note: PR file pagination was capped; run the local CLI for a complete review.");
|
|
862
860
|
}
|
package/package.json
CHANGED