ai-saas-guard 0.27.1 → 0.28.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/CONTRIBUTING.md +1 -1
- package/README.md +13 -11
- package/dist/hosted/worker.d.ts +43 -0
- package/dist/hosted/worker.js +267 -0
- package/{README.zh-CN.md → docs/README.zh-CN.md} +35 -34
- package/docs/github-action.md +1 -1
- package/docs/github-app-deployment.md +1 -1
- package/docs/hosted-operations-evidence.md +14 -13
- package/docs/hosted-preimplementation-contracts.md +26 -1
- package/docs/npm-publishing.md +3 -3
- package/docs/project-handoff.md +5 -4
- package/docs/release-quality-knowledge-base.md +1 -1
- package/docs/repository-trust-hardening.md +1 -1
- package/hosted/cloudflare-worker/README.md +3 -3
- package/hosted/cloudflare-worker/wrangler.jsonc +1 -1
- package/package.json +6 -3
package/CONTRIBUTING.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
1. Open an issue or comment on an existing issue before large feature work.
|
|
8
8
|
2. Keep pull requests focused. Separate scanner behavior, docs, workflow changes, and release work when practical.
|
|
9
9
|
3. Include tests for behavior changes. New scanner rules need a vulnerable fixture, a safe fixture, and assertions for both.
|
|
10
|
-
4. Update documentation when behavior, commands, outputs, or release expectations change. If `README.md` changes, review and update `README.zh-CN.md` in the same pull request.
|
|
10
|
+
4. Update documentation when behavior, commands, outputs, or release expectations change. If `README.md` changes, review and update `docs/README.zh-CN.md` in the same pull request.
|
|
11
11
|
5. Fill out the pull request template with release gate evidence and known limitations.
|
|
12
12
|
|
|
13
13
|
## Local Development
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
15
|
<p align="center">
|
|
16
|
-
English | <a href="README.zh-CN.md">中文 README</a>
|
|
16
|
+
English | <a href="docs/README.zh-CN.md">中文 README</a>
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
19
|
<p align="center">
|
|
@@ -73,16 +73,16 @@ The CLI is published on npm as `ai-saas-guard`, and the GitHub Action is availab
|
|
|
73
73
|
| Area | Status |
|
|
74
74
|
| --- | --- |
|
|
75
75
|
| Public GitHub repository | Available |
|
|
76
|
-
| npm CLI | `ai-saas-guard@0.
|
|
77
|
-
| GitHub Action | `zr9959/ai-saas-guard@v0` or fixed tag `v0.
|
|
76
|
+
| npm CLI | `ai-saas-guard@0.28.0` |
|
|
77
|
+
| GitHub Action | `zr9959/ai-saas-guard@v0` or fixed tag `v0.28.0` |
|
|
78
78
|
| Outputs | Terminal, JSON, SARIF, and PR-focused markdown |
|
|
79
79
|
| Project config | `.ai-saas-guard.json` rule toggles, severity overrides, suppressions, and fail thresholds |
|
|
80
80
|
| Privacy model | Local-first, read-only scan commands, no LLM calls, no code upload |
|
|
81
|
-
| Versioned Action tags | `v0.
|
|
82
|
-
| Current release | `0.
|
|
81
|
+
| Versioned Action tags | `v0.28.0`, `v0` |
|
|
82
|
+
| Current release | `0.28.0` hosted read-only checkout worker export, hosted Check Run smoke evidence, and README sync |
|
|
83
83
|
| npm publishing | Trusted Publisher/OIDC, no long-lived publish token |
|
|
84
84
|
| Repository trust hardening | Strict branch protection, Dependabot, CodeQL, fast-check fuzzing, signed release provenance assets, private vulnerability reporting, secret scanning, and push protection |
|
|
85
|
-
| Cloudflare hosted ingress | Deployed at `https://ai-saas-guard-hosted.zr9959.workers.dev`;
|
|
85
|
+
| 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 |
|
|
86
86
|
| Hosted GitHub App staging | Private App `ai-saas-guard-hosted` (`3834787`) installed on `zr9959/ai-saas-guard`; hosted operations evidence is in [docs/hosted-operations-evidence.md](docs/hosted-operations-evidence.md) |
|
|
87
87
|
| OpenSSF Best Practices | Passing badge, project `12955`; `.bestpractices.json` remains the conservative evidence record |
|
|
88
88
|
|
|
@@ -240,13 +240,15 @@ The hosted GitHub App deployment planner is documented in [docs/github-app-deplo
|
|
|
240
240
|
|
|
241
241
|
The hosted production adapter layer is documented in [docs/hosted-production-adapters.md](docs/hosted-production-adapters.md). It exports `createHostedGitHubAppJwt`, `planHostedGitHubInstallationTokenRequest`, and `planHostedProductionWorkerExecution` from `ai-saas-guard/hosted/production-adapters`. It adds RS256 GitHub App JWT generation, selected-repository installation-token request plans, separate worker and Check Run token scopes, a fixed read-only worker command, bounded timeout and output budgets, compact JSON-only output, and cleanup plans for success, failure, timeout, and cancellation. It still does not expose a public hosted service by itself.
|
|
242
242
|
|
|
243
|
+
The hosted read-only checkout worker is exported from `ai-saas-guard/hosted/worker`. It creates a temporary checkout from trusted GitHub App identity, uses a runtime installation token only through git askpass, runs the fixed `ai-saas-guard pr-risk --json` command with bounded timeout/output, converts CLI JSON into compact findings, and deletes the checkout after success or failure. It does not return source, diffs, secrets, checkout paths, PR-authored commands, or installation tokens.
|
|
244
|
+
|
|
243
245
|
The hosted Node/container app skeleton is documented in [docs/hosted-node-container-app.md](docs/hosted-node-container-app.md). It exports `createHostedHttpApp`, `createInMemoryHostedAppPlatform`, and `planHostedNodeContainerDeployment` from `ai-saas-guard/hosted/app`. It adds a safe `/healthz` route, signed `/github/webhook` ingress, one-job worker tick, in-memory provider adapters for tests, and deployment-plan validation for secret manager, queue, compact report store, worker sandbox, and GitHub Checks publisher references. It still does not deploy or expose a public hosted service by itself.
|
|
244
246
|
|
|
245
247
|
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.
|
|
246
248
|
|
|
247
249
|
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.
|
|
248
250
|
|
|
249
|
-
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
|
|
251
|
+
The first live hosted ingress is deployed on Cloudflare Workers at `https://ai-saas-guard-hosted.zr9959.workers.dev` and documented in [hosted/cloudflare-worker/README.md](hosted/cloudflare-worker/README.md). It exposes `/healthz`, `/github/app/manifest-callback`, and signed `/github/webhook` intake backed by Cloudflare KV. A private staging GitHub App, `ai-saas-guard-hosted`, is installed on `zr9959/ai-saas-guard` with selected-repository access and the first-slice permission contract. The Worker verifies signatures, stores compact pull request identity records, exchanges a scoped installation token, fetches PR file metadata from GitHub, classifies PR-risk hotspots, and publishes a bounded Check Run summary. Current deployed evidence is tracked in [docs/hosted-operations-evidence.md](docs/hosted-operations-evidence.md): health, signed webhook delivery, compact KV records, cleanup, and Check Run publication pass for the staging smoke. The Cloudflare Worker still does not run a full source checkout scan worker or store raw webhook payloads, PR title/body text, raw diffs, source, secrets, checkout paths, or installation tokens.
|
|
250
252
|
|
|
251
253
|
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.
|
|
252
254
|
|
|
@@ -254,7 +256,7 @@ Hosted uninstall and data deletion behavior is documented in [docs/hosted-uninst
|
|
|
254
256
|
|
|
255
257
|
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.
|
|
256
258
|
|
|
257
|
-
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.
|
|
259
|
+
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`, a concrete Node read-only checkout scan runner, 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.
|
|
258
260
|
|
|
259
261
|
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.
|
|
260
262
|
|
|
@@ -294,7 +296,7 @@ Use `suppressions` for narrower false-positive handling when one rule is noisy o
|
|
|
294
296
|
|
|
295
297
|
## GitHub Action
|
|
296
298
|
|
|
297
|
-
The repo includes a composite Action. Use `v0` for the latest compatible pre-1.0 Action, a specific release tag such as `v0.
|
|
299
|
+
The repo includes a composite Action. Use `v0` for the latest compatible pre-1.0 Action, a specific release tag such as `v0.28.0` for controlled upgrades, or pin a reviewed commit SHA for stricter supply-chain control:
|
|
298
300
|
|
|
299
301
|
```yaml
|
|
300
302
|
name: ai-saas-guard
|
|
@@ -426,8 +428,8 @@ Open-source core:
|
|
|
426
428
|
|
|
427
429
|
Near-term priorities:
|
|
428
430
|
|
|
429
|
-
-
|
|
430
|
-
- Keep hosted exposure blocked until the operational release gate has fresh evidence from
|
|
431
|
+
- Bind the Node/container read-only checkout worker to real provider references, deploy it behind staging queue/sandbox controls, and collect monitoring, rollback, incident-response, dependency, and container-scan evidence.
|
|
432
|
+
- Keep production hosted exposure blocked until the operational release gate has fresh evidence from the deployed checkout worker artifact.
|
|
431
433
|
|
|
432
434
|
Potential paid layer later:
|
|
433
435
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { HostedServiceScanRunner, HostedServiceScanRunnerInput, HostedServiceScanRunnerResult } from "./service.js";
|
|
2
|
+
export type HostedReadOnlyCheckoutCommandStage = "git_init" | "git_remote_add" | "git_fetch_head" | "git_fetch_base" | "git_checkout" | "cli_scan";
|
|
3
|
+
export interface HostedReadOnlyCheckoutCommand {
|
|
4
|
+
stage: HostedReadOnlyCheckoutCommandStage;
|
|
5
|
+
command: string;
|
|
6
|
+
args: string[];
|
|
7
|
+
cwd: string;
|
|
8
|
+
env: Record<string, string>;
|
|
9
|
+
timeoutMs: number;
|
|
10
|
+
maxOutputBytes: number;
|
|
11
|
+
shell: false;
|
|
12
|
+
}
|
|
13
|
+
export interface HostedReadOnlyCheckoutCommandResult {
|
|
14
|
+
stdout: string;
|
|
15
|
+
}
|
|
16
|
+
export type HostedReadOnlyCheckoutCommandRunner = (command: HostedReadOnlyCheckoutCommand) => Promise<HostedReadOnlyCheckoutCommandResult> | HostedReadOnlyCheckoutCommandResult;
|
|
17
|
+
export type HostedInstallationTokenProvider = (input: HostedServiceScanRunnerInput) => Promise<string> | string;
|
|
18
|
+
export interface HostedReadOnlyCheckoutScanRunnerOptions {
|
|
19
|
+
checkoutRoot?: string;
|
|
20
|
+
githubCloneBaseUrl?: string;
|
|
21
|
+
gitCommand?: string;
|
|
22
|
+
cliCommand?: string;
|
|
23
|
+
fetchDepth?: number;
|
|
24
|
+
timeoutMs?: number;
|
|
25
|
+
maxOutputBytes?: number;
|
|
26
|
+
installationTokenProvider: HostedInstallationTokenProvider;
|
|
27
|
+
commandRunner?: HostedReadOnlyCheckoutCommandRunner;
|
|
28
|
+
}
|
|
29
|
+
export type HostedReadOnlyCheckoutScanSafeReason = "invalid_worker_plan" | "invalid_repository_full_name" | "invalid_clone_base_url" | "missing_installation_token" | "git_init_failed" | "git_remote_add_failed" | "git_fetch_head_failed" | "git_fetch_base_failed" | "git_checkout_failed" | "cli_scan_failed" | "invalid_cli_output" | "cleanup_failed";
|
|
30
|
+
export declare class HostedReadOnlyCheckoutScanError extends Error {
|
|
31
|
+
readonly safeReason: HostedReadOnlyCheckoutScanSafeReason;
|
|
32
|
+
readonly privacy: {
|
|
33
|
+
readonly includesTemporaryCheckoutRoot: false;
|
|
34
|
+
readonly includesRawSource: false;
|
|
35
|
+
readonly includesRawDiffs: false;
|
|
36
|
+
readonly includesSecrets: false;
|
|
37
|
+
readonly includesCustomerPayloads: false;
|
|
38
|
+
readonly includesInstallationToken: false;
|
|
39
|
+
};
|
|
40
|
+
constructor(safeReason: HostedReadOnlyCheckoutScanSafeReason);
|
|
41
|
+
}
|
|
42
|
+
export declare function createHostedReadOnlyCheckoutScanRunner(options: HostedReadOnlyCheckoutScanRunnerOptions): HostedServiceScanRunner;
|
|
43
|
+
export declare function runHostedReadOnlyCheckoutScan(input: HostedServiceScanRunnerInput, options: HostedReadOnlyCheckoutScanRunnerOptions): Promise<HostedServiceScanRunnerResult>;
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { chmod, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { HOSTED_WORKER_DEFAULT_TIMEOUT_MS, HOSTED_WORKER_MAX_OUTPUT_BYTES, HOSTED_WORKER_MAX_TIMEOUT_MS } from "./production-adapters.js";
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
const DEFAULT_GITHUB_CLONE_BASE_URL = "https://github.com";
|
|
9
|
+
const DEFAULT_FETCH_DEPTH = 100;
|
|
10
|
+
const MAX_FETCH_DEPTH = 1_000;
|
|
11
|
+
const MAX_COMPACT_FINDINGS = 200;
|
|
12
|
+
export class HostedReadOnlyCheckoutScanError extends Error {
|
|
13
|
+
safeReason;
|
|
14
|
+
privacy = {
|
|
15
|
+
includesTemporaryCheckoutRoot: false,
|
|
16
|
+
includesRawSource: false,
|
|
17
|
+
includesRawDiffs: false,
|
|
18
|
+
includesSecrets: false,
|
|
19
|
+
includesCustomerPayloads: false,
|
|
20
|
+
includesInstallationToken: false
|
|
21
|
+
};
|
|
22
|
+
constructor(safeReason) {
|
|
23
|
+
super(`hosted_read_only_checkout_scan_failed:${safeReason}`);
|
|
24
|
+
this.name = "HostedReadOnlyCheckoutScanError";
|
|
25
|
+
this.safeReason = safeReason;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function createHostedReadOnlyCheckoutScanRunner(options) {
|
|
29
|
+
return (input) => runHostedReadOnlyCheckoutScan(input, options);
|
|
30
|
+
}
|
|
31
|
+
export async function runHostedReadOnlyCheckoutScan(input, options) {
|
|
32
|
+
const { plan } = input;
|
|
33
|
+
const { checkout, cli } = plan;
|
|
34
|
+
if (!plan.accepted || !plan.readOnly || !checkout || !cli || cli.writeMode !== "read_only") {
|
|
35
|
+
throw new HostedReadOnlyCheckoutScanError("invalid_worker_plan");
|
|
36
|
+
}
|
|
37
|
+
const repository = parseRepositoryFullName(checkout.repositoryFullName);
|
|
38
|
+
if (!repository) {
|
|
39
|
+
throw new HostedReadOnlyCheckoutScanError("invalid_repository_full_name");
|
|
40
|
+
}
|
|
41
|
+
const cloneBaseUrl = normalizeSafeCloneBaseUrl(options.githubCloneBaseUrl ?? DEFAULT_GITHUB_CLONE_BASE_URL);
|
|
42
|
+
const cloneUrl = `${cloneBaseUrl}/${repository.owner}/${repository.repo}.git`;
|
|
43
|
+
const timeoutMs = clampPositiveInteger(options.timeoutMs, HOSTED_WORKER_DEFAULT_TIMEOUT_MS, HOSTED_WORKER_MAX_TIMEOUT_MS);
|
|
44
|
+
const maxOutputBytes = clampPositiveInteger(options.maxOutputBytes, HOSTED_WORKER_MAX_OUTPUT_BYTES, HOSTED_WORKER_MAX_OUTPUT_BYTES);
|
|
45
|
+
const fetchDepth = clampPositiveInteger(options.fetchDepth, DEFAULT_FETCH_DEPTH, MAX_FETCH_DEPTH);
|
|
46
|
+
const checkoutRoot = options.checkoutRoot ?? join(tmpdir(), "ai-saas-guard-hosted-checkouts");
|
|
47
|
+
const token = await options.installationTokenProvider(input);
|
|
48
|
+
if (typeof token !== "string" || token.trim().length === 0) {
|
|
49
|
+
throw new HostedReadOnlyCheckoutScanError("missing_installation_token");
|
|
50
|
+
}
|
|
51
|
+
await mkdir(checkoutRoot, { recursive: true, mode: 0o700 });
|
|
52
|
+
const checkoutDir = await mkdtemp(join(checkoutRoot, "job-"));
|
|
53
|
+
let terminalError;
|
|
54
|
+
try {
|
|
55
|
+
await chmod(checkoutDir, 0o700);
|
|
56
|
+
const askpassPath = join(checkoutDir, ".git-askpass.sh");
|
|
57
|
+
await writeFile(askpassPath, [
|
|
58
|
+
"#!/bin/sh",
|
|
59
|
+
"case \"$1\" in",
|
|
60
|
+
"*Username*) printf '%s' 'x-access-token' ;;",
|
|
61
|
+
"*Password*) printf '%s' \"$AI_SAAS_GUARD_GITHUB_TOKEN\" ;;",
|
|
62
|
+
"*) printf '%s' '' ;;",
|
|
63
|
+
"esac",
|
|
64
|
+
""
|
|
65
|
+
].join("\n"), { mode: 0o700 });
|
|
66
|
+
const gitEnv = safeWorkerEnv(checkoutDir, {
|
|
67
|
+
GIT_ASKPASS: askpassPath,
|
|
68
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
69
|
+
GIT_CONFIG_NOSYSTEM: "1",
|
|
70
|
+
GIT_CONFIG_GLOBAL: "/dev/null"
|
|
71
|
+
});
|
|
72
|
+
const gitSecretEnv = { AI_SAAS_GUARD_GITHUB_TOKEN: token };
|
|
73
|
+
const gitCommand = options.gitCommand ?? "git";
|
|
74
|
+
await runCommand(options, gitSecretEnv, commandSpec("git_init", gitCommand, ["init"], checkoutDir, gitEnv, timeoutMs, maxOutputBytes));
|
|
75
|
+
await runCommand(options, gitSecretEnv, commandSpec("git_remote_add", gitCommand, ["remote", "add", "origin", cloneUrl], checkoutDir, gitEnv, timeoutMs, maxOutputBytes));
|
|
76
|
+
await runCommand(options, gitSecretEnv, commandSpec("git_fetch_head", gitCommand, ["fetch", "--no-tags", "--depth", String(fetchDepth), "origin", checkout.targetCommitSha], checkoutDir, gitEnv, timeoutMs, maxOutputBytes));
|
|
77
|
+
await runCommand(options, gitSecretEnv, commandSpec("git_fetch_base", gitCommand, ["fetch", "--no-tags", "--depth", String(fetchDepth), "origin", checkout.baseSha], checkoutDir, gitEnv, timeoutMs, maxOutputBytes));
|
|
78
|
+
await runCommand(options, gitSecretEnv, commandSpec("git_checkout", gitCommand, ["checkout", "--detach", checkout.targetCommitSha], checkoutDir, gitEnv, timeoutMs, maxOutputBytes));
|
|
79
|
+
const cliEnv = safeWorkerEnv(checkoutDir);
|
|
80
|
+
const cliArgs = cli.args.map((arg) => arg === "<worker-checkout>" ? checkoutDir : arg);
|
|
81
|
+
const cliResult = await runCommand(options, {}, commandSpec("cli_scan", options.cliCommand ?? cli.command, cliArgs, checkoutDir, cliEnv, timeoutMs, maxOutputBytes));
|
|
82
|
+
return compactScanRunnerResult(cliResult.stdout);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
terminalError =
|
|
86
|
+
error instanceof HostedReadOnlyCheckoutScanError
|
|
87
|
+
? error
|
|
88
|
+
: new HostedReadOnlyCheckoutScanError("cli_scan_failed");
|
|
89
|
+
throw terminalError;
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
try {
|
|
93
|
+
await rm(checkoutDir, { recursive: true, force: true });
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
if (!terminalError) {
|
|
97
|
+
throw new HostedReadOnlyCheckoutScanError("cleanup_failed");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function commandSpec(stage, command, args, cwd, env, timeoutMs, maxOutputBytes) {
|
|
103
|
+
return {
|
|
104
|
+
stage,
|
|
105
|
+
command,
|
|
106
|
+
args,
|
|
107
|
+
cwd,
|
|
108
|
+
env,
|
|
109
|
+
timeoutMs,
|
|
110
|
+
maxOutputBytes,
|
|
111
|
+
shell: false
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
async function runCommand(options, secretEnv, command) {
|
|
115
|
+
try {
|
|
116
|
+
if (options.commandRunner) {
|
|
117
|
+
return await options.commandRunner(command);
|
|
118
|
+
}
|
|
119
|
+
const { stdout } = await execFileAsync(command.command, command.args, {
|
|
120
|
+
cwd: command.cwd,
|
|
121
|
+
env: { ...command.env, ...secretEnv },
|
|
122
|
+
timeout: command.timeoutMs,
|
|
123
|
+
maxBuffer: command.maxOutputBytes,
|
|
124
|
+
encoding: "utf8",
|
|
125
|
+
shell: false
|
|
126
|
+
});
|
|
127
|
+
return { stdout };
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
throw new HostedReadOnlyCheckoutScanError(`${command.stage}_failed`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function compactScanRunnerResult(stdout) {
|
|
134
|
+
try {
|
|
135
|
+
const report = JSON.parse(stdout);
|
|
136
|
+
const findings = Array.isArray(report.findings) ? report.findings : [];
|
|
137
|
+
return {
|
|
138
|
+
summaryCounts: normalizeSummaryCounts(report.summary),
|
|
139
|
+
findings: findings.slice(0, MAX_COMPACT_FINDINGS).flatMap(compactFinding)
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
throw new HostedReadOnlyCheckoutScanError("invalid_cli_output");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function compactFinding(value) {
|
|
147
|
+
if (!isRecord(value))
|
|
148
|
+
return [];
|
|
149
|
+
const ruleId = stringValue(value.ruleId);
|
|
150
|
+
const severity = stringValue(value.severity);
|
|
151
|
+
const evidence = Array.isArray(value.evidence) ? value.evidence.find(isRecord) : undefined;
|
|
152
|
+
const file = stringValue(evidence?.file) ?? stringValue(value.file);
|
|
153
|
+
if (!ruleId || !severity || !file)
|
|
154
|
+
return [];
|
|
155
|
+
const line = integerValue(evidence?.line) ?? integerValue(value.line);
|
|
156
|
+
return [
|
|
157
|
+
{
|
|
158
|
+
ruleId,
|
|
159
|
+
severity,
|
|
160
|
+
file,
|
|
161
|
+
...(line === undefined ? {} : { line })
|
|
162
|
+
}
|
|
163
|
+
];
|
|
164
|
+
}
|
|
165
|
+
function normalizeSummaryCounts(value) {
|
|
166
|
+
const record = isRecord(value) ? value : {};
|
|
167
|
+
const summary = {
|
|
168
|
+
critical: numberValue(record.critical),
|
|
169
|
+
high: numberValue(record.high),
|
|
170
|
+
medium: numberValue(record.medium),
|
|
171
|
+
low: numberValue(record.low),
|
|
172
|
+
info: numberValue(record.info)
|
|
173
|
+
};
|
|
174
|
+
summary.total = numberValue(record.total, summary.critical + summary.high + summary.medium + summary.low + summary.info);
|
|
175
|
+
return summary;
|
|
176
|
+
}
|
|
177
|
+
function safeWorkerEnv(checkoutDir, extra = {}) {
|
|
178
|
+
return {
|
|
179
|
+
PATH: process.env.PATH ?? "/usr/bin:/bin",
|
|
180
|
+
HOME: checkoutDir,
|
|
181
|
+
TMPDIR: checkoutDir,
|
|
182
|
+
CI: "true",
|
|
183
|
+
NO_COLOR: "1",
|
|
184
|
+
...extra
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function parseRepositoryFullName(value) {
|
|
188
|
+
const match = /^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/.exec(value);
|
|
189
|
+
if (!match)
|
|
190
|
+
return undefined;
|
|
191
|
+
return {
|
|
192
|
+
owner: match[1],
|
|
193
|
+
repo: match[2]
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function normalizeSafeCloneBaseUrl(value) {
|
|
197
|
+
try {
|
|
198
|
+
const url = new URL(value);
|
|
199
|
+
if (url.protocol !== "https:" ||
|
|
200
|
+
url.username ||
|
|
201
|
+
url.password ||
|
|
202
|
+
url.search ||
|
|
203
|
+
url.hash ||
|
|
204
|
+
hasNonSlashPath(url.pathname) ||
|
|
205
|
+
isUnsafeHostedHostname(url.hostname)) {
|
|
206
|
+
throw new HostedReadOnlyCheckoutScanError("invalid_clone_base_url");
|
|
207
|
+
}
|
|
208
|
+
return `${url.protocol}//${url.host}`;
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
if (error instanceof HostedReadOnlyCheckoutScanError)
|
|
212
|
+
throw error;
|
|
213
|
+
throw new HostedReadOnlyCheckoutScanError("invalid_clone_base_url");
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function hasNonSlashPath(pathname) {
|
|
217
|
+
for (const character of pathname) {
|
|
218
|
+
if (character !== "/")
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
function isUnsafeHostedHostname(hostname) {
|
|
224
|
+
const normalized = hostname.toLowerCase().replace(/\.$/, "");
|
|
225
|
+
return (normalized === "localhost" ||
|
|
226
|
+
normalized.endsWith(".localhost") ||
|
|
227
|
+
isUnsafeIpv4Hostname(normalized) ||
|
|
228
|
+
normalized === "::1" ||
|
|
229
|
+
normalized.startsWith("fc") ||
|
|
230
|
+
normalized.startsWith("fd") ||
|
|
231
|
+
normalized.startsWith("fe80:"));
|
|
232
|
+
}
|
|
233
|
+
function isUnsafeIpv4Hostname(hostname) {
|
|
234
|
+
const parts = hostname.split(".");
|
|
235
|
+
if (parts.length !== 4 || !parts.every((part) => /^\d+$/.test(part)))
|
|
236
|
+
return false;
|
|
237
|
+
const octets = parts.map((part) => Number(part));
|
|
238
|
+
if (octets.some((octet) => !Number.isInteger(octet) || octet < 0 || octet > 255)) {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
const [first, second] = octets;
|
|
242
|
+
return (first === 0 ||
|
|
243
|
+
first === 10 ||
|
|
244
|
+
first === 127 ||
|
|
245
|
+
(first === 169 && second === 254) ||
|
|
246
|
+
(first === 172 && second >= 16 && second <= 31) ||
|
|
247
|
+
(first === 192 && second === 168) ||
|
|
248
|
+
(first === 100 && second >= 64 && second <= 127) ||
|
|
249
|
+
first >= 224);
|
|
250
|
+
}
|
|
251
|
+
function clampPositiveInteger(value, fallback, maximum) {
|
|
252
|
+
if (value === undefined || !Number.isFinite(value))
|
|
253
|
+
return fallback;
|
|
254
|
+
return Math.min(maximum, Math.max(1, Math.floor(value)));
|
|
255
|
+
}
|
|
256
|
+
function isRecord(value) {
|
|
257
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
258
|
+
}
|
|
259
|
+
function stringValue(value) {
|
|
260
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
261
|
+
}
|
|
262
|
+
function integerValue(value) {
|
|
263
|
+
return typeof value === "number" && Number.isSafeInteger(value) ? value : undefined;
|
|
264
|
+
}
|
|
265
|
+
function numberValue(value, fallback = 0) {
|
|
266
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
267
|
+
}
|
|
@@ -13,15 +13,15 @@
|
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
15
|
<p align="center">
|
|
16
|
-
<a href="README.md">English README</a> | 中文
|
|
16
|
+
<a href="../README.md">English README</a> | 中文
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
19
|
<p align="center">
|
|
20
20
|
<a href="https://github.com/zr9959/ai-saas-guard/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/zr9959/ai-saas-guard/actions/workflows/ci.yml/badge.svg"></a>
|
|
21
21
|
<a href="https://www.bestpractices.dev/projects/12955"><img alt="OpenSSF Best Practices" src="https://www.bestpractices.dev/projects/12955/badge"></a>
|
|
22
22
|
<a href="https://www.npmjs.com/package/ai-saas-guard"><img alt="npm" src="https://img.shields.io/npm/v/ai-saas-guard.svg"></a>
|
|
23
|
-
<a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
|
|
24
|
-
<a href="package.json"><img alt="Node.js >=20" src="https://img.shields.io/badge/node-%3E%3D20-339933.svg"></a>
|
|
23
|
+
<a href="../LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
|
|
24
|
+
<a href="../package.json"><img alt="Node.js >=20" src="https://img.shields.io/badge/node-%3E%3D20-339933.svg"></a>
|
|
25
25
|
</p>
|
|
26
26
|
|
|
27
27
|
---
|
|
@@ -67,22 +67,22 @@ AI 能很快把一个 SaaS 做到“看起来能用”。真正危险的是上
|
|
|
67
67
|
|
|
68
68
|
这个仓库是公开 GitHub 仓库。
|
|
69
69
|
|
|
70
|
-
CLI 已发布到 npm:`ai-saas-guard@0.
|
|
70
|
+
CLI 已发布到 npm:`ai-saas-guard@0.28.0`。GitHub Action 支持 `v0` 浮动标签,也支持固定版本标签,例如 `v0.28.0`。
|
|
71
71
|
|
|
72
72
|
| 模块 | 状态 |
|
|
73
73
|
| --- | --- |
|
|
74
74
|
| 公开 GitHub 仓库 | 已可用 |
|
|
75
|
-
| npm CLI | `ai-saas-guard@0.
|
|
76
|
-
| GitHub Action | `zr9959/ai-saas-guard@v0` 或固定标签 `v0.
|
|
75
|
+
| npm CLI | `ai-saas-guard@0.28.0` |
|
|
76
|
+
| GitHub Action | `zr9959/ai-saas-guard@v0` 或固定标签 `v0.28.0` |
|
|
77
77
|
| 输出格式 | Terminal、JSON、SARIF 和 PR markdown |
|
|
78
78
|
| 项目配置 | `.ai-saas-guard.json` 支持规则开关、severity 覆盖、suppressions 和 fail threshold |
|
|
79
79
|
| 隐私模型 | 本地优先、只读扫描、不调用 LLM、不上传代码 |
|
|
80
|
-
| 当前版本 | `0.
|
|
81
|
-
| Action 标签 | `v0.
|
|
80
|
+
| 当前版本 | `0.28.0` hosted read-only checkout worker export、hosted Check Run smoke evidence 和 README 同步 |
|
|
81
|
+
| Action 标签 | `v0.28.0`、`v0` |
|
|
82
82
|
| npm 发布 | GitHub Actions Trusted Publisher/OIDC,无需长期 npm token |
|
|
83
83
|
| 仓库可信度加固 | 严格 branch protection、Dependabot、CodeQL、fast-check fuzzing、signed release provenance assets、private vulnerability reporting、secret scanning 和 push protection |
|
|
84
|
-
| Cloudflare hosted ingress | 已部署到 `https://ai-saas-guard-hosted.zr9959.workers.dev
|
|
85
|
-
| Hosted GitHub App staging | 私有 App `ai-saas-guard-hosted`(`3834787`)已安装到 `zr9959/ai-saas-guard`;hosted operations evidence 见 [docs/hosted-operations-evidence.md](
|
|
84
|
+
| Cloudflare hosted ingress | 已部署到 `https://ai-saas-guard-hosted.zr9959.workers.dev`;签名 GitHub App webhook delivery 和 compact Check Run staging smoke 已通过 |
|
|
85
|
+
| 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) |
|
|
86
86
|
| OpenSSF Best Practices | 已获得 passing badge,项目 `12955`;`.bestpractices.json` 继续作为保守证据记录 |
|
|
87
87
|
|
|
88
88
|
## 快速开始
|
|
@@ -148,17 +148,17 @@ node dist/cli.js scan --root /path/to/your-saas
|
|
|
148
148
|
| GitHub Actions | workflow 权限过宽、PR workflow 缺 concurrency cancel、docs-only 改动跑全量 CI、secret/tool version 缺 fail-fast、`pr-risk` checkout 太浅、Action 未 pin SHA |
|
|
149
149
|
| PR risk | auth、billing、RLS、env、deploy、API、storage、silent-success、测试删除、缺 spec/context、大型混合 diff |
|
|
150
150
|
|
|
151
|
-
完整规则请看 [docs/rules.md](
|
|
151
|
+
完整规则请看 [docs/rules.md](rules.md)。
|
|
152
152
|
|
|
153
153
|
## 仓库可信度加固
|
|
154
154
|
|
|
155
|
-
公开仓库的维护和发布控制见 [docs/repository-trust-hardening.md](
|
|
155
|
+
公开仓库的维护和发布控制见 [docs/repository-trust-hardening.md](repository-trust-hardening.md)。当前已经配置严格 branch protection、required CI checks、Dependabot npm/GitHub Actions 更新、CodeQL SAST、fast-check fuzz/property tests、基于 npm trusted publishing provenance 的 signed GitHub release assets、private vulnerability reporting、secret scanning 和 push protection。
|
|
156
156
|
|
|
157
157
|
最新 GitHub releases 会镜像 npm package tarball,并附带 `*.tgz.sigstore.json` 和 `*.tgz.intoto.jsonl` provenance assets。上传前会用 npm registry metadata 校验 tarball digest,并使用 npm provenance 作为来源。
|
|
158
158
|
|
|
159
159
|
当前 Scorecard 提升路线优先做真实控制,不做表面刷分:更严格的 review gate、可被检测到的 fuzzing、以及 OpenSSF Best Practices Badge 流程。仓库年龄、贡献者多样性、已 review 的 PR 历史这些分数只能随着真实维护逐步提升。
|
|
160
160
|
|
|
161
|
-
仓库现在已经获得 [OpenSSF Best Practices passing badge](https://www.bestpractices.dev/projects/12955)。[.bestpractices.json](
|
|
161
|
+
仓库现在已经获得 [OpenSSF Best Practices passing badge](https://www.bestpractices.dev/projects/12955)。[.bestpractices.json](../.bestpractices.json) 继续作为公开项目条目的保守证据记录。`dynamic_analysis_enable_assertions` 仍然谨慎标为 unmet,直到运行时断言覆盖面超过当前测试、property 和 fuzz assertions。
|
|
162
162
|
|
|
163
163
|
## PR 风险分流
|
|
164
164
|
|
|
@@ -229,7 +229,7 @@ jobs:
|
|
|
229
229
|
config: .ai-saas-guard.json
|
|
230
230
|
```
|
|
231
231
|
|
|
232
|
-
更多 GitHub Action 示例请看 [docs/github-action.md](
|
|
232
|
+
更多 GitHub Action 示例请看 [docs/github-action.md](github-action.md)。
|
|
233
233
|
|
|
234
234
|
## 项目配置
|
|
235
235
|
|
|
@@ -270,36 +270,37 @@ jobs:
|
|
|
270
270
|
|
|
271
271
|
## Hosted GitHub App 设计
|
|
272
272
|
|
|
273
|
-
当前仓库已经包含未来 Hosted GitHub App
|
|
273
|
+
当前仓库已经包含未来 Hosted GitHub App 的设计文档、纯契约测试、第一个真实 Cloudflare hosted ingress,以及 Node/container read-only checkout scan runner。私有 staging GitHub App `ai-saas-guard-hosted` 已安装到 `zr9959/ai-saas-guard`,Cloudflare 已配置所需的云端凭据绑定。Worker 代码已经能接收签名 webhook、写入 KV 队列、换取 scoped installation token、读取 GitHub PR file metadata、做 compact PR-risk classification,并发布有长度上限的 Check Run summary;当前端到端 GitHub App webhook delivery smoke 已通过,证据记录在 [docs/hosted-operations-evidence.md](hosted-operations-evidence.md)。Cloudflare ingress 本身仍不是完整 source checkout scan worker。
|
|
274
274
|
|
|
275
275
|
相关文档:
|
|
276
276
|
|
|
277
|
-
- [docs/github-app-design.md](
|
|
278
|
-
- [docs/github-app-deployment.md](
|
|
279
|
-
- [docs/hosted-first-service-slice.md](
|
|
280
|
-
- [docs/hosted-deployment-model.md](
|
|
281
|
-
- [docs/hosted-service-runtime.md](
|
|
282
|
-
- [docs/hosted-production-adapters.md](
|
|
283
|
-
- [docs/hosted-node-container-app.md](
|
|
284
|
-
- [docs/hosted-staging-deployment.md](
|
|
285
|
-
- [docs/hosted-staging-harness.md](
|
|
286
|
-
- [docs/hosted-operational-release-gate.md](
|
|
287
|
-
- [docs/hosted-uninstall-data-deletion.md](
|
|
288
|
-
- [docs/hosted-pricing-packaging.md](
|
|
289
|
-
- [docs/hosted-preimplementation-contracts.md](
|
|
277
|
+
- [docs/github-app-design.md](github-app-design.md)
|
|
278
|
+
- [docs/github-app-deployment.md](github-app-deployment.md)
|
|
279
|
+
- [docs/hosted-first-service-slice.md](hosted-first-service-slice.md)
|
|
280
|
+
- [docs/hosted-deployment-model.md](hosted-deployment-model.md)
|
|
281
|
+
- [docs/hosted-service-runtime.md](hosted-service-runtime.md)
|
|
282
|
+
- [docs/hosted-production-adapters.md](hosted-production-adapters.md)
|
|
283
|
+
- [docs/hosted-node-container-app.md](hosted-node-container-app.md)
|
|
284
|
+
- [docs/hosted-staging-deployment.md](hosted-staging-deployment.md)
|
|
285
|
+
- [docs/hosted-staging-harness.md](hosted-staging-harness.md)
|
|
286
|
+
- [docs/hosted-operational-release-gate.md](hosted-operational-release-gate.md)
|
|
287
|
+
- [docs/hosted-uninstall-data-deletion.md](hosted-uninstall-data-deletion.md)
|
|
288
|
+
- [docs/hosted-pricing-packaging.md](hosted-pricing-packaging.md)
|
|
289
|
+
- [docs/hosted-preimplementation-contracts.md](hosted-preimplementation-contracts.md)
|
|
290
290
|
|
|
291
291
|
已经实现的 hosted 预实现纯契约包括:
|
|
292
292
|
|
|
293
293
|
- pull request webhook intake planner:先验签,再解析 payload、生成可信 identity、校验 selected-repository scope,并默认只走 check-run-only 输出
|
|
294
294
|
- durable scan queue planner:同一个 trusted scan key 的 queued/running/completed job 会复用,不重复排 worker,也不会把源码、diff、secret 或 PR 正文放进队列 payload
|
|
295
295
|
- worker read-only scan planner:只用 trusted identity 规划临时 worker checkout,要求 repository `contents: read`,固定运行 `ai-saas-guard pr-risk --json`,并忽略 PR 正文里的 repo 名、token scope 或命令
|
|
296
|
+
- hosted read-only checkout worker:`ai-saas-guard/hosted/worker` 导出 `createHostedReadOnlyCheckoutScanRunner`,从 trusted GitHub App identity 创建临时 checkout,只通过 git askpass 使用 runtime installation token,运行固定 `ai-saas-guard pr-risk --json`,把 CLI JSON 转成 compact findings,并在成功或失败后删除 checkout;不会返回源码、diff、secret、checkout path、PR 里写的命令或 installation token
|
|
296
297
|
- hosted service runtime:`ai-saas-guard/hosted/service` 导出 `createHostedServiceRuntime`,把签名 webhook intake、幂等 queue upsert、read-only worker 编排、compact report 存储、Check Run 发布 adapter 和 worker cleanup 串成可测试的服务核心;它本身不部署公开 hosted 环境
|
|
297
298
|
- GitHub App deployment planner:`ai-saas-guard/hosted/github-app` 导出 `planHostedGitHubAppDeployment`,生成 first slice 最小权限 manifest,并在 release gate、公开 HTTPS URL、container digest、secret 引用、原始 secret 输入、permission 或 event 不安全时阻止创建
|
|
298
299
|
- 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 服务
|
|
299
300
|
- 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 服务
|
|
300
301
|
- 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 服务
|
|
301
302
|
- 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 服务
|
|
302
|
-
- Cloudflare hosted ingress:`hosted/cloudflare-worker` 已部署到 `https://ai-saas-guard-hosted.zr9959.workers.dev`,提供 `/healthz`、`/github/app/manifest-callback` 和签名 `/github/webhook` intake;Worker 已具备 compact pull request identity、file/category risk signal 和 Check Run metadata 路径;staging GitHub App ID 为 `3834787`,installation ID 为 `135085075`;真实 GitHub App webhook delivery
|
|
303
|
+
- Cloudflare hosted ingress:`hosted/cloudflare-worker` 已部署到 `https://ai-saas-guard-hosted.zr9959.workers.dev`,提供 `/healthz`、`/github/app/manifest-callback` 和签名 `/github/webhook` intake;Worker 已具备 compact pull request identity、file/category risk signal 和 Check Run metadata 路径;staging GitHub App ID 为 `3834787`,installation ID 为 `135085075`;真实 GitHub App webhook delivery 和 Check Run smoke 已通过;完整 source checkout worker deployment、monitoring、rollback 和 incident-response evidence 仍需要通过 hosted operational release gate
|
|
303
304
|
- webhook event parser
|
|
304
305
|
- check-run summary renderer
|
|
305
306
|
- Check Run publication planner:要求 repository `checks: write`,只从 compact report 生成有长度上限的 Check Run payload,包含 review categories、优先 review 文件、verification steps 和本地 CLI 复现命令;MVP 不发 PR comment
|
|
@@ -307,7 +308,7 @@ jobs:
|
|
|
307
308
|
- worker checkout cleanup planner
|
|
308
309
|
- retention/deletion cleanup planner:把 compact report 删除、按仓库或 installation 范围取消队列和 running job、worker checkout 删除、retention 过期清理、最小审计记录合成一个安全计划;不会输出源码、diff、secret、customer payload、private URL、checkout path 或底层 cleanup error
|
|
309
310
|
- operational release gate evaluator:检查 hosted 暴露前是否具备 fresh CI、webhook replay、workflow static check、dependency/container scan、cleanup、privacy、monitoring、rollback、incident response 和 release cleanup 证据;缺任何 P0 证据都会阻止 hosted exposure
|
|
310
|
-
- hosted compact report fixture:[examples/hosted-compact-report.json](examples/hosted-compact-report.json)
|
|
311
|
+
- hosted compact report fixture:[examples/hosted-compact-report.json](../examples/hosted-compact-report.json)
|
|
311
312
|
|
|
312
313
|
这些 helper 不会暴露公开服务、不会直接调用 GitHub API、不会持久化 installation token、不会真实写 check run、不会发 PR comment,也不会上传源码。
|
|
313
314
|
|
|
@@ -334,12 +335,12 @@ npm run build
|
|
|
334
335
|
node dist/cli.js scan --root .
|
|
335
336
|
```
|
|
336
337
|
|
|
337
|
-
发布 CLI、GitHub Action、npm package 或任何公开仓库更新前,必须按照 [docs/release-quality-knowledge-base.md](
|
|
338
|
+
发布 CLI、GitHub Action、npm package 或任何公开仓库更新前,必须按照 [docs/release-quality-knowledge-base.md](release-quality-knowledge-base.md) 的 release gate 执行。
|
|
338
339
|
|
|
339
|
-
以后更新英文 `README.md` 时,也要同步检查并更新本中文 `README.zh-CN.md`。
|
|
340
|
+
以后更新英文 `README.md` 时,也要同步检查并更新本中文 `docs/README.zh-CN.md`。
|
|
340
341
|
|
|
341
|
-
贡献要求见 [CONTRIBUTING.md](CONTRIBUTING.md),里面说明了 PR 流程、测试要求、规则设计、release gate evidence 和公开安全边界。
|
|
342
|
+
贡献要求见 [CONTRIBUTING.md](../CONTRIBUTING.md),里面说明了 PR 流程、测试要求、规则设计、release gate evidence 和公开安全边界。
|
|
342
343
|
|
|
343
344
|
## 安全报告
|
|
344
345
|
|
|
345
|
-
报告漏洞前请阅读 [SECURITY.md](SECURITY.md)。不要在公开 issue 中发布真实 API key、客户数据、私有源码或生产 URL。
|
|
346
|
+
报告漏洞前请阅读 [SECURITY.md](../SECURITY.md)。不要在公开 issue 中发布真实 API key、客户数据、私有源码或生产 URL。
|
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.28.0` or a reviewed commit SHA when reproducibility is more important than automatic minor updates.
|
|
6
6
|
|
|
7
7
|
## PR Summary
|
|
8
8
|
|
|
@@ -87,6 +87,6 @@ The repository can now produce and validate the deployment plan, and a private s
|
|
|
87
87
|
- Webhook URL: `https://ai-saas-guard-hosted.zr9959.workers.dev/github/webhook`
|
|
88
88
|
- Secret storage: Cloudflare Worker secrets for `WEBHOOK_SECRET` and `GITHUB_APP_PRIVATE_KEY`
|
|
89
89
|
|
|
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
|
|
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
92
|
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,27 +6,28 @@ Passing these checks does not make the project a pentest, certification, or full
|
|
|
6
6
|
|
|
7
7
|
## Current Evidence
|
|
8
8
|
|
|
9
|
-
Recorded on 2026-05-24 from the deployed Cloudflare Worker.
|
|
9
|
+
Recorded on 2026-05-24 from the deployed Cloudflare Worker and a temporary no-file-change GitHub PR smoke.
|
|
10
10
|
|
|
11
11
|
| Check | Evidence | Result |
|
|
12
12
|
| --- | --- | --- |
|
|
13
|
-
| Cloudflare Worker health | `GET https://ai-saas-guard-hosted.zr9959.workers.dev/healthz` returned `ok: true`, `checkRunPublisher: "configured"`, `scannerVersion: "0.
|
|
14
|
-
| Deployed Worker version | `wrangler deployments list` showed current version `
|
|
13
|
+
| 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
|
+
| Deployed Worker version | `wrangler deployments list` showed current version `531d2286-86c6-4327-bfd0-67cad8693c10`, deployed at `2026-05-24T09:01:25.706Z` | Passed |
|
|
15
15
|
| KV cleanup | `wrangler kv key list --namespace-id fa5344fbd7944de6a776bf8731d58460 --remote` returned `[]` after smoke cleanup | Passed |
|
|
16
|
-
| Temporary smoke PR cleanup | Temporary PR `#
|
|
17
|
-
| End-to-end GitHub App delivery | Temporary PR `#
|
|
16
|
+
| Temporary smoke PR cleanup | Temporary PR `#52` was closed, branch `codex/hosted-smoke-20260524170208` was deleted, and in-progress workflow run `26357038569` was cancelled | Passed |
|
|
17
|
+
| End-to-end GitHub App delivery | Temporary PR `#52` created `ai-saas-guard PR risk` from GitHub App `ai-saas-guard-hosted`; Check Run `77585561127` completed with conclusion `success` for head SHA `408925d2bf4df564082dabc3e1893a72c25bdd19` | Passed |
|
|
18
|
+
| Compact hosted record | KV scan record `scan:135085075:1247239389:52:408925d2bf4df564082dabc3e1893a72c25bdd19:0.28.0` completed with zero findings, `conclusion: "success"`, and all privacy flags set to false for raw payloads, PR text, source, diffs, secrets, customer payloads, checkout paths, and installation tokens | Passed |
|
|
18
19
|
|
|
19
|
-
##
|
|
20
|
+
## Remaining Release Gate Gaps
|
|
20
21
|
|
|
21
|
-
The deployed Worker
|
|
22
|
+
The deployed Cloudflare Worker now receives signed GitHub App webhook delivery for pull request events and publishes bounded compact Check Runs. This is still staging evidence, not production hosted exposure.
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
The hosted release gate still requires fresh deployed evidence for:
|
|
24
25
|
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
26
|
+
- full Node/container read-only checkout scan worker deployment
|
|
27
|
+
- worker sandbox network restrictions and cleanup evidence
|
|
28
|
+
- logs, metrics, alerting, rollback, and incident-response drills
|
|
29
|
+
- dependency and container artifact scanning for the deployed worker image
|
|
30
|
+
- retention and uninstall cleanup against the deployed provider stores
|
|
30
31
|
|
|
31
32
|
## Smoke Procedure
|
|
32
33
|
|
|
@@ -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`. The local staging harness lives in `src/hosted/staging-harness.ts` and is exported from `ai-saas-guard/hosted/staging-harness`.
|
|
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 concrete read-only checkout worker runner lives in `src/hosted/worker.ts` and is exported from `ai-saas-guard/hosted/worker`. 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
|
|
|
@@ -70,6 +70,29 @@ Trust boundaries:
|
|
|
70
70
|
|
|
71
71
|
The exported helper is `planHostedWorkerReadOnlyScan`. It is intended to be the worker-provider-independent contract for the first real hosted worker implementation.
|
|
72
72
|
|
|
73
|
+
## Read-Only Checkout Worker Runner
|
|
74
|
+
|
|
75
|
+
The read-only checkout worker runner turns the worker plan into a concrete Node/container runner without changing the product boundary. It is still a hosted building block, not a public hosted service by itself.
|
|
76
|
+
|
|
77
|
+
Default behavior:
|
|
78
|
+
|
|
79
|
+
- derive the GitHub clone URL only from trusted repository identity
|
|
80
|
+
- require a runtime installation token provider and keep the token out of command arguments, returned results, compact reports, and serialized plans
|
|
81
|
+
- pass the installation token to git only through a temporary askpass helper inside the worker checkout
|
|
82
|
+
- run `git init`, add the trusted remote, fetch the trusted head and base SHAs with bounded depth, and checkout the trusted head SHA
|
|
83
|
+
- run the fixed `ai-saas-guard pr-risk --root <worker-checkout> --base <baseSha> --json` command without shell parsing
|
|
84
|
+
- cap command timeout and output bytes
|
|
85
|
+
- parse only compact JSON fields from CLI output: summary counts, rule IDs, severity, file, and line
|
|
86
|
+
- delete the temporary checkout after success or failure
|
|
87
|
+
|
|
88
|
+
Privacy boundaries:
|
|
89
|
+
|
|
90
|
+
- do not return temporary checkout paths, raw source, raw diffs, evidence snippets, secrets, customer payloads, PR-authored commands, PR-authored repository names, or installation tokens
|
|
91
|
+
- do not persist source checkout contents beyond the worker run
|
|
92
|
+
- rely on the deployment sandbox for network egress restrictions around the CLI phase; the runner itself removes GitHub credentials before invoking the CLI
|
|
93
|
+
|
|
94
|
+
The exported helper is `createHostedReadOnlyCheckoutScanRunner`.
|
|
95
|
+
|
|
73
96
|
## Production Adapter Plans
|
|
74
97
|
|
|
75
98
|
The production adapter layer turns the pure hosted contracts into a safer shape for real platform wiring. It is still provider-independent: it does not start a worker, call GitHub, request live installation tokens, write Check Runs, or upload source code.
|
|
@@ -381,6 +404,8 @@ Automated tests must cover:
|
|
|
381
404
|
- worker read-only scan planning requires repository `contents: read` permissions
|
|
382
405
|
- worker read-only scan planning uses trusted identity for checkout target and fixed CLI command
|
|
383
406
|
- worker read-only scan planning does not persist raw source, raw diffs, secrets, customer payloads, checkout paths, PR-authored commands, or PR-authored token scopes
|
|
407
|
+
- read-only checkout worker runner uses trusted clone targets, bounded command execution, compact output parsing, and cleanup after success or failure
|
|
408
|
+
- read-only checkout worker runner does not expose installation tokens, checkout paths, raw source, raw diffs, secret values, customer payloads, or PR-authored commands
|
|
384
409
|
- accepted pull request events build the expected trusted scan identity
|
|
385
410
|
- unsupported actions are rejected
|
|
386
411
|
- draft pull requests are rejected by default
|
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.28.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.28.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.28.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
|
@@ -60,6 +60,7 @@ Implemented surfaces:
|
|
|
60
60
|
- hosted service runtime document and provider-independent runtime core for signed webhook intake, idempotent queue upsert, read-only worker orchestration, compact report storage, Check Run publication adapters, and worker cleanup planning
|
|
61
61
|
- hosted GitHub App deployment planner document and least-privilege manifest planner for required permissions, events, public HTTPS URLs, container digest, secret references, raw secret input blockers, and release-gate checks
|
|
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
|
+
- hosted read-only checkout worker runner exported from `ai-saas-guard/hosted/worker`, with trusted clone targets, git askpass token handling, bounded command execution, compact CLI JSON parsing, and checkout cleanup after success or failure
|
|
63
64
|
- 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
65
|
- 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
66
|
- 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
|
|
@@ -149,8 +150,8 @@ Hosted staging:
|
|
|
149
150
|
- GitHub App ID: `3834787`
|
|
150
151
|
- GitHub App installation ID: `135085075`
|
|
151
152
|
- Installed repository: `zr9959/ai-saas-guard`
|
|
152
|
-
- Current hosted mode: deployed Worker health and Check Run
|
|
153
|
-
- Not yet complete:
|
|
153
|
+
- Current hosted mode: deployed Worker health, signed GitHub App webhook delivery, compact KV records, cleanup, and Check Run publication pass in staging; code supports signed webhook ingress, compact queueing, scoped GitHub App token exchange, PR file risk classification, bounded Check Run publishing, and a Node/container read-only checkout worker runner
|
|
154
|
+
- Not yet complete: deployed full source checkout scan worker with sandbox evidence, monitoring evidence, rollback evidence, incident-response evidence, production hosted exposure, and paid hosted workflow features
|
|
154
155
|
|
|
155
156
|
OpenSSF Best Practices:
|
|
156
157
|
|
|
@@ -160,7 +161,7 @@ OpenSSF Best Practices:
|
|
|
160
161
|
Publishing:
|
|
161
162
|
|
|
162
163
|
- npm package: `ai-saas-guard`
|
|
163
|
-
- Current published release line: `v0.
|
|
164
|
+
- Current published release line: `v0.28.0` pending this branch release
|
|
164
165
|
- Next source candidate: none
|
|
165
166
|
- Publish workflow: `.github/workflows/npm-publish.yml`
|
|
166
167
|
- Trusted Publisher: GitHub Actions for `zr9959/ai-saas-guard`, workflow `npm-publish.yml`
|
|
@@ -173,7 +174,7 @@ Allowed in this public repository:
|
|
|
173
174
|
- CLI source code
|
|
174
175
|
- tests and intentionally vulnerable fixtures
|
|
175
176
|
- public docs
|
|
176
|
-
- English README and Chinese README; when `README.md` changes, review and update `README.zh-CN.md` in the same change
|
|
177
|
+
- English README and Chinese README; when `README.md` changes, review and update `docs/README.zh-CN.md` in the same change
|
|
177
178
|
- GitHub Action wrapper
|
|
178
179
|
- examples that contain only inert fake data
|
|
179
180
|
- release-quality process docs
|
|
@@ -235,7 +235,7 @@ P0:
|
|
|
235
235
|
- `types` points to generated declaration files if package exports TypeScript API.
|
|
236
236
|
- `exports` is accurate.
|
|
237
237
|
- README install examples match the published package name.
|
|
238
|
-
- If `README.md` changes, `README.zh-CN.md` must be reviewed and updated or explicitly confirmed still current.
|
|
238
|
+
- If `README.md` changes, `docs/README.zh-CN.md` must be reviewed and updated or explicitly confirmed still current.
|
|
239
239
|
- Version follows semver.
|
|
240
240
|
- Release notes state breaking changes, new checks, false-positive changes, and migration notes.
|
|
241
241
|
- Publish with npm trusted publishing/OIDC when possible.
|
|
@@ -125,6 +125,6 @@ The repository also includes `.bestpractices.json` with conservative proposed an
|
|
|
125
125
|
Current badge evidence:
|
|
126
126
|
|
|
127
127
|
- `CONTRIBUTING.md` documents the pull request process, test expectations, rule-design requirements, release gate evidence, and public-safety constraints.
|
|
128
|
-
- `README.md` and `README.zh-CN.md` document the problem, install path, CLI commands, privacy model, GitHub Action use, hosted boundaries, and trust-hardening controls.
|
|
128
|
+
- `README.md` and `docs/README.zh-CN.md` document the problem, install path, CLI commands, privacy model, GitHub Action use, hosted boundaries, and trust-hardening controls.
|
|
129
129
|
- `.bestpractices.json` records only repository-backed OpenSSF Best Practices proposed answers; unknown or future claims should stay out until they can be supported by public evidence.
|
|
130
130
|
- `dynamic_analysis_enable_assertions` is intentionally still marked unmet because the production CLI does not yet have broad runtime assertion coverage beyond tests, property tests, and fuzzing.
|
|
@@ -13,14 +13,14 @@ It is intentionally narrow:
|
|
|
13
13
|
- Duplicate GitHub delivery IDs are accepted idempotently.
|
|
14
14
|
- 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
15
|
|
|
16
|
-
This Worker is a real hosted ingress with first-slice Check Run publishing code, not yet the complete scan worker. `shouldCreateCheckRun` is `true` only when the GitHub App bindings are present and the event passes installation scope checks. Current operations evidence is tracked in [docs/hosted-operations-evidence.md](../../docs/hosted-operations-evidence.md); the Worker health check
|
|
16
|
+
This Worker is a real hosted ingress with first-slice Check Run publishing code, not yet the complete scan worker. `shouldCreateCheckRun` is `true` only when the GitHub App bindings are present and the event passes installation scope checks. Current operations evidence is tracked in [docs/hosted-operations-evidence.md](../../docs/hosted-operations-evidence.md); the Worker health check, signed webhook delivery, KV cleanup, and compact Check Run smoke pass in staging. Full source checkout scanning remains gated behind the hosted operational release gate and the Node/container checkout worker deployment path.
|
|
17
17
|
|
|
18
18
|
## Required Cloudflare Bindings
|
|
19
19
|
|
|
20
20
|
- `HOSTED_EVENTS`: Cloudflare KV namespace for compact delivery and queued scan records.
|
|
21
21
|
- `WEBHOOK_SECRET`: Worker secret matching the GitHub App webhook secret.
|
|
22
22
|
- `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.
|
|
23
|
+
- `SCANNER_VERSION`: public version string, currently `0.28.0`.
|
|
24
24
|
- `GITHUB_APP_ID`, `GITHUB_APP_SLUG`, `GITHUB_APP_INSTALLATION_ID`: public staging identifiers for the private GitHub App installation.
|
|
25
25
|
|
|
26
26
|
## Deployment
|
|
@@ -45,7 +45,7 @@ Current public staging endpoint:
|
|
|
45
45
|
- GitHub App ID: `3834787`
|
|
46
46
|
- GitHub App installation ID: `135085075`
|
|
47
47
|
- Installed repository: `zr9959/ai-saas-guard`
|
|
48
|
-
- Mode: signed webhook ingress
|
|
48
|
+
- Mode: signed webhook ingress, compact queueing, PR file metadata classification, and bounded Check Run publishing
|
|
49
49
|
|
|
50
50
|
## Release Boundary
|
|
51
51
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-saas-guard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "Repo-local launch-readiness scanner for AI-built SaaS apps.",
|
|
5
5
|
"readmeFilename": "README.md",
|
|
6
6
|
"type": "module",
|
|
@@ -58,6 +58,10 @@
|
|
|
58
58
|
"./hosted/staging-harness": {
|
|
59
59
|
"types": "./dist/hosted/staging-harness.d.ts",
|
|
60
60
|
"default": "./dist/hosted/staging-harness.js"
|
|
61
|
+
},
|
|
62
|
+
"./hosted/worker": {
|
|
63
|
+
"types": "./dist/hosted/worker.d.ts",
|
|
64
|
+
"default": "./dist/hosted/worker.js"
|
|
61
65
|
}
|
|
62
66
|
},
|
|
63
67
|
"bin": {
|
|
@@ -81,8 +85,7 @@
|
|
|
81
85
|
"dist",
|
|
82
86
|
"examples",
|
|
83
87
|
"hosted",
|
|
84
|
-
"README.md"
|
|
85
|
-
"README.zh-CN.md"
|
|
88
|
+
"README.md"
|
|
86
89
|
],
|
|
87
90
|
"license": "MIT",
|
|
88
91
|
"devDependencies": {
|