ai-saas-guard 0.8.0 → 0.10.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 +24 -4
- package/dist/hosted/contracts.d.ts +285 -0
- package/dist/hosted/contracts.js +475 -0
- package/dist/scanners/stripe.js +4 -1
- package/docs/github-action.md +1 -1
- package/docs/github-app-design.md +140 -22
- package/docs/hosted-deployment-model.md +222 -0
- package/docs/hosted-first-service-slice.md +199 -0
- package/docs/hosted-operational-release-gate.md +226 -0
- package/docs/hosted-preimplementation-contracts.md +160 -0
- package/docs/hosted-pricing-packaging.md +195 -0
- package/docs/hosted-uninstall-data-deletion.md +166 -0
- package/docs/npm-publishing.md +3 -3
- package/docs/project-handoff.md +17 -8
- package/docs/rules.md +1 -1
- package/docs/stripe-webhook-replay.md +21 -0
- package/examples/hosted-compact-report.json +38 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -51,8 +51,8 @@ The CLI is published on npm as `ai-saas-guard`, and the GitHub Action is availab
|
|
|
51
51
|
| JSON and SARIF output | Available |
|
|
52
52
|
| Composite GitHub Action | Available |
|
|
53
53
|
| Project config | `.ai-saas-guard.json` rule toggles, severity overrides, and fail thresholds |
|
|
54
|
-
| Versioned Action tags | `v0.
|
|
55
|
-
| npm package | `ai-saas-guard@0.
|
|
54
|
+
| Versioned Action tags | `v0.10.0`, `v0` |
|
|
55
|
+
| npm package | `ai-saas-guard@0.10.0` |
|
|
56
56
|
| npm publishing | Trusted Publisher/OIDC, no long-lived publish token |
|
|
57
57
|
|
|
58
58
|
## Quick Start
|
|
@@ -180,7 +180,27 @@ Use [docs/stripe-webhook-replay.md](docs/stripe-webhook-replay.md) after `check-
|
|
|
180
180
|
|
|
181
181
|
## Hosted GitHub App Design
|
|
182
182
|
|
|
183
|
-
See [docs/github-app-design.md](docs/github-app-design.md) for the proposed hosted GitHub App layer. The note covers least-privilege permissions, webhook verification, PR comments, check runs, privacy, data retention, prompt injection handling, and why the hosted app should not replace the local CLI.
|
|
183
|
+
See [docs/github-app-design.md](docs/github-app-design.md) for the proposed hosted GitHub App layer. The note covers least-privilege permissions, selected repositories, webhook verification, PR comments, check runs, privacy, data retention, prompt injection handling, and why the hosted app should not replace the local CLI.
|
|
184
|
+
|
|
185
|
+
The first hosted service slice is defined in [docs/hosted-first-service-slice.md](docs/hosted-first-service-slice.md). It is intentionally check-run-only: signed GitHub App webhook intake, trusted scan identity, idempotent scan queueing, read-only worker behavior, compact report storage, and no PR comments, dashboard, billing, or AI summaries.
|
|
186
|
+
|
|
187
|
+
The hosted deployment model is documented in [docs/hosted-deployment-model.md](docs/hosted-deployment-model.md). It chooses a containerized Node.js ingress and worker model with a managed durable queue, platform secret manager, structured redacted logs, installation/repository rate limits, and rollback/incident response paths.
|
|
188
|
+
|
|
189
|
+
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.
|
|
190
|
+
|
|
191
|
+
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.
|
|
192
|
+
|
|
193
|
+
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.
|
|
194
|
+
|
|
195
|
+
Hosted pre-implementation pure contracts are documented in [docs/hosted-preimplementation-contracts.md](docs/hosted-preimplementation-contracts.md). They cover queue-safe webhook event parsing, bounded check-run summary rendering, idempotent queue cleanup planning, worker checkout cleanup planning, and other service-free helpers exported from `ai-saas-guard/hosted/contracts`.
|
|
196
|
+
|
|
197
|
+
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.
|
|
198
|
+
|
|
199
|
+
The proposed hosted app permission boundary is intentionally narrow: repository contents read, pull requests read, checks write, and metadata read for the first version. Optional PR comments require repository policy opt-in, and broad permissions such as administration, deployments, Actions write, and repository secrets are out of scope.
|
|
200
|
+
|
|
201
|
+
The repository also includes pure pre-implementation hosted contract helpers and tests for webhook verification, installation token scoping, queue idempotency, compact reports, and retention limits. These helpers do not implement or deploy a hosted service.
|
|
202
|
+
|
|
203
|
+
Users should prefer the local CLI for private repositories, offline review, or no-account workflows where hosted code processing is not acceptable.
|
|
184
204
|
|
|
185
205
|
## Project Configuration
|
|
186
206
|
|
|
@@ -212,7 +232,7 @@ Use `suppressions` for narrower false-positive handling when one rule is noisy o
|
|
|
212
232
|
|
|
213
233
|
## GitHub Action
|
|
214
234
|
|
|
215
|
-
The repo includes a composite Action. Use `v0` for the latest compatible pre-1.0 Action, a specific release tag such as `v0.
|
|
235
|
+
The repo includes a composite Action. Use `v0` for the latest compatible pre-1.0 Action, a specific release tag such as `v0.10.0` for controlled upgrades, or pin a reviewed commit SHA for stricter supply-chain control:
|
|
216
236
|
|
|
217
237
|
```yaml
|
|
218
238
|
name: ai-saas-guard
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
export type WebhookRejectReason = "missing_signature" | "malformed_signature" | "invalid_signature" | "replayed_delivery_id";
|
|
2
|
+
export interface GitHubWebhookDecision {
|
|
3
|
+
accepted: boolean;
|
|
4
|
+
reason?: WebhookRejectReason;
|
|
5
|
+
shouldQueueScanJob: boolean;
|
|
6
|
+
shouldFetchRepository: boolean;
|
|
7
|
+
deliveryId?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface GitHubWebhookInput {
|
|
10
|
+
payload: string | Buffer;
|
|
11
|
+
signatureHeader?: string;
|
|
12
|
+
signingKey: string | Buffer;
|
|
13
|
+
deliveryId?: string;
|
|
14
|
+
seenDeliveryIds?: Set<string>;
|
|
15
|
+
}
|
|
16
|
+
export interface HostedScanIdentityInput {
|
|
17
|
+
installationId: number;
|
|
18
|
+
repositoryId: number;
|
|
19
|
+
repositoryFullName: string;
|
|
20
|
+
pullRequestNumber: number;
|
|
21
|
+
baseSha: string;
|
|
22
|
+
headSha: string;
|
|
23
|
+
scannerVersion: string;
|
|
24
|
+
untrustedPrText?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface HostedScanIdentity {
|
|
27
|
+
installationId: number;
|
|
28
|
+
repositoryId: number;
|
|
29
|
+
repositoryFullName: string;
|
|
30
|
+
pullRequestNumber: number;
|
|
31
|
+
baseSha: string;
|
|
32
|
+
headSha: string;
|
|
33
|
+
scannerVersion: string;
|
|
34
|
+
}
|
|
35
|
+
export type PullRequestEventRejectReason = "unsupported_action" | "draft_pull_request" | "missing_required_field";
|
|
36
|
+
export interface HostedPullRequestEventInput {
|
|
37
|
+
payload: unknown;
|
|
38
|
+
scannerVersion: string;
|
|
39
|
+
allowDraft?: boolean;
|
|
40
|
+
supportedActions?: string[];
|
|
41
|
+
}
|
|
42
|
+
export interface HostedPullRequestEventDecision {
|
|
43
|
+
accepted: boolean;
|
|
44
|
+
shouldQueueScanJob: boolean;
|
|
45
|
+
reason?: PullRequestEventRejectReason;
|
|
46
|
+
action?: string;
|
|
47
|
+
identity?: HostedScanIdentity;
|
|
48
|
+
}
|
|
49
|
+
export type InstallationScopeRejectReason = "installation_mismatch" | "repository_not_installed" | "repository_removed_from_installation";
|
|
50
|
+
export interface InstallationScopeInput {
|
|
51
|
+
identity: HostedScanIdentity;
|
|
52
|
+
installationId: number;
|
|
53
|
+
selectedRepositoryIds: number[];
|
|
54
|
+
removedRepositoryIds?: number[];
|
|
55
|
+
}
|
|
56
|
+
export interface InstallationScopeDecision {
|
|
57
|
+
authorized: boolean;
|
|
58
|
+
shouldFetchSource: boolean;
|
|
59
|
+
reason?: InstallationScopeRejectReason;
|
|
60
|
+
}
|
|
61
|
+
export interface HostedScanJobState {
|
|
62
|
+
key: string;
|
|
63
|
+
attempt: number;
|
|
64
|
+
deliveryIds: string[];
|
|
65
|
+
}
|
|
66
|
+
export interface HostedScanJobInput {
|
|
67
|
+
identity: HostedScanIdentity;
|
|
68
|
+
deliveryId: string;
|
|
69
|
+
manualRerun?: boolean;
|
|
70
|
+
}
|
|
71
|
+
export interface HostedScanJobDecision {
|
|
72
|
+
key: string;
|
|
73
|
+
created: boolean;
|
|
74
|
+
reusedExistingReport: boolean;
|
|
75
|
+
attempt: number;
|
|
76
|
+
shouldCreateCheckRun: boolean;
|
|
77
|
+
shouldCreatePrComment: boolean;
|
|
78
|
+
}
|
|
79
|
+
export type HostedQueueJobStatus = "queued" | "running" | "completed" | "failed" | "cancelled";
|
|
80
|
+
export type HostedQueueCleanupTrigger = "repository_removed" | "installation_deleted" | "repeated_cleanup";
|
|
81
|
+
export interface HostedQueueCleanupJobState {
|
|
82
|
+
key: string;
|
|
83
|
+
identity: HostedScanIdentity;
|
|
84
|
+
status: HostedQueueJobStatus;
|
|
85
|
+
attempt?: number;
|
|
86
|
+
deliveryIds?: string[];
|
|
87
|
+
}
|
|
88
|
+
export interface HostedQueueCleanupPlanInput {
|
|
89
|
+
trigger: HostedQueueCleanupTrigger;
|
|
90
|
+
installationId: number;
|
|
91
|
+
repositoryId?: number;
|
|
92
|
+
requestedAt: string;
|
|
93
|
+
jobs: HostedQueueCleanupJobState[];
|
|
94
|
+
}
|
|
95
|
+
export interface HostedQueueCleanupPlan {
|
|
96
|
+
trigger: HostedQueueCleanupTrigger;
|
|
97
|
+
scope: "repository" | "installation";
|
|
98
|
+
installationId: number;
|
|
99
|
+
repositoryId?: number;
|
|
100
|
+
requestedAt: string;
|
|
101
|
+
idempotencyKey: string;
|
|
102
|
+
idempotent: true;
|
|
103
|
+
matchedJobKeys: string[];
|
|
104
|
+
cancelQueuedJobKeys: string[];
|
|
105
|
+
requestRunningCancellationJobKeys: string[];
|
|
106
|
+
preserveTerminalJobKeys: string[];
|
|
107
|
+
keepUnmatchedJobKeys: string[];
|
|
108
|
+
cancelQueuedJobs: true;
|
|
109
|
+
requestRunningCancellation: true;
|
|
110
|
+
deleteRawSource: false;
|
|
111
|
+
deleteRawDiffs: false;
|
|
112
|
+
deleteSecrets: false;
|
|
113
|
+
deleteCustomerPayloads: false;
|
|
114
|
+
}
|
|
115
|
+
export type HostedWorkerCheckoutTerminalState = "success" | "failure" | "timeout" | "cancellation" | "cleanup_failure";
|
|
116
|
+
export type HostedWorkerCheckoutCleanupAction = "delete_checkout" | "record_cleanup_failure";
|
|
117
|
+
export interface HostedWorkerCheckoutCleanupInput {
|
|
118
|
+
identity: HostedScanIdentity;
|
|
119
|
+
jobKey: string;
|
|
120
|
+
terminalState: HostedWorkerCheckoutTerminalState;
|
|
121
|
+
finishedAt: string;
|
|
122
|
+
checkoutPath?: string;
|
|
123
|
+
cleanupError?: string;
|
|
124
|
+
rawSource?: string;
|
|
125
|
+
rawDiff?: string;
|
|
126
|
+
secretValues?: string[];
|
|
127
|
+
customerPayload?: unknown;
|
|
128
|
+
}
|
|
129
|
+
export interface HostedWorkerCheckoutCleanupPlan {
|
|
130
|
+
cleanupAction: HostedWorkerCheckoutCleanupAction;
|
|
131
|
+
shouldDeleteWorkerCheckout: boolean;
|
|
132
|
+
shouldRemoveCredentials: boolean;
|
|
133
|
+
shouldRemoveRawSource: boolean;
|
|
134
|
+
shouldRemoveRawDiffs: boolean;
|
|
135
|
+
shouldRemoveGeneratedArtifacts: boolean;
|
|
136
|
+
requiresOperatorReview: boolean;
|
|
137
|
+
preserveAuditRecord: true;
|
|
138
|
+
visibleUserMessage: string;
|
|
139
|
+
safeMetadata: {
|
|
140
|
+
jobKey: string;
|
|
141
|
+
installationId: number;
|
|
142
|
+
repositoryId: number;
|
|
143
|
+
repositoryFullName: string;
|
|
144
|
+
pullRequestNumber: number;
|
|
145
|
+
scannerVersion: string;
|
|
146
|
+
terminalState: HostedWorkerCheckoutTerminalState;
|
|
147
|
+
finishedAt: string;
|
|
148
|
+
};
|
|
149
|
+
privacy: {
|
|
150
|
+
returnsCheckoutPath: false;
|
|
151
|
+
returnsCleanupError: false;
|
|
152
|
+
returnsRawSource: false;
|
|
153
|
+
returnsRawDiffs: false;
|
|
154
|
+
returnsSecrets: false;
|
|
155
|
+
returnsCustomerPayloads: false;
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
export interface CompactHostedFinding {
|
|
159
|
+
ruleId: string;
|
|
160
|
+
severity: string;
|
|
161
|
+
file: string;
|
|
162
|
+
line?: number;
|
|
163
|
+
}
|
|
164
|
+
export interface CompactHostedReportInput {
|
|
165
|
+
identity: HostedScanIdentity;
|
|
166
|
+
summaryCounts: Record<string, number>;
|
|
167
|
+
findings: CompactHostedFinding[];
|
|
168
|
+
retentionDays?: number;
|
|
169
|
+
rawDiff?: string;
|
|
170
|
+
fullFileContents?: string;
|
|
171
|
+
secretValues?: string[];
|
|
172
|
+
customerPayload?: unknown;
|
|
173
|
+
}
|
|
174
|
+
export interface CompactHostedReport {
|
|
175
|
+
installationId: number;
|
|
176
|
+
repositoryId: number;
|
|
177
|
+
repositoryFullName: string;
|
|
178
|
+
pullRequestNumber: number;
|
|
179
|
+
baseSha: string;
|
|
180
|
+
headSha: string;
|
|
181
|
+
scannerVersion: string;
|
|
182
|
+
summaryCounts: Record<string, number>;
|
|
183
|
+
ruleIds: string[];
|
|
184
|
+
evidence: Array<{
|
|
185
|
+
ruleId: string;
|
|
186
|
+
severity: string;
|
|
187
|
+
file: string;
|
|
188
|
+
line?: number;
|
|
189
|
+
}>;
|
|
190
|
+
retentionDays: number;
|
|
191
|
+
modelTraining: "disabled";
|
|
192
|
+
workerCheckoutDeletion: "after_scan_completion";
|
|
193
|
+
}
|
|
194
|
+
export type HostedCheckRunConclusion = "success" | "neutral" | "failure";
|
|
195
|
+
export type HostedCheckRunSeverityThreshold = "critical" | "high" | "medium" | "low" | "info";
|
|
196
|
+
export type HostedCheckRunAnnotationLevel = "notice" | "warning" | "failure";
|
|
197
|
+
export interface HostedCheckRunSummaryInput {
|
|
198
|
+
report: CompactHostedReport;
|
|
199
|
+
failOnSeverity?: HostedCheckRunSeverityThreshold;
|
|
200
|
+
maxMarkdownChars?: number;
|
|
201
|
+
}
|
|
202
|
+
export interface HostedCheckRunAnnotation {
|
|
203
|
+
path: string;
|
|
204
|
+
startLine: number;
|
|
205
|
+
endLine: number;
|
|
206
|
+
annotationLevel: HostedCheckRunAnnotationLevel;
|
|
207
|
+
title: string;
|
|
208
|
+
message: string;
|
|
209
|
+
}
|
|
210
|
+
export interface HostedCheckRunSummary {
|
|
211
|
+
name: "AI SaaS Guard";
|
|
212
|
+
conclusion: HostedCheckRunConclusion;
|
|
213
|
+
output: {
|
|
214
|
+
title: string;
|
|
215
|
+
summary: string;
|
|
216
|
+
text: string;
|
|
217
|
+
};
|
|
218
|
+
annotations: HostedCheckRunAnnotation[];
|
|
219
|
+
localCliCommand: string;
|
|
220
|
+
privacy: {
|
|
221
|
+
includesRawSource: false;
|
|
222
|
+
includesRawDiffs: false;
|
|
223
|
+
includesSecrets: false;
|
|
224
|
+
includesCustomerPayloads: false;
|
|
225
|
+
modelTraining: "disabled";
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
export type HostedDeletionTrigger = "repository_removed" | "installation_deleted" | "repeated_cleanup";
|
|
229
|
+
export interface HostedDeletionPlanInput {
|
|
230
|
+
trigger: HostedDeletionTrigger;
|
|
231
|
+
installationId: number;
|
|
232
|
+
repositoryId?: number;
|
|
233
|
+
requestedAt: string;
|
|
234
|
+
auditRecordRetentionDays?: number;
|
|
235
|
+
}
|
|
236
|
+
export interface HostedDeletionPlan {
|
|
237
|
+
trigger: HostedDeletionTrigger;
|
|
238
|
+
scope: "repository" | "installation";
|
|
239
|
+
installationId: number;
|
|
240
|
+
repositoryId?: number;
|
|
241
|
+
requestedAt: string;
|
|
242
|
+
idempotencyKey: string;
|
|
243
|
+
idempotent: true;
|
|
244
|
+
deleteCompactReports: true;
|
|
245
|
+
cancelQueuedJobs: true;
|
|
246
|
+
deleteWorkerCheckouts: true;
|
|
247
|
+
deleteRawSource: false;
|
|
248
|
+
deleteRawDiffs: false;
|
|
249
|
+
deleteSecrets: false;
|
|
250
|
+
deleteCustomerPayloads: false;
|
|
251
|
+
deleteGitHubOwnedCheckRuns: false;
|
|
252
|
+
preserveAuditRecord: true;
|
|
253
|
+
auditRecordRetentionDays: number;
|
|
254
|
+
visibleUserMessage: string;
|
|
255
|
+
}
|
|
256
|
+
export declare const HOSTED_PRIVACY_DEFAULTS: {
|
|
257
|
+
readonly retentionDays: 30;
|
|
258
|
+
readonly auditRecordRetentionDays: 90;
|
|
259
|
+
readonly modelTraining: "disabled";
|
|
260
|
+
readonly deleteWorkerCheckout: "after_scan_completion";
|
|
261
|
+
};
|
|
262
|
+
export declare function verifyGitHubWebhook(input: GitHubWebhookInput): GitHubWebhookDecision;
|
|
263
|
+
export declare function buildHostedScanIdentity(input: HostedScanIdentityInput): HostedScanIdentity;
|
|
264
|
+
export declare function parseHostedPullRequestEvent(input: HostedPullRequestEventInput): HostedPullRequestEventDecision;
|
|
265
|
+
export declare function authorizeInstallationTokenScope(input: InstallationScopeInput): InstallationScopeDecision;
|
|
266
|
+
export declare function getHostedScanIdempotencyKey(identity: HostedScanIdentity): string;
|
|
267
|
+
export declare function upsertHostedScanJob(queue: Map<string, HostedScanJobState>, input: HostedScanJobInput): HostedScanJobDecision;
|
|
268
|
+
export declare function getHostedQueueCleanupIdempotencyKey(input: {
|
|
269
|
+
trigger: HostedQueueCleanupTrigger;
|
|
270
|
+
installationId: number;
|
|
271
|
+
repositoryId?: number;
|
|
272
|
+
}): string;
|
|
273
|
+
export declare function createHostedQueueCleanupPlan(input: HostedQueueCleanupPlanInput): HostedQueueCleanupPlan;
|
|
274
|
+
export declare function createHostedWorkerCheckoutCleanupPlan(input: HostedWorkerCheckoutCleanupInput): HostedWorkerCheckoutCleanupPlan;
|
|
275
|
+
export declare function resolveHostedRetentionDays(input?: {
|
|
276
|
+
teamRequestedDays?: number;
|
|
277
|
+
}): number;
|
|
278
|
+
export declare function createCompactHostedReport(input: CompactHostedReportInput): CompactHostedReport;
|
|
279
|
+
export declare function createHostedCheckRunSummary(input: HostedCheckRunSummaryInput): HostedCheckRunSummary;
|
|
280
|
+
export declare function getHostedDeletionIdempotencyKey(input: {
|
|
281
|
+
trigger: HostedDeletionTrigger;
|
|
282
|
+
installationId: number;
|
|
283
|
+
repositoryId?: number;
|
|
284
|
+
}): string;
|
|
285
|
+
export declare function createHostedDeletionPlan(input: HostedDeletionPlanInput): HostedDeletionPlan;
|