ai-saas-guard 0.22.0 → 0.24.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 +13 -5
- package/README.zh-CN.md +11 -3
- package/dist/hosted/staging-harness.d.ts +85 -0
- package/dist/hosted/staging-harness.js +215 -0
- package/docs/github-action.md +1 -1
- package/docs/hosted-preimplementation-contracts.md +23 -1
- package/docs/hosted-service-runtime.md +3 -0
- package/docs/hosted-staging-deployment.md +4 -0
- package/docs/hosted-staging-harness.md +63 -0
- package/docs/npm-publishing.md +3 -3
- package/docs/project-handoff.md +8 -3
- package/docs/repository-trust-hardening.md +77 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -73,13 +73,15 @@ The CLI is published on npm as `ai-saas-guard`, and the GitHub Action is availab
|
|
|
73
73
|
| JSON and SARIF output | Available |
|
|
74
74
|
| Composite GitHub Action | Available |
|
|
75
75
|
| Project config | `.ai-saas-guard.json` rule toggles, severity overrides, and fail thresholds |
|
|
76
|
-
| Versioned Action tags | `v0.
|
|
77
|
-
| npm package | `ai-saas-guard@0.
|
|
76
|
+
| Versioned Action tags | `v0.24.0`, `v0` |
|
|
77
|
+
| npm package | `ai-saas-guard@0.24.0` |
|
|
78
78
|
| npm publishing | Trusted Publisher/OIDC, no long-lived publish token |
|
|
79
|
+
| Repository trust hardening | Branch protection, Dependabot, CodeQL, private vulnerability reporting, secret scanning, and push protection |
|
|
79
80
|
| Runtime hardening | Per-file and total text scan caps, escaped markdown evidence, stricter hosted deployment blockers |
|
|
80
81
|
| Hosted production adapters | GitHub App JWT signing, installation-token request planning, bounded worker execution, and terminal-state cleanup planning |
|
|
81
82
|
| Hosted app skeleton | Node/container HTTP ingress, health route, worker tick, in-memory provider adapters, and deployment plan validation |
|
|
82
83
|
| Hosted staging deployment planner | Provider binding, staging release-gate evidence, Node/container deployment composition, and GitHub App promotion gating |
|
|
84
|
+
| Hosted staging harness | File-backed webhook replay, queue/report/Check Run artifacts, worker cleanup verification, and local release-gate evidence fixtures |
|
|
83
85
|
|
|
84
86
|
## Quick Start
|
|
85
87
|
|
|
@@ -200,6 +202,10 @@ If `--base` cannot be resolved, `pr-risk` emits `pr-risk.diff-unavailable` inste
|
|
|
200
202
|
|
|
201
203
|
Use [docs/launch-readiness-checklist.md](docs/launch-readiness-checklist.md) when an app is close to inviting real users. It explains how to combine `ai-saas-guard` output with manual two-account authorization testing, Stripe webhook verification, MCP config review, Supabase policy review, deploy checks, rollback planning, and a clear reminder that this is not a full security audit.
|
|
202
204
|
|
|
205
|
+
## Repository Trust Hardening
|
|
206
|
+
|
|
207
|
+
See [docs/repository-trust-hardening.md](docs/repository-trust-hardening.md) for the public repository controls behind this release line: branch protection, required CI checks, Dependabot for npm and GitHub Actions, CodeQL SAST, private vulnerability reporting, secret scanning, and push protection.
|
|
208
|
+
|
|
203
209
|
## Stripe Webhook Replay
|
|
204
210
|
|
|
205
211
|
Use [docs/stripe-webhook-replay.md](docs/stripe-webhook-replay.md) after `check-stripe` flags missing signature verification, idempotency, lifecycle handlers, or entitlement updates. The cookbook maps findings to concrete `stripe listen` and `stripe trigger` commands for checkout success, failed renewal, subscription update, cancellation, refund, duplicate delivery, and out-of-order event review.
|
|
@@ -222,13 +228,15 @@ The hosted Node/container app skeleton is documented in [docs/hosted-node-contai
|
|
|
222
228
|
|
|
223
229
|
The hosted staging deployment planner is documented in [docs/hosted-staging-deployment.md](docs/hosted-staging-deployment.md). It exports `planHostedProviderBinding`, `planHostedStagingDeployment`, and `planHostedGitHubAppPromotion` from `ai-saas-guard/hosted/staging`. It composes real provider references, the Node/container deployment plan, hosted operational release-gate evidence, and GitHub App deployment planning so staging and production promotion stay blocked until the required queue, store, worker sandbox, Check Run publisher, logs, metrics, rollback, and incident-response references are present. It still does not call a cloud provider, create a GitHub App, or expose a public hosted service by itself.
|
|
224
230
|
|
|
231
|
+
The hosted staging harness is documented in [docs/hosted-staging-harness.md](docs/hosted-staging-harness.md). It exports `createFileBackedHostedStagingHarness` and `createHostedStagingHarnessEvidence` from `ai-saas-guard/hosted/staging-harness`. It runs signed webhook replay through the provider-independent hosted runtime with local file-backed queue, compact report, and Check Run adapters, then verifies worker sandbox cleanup. It is a staging rehearsal tool only; it does not call cloud providers, create a GitHub App, publish live Check Runs, or expose a public hosted service.
|
|
232
|
+
|
|
225
233
|
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.
|
|
226
234
|
|
|
227
235
|
Hosted uninstall and data deletion behavior is documented in [docs/hosted-uninstall-data-deletion.md](docs/hosted-uninstall-data-deletion.md). It defines repository removal, full app uninstall, compact report deletion, queue cancellation, audit record retention, repeated cleanup, and user-facing deletion wording.
|
|
228
236
|
|
|
229
237
|
Hosted pricing and packaging boundaries are documented in [docs/hosted-pricing-packaging.md](docs/hosted-pricing-packaging.md). Core local scanning stays useful without an account; hosted plans may add workflow convenience, saved reports, team policy, and optional human review, but they do not gate local CLI scanning.
|
|
230
238
|
|
|
231
|
-
Hosted pre-implementation pure contracts are documented in [docs/hosted-preimplementation-contracts.md](docs/hosted-preimplementation-contracts.md). They now include a pull request webhook intake planner that verifies signatures before parsing or queueing, a durable scan queue planner that reuses queued, running, and completed jobs for the same trusted scan key, a worker read-only scan planner that fixes the CLI command and requires repository `contents: read`, and a Check Run publication planner that requires repository `checks: write` and builds bounded check-only payloads from compact reports. They also cover queue-safe webhook event parsing, bounded check-run summary rendering, idempotent queue cleanup planning, worker checkout cleanup planning, a retention/deletion cleanup planner, an operational release gate evaluator, the production adapter plans needed for GitHub App auth and bounded worker execution, the Node/container app skeleton needed for real provider wiring,
|
|
239
|
+
Hosted pre-implementation pure contracts are documented in [docs/hosted-preimplementation-contracts.md](docs/hosted-preimplementation-contracts.md). They now include a pull request webhook intake planner that verifies signatures before parsing or queueing, a durable scan queue planner that reuses queued, running, and completed jobs for the same trusted scan key, a worker read-only scan planner that fixes the CLI command and requires repository `contents: read`, and a Check Run publication planner that requires repository `checks: write` and builds bounded check-only payloads from compact reports. They also cover queue-safe webhook event parsing, bounded check-run summary rendering, idempotent queue cleanup planning, worker checkout cleanup planning, a retention/deletion cleanup planner, an operational release gate evaluator, the production adapter plans needed for GitHub App auth and bounded worker execution, the Node/container app skeleton needed for real provider wiring, the staging deployment planner needed before production GitHub App promotion, and the local staging harness needed to rehearse webhook replay, persistence, publication, and cleanup without cloud calls. The service runtime composes these contracts behind replaceable adapters. PR comments remain a later workflow or paid hosted feature, not part of the hosted MVP contract.
|
|
232
240
|
|
|
233
241
|
A public hosted compact report schema fixture is available at [examples/hosted-compact-report.json](examples/hosted-compact-report.json). It is synthetic and public-safe: compact evidence only, no raw source, raw diffs, secrets, webhook payload bodies, customer payloads, private URLs, or worker checkout paths.
|
|
234
242
|
|
|
@@ -268,7 +276,7 @@ Use `suppressions` for narrower false-positive handling when one rule is noisy o
|
|
|
268
276
|
|
|
269
277
|
## GitHub Action
|
|
270
278
|
|
|
271
|
-
The repo includes a composite Action. Use `v0` for the latest compatible pre-1.0 Action, a specific release tag such as `v0.
|
|
279
|
+
The repo includes a composite Action. Use `v0` for the latest compatible pre-1.0 Action, a specific release tag such as `v0.24.0` for controlled upgrades, or pin a reviewed commit SHA for stricter supply-chain control:
|
|
272
280
|
|
|
273
281
|
```yaml
|
|
274
282
|
name: ai-saas-guard
|
|
@@ -398,7 +406,7 @@ Open-source core:
|
|
|
398
406
|
|
|
399
407
|
Near-term priorities:
|
|
400
408
|
|
|
401
|
-
- Use the hosted staging
|
|
409
|
+
- Use the hosted staging harness to rehearse webhook replay, Check Run publication, compact report persistence, and worker cleanup locally; then bind real provider references, deploy a staging artifact, and collect monitoring, rollback, and incident-response evidence from that artifact.
|
|
402
410
|
- Keep hosted exposure blocked until the operational release gate has fresh evidence from a deployed artifact.
|
|
403
411
|
|
|
404
412
|
Potential paid layer later:
|
package/README.zh-CN.md
CHANGED
|
@@ -55,7 +55,7 @@ AI 能很快把一个 SaaS 从想法做成可运行的产品。真正难的是
|
|
|
55
55
|
|
|
56
56
|
这个仓库是公开 GitHub 仓库。
|
|
57
57
|
|
|
58
|
-
CLI 已发布到 npm:`ai-saas-guard@0.
|
|
58
|
+
CLI 已发布到 npm:`ai-saas-guard@0.24.0`。GitHub Action 支持 `v0` 浮动标签,也支持固定版本标签,例如 `v0.24.0`。
|
|
59
59
|
|
|
60
60
|
| 模块 | 状态 |
|
|
61
61
|
| --- | --- |
|
|
@@ -66,13 +66,15 @@ CLI 已发布到 npm:`ai-saas-guard@0.22.0`。GitHub Action 支持 `v0` 浮动
|
|
|
66
66
|
| Markdown PR summary | 已可用 |
|
|
67
67
|
| GitHub Action | 已可用 |
|
|
68
68
|
| 项目配置 | `.ai-saas-guard.json` 支持规则开关、severity 覆盖和 fail threshold |
|
|
69
|
-
| 当前版本 | `0.
|
|
70
|
-
| Action 标签 | `v0.
|
|
69
|
+
| 当前版本 | `0.24.0` |
|
|
70
|
+
| Action 标签 | `v0.24.0`、`v0` |
|
|
71
71
|
| npm 发布 | GitHub Actions Trusted Publisher/OIDC,无需长期 npm token |
|
|
72
|
+
| 仓库可信度加固 | branch protection、Dependabot、CodeQL、private vulnerability reporting、secret scanning 和 push protection |
|
|
72
73
|
| 运行时加固 | 单文件和总扫描文本预算、markdown evidence 转义、更严格的 hosted deployment 阻断 |
|
|
73
74
|
| Hosted production adapters | GitHub App JWT 签名、installation-token 请求规划、有边界的 worker 执行和终态 cleanup 规划 |
|
|
74
75
|
| Hosted app skeleton | Node/container HTTP ingress、health route、worker tick、in-memory provider adapters 和 deployment plan 校验 |
|
|
75
76
|
| Hosted staging deployment planner | provider binding、staging release-gate evidence、Node/container deployment 组合和 GitHub App promotion gating |
|
|
77
|
+
| Hosted staging harness | 本地 file-backed webhook replay、queue/report/Check Run artifact、worker cleanup 校验和 release-gate evidence fixture |
|
|
76
78
|
|
|
77
79
|
## 快速开始
|
|
78
80
|
|
|
@@ -133,6 +135,10 @@ node dist/cli.js scan --root /path/to/your-saas
|
|
|
133
135
|
|
|
134
136
|
完整规则请看 [docs/rules.md](docs/rules.md)。
|
|
135
137
|
|
|
138
|
+
## 仓库可信度加固
|
|
139
|
+
|
|
140
|
+
公开仓库的维护和发布控制见 [docs/repository-trust-hardening.md](docs/repository-trust-hardening.md)。当前已经配置 branch protection、required CI checks、Dependabot npm/GitHub Actions 更新、CodeQL SAST、private vulnerability reporting、secret scanning 和 push protection。
|
|
141
|
+
|
|
136
142
|
## PR 风险分流
|
|
137
143
|
|
|
138
144
|
`scan` 可以扫整个仓库,但这个项目更锋利的入口是 PR review。
|
|
@@ -255,6 +261,7 @@ jobs:
|
|
|
255
261
|
- [docs/hosted-production-adapters.md](docs/hosted-production-adapters.md)
|
|
256
262
|
- [docs/hosted-node-container-app.md](docs/hosted-node-container-app.md)
|
|
257
263
|
- [docs/hosted-staging-deployment.md](docs/hosted-staging-deployment.md)
|
|
264
|
+
- [docs/hosted-staging-harness.md](docs/hosted-staging-harness.md)
|
|
258
265
|
- [docs/hosted-operational-release-gate.md](docs/hosted-operational-release-gate.md)
|
|
259
266
|
- [docs/hosted-uninstall-data-deletion.md](docs/hosted-uninstall-data-deletion.md)
|
|
260
267
|
- [docs/hosted-pricing-packaging.md](docs/hosted-pricing-packaging.md)
|
|
@@ -270,6 +277,7 @@ jobs:
|
|
|
270
277
|
- Hosted production adapter layer:`ai-saas-guard/hosted/production-adapters` 导出 `createHostedGitHubAppJwt`、`planHostedGitHubInstallationTokenRequest` 和 `planHostedProductionWorkerExecution`,用于 GitHub App RS256 JWT、selected-repository installation token 请求规划、worker/check-run 分离 token scope、固定只读 worker 命令、timeout/output 预算、compact JSON-only 输出,以及 success/failure/timeout/cancellation 的 cleanup 规划;它本身仍然不部署公开 hosted 服务
|
|
271
278
|
- Hosted Node/container app skeleton:`ai-saas-guard/hosted/app` 导出 `createHostedHttpApp`、`createInMemoryHostedAppPlatform` 和 `planHostedNodeContainerDeployment`,提供安全 `/healthz`、签名 `/github/webhook` ingress、单 job worker tick、测试用 in-memory provider adapters,以及 secret manager、queue、compact report store、worker sandbox、GitHub Checks publisher 的部署引用校验;它本身仍然不部署或暴露公开 hosted 服务
|
|
272
279
|
- 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 服务
|
|
280
|
+
- Hosted staging harness:`ai-saas-guard/hosted/staging-harness` 导出 `createFileBackedHostedStagingHarness` 和 `createHostedStagingHarnessEvidence`,可以在本地用 file-backed queue、compact report、Check Run request 和 worker sandbox 跑通签名 webhook replay、worker tick 和 cleanup 校验;它只是 staging 演练工具,不会调用云平台、创建 GitHub App、写真实 Check Run 或暴露公开 hosted 服务
|
|
273
281
|
- webhook event parser
|
|
274
282
|
- check-run summary renderer
|
|
275
283
|
- Check Run publication planner:要求 repository `checks: write`,只从 compact report 生成有长度上限的 Check Run payload,包含 review categories、优先 review 文件、verification steps 和本地 CLI 复现命令;MVP 不发 PR comment
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { type HostedOperationalReleaseGateEvidence } from "./contracts.js";
|
|
2
|
+
import { type HostedServiceRuntimeOptions, type HostedServiceScanRunnerResult, type HostedServiceWebhookStage } from "./service.js";
|
|
3
|
+
type RepositoryIdSource = HostedServiceRuntimeOptions["selectedRepositoryIdsByInstallation"];
|
|
4
|
+
export interface FileBackedHostedStagingHarnessOptions {
|
|
5
|
+
rootDir: string;
|
|
6
|
+
signingKey: string | Buffer;
|
|
7
|
+
scannerVersion: string;
|
|
8
|
+
selectedRepositoryIdsByInstallation: RepositoryIdSource;
|
|
9
|
+
removedRepositoryIdsByInstallation?: RepositoryIdSource;
|
|
10
|
+
scanResult: HostedServiceScanRunnerResult;
|
|
11
|
+
now?: () => string;
|
|
12
|
+
}
|
|
13
|
+
export interface FileBackedHostedStagingHarness {
|
|
14
|
+
paths: FileBackedHostedStagingHarnessPaths;
|
|
15
|
+
runWebhookReplay(input: HostedStagingHarnessWebhookReplayInput): Promise<HostedStagingHarnessReplayResult>;
|
|
16
|
+
runWorkerTick(): Promise<HostedStagingHarnessWorkerTickResult>;
|
|
17
|
+
}
|
|
18
|
+
export interface FileBackedHostedStagingHarnessPaths {
|
|
19
|
+
rootDir: string;
|
|
20
|
+
queueDir: string;
|
|
21
|
+
queueSnapshot: string;
|
|
22
|
+
reportDir: string;
|
|
23
|
+
reportIndex: string;
|
|
24
|
+
checkRunDir: string;
|
|
25
|
+
checkRunIndex: string;
|
|
26
|
+
workerSandboxRoot: string;
|
|
27
|
+
}
|
|
28
|
+
export interface HostedStagingHarnessWebhookReplayInput {
|
|
29
|
+
payload: string | Buffer;
|
|
30
|
+
signatureHeader?: string;
|
|
31
|
+
deliveryId?: string;
|
|
32
|
+
manualRerun?: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface HostedStagingHarnessReplayResult {
|
|
35
|
+
accepted: boolean;
|
|
36
|
+
stage: HostedServiceWebhookStage;
|
|
37
|
+
reason?: string;
|
|
38
|
+
deliveryId?: string;
|
|
39
|
+
queuedWorker: boolean;
|
|
40
|
+
shouldCreateCheckRun: boolean;
|
|
41
|
+
shouldCreatePrComment: false;
|
|
42
|
+
privacy: HostedStagingHarnessPrivacy;
|
|
43
|
+
}
|
|
44
|
+
export type HostedStagingHarnessWorkerTickResult = {
|
|
45
|
+
processed: false;
|
|
46
|
+
reason: "empty_queue";
|
|
47
|
+
privacy: HostedStagingHarnessPrivacy;
|
|
48
|
+
} | {
|
|
49
|
+
processed: true;
|
|
50
|
+
status: "completed";
|
|
51
|
+
checkRunPublished: boolean;
|
|
52
|
+
compactReportStored: boolean;
|
|
53
|
+
workerSandboxDeleted: boolean;
|
|
54
|
+
activeWorkerSandboxCount: number;
|
|
55
|
+
cleanupVerified: boolean;
|
|
56
|
+
privacy: HostedStagingHarnessPrivacy;
|
|
57
|
+
} | {
|
|
58
|
+
processed: true;
|
|
59
|
+
status: "failed";
|
|
60
|
+
errorClass: "worker_plan_rejected" | "check_run_publication_rejected" | "scan_runner_failed";
|
|
61
|
+
reason?: string;
|
|
62
|
+
workerSandboxDeleted: boolean;
|
|
63
|
+
activeWorkerSandboxCount: number;
|
|
64
|
+
cleanupVerified: boolean;
|
|
65
|
+
privacy: HostedStagingHarnessPrivacy;
|
|
66
|
+
};
|
|
67
|
+
export interface HostedStagingHarnessEvidenceInput {
|
|
68
|
+
collectedAt: string;
|
|
69
|
+
evidenceBaseUrl: string;
|
|
70
|
+
owner: string;
|
|
71
|
+
}
|
|
72
|
+
export interface HostedStagingHarnessPrivacy {
|
|
73
|
+
includesRawWebhookPayload: false;
|
|
74
|
+
includesUntrustedPrText: false;
|
|
75
|
+
includesRawSource: false;
|
|
76
|
+
includesRawDiffs: false;
|
|
77
|
+
includesSecrets: false;
|
|
78
|
+
includesCustomerPayloads: false;
|
|
79
|
+
includesPrivateCheckoutPath: false;
|
|
80
|
+
includesInstallationToken: false;
|
|
81
|
+
claimsLiveHostedService: false;
|
|
82
|
+
}
|
|
83
|
+
export declare function createFileBackedHostedStagingHarness(options: FileBackedHostedStagingHarnessOptions): FileBackedHostedStagingHarness;
|
|
84
|
+
export declare function createHostedStagingHarnessEvidence(input: HostedStagingHarnessEvidenceInput): HostedOperationalReleaseGateEvidence[];
|
|
85
|
+
export {};
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { mkdir, readdir, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { HOSTED_OPERATIONAL_RELEASE_GATE_REQUIREMENTS } from "./contracts.js";
|
|
4
|
+
import { createHostedServiceRuntime } from "./service.js";
|
|
5
|
+
export function createFileBackedHostedStagingHarness(options) {
|
|
6
|
+
const paths = hostedStagingHarnessPaths(options.rootDir);
|
|
7
|
+
const queue = { records: new Map() };
|
|
8
|
+
const reportStore = createFileBackedReportStore(paths);
|
|
9
|
+
const checkRunPublisher = createFileBackedCheckRunPublisher(paths);
|
|
10
|
+
const workerSandboxPaths = new Set();
|
|
11
|
+
const runtime = createHostedServiceRuntime({
|
|
12
|
+
signingKey: options.signingKey,
|
|
13
|
+
scannerVersion: options.scannerVersion,
|
|
14
|
+
selectedRepositoryIdsByInstallation: options.selectedRepositoryIdsByInstallation,
|
|
15
|
+
removedRepositoryIdsByInstallation: options.removedRepositoryIdsByInstallation,
|
|
16
|
+
queue,
|
|
17
|
+
compactReportStore: reportStore,
|
|
18
|
+
checkRunPublisher,
|
|
19
|
+
scanRunner: async ({ queueRecord }) => {
|
|
20
|
+
const sandboxPath = join(paths.workerSandboxRoot, safeFileSegment(queueRecord.key));
|
|
21
|
+
workerSandboxPaths.add(sandboxPath);
|
|
22
|
+
await mkdir(sandboxPath, { recursive: true });
|
|
23
|
+
await writeFile(join(sandboxPath, "source.ts"), options.scanResult.rawSource ?? "", "utf8");
|
|
24
|
+
return options.scanResult;
|
|
25
|
+
},
|
|
26
|
+
now: options.now
|
|
27
|
+
});
|
|
28
|
+
return {
|
|
29
|
+
paths,
|
|
30
|
+
async runWebhookReplay(input) {
|
|
31
|
+
await ensureBaseDirectories(paths);
|
|
32
|
+
const result = runtime.handlePullRequestWebhook({
|
|
33
|
+
payload: input.payload,
|
|
34
|
+
signatureHeader: input.signatureHeader,
|
|
35
|
+
deliveryId: input.deliveryId,
|
|
36
|
+
manualRerun: input.manualRerun
|
|
37
|
+
});
|
|
38
|
+
await writeQueueSnapshot(paths, queue);
|
|
39
|
+
return {
|
|
40
|
+
accepted: result.accepted,
|
|
41
|
+
stage: result.stage,
|
|
42
|
+
...(result.reason === undefined ? {} : { reason: result.reason }),
|
|
43
|
+
...(result.deliveryId === undefined ? {} : { deliveryId: result.deliveryId }),
|
|
44
|
+
queuedWorker: result.queueDecision?.shouldEnqueueWorker ?? false,
|
|
45
|
+
shouldCreateCheckRun: result.shouldCreateCheckRun,
|
|
46
|
+
shouldCreatePrComment: false,
|
|
47
|
+
privacy: hostedStagingHarnessPrivacy()
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
async runWorkerTick() {
|
|
51
|
+
await ensureBaseDirectories(paths);
|
|
52
|
+
const result = await runtime.runNextQueuedScan();
|
|
53
|
+
await removeWorkerSandboxes(workerSandboxPaths);
|
|
54
|
+
await writeQueueSnapshot(paths, queue);
|
|
55
|
+
const activeWorkerSandboxCount = await countDirectoryEntries(paths.workerSandboxRoot);
|
|
56
|
+
if (!result.processed) {
|
|
57
|
+
return {
|
|
58
|
+
processed: false,
|
|
59
|
+
reason: "empty_queue",
|
|
60
|
+
privacy: hostedStagingHarnessPrivacy()
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (result.status === "completed") {
|
|
64
|
+
return {
|
|
65
|
+
processed: true,
|
|
66
|
+
status: "completed",
|
|
67
|
+
checkRunPublished: result.checkRunPublication.shouldWriteCheckRun,
|
|
68
|
+
compactReportStored: true,
|
|
69
|
+
workerSandboxDeleted: activeWorkerSandboxCount === 0,
|
|
70
|
+
activeWorkerSandboxCount,
|
|
71
|
+
cleanupVerified: result.cleanup.shouldDeleteWorkerCheckout && activeWorkerSandboxCount === 0,
|
|
72
|
+
privacy: hostedStagingHarnessPrivacy()
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
processed: true,
|
|
77
|
+
status: "failed",
|
|
78
|
+
errorClass: result.errorClass,
|
|
79
|
+
...(result.reason === undefined ? {} : { reason: result.reason }),
|
|
80
|
+
workerSandboxDeleted: activeWorkerSandboxCount === 0,
|
|
81
|
+
activeWorkerSandboxCount,
|
|
82
|
+
cleanupVerified: (result.cleanup?.shouldDeleteWorkerCheckout ?? true) && activeWorkerSandboxCount === 0,
|
|
83
|
+
privacy: hostedStagingHarnessPrivacy()
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export function createHostedStagingHarnessEvidence(input) {
|
|
89
|
+
return HOSTED_OPERATIONAL_RELEASE_GATE_REQUIREMENTS.map((requirement) => ({
|
|
90
|
+
id: requirement.id,
|
|
91
|
+
status: "passed",
|
|
92
|
+
collectedAt: input.collectedAt,
|
|
93
|
+
evidenceUrl: `${input.evidenceBaseUrl.replace(/\/+$/, "")}/${requirement.id}.json`,
|
|
94
|
+
note: `Local staging harness evidence for ${requirement.label}. This is not hosted exposure.`,
|
|
95
|
+
owner: input.owner
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
function hostedStagingHarnessPaths(rootDir) {
|
|
99
|
+
const queueDir = join(rootDir, "queue");
|
|
100
|
+
const reportDir = join(rootDir, "reports");
|
|
101
|
+
const checkRunDir = join(rootDir, "check-runs");
|
|
102
|
+
return {
|
|
103
|
+
rootDir,
|
|
104
|
+
queueDir,
|
|
105
|
+
queueSnapshot: join(queueDir, "jobs.json"),
|
|
106
|
+
reportDir,
|
|
107
|
+
reportIndex: join(reportDir, "index.json"),
|
|
108
|
+
checkRunDir,
|
|
109
|
+
checkRunIndex: join(checkRunDir, "index.json"),
|
|
110
|
+
workerSandboxRoot: join(rootDir, "worker-sandbox")
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function createFileBackedReportStore(paths) {
|
|
114
|
+
const records = [];
|
|
115
|
+
return {
|
|
116
|
+
records,
|
|
117
|
+
async save(record) {
|
|
118
|
+
records.push(record);
|
|
119
|
+
await mkdir(paths.reportDir, { recursive: true });
|
|
120
|
+
const file = join(paths.reportDir, `${safeFileSegment(record.id)}.json`);
|
|
121
|
+
await writeJson(file, {
|
|
122
|
+
id: record.id,
|
|
123
|
+
jobKey: record.jobKey,
|
|
124
|
+
createdAt: record.createdAt,
|
|
125
|
+
report: record.report
|
|
126
|
+
});
|
|
127
|
+
await writeJson(paths.reportIndex, {
|
|
128
|
+
records: records.map((item) => ({
|
|
129
|
+
id: item.id,
|
|
130
|
+
jobKey: item.jobKey,
|
|
131
|
+
createdAt: item.createdAt,
|
|
132
|
+
file: `${safeFileSegment(item.id)}.json`
|
|
133
|
+
}))
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function createFileBackedCheckRunPublisher(paths) {
|
|
139
|
+
const requests = [];
|
|
140
|
+
return {
|
|
141
|
+
requests,
|
|
142
|
+
async publish(request) {
|
|
143
|
+
requests.push(request);
|
|
144
|
+
await mkdir(paths.checkRunDir, { recursive: true });
|
|
145
|
+
const fileName = `${String(request.payload.external_id ?? requests.length).replace(/[^A-Za-z0-9._-]/g, "_")}.json`;
|
|
146
|
+
await writeJson(join(paths.checkRunDir, fileName), {
|
|
147
|
+
method: request.method,
|
|
148
|
+
endpoint: request.endpoint,
|
|
149
|
+
payload: request.payload
|
|
150
|
+
});
|
|
151
|
+
await writeJson(paths.checkRunIndex, {
|
|
152
|
+
records: requests.map((item, index) => ({
|
|
153
|
+
index,
|
|
154
|
+
endpoint: item.endpoint,
|
|
155
|
+
name: item.payload.name,
|
|
156
|
+
conclusion: item.payload.conclusion
|
|
157
|
+
}))
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
async function ensureBaseDirectories(paths) {
|
|
163
|
+
await Promise.all([
|
|
164
|
+
mkdir(paths.queueDir, { recursive: true }),
|
|
165
|
+
mkdir(paths.reportDir, { recursive: true }),
|
|
166
|
+
mkdir(paths.checkRunDir, { recursive: true }),
|
|
167
|
+
mkdir(paths.workerSandboxRoot, { recursive: true })
|
|
168
|
+
]);
|
|
169
|
+
}
|
|
170
|
+
async function writeQueueSnapshot(paths, queue) {
|
|
171
|
+
await mkdir(paths.queueDir, { recursive: true });
|
|
172
|
+
await writeJson(paths.queueSnapshot, {
|
|
173
|
+
records: [...queue.records.values()].map((record) => ({
|
|
174
|
+
key: record.key,
|
|
175
|
+
status: record.status,
|
|
176
|
+
attempt: record.attempt,
|
|
177
|
+
deliveryIds: record.deliveryIds,
|
|
178
|
+
createdAt: record.createdAt,
|
|
179
|
+
updatedAt: record.updatedAt,
|
|
180
|
+
reportId: record.reportId,
|
|
181
|
+
identity: record.identity
|
|
182
|
+
}))
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
async function removeWorkerSandboxes(paths) {
|
|
186
|
+
await Promise.all([...paths].map((path) => rm(path, { recursive: true, force: true })));
|
|
187
|
+
paths.clear();
|
|
188
|
+
}
|
|
189
|
+
async function countDirectoryEntries(path) {
|
|
190
|
+
try {
|
|
191
|
+
return (await readdir(path)).length;
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async function writeJson(path, value) {
|
|
198
|
+
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
199
|
+
}
|
|
200
|
+
function safeFileSegment(value) {
|
|
201
|
+
return value.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
202
|
+
}
|
|
203
|
+
function hostedStagingHarnessPrivacy() {
|
|
204
|
+
return {
|
|
205
|
+
includesRawWebhookPayload: false,
|
|
206
|
+
includesUntrustedPrText: false,
|
|
207
|
+
includesRawSource: false,
|
|
208
|
+
includesRawDiffs: false,
|
|
209
|
+
includesSecrets: false,
|
|
210
|
+
includesCustomerPayloads: false,
|
|
211
|
+
includesPrivateCheckoutPath: false,
|
|
212
|
+
includesInstallationToken: false,
|
|
213
|
+
claimsLiveHostedService: false
|
|
214
|
+
};
|
|
215
|
+
}
|
package/docs/github-action.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`ai-saas-guard` ships as a composite GitHub Action for pull request and code scanning workflows.
|
|
4
4
|
|
|
5
|
-
Use `zr9959/ai-saas-guard@v0` for the latest compatible pre-1.0 Action. Use a specific tag such as `v0.
|
|
5
|
+
Use `zr9959/ai-saas-guard@v0` for the latest compatible pre-1.0 Action. Use a specific tag such as `v0.24.0` or a reviewed commit SHA when reproducibility is more important than automatic minor updates.
|
|
6
6
|
|
|
7
7
|
## PR Summary
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This document collects pure hosted contracts that can be tested before any hosted GitHub App service is deployed. These contracts keep the hosted design inspectable, local-first, and implementation-ready without adding network calls, credentials, queues, workers, or GitHub API writes. They are no network calls contracts by design.
|
|
4
4
|
|
|
5
|
-
The helpers live in `src/hosted/contracts.ts` and are exported from `ai-saas-guard/hosted/contracts`. The production adapter plans live in `src/hosted/production-adapters.ts` and are exported from `ai-saas-guard/hosted/production-adapters`. The Node/container app skeleton lives in `src/hosted/app.ts` and is exported from `ai-saas-guard/hosted/app`. The staging deployment planner lives in `src/hosted/staging.ts` and is exported from `ai-saas-guard/hosted/staging`.
|
|
5
|
+
The helpers live in `src/hosted/contracts.ts` and are exported from `ai-saas-guard/hosted/contracts`. The production adapter plans live in `src/hosted/production-adapters.ts` and are exported from `ai-saas-guard/hosted/production-adapters`. The Node/container app skeleton lives in `src/hosted/app.ts` and is exported from `ai-saas-guard/hosted/app`. The staging deployment planner lives in `src/hosted/staging.ts` and is exported from `ai-saas-guard/hosted/staging`. The local staging harness lives in `src/hosted/staging-harness.ts` and is exported from `ai-saas-guard/hosted/staging-harness`.
|
|
6
6
|
|
|
7
7
|
## Pull Request Webhook Intake Planner
|
|
8
8
|
|
|
@@ -138,6 +138,28 @@ Privacy boundaries:
|
|
|
138
138
|
|
|
139
139
|
The exported helpers are `planHostedProviderBinding`, `planHostedStagingDeployment`, and `planHostedGitHubAppPromotion`.
|
|
140
140
|
|
|
141
|
+
## Local Staging Harness
|
|
142
|
+
|
|
143
|
+
The local staging harness is the first executable hosted rehearsal that persists artifacts outside in-memory tests. It is still local-only: it does not call a cloud provider, create a GitHub App, fetch repositories from GitHub, publish live Check Runs, or expose a public service.
|
|
144
|
+
|
|
145
|
+
Default behavior:
|
|
146
|
+
|
|
147
|
+
- replay a signed pull request webhook through the hosted service runtime
|
|
148
|
+
- persist a safe queue snapshot to a file-backed harness root
|
|
149
|
+
- persist compact hosted reports without raw source or diffs
|
|
150
|
+
- persist fake Check Run publication requests for review
|
|
151
|
+
- create a temporary worker sandbox during the scan runner phase
|
|
152
|
+
- remove worker sandbox contents before returning the worker result
|
|
153
|
+
- create hosted operational release-gate evidence fixtures with the same shape required by the deployment gate
|
|
154
|
+
|
|
155
|
+
Privacy boundaries:
|
|
156
|
+
|
|
157
|
+
- invalid signatures create no queue, report, or Check Run side effects
|
|
158
|
+
- result objects do not include raw webhook payloads, untrusted PR text, raw source, raw diffs, secrets, customer payloads, checkout paths, or installation tokens
|
|
159
|
+
- local evidence is labeled as harness evidence and must not be used to claim live hosted exposure
|
|
160
|
+
|
|
161
|
+
The exported helpers are `createFileBackedHostedStagingHarness` and `createHostedStagingHarnessEvidence`.
|
|
162
|
+
|
|
141
163
|
## Webhook Event Parser
|
|
142
164
|
|
|
143
165
|
The webhook event parser runs after webhook signature verification. It converts a reduced GitHub `pull_request` webhook payload into a queue-safe scan request identity.
|
|
@@ -47,6 +47,8 @@ The Node/container app skeleton in [hosted-node-container-app.md](hosted-node-co
|
|
|
47
47
|
|
|
48
48
|
The staging deployment planner in [hosted-staging-deployment.md](hosted-staging-deployment.md) composes the runtime-facing provider references with hosted operational release-gate evidence and GitHub App promotion gating.
|
|
49
49
|
|
|
50
|
+
The staging harness in [hosted-staging-harness.md](hosted-staging-harness.md) exercises this runtime with local file-backed queue, report, Check Run, and worker sandbox adapters so webhook replay and cleanup can be verified before any public staging deployment.
|
|
51
|
+
|
|
50
52
|
## Privacy
|
|
51
53
|
|
|
52
54
|
The runtime intentionally returns safe planning and status objects only.
|
|
@@ -90,6 +92,7 @@ This runtime makes the hosted service implementation-ready inside the repository
|
|
|
90
92
|
- production adapters wired to the platform secret manager and GitHub Checks API
|
|
91
93
|
- Node/container app skeleton wired to real HTTP, queue, store, worker sandbox, and Check publisher infrastructure
|
|
92
94
|
- staging deployment planner passed with real provider references and fresh release-gate evidence
|
|
95
|
+
- local staging harness replay passed with queue, report, Check Run, and worker cleanup artifacts
|
|
93
96
|
- container image and digest
|
|
94
97
|
- live monitoring and rollback evidence
|
|
95
98
|
- hosted operational release gate evidence from the deployed artifact
|
|
@@ -14,6 +14,8 @@ The package exports `ai-saas-guard/hosted/staging` with:
|
|
|
14
14
|
|
|
15
15
|
These helpers are pure planners. They do not call a cloud provider, create a GitHub App, read secrets, write Check Runs, or expose a hosted service.
|
|
16
16
|
|
|
17
|
+
The local staging harness in [hosted-staging-harness.md](hosted-staging-harness.md) is the executable rehearsal for this plan. It runs signed webhook replay through file-backed queue, report, Check Run, and worker sandbox adapters without calling a cloud provider.
|
|
18
|
+
|
|
17
19
|
## Provider Binding
|
|
18
20
|
|
|
19
21
|
`planHostedProviderBinding` validates the real provider references a hosted deployment needs:
|
|
@@ -90,4 +92,6 @@ The staging planner never returns:
|
|
|
90
92
|
|
|
91
93
|
The repository can now produce a staging deployment plan that ties together provider references, release-gate evidence, Node/container deployment, and GitHub App promotion readiness.
|
|
92
94
|
|
|
95
|
+
The repository can also run a local staging harness that proves the runtime path can accept a signed replay, persist safe artifacts, publish a fake Check Run request, and remove the temporary worker sandbox.
|
|
96
|
+
|
|
93
97
|
This still is not a live hosted service. A real staging environment still requires actual platform infrastructure, deployed containers, secret manager entries, durable queue/storage resources, worker sandboxing, GitHub Checks runtime credentials, monitoring, rollback evidence, and incident-response evidence collected from the deployed artifact.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Hosted Staging Harness
|
|
2
|
+
|
|
3
|
+
This document describes the local hosted staging harness implemented in `src/hosted/staging-harness.ts`.
|
|
4
|
+
|
|
5
|
+
It does not announce a public hosted service. The harness is a local, file-backed way to exercise the hosted runtime end to end before a real staging platform exists.
|
|
6
|
+
|
|
7
|
+
## What Exists
|
|
8
|
+
|
|
9
|
+
The package exports `ai-saas-guard/hosted/staging-harness` with:
|
|
10
|
+
|
|
11
|
+
- `createFileBackedHostedStagingHarness`
|
|
12
|
+
- `createHostedStagingHarnessEvidence`
|
|
13
|
+
|
|
14
|
+
The harness composes the hosted service runtime with local adapters:
|
|
15
|
+
|
|
16
|
+
- signed pull request webhook replay
|
|
17
|
+
- queue snapshots written to `queue/jobs.json`
|
|
18
|
+
- compact report files written under `reports/`
|
|
19
|
+
- fake GitHub Check Run requests written under `check-runs/`
|
|
20
|
+
- a temporary worker sandbox under `worker-sandbox/`
|
|
21
|
+
- cleanup verification after a worker tick
|
|
22
|
+
- local hosted release-gate evidence fixtures
|
|
23
|
+
|
|
24
|
+
## Replay Flow
|
|
25
|
+
|
|
26
|
+
The expected staging rehearsal is:
|
|
27
|
+
|
|
28
|
+
1. create a temporary harness root
|
|
29
|
+
2. replay a signed pull request webhook with `runWebhookReplay`
|
|
30
|
+
3. process one queued scan with `runWorkerTick`
|
|
31
|
+
4. inspect the queue, report, and Check Run request files
|
|
32
|
+
5. verify that the worker sandbox is empty after cleanup
|
|
33
|
+
|
|
34
|
+
Invalid signatures stop at the signature stage and create no queue, report, or Check Run side effects.
|
|
35
|
+
|
|
36
|
+
## Evidence Fixture
|
|
37
|
+
|
|
38
|
+
`createHostedStagingHarnessEvidence` creates one passed evidence item for each hosted operational release-gate requirement. This is useful for local tests and staging rehearsals that need the same evidence shape as a deployed environment.
|
|
39
|
+
|
|
40
|
+
The generated notes are explicit that the evidence is local harness evidence, not hosted exposure.
|
|
41
|
+
|
|
42
|
+
## Privacy
|
|
43
|
+
|
|
44
|
+
The harness returns safe status objects and compact artifacts only.
|
|
45
|
+
|
|
46
|
+
It does not return:
|
|
47
|
+
|
|
48
|
+
- raw webhook payloads
|
|
49
|
+
- untrusted PR text
|
|
50
|
+
- raw source
|
|
51
|
+
- raw diffs
|
|
52
|
+
- secrets
|
|
53
|
+
- customer payloads
|
|
54
|
+
- private checkout paths
|
|
55
|
+
- installation tokens
|
|
56
|
+
|
|
57
|
+
The worker sandbox may contain temporary scan input during a worker tick. The harness removes that sandbox before returning the worker result and reports whether cleanup was verified.
|
|
58
|
+
|
|
59
|
+
## Current Status
|
|
60
|
+
|
|
61
|
+
The repository can now run a local staging rehearsal across webhook intake, queue persistence, worker execution, compact report storage, Check Run publication, and worker cleanup.
|
|
62
|
+
|
|
63
|
+
This still is not a live hosted service. A real staging environment still requires deployed platform infrastructure, public HTTPS ingress, platform secret references, durable queue/storage resources, worker isolation, GitHub Checks runtime credentials, monitoring, rollback evidence, and incident-response evidence collected from the deployed artifact.
|
package/docs/npm-publishing.md
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
## Current State
|
|
6
6
|
|
|
7
7
|
- Package name: `ai-saas-guard`
|
|
8
|
-
- Current version: `0.
|
|
8
|
+
- Current version: `0.24.0`
|
|
9
9
|
- npm registry state: published at <https://www.npmjs.com/package/ai-saas-guard>
|
|
10
10
|
- First npm-published version: `0.1.1`
|
|
11
|
-
- GitHub Release: `v0.
|
|
11
|
+
- GitHub Release: `v0.24.0`
|
|
12
12
|
- Publish workflow: `.github/workflows/npm-publish.yml`
|
|
13
13
|
- Trusted Publisher: GitHub Actions, `zr9959/ai-saas-guard`, workflow `npm-publish.yml`, allowed action `npm publish`
|
|
14
14
|
- Long-lived npm publish token: not required
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
Use GitHub Actions with npm Trusted Publisher/OIDC:
|
|
19
19
|
|
|
20
|
-
1. Create and review a release tag such as `v0.
|
|
20
|
+
1. Create and review a release tag such as `v0.24.0`.
|
|
21
21
|
2. Publish from the GitHub Release or run the `Publish npm` workflow manually with `ref` set to that tag.
|
|
22
22
|
3. Keep `permissions.id-token: write` in the workflow so npm can exchange the GitHub Actions OIDC identity for a short-lived publish credential.
|
|
23
23
|
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
|
@@ -62,12 +62,14 @@ Implemented surfaces:
|
|
|
62
62
|
- hosted production adapter layer document and helpers for RS256 GitHub App JWT creation, selected-repository installation-token request planning, separate worker/check-run token scopes, fixed read-only worker execution, timeout/output budgets, and cleanup planning for success, failure, timeout, and cancellation
|
|
63
63
|
- 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`
|
|
64
64
|
- hosted staging deployment planner document and helpers for provider binding, staging release-gate evidence, Node/container deployment composition, and production GitHub App promotion gating
|
|
65
|
+
- 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
|
|
65
66
|
- resource caps for repository text collection, including per-file, total-file, and total-byte scan budgets to reduce worst-case memory use
|
|
66
67
|
- 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
|
|
67
68
|
- implementation-ready hosted GitHub App permission contract for required permissions, optional PR comment permissions, selected repository installation, and out-of-scope broad permissions
|
|
68
|
-
- hosted GitHub App contract helpers and tests for webhook intake order, webhook verification, installation token scoping, durable scan queue idempotency, compact reports, retention limits, uninstall cleanup, repeated cleanup idempotency, scoped deletion planning, operational release gate blocking, provider-independent service runtime orchestration, GitHub App deployment planning, hosted production adapter planning, Node/container app skeleton planning,
|
|
69
|
+
- hosted GitHub App contract helpers and tests for webhook intake order, webhook verification, installation token scoping, durable scan queue idempotency, compact reports, retention limits, uninstall cleanup, repeated cleanup idempotency, scoped deletion planning, operational release gate blocking, provider-independent service runtime orchestration, GitHub App deployment planning, hosted production adapter planning, Node/container app skeleton planning, hosted staging deployment planning, and local staging harness replay
|
|
69
70
|
- GitHub issue templates for bug reports, false positives, false negatives, rule requests, and public-safe security reports
|
|
70
71
|
- CODEOWNERS for source, tests, docs, workflows, Action, and package metadata
|
|
72
|
+
- repository trust hardening with `main` branch protection, required CI status checks, Dependabot for npm and GitHub Actions, CodeQL, private vulnerability reporting, secret scanning, and push protection
|
|
71
73
|
- JSON output
|
|
72
74
|
- SARIF output
|
|
73
75
|
- composite GitHub Action wrapper
|
|
@@ -131,12 +133,15 @@ CI:
|
|
|
131
133
|
- Workflow: `.github/workflows/ci.yml`
|
|
132
134
|
- Runs on pull requests and pushes to `main`
|
|
133
135
|
- Uses `permissions: contents: read`
|
|
134
|
-
-
|
|
136
|
+
- Static workflow checks: `actionlint` and `zizmor`
|
|
137
|
+
- Code scanning workflow: `.github/workflows/codeql.yml`
|
|
138
|
+
- Dependabot config: `.github/dependabot.yml` with weekly schedules, bounded PR volume, and cooldown windows
|
|
139
|
+
- Latest verified run for the repository trust hardening release must succeed before publishing
|
|
135
140
|
|
|
136
141
|
Publishing:
|
|
137
142
|
|
|
138
143
|
- npm package: `ai-saas-guard`
|
|
139
|
-
- Current release line: `v0.
|
|
144
|
+
- Current release line: `v0.24.0`
|
|
140
145
|
- Publish workflow: `.github/workflows/npm-publish.yml`
|
|
141
146
|
- Trusted Publisher: GitHub Actions for `zr9959/ai-saas-guard`, workflow `npm-publish.yml`
|
|
142
147
|
- Long-lived npm publish tokens should not be required.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Repository Trust Hardening
|
|
2
|
+
|
|
3
|
+
This document records the public repository controls used to keep `ai-saas-guard` releases reviewable and safer to consume.
|
|
4
|
+
|
|
5
|
+
These controls do not prove the project is secure. They reduce supply-chain and maintenance risk around the public CLI, GitHub Action, npm package, and future hosted service work.
|
|
6
|
+
|
|
7
|
+
## Branch Protection
|
|
8
|
+
|
|
9
|
+
The `main` branch uses branch protection with:
|
|
10
|
+
|
|
11
|
+
- required status checks before merge
|
|
12
|
+
- strict status check freshness
|
|
13
|
+
- required pull request review for non-admin merges
|
|
14
|
+
- linear history
|
|
15
|
+
- force pushes disabled
|
|
16
|
+
- branch deletion disabled
|
|
17
|
+
|
|
18
|
+
Required status checks:
|
|
19
|
+
|
|
20
|
+
- `test`
|
|
21
|
+
- `actionlint`
|
|
22
|
+
- `zizmor`
|
|
23
|
+
|
|
24
|
+
Maintainer admin bypass is not enforced so emergency release repair remains possible, but normal contribution flow should use pull requests and CI.
|
|
25
|
+
|
|
26
|
+
## Dependency Updates
|
|
27
|
+
|
|
28
|
+
Dependabot is configured in `.github/dependabot.yml`.
|
|
29
|
+
|
|
30
|
+
It covers:
|
|
31
|
+
|
|
32
|
+
- npm dependencies
|
|
33
|
+
- GitHub Actions
|
|
34
|
+
|
|
35
|
+
The schedule is weekly with cooldown windows and a small open pull request limit. This keeps update noise low while still surfacing security and maintenance updates.
|
|
36
|
+
|
|
37
|
+
Dependabot security updates and vulnerability alerts are enabled in repository settings.
|
|
38
|
+
|
|
39
|
+
## CodeQL
|
|
40
|
+
|
|
41
|
+
CodeQL is configured in `.github/workflows/codeql.yml`.
|
|
42
|
+
|
|
43
|
+
The workflow:
|
|
44
|
+
|
|
45
|
+
- runs on pull requests
|
|
46
|
+
- runs on pushes to `main`
|
|
47
|
+
- runs on a weekly schedule
|
|
48
|
+
- analyzes JavaScript and TypeScript
|
|
49
|
+
- uses `build-mode: none`
|
|
50
|
+
- uses least-privilege permissions: repository contents read, Actions metadata read, and security event upload
|
|
51
|
+
- pins the CodeQL Action by commit SHA
|
|
52
|
+
|
|
53
|
+
CodeQL is an additional SAST signal. It does not replace `ai-saas-guard`'s release gate, local tests, workflow checks, self-scan, dependency audit, package inspection, or human review.
|
|
54
|
+
|
|
55
|
+
## Vulnerability Intake
|
|
56
|
+
|
|
57
|
+
The repository has:
|
|
58
|
+
|
|
59
|
+
- `SECURITY.md`
|
|
60
|
+
- private vulnerability reporting enabled
|
|
61
|
+
- secret scanning enabled
|
|
62
|
+
- push protection enabled
|
|
63
|
+
|
|
64
|
+
Public issues should not include real credentials, customer data, private source code, or production URLs.
|
|
65
|
+
|
|
66
|
+
## Release Impact
|
|
67
|
+
|
|
68
|
+
Every public release should keep these controls intact. If a release changes workflows, package metadata, Action behavior, or hosted service boundaries, the release notes should include fresh evidence for:
|
|
69
|
+
|
|
70
|
+
- local tests
|
|
71
|
+
- GitHub CI
|
|
72
|
+
- `actionlint`
|
|
73
|
+
- `zizmor`
|
|
74
|
+
- self-scan JSON and SARIF
|
|
75
|
+
- dependency audit
|
|
76
|
+
- npm package inspection
|
|
77
|
+
- packaged CLI smoke test
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-saas-guard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Repo-local launch-readiness scanner for AI-built SaaS apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://github.com/zr9959/ai-saas-guard#readme",
|
|
@@ -53,6 +53,10 @@
|
|
|
53
53
|
"./hosted/staging": {
|
|
54
54
|
"types": "./dist/hosted/staging.d.ts",
|
|
55
55
|
"default": "./dist/hosted/staging.js"
|
|
56
|
+
},
|
|
57
|
+
"./hosted/staging-harness": {
|
|
58
|
+
"types": "./dist/hosted/staging-harness.d.ts",
|
|
59
|
+
"default": "./dist/hosted/staging-harness.js"
|
|
56
60
|
}
|
|
57
61
|
},
|
|
58
62
|
"bin": {
|